Skip to content

Fix/Add Patches 13 (2D modal) and 14 (3D spherical harmonics)#1163

Open
ChrisZYJ wants to merge 12 commits intoMFlowCode:masterfrom
ChrisZYJ:modal_shapes
Open

Fix/Add Patches 13 (2D modal) and 14 (3D spherical harmonics)#1163
ChrisZYJ wants to merge 12 commits intoMFlowCode:masterfrom
ChrisZYJ:modal_shapes

Conversation

@ChrisZYJ
Copy link
Contributor

@ChrisZYJ ChrisZYJ commented Feb 19, 2026

User description

Description

This PR fixes the 3D spherical harmonics and adds a dedicated 2D modal patch type:

  • Geometry 13 is 2D modal (Fourier), with explicit fourier_cos/fourier_sin, optional additive vs. exponential form, and optional radius clipping.

  • Geometry 14 is 3D spherical harmonics and uses real spherical harmonics with an explicit sph_har_coeff(l,m) surface definition. It replaces the old spherical-harmonic subroutine, which had buggy and undocumented behavior. There are no backward compatibility issues, as the original Geometry 14 was not used in any examples or tests. This PR adds clear examples and documentation.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Other: describe

Testing

image image

Checklist

  • I added or updated tests for new behavior (as examples)
  • I updated documentation if user-facing behavior changed

See the developer guide for full coding standards.

GPU changes (expand if you modified src/simulation/)
  • GPU results match CPU results
  • Tested on NVIDIA GPU or AMD GPU

CodeAnt-AI Description

Add 2D Fourier modal patch (geometry 13) and correct 3D spherical-harmonic patch (geometry 14)

What Changed

  • New geometry 13: define a 2D boundary by centroid + radius modulated by per-mode Fourier cos/sin amplitudes (modes 1–10); supports additive or exponential forms and optional minimum-radius clipping; included input validation, broadcasting, defaults, and example cases.
  • Geometry 14 replaced the previous spherical-harmonic routine with a robust real spherical-harmonic surface: users set centroid + radius and per-(l,m) real coefficients (l=0..5, m=-l..l); the code now computes theta/phi safely near the centroid and sums Y_lm contributions; included input validation, broadcasting, defaults, and an example case.
  • Math helpers and constants added: real Y_lm and associated Legendre routines with domain guards, max mode/degree limits, and a tiny radius cutoff to avoid division-by-zero near the patch center; parameter registry, docs, and examples updated to expose new inputs.

Impact

✅ New 2D modal boundary shapes available for 2D cases
✅ Clearer and correct 3D spherical-harmonic surfaces from explicit coefficients
✅ Fewer numerical errors near patch centroids (no division-by-zero)

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai codeant-ai bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Feb 19, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 22 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
tests/1967AE06/golden-metadata.txt (1)

7-7: Golden metadata generated from a dirty working tree.

The git state shows (dirty), meaning there were uncommitted changes when this golden file was generated. Consider regenerating the golden files from a clean commit to ensure the test baselines are reproducible and traceable. Based on learnings: "Run ./mfc.sh test locally before requesting review" and "Use ./mfc.sh test --generate -o <test_id> to create golden files for new test cases."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/1967AE06/golden-metadata.txt` at line 7, The golden metadata shows a
"(dirty)" git state which means it was generated with uncommitted changes; to
fix, reset or commit your working tree so it's clean, regenerate the golden file
for the test ID 1967AE06 by running the referenced test-generation command (e.g.
run ./mfc.sh test --generate -o 1967AE06 or run ./mfc.sh test locally) and
replace tests/1967AE06/golden-metadata.txt with the new output so the Git line
no longer contains "(dirty)" and the baseline is reproducible.
examples/2D_patch_modal_shape_exp/case.py (1)

7-7: Nit: Define Ly explicitly for clarity.

Lx is used for both length_x and length_y (line 74). The additive-form example defines both Lx, Ly. For consistency and readability, consider defining Ly = 8.0 explicitly here too.

Proposed fix
-Lx = 8.0
+Lx, Ly = 8.0, 8.0

and

-            "patch_icpp(1)%length_y": Lx,
+            "patch_icpp(1)%length_y": Ly,

Also applies to: 73-74

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/2D_patch_modal_shape_exp/case.py` at line 7, Define Ly explicitly to
match Lx instead of reusing Lx for both dimensions: add a line setting Ly = 8.0
and update any places that set length_y (or pass length_y) to use Ly (e.g.,
where length_x and length_y are supplied) so the mesh/patch geometry uses Lx and
Ly separately; update the example to mirror the additive-form pattern.
src/common/m_derived_types.fpp (1)

219-232: Verify all three executables after extending ic_patch_parameters.

Since this type lives in src/common, please confirm pre_process, simulation, and post_process builds/tests still pass with the new fields.

Based on learnings: "Code in src/common/ affects all three executables (pre_process, simulation, post_process). Changes here have wide blast radius and require testing all three targets."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/common/m_derived_types.fpp` around lines 219 - 232, You extended the
derived type used across executables (see ic_patch_parameters in
m_derived_types.fpp and nearby fields like fourier_cos, fourier_sin,
modal_clip_r_to_min, modal_r_min, modal_use_exp_form, sph_har_coeff), so rebuild
and run all three targets: pre_process, simulation, and post_process; fix any
compile/link errors by updating all modules and call sites that reference
ic_patch_parameters (parsing, I/O/serialization, initial-condition setup),
update any input/config files or unit tests that instantiate this type, and run
the test suites for each executable to confirm no runtime failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/documentation/case.md`:
- Around line 1105-1117: The inline LaTeX math in the Geometry 13 documentation
uses $...$ which fails the linter; update every inline occurrence to use the
sphinx-friendly \f$...\f$ form (e.g., replace θ = atan2(...) written as $...$
with \f$...\f$ and similarly for R_boundary, radius, fourier_cos, fourier_sin,
modal_use_exp_form, modal_clip_r_to_min, modal_r_min expressions); do not change
the math content, only swap the inline delimiters throughout that paragraph so
the formulas remain identical but use \f$...\f$.

In `@src/pre_process/m_icpp_patches.fpp`:
- Around line 1042-1152: Both s_icpp_2d_modal and s_icpp_3d_spherical_harmonic
are missing the analytic/hardcoded override hooks used by other patch routines;
add the same call sequence used elsewhere so analytic expressions or hardcoded
patches can override before falling back to s_assign_patch_primitive_variables.
Specifically, at the site where you currently call
s_assign_patch_primitive_variables (inside the if that checks r<=R and
alter_patch/smooth_patch_id), first invoke the project's analytical override
hook and the hardcoded override hook (the same functions/calls used by other
s_icpp_* routines), and only call s_assign_patch_primitive_variables when those
hooks did not perform an override; make the change in both s_icpp_2d_modal and
s_icpp_3d_spherical_harmonic.

In `@tests/87FBC893/golden-metadata.txt`:
- Around line 5-7: The golden-metadata.txt files contain machine-specific
absolute paths and git state and should not be tracked; update .gitignore to
stop forcing these files into the repo by removing or changing the negated
pattern `!/tests/*/golden-metadata.txt` (or add an explicit ignore entry for
tests/*/golden-metadata.txt), then untrack existing committed files (e.g., git
rm --cached tests/*/golden-metadata.txt) and commit the change so future runs
don’t produce spurious diffs; ensure any CI that relied on these files is
adjusted to regenerate them as needed.

---

Duplicate comments:
In `@tests/3F864CFF/golden-metadata.txt`:
- Around line 5-7: The golden-metadata file contains machine-specific and
working-tree state data (lines starting with "Invocation:", "Lock:", and "Git:")
that makes tests non-deterministic; update the metadata generation so it does
not embed local invocation paths or the dirty branch state: remove or normalize
the "Invocation:" path, remove machine-specific "Lock:" details or replace with
stable feature flags, and ensure "Git:" records a stable commit hash only (no
branch name or "dirty" marker) or use a placeholder when the repo isn't clean.
Locate the code that writes golden-metadata.txt (search for writers emitting the
"Invocation:", "Lock:", or "Git:" strings) and change it to emit
normalized/placeholder values or omit these lines so the golden file is
machine-independent.

---

Nitpick comments:
In `@examples/2D_patch_modal_shape_exp/case.py`:
- Line 7: Define Ly explicitly to match Lx instead of reusing Lx for both
dimensions: add a line setting Ly = 8.0 and update any places that set length_y
(or pass length_y) to use Ly (e.g., where length_x and length_y are supplied) so
the mesh/patch geometry uses Lx and Ly separately; update the example to mirror
the additive-form pattern.

In `@src/common/m_derived_types.fpp`:
- Around line 219-232: You extended the derived type used across executables
(see ic_patch_parameters in m_derived_types.fpp and nearby fields like
fourier_cos, fourier_sin, modal_clip_r_to_min, modal_r_min, modal_use_exp_form,
sph_har_coeff), so rebuild and run all three targets: pre_process, simulation,
and post_process; fix any compile/link errors by updating all modules and call
sites that reference ic_patch_parameters (parsing, I/O/serialization,
initial-condition setup), update any input/config files or unit tests that
instantiate this type, and run the test suites for each executable to confirm no
runtime failures.

In `@tests/1967AE06/golden-metadata.txt`:
- Line 7: The golden metadata shows a "(dirty)" git state which means it was
generated with uncommitted changes; to fix, reset or commit your working tree so
it's clean, regenerate the golden file for the test ID 1967AE06 by running the
referenced test-generation command (e.g. run ./mfc.sh test --generate -o
1967AE06 or run ./mfc.sh test locally) and replace
tests/1967AE06/golden-metadata.txt with the new output so the Git line no longer
contains "(dirty)" and the baseline is reproducible.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/documentation/case.md`:
- Around line 1105-1117: The docs lack a Geometry 14 description for 3D
spherical-harmonic surfaces; add a block matching Geometry 13 that explains
coordinate conventions and coefficient semantics: describe that Geometry 14 (3D
spherical harmonic) defines the surface as r(θ,φ)=radius + sum_{l,m}
sph_har_coeff(l,m) * Y_l^m(θ,φ) (or an exponential variant if you support
modal_use_exp_form), state the polar and azimuthal conventions θ = acos(z/r) and
φ = atan2(y,x) relative to the centroid, clarify that the implementation uses a
real-Ylm basis and list the coefficient indexing (l = 0..L, m = -l..l) and
whether coefficients are absolute (length) or relative (dimensionless) and any
clipping behavior analogous to modal_clip_r_to_min/modal_r_min.

---

Duplicate comments:
In `@src/pre_process/m_icpp_patches.fpp`:
- Around line 1146-1156: The s_icpp_3d_spherical_harmonic subroutine is missing
the same hooks/bookkeeping/Fypp macros and local declarations present in other
3D patch routines; add the same variable declarations (analytical flags,
Hardcoded3D markers, bookkeeping arrays/indices and any Fypp-generated locals)
near the existing declarations around where s_icpp_3d_spherical_harmonic begins
(matching the pattern used in s_icpp_2d_modal and other 3D routines), insert the
analytical/@:Hardcoded3D/Fypp macro calls and bookkeeping statements immediately
after the call to s_assign_patch_primitive_variables(patch_id, i, j, k,
eta_local, q_prim_vf, patch_id_fp) to mirror other patches, and ensure any
allocated bookkeeping arrays are deallocated before the end of the subroutine to
match the resource handling in the other 3D patch implementations.
- Around line 1086-1095: In s_icpp_2d_modal, add the missing
analytical/hardcoded hooks, bookkeeping update, and Fypp variable/deallocation
macros: declare the
HardcodedDimensionsExtrusion()/Hardcoded2DVariables()/HardcodedDellacation()
variables near the existing declarations at the top of the subroutine, after the
declaration block around line ~1056; immediately after the call to
s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf,
patch_id_fp) insert calls to @:analytical() and an if-block for @:Hardcoded2D()
and update the patch_id_fp bookkeeping (set patch_id_fp(i,j,0)=patch_id where
appropriate); finally add the Fypp deallocation macro invocation
(HardcodedDellacation()) before the end of subroutine s_icpp_2d_modal so the
hardcoded 2D variables are cleaned up.

@codecov
Copy link

codecov bot commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 66.33663% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 45.34%. Comparing base (506c6f5) to head (a53c1a8).

Files with missing lines Patch % Lines
src/pre_process/m_icpp_patches.fpp 58.33% 19 Missing and 6 partials ⚠️
src/pre_process/m_check_patches.fpp 58.33% 5 Missing ⚠️
src/common/m_helper.fpp 78.94% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1163      +/-   ##
==========================================
+ Coverage   44.95%   45.34%   +0.39%     
==========================================
  Files          70       70              
  Lines       20504    20510       +6     
  Branches     1946     1951       +5     
==========================================
+ Hits         9217     9301      +84     
+ Misses      10166    10082      -84     
- Partials     1121     1127       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ChrisZYJ
Copy link
Contributor Author

@sbryngelson I think this PR is ready. Could you please take a look? Thank you.

@sbryngelson
Copy link
Member

i need to force it to run benchmark for now, since i somehow partially broke that and also we now have many amdflang workarounds for case optimization (used in benchmark)

@codeant-ai codeant-ai bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Feb 19, 2026
@codeant-ai codeant-ai bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Feb 22, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pre_process/m_check_patches.fpp (1)

253-277: ⚠️ Potential issue | 🟠 Major

Add validation checks in toolchain/mfc/case_validator.py for geometry 13/14 modal and spherical harmonic parameters.

The new parameters fourier_cos/sin(1-10), modal_clip_r_to_min, modal_r_min, modal_use_exp_form, and spherical harmonic coefficients are registered in definitions.py but lack corresponding validation in the case validator. Per project standards, add geometry-type-specific checks (e.g., check_patch_icpp_geometry_modal_ and check_patch_icpp_geometry_spherical_harmonic_) to validate:

  • Fourier coefficient indexing and value ranges
  • Modal option consistency (clip_r_to_min, r_min, use_exp_form constraints)
  • Spherical harmonic coefficient ranges and completeness

Also document these validations in the PHYSICS_DOCS dict as required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pre_process/m_check_patches.fpp` around lines 253 - 277, Add
geometry-type-specific validation to toolchain/mfc/case_validator.py: implement
check_patch_icpp_geometry_modal_ (called for geometry types 13/14 modal) to
verify Fourier coefficients fourier_cos1..fourier_cos10 and
fourier_sin1..fourier_sin10 are only indexed 1..10, numeric and within allowed
ranges, and to enforce modal option consistency: if modal_clip_r_to_min is true
then modal_r_min must be present and >0, modal_r_min must be >=0 when set, and
modal_use_exp_form must be a valid boolean flag (and incompatible combinations
flagged). Implement check_patch_icpp_geometry_spherical_harmonic_ to ensure
spherical harmonic coefficient sets are complete for required orders, each
coefficient is numeric and within valid ranges, and required centroids/radius
checks are present. Register these new checks where other patch_icpp geometry
checks are invoked and add concise entries describing the modal and spherical
harmonic validations to the PHYSICS_DOCS dict.
🧹 Nitpick comments (2)
src/common/m_constants.fpp (1)

18-30: Run pre_process, simulation, and post_process tests for this common change.
These constants live in src/common/, so a full three-target test sweep is prudent.

Based on learnings: Code in src/common/ affects all three executables (pre_process, simulation, post_process) - changes have wide blast radius and must be tested thoroughly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/common/m_constants.fpp` around lines 18 - 30, Changes to common constants
in src/common/m_constants.fpp (e.g., small_radius, num_fluids_max,
num_probes_max, num_patches_max, num_bc_patches_max, max_2d_fourier_modes,
max_sph_harm_degree) can impact all three executables; run the full test sweep:
build and run the pre_process, simulation, and post_process targets (or their
respective test suites) and verify no regressions or failures, paying special
attention to code paths that read these parameters (stencil logic, Fourier/modal
routines, spherical-harmonic geometry 14, and fluid/probe/patch allocation) and
update any dependent tests or bounds if failures surface.
src/common/m_helper.fpp (1)

569-572: m_order <= 0 is always m_order == 0 after the domain guard

After the early-return check on line 564 (m_order < 0 → return), the remaining code has m_order >= 0 guaranteed. The conditions m_order <= 0 on lines 569 and 571 are therefore equivalent to m_order == 0, which is clearer and reflects the actual intent.

♻️ Optional readability cleanup
-        if (m_order <= 0 .and. l <= 0) then
+        if (m_order == 0 .and. l == 0) then
             result_P = 1._wp
-        elseif (l == 1 .and. m_order <= 0) then
+        elseif (l == 1 .and. m_order == 0) then
             result_P = x
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/common/m_helper.fpp` around lines 569 - 572, The conditions using
"m_order <= 0" are misleading because earlier code already guards out negative
m_order, so update the checks in this routine (referencing m_order and result_P
in src/common/m_helper.fpp) to use "m_order == 0" instead of "<= 0" (apply to
both occurrences that set result_P = 1._wp and result_P = x) to reflect the
actual domain and intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/common/m_helper.fpp`:
- Line 538: The current prefac computation in real_ylm uses factorial(...) which
returns int64 and overflows for l+|m|≥21; replace the factorial-based ratio with
an overflow-safe log-gamma formulation: use the Fortran intrinsic log_gamma to
compute the log of factorials (log_gamma(n+1) == log(n!)) and compute the ratio
via exp(log_gamma(l - m_abs + 1) - log_gamma(l + m_abs + 1)) inside the sqrt,
keeping the rest of the expression (2*l+1)/(4._wp*pi) unchanged; update the
prefac assignment (the prefac symbol inside real_ylm) to use this log-gamma/exp
form so normalization is correct for all l and m.
- Line 579: The expression computing result_P uses double_factorial(2*l - 1)
which returns int64 and overflows for l≥18; replace the large-integer factorial
path with an overflow-free computation: compute P_l^l without calling
double_factorial by either (a) using the same real-type accumulation approach
used in real_ylm (convert multipliers to real(wp) and multiply progressively to
form the diagonal term) or (b) iteratively accumulate the diagonal recurrence
starting from P_0^0 = 1 and applying the diagonal step l times to produce
result_P; update the code that sets result_P to use one of these overflow-safe
strategies instead of real(double_factorial(...), wp).

In `@src/pre_process/m_check_patches.fpp`:
- Around line 253-264: In s_check_2d_modal_patch_geometry add validation for
patch_icpp(patch_id)%modal_r_min when patch_icpp(patch_id)%modal_clip_r_to_min
is true: check that modal_r_min is set (not f_is_default) and greater than zero
(and optionally less than or equal to patch_icpp(patch_id)%radius) by adding
appropriate @:PROHIBIT checks that reference
patch_icpp(patch_id)%modal_clip_r_to_min and patch_icpp(patch_id)%modal_r_min to
fail fast when clipping is enabled but the minimum radius is unset or
non-positive.

In `@src/pre_process/m_icpp_patches.fpp`:
- Around line 1153-1156: The code currently calls
s_assign_patch_primitive_variables for 3D spherical-harmonic patches but does
not update the bookkeeping array patch_id_fp, which breaks later alter/smoothing
logic; after the call to s_assign_patch_primitive_variables (inside the same
conditional that checks patch_icpp(patch_id)%alter_patch and smooth_patch_id)
set patch_id_fp(i, j, k) to patch_id so the footprint reflects the new
assignment, ensuring subsequent tests against patch_id_fp, smooth_patch_id, and
alter_patch behave correctly.
- Around line 1093-1096: After calling s_assign_patch_primitive_variables for 2D
modal patches you must update the patch bookkeeping so subsequent checks (e.g.
patch_icpp(...)%alter_patch(...) and smoothing using smooth_patch_id) see the
new owner; modify the branch that currently calls
s_assign_patch_primitive_variables(patch_id, i, j, 0, ...) to also set
patch_id_fp(i, j, 0) = patch_id immediately after the call so patch_id_fp
reflects the assignment.

---

Outside diff comments:
In `@src/pre_process/m_check_patches.fpp`:
- Around line 253-277: Add geometry-type-specific validation to
toolchain/mfc/case_validator.py: implement check_patch_icpp_geometry_modal_
(called for geometry types 13/14 modal) to verify Fourier coefficients
fourier_cos1..fourier_cos10 and fourier_sin1..fourier_sin10 are only indexed
1..10, numeric and within allowed ranges, and to enforce modal option
consistency: if modal_clip_r_to_min is true then modal_r_min must be present and
>0, modal_r_min must be >=0 when set, and modal_use_exp_form must be a valid
boolean flag (and incompatible combinations flagged). Implement
check_patch_icpp_geometry_spherical_harmonic_ to ensure spherical harmonic
coefficient sets are complete for required orders, each coefficient is numeric
and within valid ranges, and required centroids/radius checks are present.
Register these new checks where other patch_icpp geometry checks are invoked and
add concise entries describing the modal and spherical harmonic validations to
the PHYSICS_DOCS dict.

---

Duplicate comments:
In `@docs/documentation/case.md`:
- Around line 1105-1117: Add a full descriptive block for "Geometry 14"
mirroring the level of detail in "Geometry 13": define the coordinate convention
(how θ is computed, e.g., θ = atan2(y - y_centroid, x - x_centroid)), specify
the basis and functional form(s) (additive vs exponential controlled by
modal_use_exp_form), show the exact formulas for R_boundary using the same
coefficient naming scheme (e.g., fourier_cos(n), fourier_sin(n) or equivalent),
describe coefficient indexing and units (absolute vs relative), and document
clipping behavior and knobs (modal_clip_r_to_min, modal_r_min, clipping negative
R_boundary to zero) so readers can unambiguously reproduce and interpret
Geometry 14.

In `@src/pre_process/m_icpp_patches.fpp`:
- Around line 1093-1096: The block that calls s_assign_patch_primitive_variables
for patch faces (using patch_id_fp, patch_icpp%alter_patch, smooth_patch_id)
lacks the analytical/hardcoded geometry overrides for geometry types 13 and 14;
update this conditional branch to call the same analytical/hardcoded initializer
hooks used by other patch initializers when patch_icpp(patch_id)%geometry equals
13 or 14 (and/or when patch_id_fp indicates those geometries), ensuring the code
invokes the hardcoded/analytical override paths before or instead of
s_assign_patch_primitive_variables for these geometry IDs so the analytical
hooks for geometry 13/14 are executed consistently.

---

Nitpick comments:
In `@src/common/m_constants.fpp`:
- Around line 18-30: Changes to common constants in src/common/m_constants.fpp
(e.g., small_radius, num_fluids_max, num_probes_max, num_patches_max,
num_bc_patches_max, max_2d_fourier_modes, max_sph_harm_degree) can impact all
three executables; run the full test sweep: build and run the pre_process,
simulation, and post_process targets (or their respective test suites) and
verify no regressions or failures, paying special attention to code paths that
read these parameters (stencil logic, Fourier/modal routines, spherical-harmonic
geometry 14, and fluid/probe/patch allocation) and update any dependent tests or
bounds if failures surface.

In `@src/common/m_helper.fpp`:
- Around line 569-572: The conditions using "m_order <= 0" are misleading
because earlier code already guards out negative m_order, so update the checks
in this routine (referencing m_order and result_P in src/common/m_helper.fpp) to
use "m_order == 0" instead of "<= 0" (apply to both occurrences that set
result_P = 1._wp and result_P = x) to reflect the actual domain and intent.

elseif (m_order > 0) then
Y = (-1._wp)**m_order*sqrt(2._wp)*prefactor*associated_legendre(x, l, m_order)*cos(m_order*phi);
x = cos(theta)
prefac = sqrt((2*l + 1)*real(factorial(l - m_abs), wp)/real(factorial(l + m_abs), wp)/(4._wp*pi))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

factorial(l + m_abs) silently overflows int64 for l + |m| ≥ 21

factorial returns an int64 result. 20! ≈ 2.43 × 10¹⁸ fits (barely); 21! ≈ 5.11 × 10¹⁹ exceeds int64 max (≈ 9.22 × 10¹⁸), so the integer wraps silently before the real(…, wp) cast, producing a wrong normalization coefficient.

The PR currently restricts to modes 1–10 (max l + |m| = 20, right at the boundary), but real_ylm is a public API function and contains no guard that enforces this limit. A user supplying l = 11 would get silently wrong Y_lm values.

A numerically safer and overflow-free alternative is to compute the log-ratio directly:

🛡️ Suggested fix: log-gamma ratio for normalization
-        prefac = sqrt((2*l + 1)*real(factorial(l - m_abs), wp)/real(factorial(l + m_abs), wp)/(4._wp*pi))
+        ! Use log-gamma to avoid int64 overflow for large l
+        prefac = sqrt(real(2*l + 1, wp) &
+                      *exp(log_gamma(real(l - m_abs + 1, wp)) - log_gamma(real(l + m_abs + 1, wp))) &
+                      /(4._wp*pi))

log_gamma is a Fortran 2008 intrinsic (standard on all MFC-targeted compilers) and handles arbitrary l without overflow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/common/m_helper.fpp` at line 538, The current prefac computation in
real_ylm uses factorial(...) which returns int64 and overflows for l+|m|≥21;
replace the factorial-based ratio with an overflow-safe log-gamma formulation:
use the Fortran intrinsic log_gamma to compute the log of factorials
(log_gamma(n+1) == log(n!)) and compute the ratio via exp(log_gamma(l - m_abs +
1) - log_gamma(l + m_abs + 1)) inside the sqrt, keeping the rest of the
expression (2*l+1)/(4._wp*pi) unchanged; update the prefac assignment (the
prefac symbol inside real_ylm) to use this log-gamma/exp form so normalization
is correct for all l and m.

result_P = (-1)**l*double_factorial(2*l - 1)*(1 - x**2)**(l/2);
! P_l^l(x) = (-1)^l (2l-1)!! (1-x^2)^(l/2). Use real exponent for odd l
one_minus_x2 = max(0._wp, 1._wp - x**2)
result_P = (-1)**l*real(double_factorial(2*l - 1), wp)*one_minus_x2**(0.5_wp*real(l, wp))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

double_factorial(2*l - 1) overflows int64 for l ≥ 18

double_factorial returns int64. 33!! ≈ 6.33 × 10¹⁸ fits; 35!! ≈ 2.22 × 10²⁰ overflows (l = 18, since 2×18−1 = 35). The subsequent real(…, wp) cast then silently returns a garbage P_l^l value.

For the current PR's l ≤ 10 use case this does not trigger (19!! ≈ 6.5 × 10⁸), but there is no guard inside the function. Consistent with the fix suggested for real_ylm, compute the diagonal term without large intermediate integers:

🛡️ Suggested fix for P_l^l
         elseif (m_order == l) then
-            ! P_l^l(x) = (-1)^l (2l-1)!! (1-x^2)^(l/2). Use real exponent for odd l
             one_minus_x2 = max(0._wp, 1._wp - x**2)
-            result_P = (-1)**l*real(double_factorial(2*l - 1), wp)*one_minus_x2**(0.5_wp*real(l, wp))
+            ! P_l^l via log-sum to avoid int64 overflow: (2l-1)!! = exp(sum_{k=1}^{l} log(2k-1))
+            block
+                integer :: k
+                real(wp) :: log_dfac
+                log_dfac = 0._wp
+                do k = 1, l
+                    log_dfac = log_dfac + log(real(2*k - 1, wp))
+                end do
+                result_P = real((-1)**l, wp)*exp(log_dfac)*one_minus_x2**(0.5_wp*real(l, wp))
+            end block

Alternatively, accumulate P_l^l iteratively (starting from P_0^0 = 1 and applying the diagonal step l times), which is fully overflow-free and avoids the recursive call chain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/common/m_helper.fpp` at line 579, The expression computing result_P uses
double_factorial(2*l - 1) which returns int64 and overflows for l≥18; replace
the large-integer factorial path with an overflow-free computation: compute
P_l^l without calling double_factorial by either (a) using the same real-type
accumulation approach used in real_ylm (convert multipliers to real(wp) and
multiply progressively to form the diagonal term) or (b) iteratively accumulate
the diagonal recurrence starting from P_0^0 = 1 and applying the diagonal step l
times to produce result_P; update the code that sets result_P to use one of
these overflow-safe strategies instead of real(double_factorial(...), wp).

Comment on lines +253 to +264
impure subroutine s_check_2d_modal_patch_geometry(patch_id)
integer, intent(in) :: patch_id

call s_int_to_str(patch_id, iStr)

@:PROHIBIT(n == 0, "2D modal patch "//trim(iStr)//": n must be greater than zero")
@:PROHIBIT(p > 0, "2D modal patch "//trim(iStr)//": p must be zero")
@:PROHIBIT(patch_icpp(patch_id)%radius <= 0._wp, "2D modal patch "//trim(iStr)//": radius must be greater than zero")
@:PROHIBIT(f_is_default(patch_icpp(patch_id)%x_centroid), "2D modal patch "//trim(iStr)//": x_centroid must be set")
@:PROHIBIT(f_is_default(patch_icpp(patch_id)%y_centroid), "2D modal patch "//trim(iStr)//": y_centroid must be set")

end subroutine s_check_2d_modal_patch_geometry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate modal_r_min when clipping is enabled.
If modal_clip_r_to_min is true but modal_r_min is unset or negative, the clip is ineffective or ambiguous. Consider enforcing a valid minimum.

🛠️ Suggested validation
 impure subroutine s_check_2d_modal_patch_geometry(patch_id)
@@
     @:PROHIBIT(f_is_default(patch_icpp(patch_id)%y_centroid), "2D modal patch "//trim(iStr)//": y_centroid must be set")
+    if (patch_icpp(patch_id)%modal_clip_r_to_min) then
+        @:PROHIBIT(f_is_default(patch_icpp(patch_id)%modal_r_min), &
+            "2D modal patch "//trim(iStr)//": modal_r_min must be set when modal_clip_r_to_min is true")
+        @:PROHIBIT(patch_icpp(patch_id)%modal_r_min < 0._wp, &
+            "2D modal patch "//trim(iStr)//": modal_r_min must be non-negative")
+    end if
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pre_process/m_check_patches.fpp` around lines 253 - 264, In
s_check_2d_modal_patch_geometry add validation for
patch_icpp(patch_id)%modal_r_min when patch_icpp(patch_id)%modal_clip_r_to_min
is true: check that modal_r_min is set (not f_is_default) and greater than zero
(and optionally less than or equal to patch_icpp(patch_id)%radius) by adding
appropriate @:PROHIBIT checks that reference
patch_icpp(patch_id)%modal_clip_r_to_min and patch_icpp(patch_id)%modal_r_min to
fail fast when clipping is enabled but the minimum radius is unset or
non-positive.

Comment on lines +1093 to +1096
if ((r <= R_boundary .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, 0))) &
.or. patch_id_fp(i, j, 0) == smooth_patch_id) then
call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
end if
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update patch_id_fp after assignments in 2D modal patches.
Without updating the patch ID bookkeeping, later alter_patch and smoothing logic can behave incorrectly.

🛠️ Suggested fix
                 if ((r <= R_boundary .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, 0))) &
                     .or. patch_id_fp(i, j, 0) == smooth_patch_id) then
                     call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
+                    if (1._wp - eta < sgm_eps) patch_id_fp(i, j, 0) = patch_id
                 end if
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pre_process/m_icpp_patches.fpp` around lines 1093 - 1096, After calling
s_assign_patch_primitive_variables for 2D modal patches you must update the
patch bookkeeping so subsequent checks (e.g. patch_icpp(...)%alter_patch(...)
and smoothing using smooth_patch_id) see the new owner; modify the branch that
currently calls s_assign_patch_primitive_variables(patch_id, i, j, 0, ...) to
also set patch_id_fp(i, j, 0) = patch_id immediately after the call so
patch_id_fp reflects the assignment.

Comment on lines +1153 to 1156
if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
.or. patch_id_fp(i, j, k) == smooth_patch_id) then
call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
end if
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update patch_id_fp after assignments in 3D spherical-harmonic patches.
This bookkeeping is required for correct alter/smoothing behavior in subsequent patches.

🛠️ Suggested fix
                     if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
                         .or. patch_id_fp(i, j, k) == smooth_patch_id) then
                         call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
+                        if (1._wp - eta_local < sgm_eps) patch_id_fp(i, j, k) = patch_id
                     end if
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pre_process/m_icpp_patches.fpp` around lines 1153 - 1156, The code
currently calls s_assign_patch_primitive_variables for 3D spherical-harmonic
patches but does not update the bookkeeping array patch_id_fp, which breaks
later alter/smoothing logic; after the call to
s_assign_patch_primitive_variables (inside the same conditional that checks
patch_icpp(patch_id)%alter_patch and smooth_patch_id) set patch_id_fp(i, j, k)
to patch_id so the footprint reflects the new assignment, ensuring subsequent
tests against patch_id_fp, smooth_patch_id, and alter_patch behave correctly.

@github-actions
Copy link

Claude Code Review

Head SHA: bf1ed75


Files Changed: 22

Changed file paths (top 15)
  1. docs/documentation/case.md
  2. examples/2D_patch_modal_shape/case.py
  3. examples/2D_patch_modal_shape_exp/case.py
  4. examples/3D_patch_spherical_harmonic/case.py
  5. src/common/m_constants.fpp
  6. src/common/m_derived_types.fpp
  7. src/common/m_helper.fpp
  8. src/pre_process/m_check_patches.fpp
  9. src/pre_process/m_global_parameters.fpp
  10. src/pre_process/m_icpp_patches.fpp
  11. src/pre_process/m_mpi_proxy.fpp
  12. toolchain/mfc/case.py
  13. toolchain/mfc/params/definitions.py
  14. toolchain/mfc/params/descriptions.py
  15. toolchain/mfc/params_tests/test_definitions.py

Summary of Changes

  • New geometry 13 — 2D Fourier modal patch: Adds a 2D patch type where the boundary radius is defined by a Fourier series R(θ) = radius + Σ[fourier_cos(n)·cos(nθ) + fourier_sin(n)·sin(nθ)] (or the exponential form radius·exp(Σ)), with up to 10 modes and optional minimum-radius clipping.
  • Fixed geometry 14 — 3D spherical harmonic patch: Replaces the old undocumented/buggy implementation with correctly normalized real Y_lm using the Condon-Shortley convention, explicit sph_har_coeff(l,m) inputs up to degree l=5, and a guard against division-by-zero near the patch center (small_radius = 1e-32).
  • Refactored math in m_helper.fpp: Removes the broken spherical_harmonic_func and unassociated_legendre functions, adds real_ylm, and fixes associated_legendre to avoid integer-division bugs (e.g., (1-x²)^(l/2) for odd l) and NaN from floating-point noise in sqrt(1-x²).
  • Full MPI coverage: All six new patch fields (fourier_cos, fourier_sin, sph_har_coeff, modal_clip_r_to_min, modal_r_min, modal_use_exp_form) are broadcast to all MPI ranks.
  • New examples and golden tests: Three example cases and three golden test directories are added for regression coverage.
  • Input registration and docs: All new parameters are registered in the Python toolchain, the geometry table in docs/documentation/case.md is updated, and mathematical descriptions of the new forms are added.

Findings

No bugs or CLAUDE.md violations found. The mathematical implementation, array bounds, MPI broadcasts, and input validation are all correct.

Improvement opportunities:

  1. src/pre_process/m_global_parameters.fpp — magic number for modal_r_min: The default value 1.e-12_wp is distinct from the already-defined small_radius = 1.e-32_wp constant. A comment explaining why 1e-12 was chosen as the user-facing default minimum radius (vs. the internal small_radius) would help future maintainers understand the distinction.

  2. toolchain/mfc/params/definitions.py — sparse vs. dense spherical harmonic registration: The PR registers sph_har_coeff for all combinations l=0..5, m=-5..5, but only the physically valid combinations where |m| ≤ l are meaningful. The remaining entries (e.g., sph_har_coeff(0,3)) are silently ignored by the Fortran loop. A comment or validation in m_check_patches.fpp noting that coefficients with |m| > l have no effect would prevent user confusion.

  3. src/common/m_helper.fppfactorial and double_factorial helpers are local to real_ylm: These utility functions are defined as internal procedures within real_ylm. If other functions in the module ever need them, they'll need to be duplicated. Moving them to module scope (still within m_helper.fpp) would be a minor improvement for reusability.


Review performed by Claude Code

@codeant-ai codeant-ai bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Feb 24, 2026
@github-actions
Copy link

Claude Code Review

PR #1163 — Fix/Add Patches 13 (2D modal) and 14 (3D spherical harmonics)


Files Changed: 22

Key paths:

  • src/common/m_constants.fpp, src/common/m_derived_types.fpp, src/common/m_helper.fpp
  • src/pre_process/m_check_patches.fpp, src/pre_process/m_global_parameters.fpp
  • src/pre_process/m_icpp_patches.fpp, src/pre_process/m_mpi_proxy.fpp
  • toolchain/mfc/params/definitions.py, toolchain/mfc/params/descriptions.py, toolchain/mfc/case.py
  • examples/2D_patch_modal_shape/case.py, examples/2D_patch_modal_shape_exp/case.py, examples/3D_patch_spherical_harmonic/case.py
  • tests/1967AE06/, tests/3F864CFF/, tests/87FBC893/ (golden files)

Summary

  • Geometry 13 is repurposed to a 2D Fourier modal patch; geometry 14 is replaced with a real spherical harmonic implementation using sph_har_coeff(l,m) coefficients. The old integer-division bug in associated_legendre for odd l is correctly fixed.
  • The parameter system is correctly updated across definitions, derived types, global parameters, and MPI proxy; new patch_icpp fields are implicitly available for namelist I/O.
  • Three golden-file test directories are added (tests/1967AE06, tests/3F864CFF, tests/87FBC893) but there are no corresponding entries in toolchain/mfc/test/cases.py, making these tests unreachable via ./mfc.sh test.
  • Two correctness gaps exist: the 3D geometry check loses its dimensionality guard, and both new patch routines omit the patch_id_fp update needed for smooth-patch tracking.
  • No forbidden patterns found (no raw !$acc/!$omp, no dsqrt/dexp/real(8), no d-exponent literals, no stop/goto).

Findings

CRITICAL — Dimension enforcement regression in s_check_3d_spherical_harmonic_patch_geometry

File: src/pre_process/m_check_patches.fpp

The old s_check_spherical_harmonic_patch_geometry had:

@:PROHIBIT(p == 0, "Spherical harmonic patch "//trim(iStr)//": p must be greater than zero")

The new s_check_3d_spherical_harmonic_patch_geometry removes this check entirely. If a user specifies geometry = 14 in a 2D simulation (p = 0), the dispatch in s_apply_icpp_patches silently skips the patch with no error rather than aborting clearly. The check must be restored:

@:PROHIBIT(p == 0, "3D spherical harmonic patch "//trim(iStr)//": p must be greater than zero (must be a 3D simulation)")
@:PROHIBIT(patch_icpp(patch_id)%radius <= 0._wp, "3D spherical harmonic patch "//trim(iStr)//": radius must be greater than zero")

MAJOR — patch_id_fp not updated in either new patch routine

File: src/pre_process/m_icpp_patches.fpp (s_icpp_2d_modal and s_icpp_3d_spherical_harmonic)

Every other patch routine that supports smoothing updates the tracking array after s_assign_patch_primitive_variables:

if (1._wp - eta < sgm_eps) patch_id_fp(i, j, 0) = patch_id

See s_icpp_sphere, s_icpp_ellipsoid, s_icpp_cylinder. Both new routines call s_assign_patch_primitive_variables and then end the if block without this update. The smooth-patch identity bookkeeping used by subsequent patch layers is broken when smoothen = .true. is set for these patch types.


MAJOR — Test golden files have no corresponding cases.py entries

Files: tests/1967AE06/, tests/3F864CFF/, tests/87FBC893/

Per project convention: "Tests are generated programmatically in toolchain/mfc/test/cases.py, not standalone files." A test UUID is the CRC32 hash of its trace string as defined in cases.py. The three new test UUIDs exist only as golden file directories with no push/pop entries in toolchain/mfc/test/cases.py. Running ./mfc.sh test --only 1967AE06 will not find these tests, and the CI pipeline cannot regenerate or validate them. New test case definitions using CaseGeneratorStack must be added to cases.py.


MINOR — Missing f_is_default(radius) check in s_check_2d_modal_patch_geometry

File: src/pre_process/m_check_patches.fpp

The 2D modal check validates radius <= 0 but does not first check f_is_default(patch_icpp(patch_id)%radius). If the user omits radius, it will have dflt_real (a large sentinel value), which passes the <= 0 check (since dflt_real is typically a large negative), triggering a confusing downstream error instead of a clear message. Compare with s_check_sphere_patch_geometry and s_check_spherical_harmonic_patch_geometry, which both check f_is_default(radius) separately.


MINOR — eta_local inconsistency in s_icpp_3d_spherical_harmonic

File: src/pre_process/m_icpp_patches.fpp

The module-level eta variable is the conventional carrier of the pseudo-volume-fraction in all other patch routines (s_icpp_sphere, s_icpp_ellipsoid, s_icpp_cylinder, etc.). s_icpp_3d_spherical_harmonic declares and uses a local eta_local instead, while s_icpp_2d_modal correctly uses the module-level eta. Not a bug in itself, but inconsistent with the established pattern.


MINOR — Documentation range "1 to 21" implies a contiguous valid range

File: docs/documentation/case.md (or equivalent)

Integers 6, 7, and 15 are deprecated/invalid (explicit @:PROHIBIT checks exist for them). The documentation would be more accurate as "an integer identifying a geometry type; see the patch types table for valid values" rather than implying the range is fully contiguous.


OBSERVATION — Correct fix for integer-division bug in associated_legendre

File: src/common/m_helper.fpp

The old P_l^l formula (1 - x**2)**(l/2) used Fortran integer division, yielding wrong results for all odd l. The new formula one_minus_x2**(0.5_wp*real(l, wp)) is correct, and the max(0._wp, 1._wp - x**2) guard preventing NaN at x = ±1 is a good addition.


Required action items before merge

  1. Restore @:PROHIBIT(p == 0, ...) in s_check_3d_spherical_harmonic_patch_geometry (src/pre_process/m_check_patches.fpp).
  2. Add if (1._wp - eta < sgm_eps) patch_id_fp(i, j, 0) = patch_id (2D) and the equivalent (i, j, k) form (3D) after each s_assign_patch_primitive_variables call in the new patch routines (src/pre_process/m_icpp_patches.fpp).
  3. Add programmatic test case definitions to toolchain/mfc/test/cases.py that produce the three new UUIDs, or remove the orphan golden files and regenerate after properly defining the test cases.

Review generated by Claude Code

@sbryngelson sbryngelson marked this pull request as draft February 26, 2026 00:12
@MFlowCode MFlowCode deleted a comment from codeant-ai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from codeant-ai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from coderabbitai bot Feb 26, 2026
@sbryngelson sbryngelson marked this pull request as ready for review March 2, 2026 17:20
Copilot AI review requested due to automatic review settings March 2, 2026 17:20
@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Add 2D modal Fourier patch (geometry 13) and fix 3D spherical harmonic patch (geometry 14)

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add geometry 13: 2D modal (Fourier) patch with cos/sin modes (1–10)
  - Supports additive and exponential boundary forms
  - Optional radius clipping and smoothing
• Fix geometry 14: Replace buggy spherical harmonic with real Y_lm coefficients
  - Explicit per-(l,m) coefficients (l=0..5, m=-l..l)
  - Safe theta/phi computation near centroid with small_radius guard
• Refactor spherical harmonic math helpers
  - Replace unassociated_legendre and spherical_harmonic_func with real_ylm
  - Improve associated_legendre with domain guards and numerical stability
• Add three example cases and update parameter registry and documentation
Diagram
flowchart LR
  A["Geometry 13<br/>2D Modal Fourier"] -->|"Fourier series<br/>theta-dependent"| B["Boundary radius<br/>R(theta)"]
  C["Geometry 14<br/>3D Spherical Harmonic"] -->|"Real Y_lm<br/>theta,phi-dependent"| D["Surface radius<br/>R(theta,phi)"]
  E["Math Helpers"] -->|"real_ylm<br/>associated_legendre"| F["Robust<br/>Computation"]
  B --> G["Patch Initialization"]
  D --> G
  F --> G
Loading

Grey Divider

File Changes

1. src/pre_process/m_icpp_patches.fpp ✨ Enhancement +101/-131

Implement geometry 13 and 14 patch initialization

src/pre_process/m_icpp_patches.fpp


2. src/common/m_helper.fpp 🐞 Bug fix +47/-54

Replace spherical harmonic functions with real_ylm

src/common/m_helper.fpp


3. src/pre_process/m_check_patches.fpp ✨ Enhancement +20/-16

Add validation for 2D modal and 3D spherical harmonic patches

src/pre_process/m_check_patches.fpp


View more (18)
4. src/common/m_constants.fpp ✨ Enhancement +3/-0

Add constants for modal and spherical harmonic limits

src/common/m_constants.fpp


5. src/pre_process/m_mpi_proxy.fpp ✨ Enhancement +7/-1

Broadcast new patch parameters for geometry 13 and 14

src/pre_process/m_mpi_proxy.fpp


6. src/common/m_derived_types.fpp ✨ Enhancement +10/-1

Add Fourier and spherical harmonic coefficient arrays to patch type

src/common/m_derived_types.fpp


7. src/pre_process/m_global_parameters.fpp ✨ Enhancement +6/-0

Initialize new modal and spherical harmonic patch parameters

src/pre_process/m_global_parameters.fpp


8. examples/3D_patch_spherical_harmonic/case.py 📝 Documentation +145/-0

Add 3D spherical harmonic patch example case

examples/3D_patch_spherical_harmonic/case.py


9. examples/2D_patch_modal_shape/case.py 📝 Documentation +124/-0

Add 2D modal Fourier patch additive form example

examples/2D_patch_modal_shape/case.py


10. examples/2D_patch_modal_shape_exp/case.py 📝 Documentation +127/-0

Add 2D modal Fourier patch exponential form example

examples/2D_patch_modal_shape_exp/case.py


11. toolchain/mfc/params/descriptions.py 📝 Documentation +6/-0

Add parameter descriptions for modal and spherical harmonic coefficients

toolchain/mfc/params/descriptions.py


12. toolchain/mfc/params/definitions.py ✨ Enhancement +11/-0

Register Fourier and spherical harmonic coefficient parameters

toolchain/mfc/params/definitions.py


13. toolchain/mfc/case.py 🐞 Bug fix +2/-2

Update analytic IC dimension mapping for geometry 13 and 14

toolchain/mfc/case.py


14. toolchain/mfc/params_tests/test_registry.py 🧪 Tests +1/-1

Update parameter count assertion for new registry entries

toolchain/mfc/params_tests/test_registry.py


15. docs/documentation/case.md 📝 Documentation +17/-4

Document geometry 13 and 14 patch types and parameters

docs/documentation/case.md


16. tests/87FBC893/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for 2D modal patch with clipping

tests/87FBC893/golden-metadata.txt


17. tests/3F864CFF/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for 3D spherical harmonic patch

tests/3F864CFF/golden-metadata.txt


18. tests/1967AE06/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for 2D modal exponential form patch

tests/1967AE06/golden-metadata.txt


19. tests/1967AE06/golden.txt Additional files +14/-0

...

tests/1967AE06/golden.txt


20. tests/3F864CFF/golden.txt Additional files +14/-0

...

tests/3F864CFF/golden.txt


21. tests/87FBC893/golden.txt Additional files +16/-0

...

tests/87FBC893/golden.txt


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 2, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Sph-harm patch skips analytical IC 🐞 Bug ✓ Correctness
Description
Geometry 14 (s_icpp_3d_spherical_harmonic) assigns patch primitive variables but never calls the
@:analytical() / @:Hardcoded3D() hooks used by other 3D patches, so analytical expressions (and HCID
overrides) generated by the toolchain won’t be applied for this geometry.
Code

src/pre_process/m_icpp_patches.fpp[R1153-1156]

+                    if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
+                        .or. patch_id_fp(i, j, k) == smooth_patch_id) then
+                        call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
                    end if
Evidence
The toolchain emits the analytical() fypp macro to set q_prim_vf for analytical patches; 3D
patches generally call @:analytical() after s_assign_patch_primitive_variables. The new 3D
spherical harmonic routine omits this call, so analytical patch expressions (and optional HCID
overrides) are skipped for geometry 14.

src/pre_process/m_icpp_patches.fpp[1150-1156]
toolchain/mfc/case.py[219-227]
toolchain/mfc/case.py[145-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Geometry 14’s patch routine (`s_icpp_3d_spherical_harmonic`) assigns primitive variables but does not invoke the fypp hook `@:analytical()` (and does not provide the usual `hcid` hardcoded hook). This means toolchain-generated analytical IC expressions for geometry 14 are never applied.

### Issue Context
`toolchain/mfc/case.py` generates a fypp macro `#:def analytical()` that writes analytical expressions into `q_prim_vf` for a given `patch_id`. Existing 3D patch routines invoke this macro via `@:analytical()` immediately after `s_assign_patch_primitive_variables`.

### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1150-1156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. ic_patch_parameters members indentation 📘 Rule violation ✓ Correctness
Description
New derived-type members for geometries 13/14 are indented with 8 spaces instead of the required
2-space indentation. This violates the Fortran style requirement for modified code blocks.
Code

src/common/m_derived_types.fpp[R229-236]

+        ! Geometry 13 (2D modal Fourier): fourier_cos(n), fourier_sin(n) for mode n
+        real(wp), dimension(1:max_2d_fourier_modes) :: fourier_cos, fourier_sin
+        logical :: modal_clip_r_to_min !< When true, clip boundary radius: R(theta) = max(R(theta), modal_r_min) (Non-exp form only)
+        real(wp) :: modal_r_min        !< Minimum boundary radius when modal_clip_r_to_min is true (Non-exp form only)
+        logical :: modal_use_exp_form  !< When true, boundary = radius*exp(Fourier series)
+
+        ! Geometry 14 (3D spherical harmonic): sph_har_coeff(l,m) for real Y_lm
+        real(wp), dimension(0:max_sph_harm_degree, -max_sph_harm_degree:max_sph_harm_degree) :: sph_har_coeff
Evidence
PR Compliance ID 9 requires 2-space indentation in new/modified Fortran code. The newly added
members under type ic_patch_parameters are indented with 8 spaces, as shown in the cited excerpt.

CLAUDE.md
src/common/m_derived_types.fpp[229-236]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New derived-type members for the geometry 13/14 patch parameters do not follow the required 2-space indentation.

## Issue Context
Compliance requires 2-space indentation for new/modified Fortran code.

## Fix Focus Areas
- src/common/m_derived_types.fpp[229-237]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

Claude Code Review

Head SHA: e85160c
Files changed: 21

Changed files
  • docs/documentation/case.md
  • examples/2D_patch_modal_shape/case.py
  • examples/2D_patch_modal_shape_exp/case.py
  • examples/3D_patch_spherical_harmonic/case.py
  • src/common/m_constants.fpp
  • src/common/m_derived_types.fpp
  • src/common/m_helper.fpp
  • src/pre_process/m_check_patches.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/pre_process/m_icpp_patches.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • toolchain/mfc/case.py
  • toolchain/mfc/params/definitions.py
  • toolchain/mfc/params/descriptions.py
  • toolchain/mfc/params_tests/test_registry.py
  • tests/1967AE06/, tests/3F864CFF/, tests/87FBC893/ (3 golden file pairs)

Summary

  • Introduces geometry 13 (2D Fourier modal patch) and rewrites geometry 14 (3D spherical harmonic patch) from scratch.
  • New math helpers: real_ylm replaces spherical_harmonic_func; associated_legendre is improved with domain guards; unassociated_legendre is removed.
  • Adds proper atan2/acos angle computation and near-centroid guards (small_radius).
  • MPI broadcast coverage for new fields added in m_mpi_proxy.fpp.
  • New example cases and updated docs included.

Findings

🔴 CRITICAL — Missing namelist binding in m_start_up.fpp

File: src/pre_process/m_start_up.fpp (not present in the diff)

Per the 4-location parameter checklist (CLAUDE.md), every new parameter must be declared in the Fortran namelist in m_start_up.fpp. This PR adds ~30 new parameters (fourier_cos, fourier_sin, modal_clip_r_to_min, modal_r_min, modal_use_exp_form, sph_har_coeff(0:5,-5:5)) to m_derived_types.fpp, m_global_parameters.fpp, and definitions.py, but does not update m_start_up.fpp. As a result the Fortran namelist reader will silently ignore all user-supplied coefficients — the patch shapes will always revert to spherical/circular regardless of what the user sets. This makes geometry 13 and the new geometry 14 non-functional.

🔴 BUG — Missing radius > 0 check for geometry 14

File: src/pre_process/m_check_patches.fpp, s_check_3d_spherical_harmonic_patch_geometry (~line 703)

s_check_2d_modal_patch_geometry correctly prohibits radius <= 0:

@:PROHIBIT(patch_icpp(patch_id)%radius <= 0._wp, ...)

The new s_check_3d_spherical_harmonic_patch_geometry omits this check entirely. A zero or negative radius leads to R_surface starting at ≤ 0, meaning the entire domain is excluded (silent empty patch) or causes undefined geometry.

🟠 ISSUE — R_surface can go negative in s_icpp_3d_spherical_harmonic

File: src/pre_process/m_icpp_patches.fpp, s_icpp_3d_spherical_harmonic (new subroutine)

R_surface = patch_icpp(patch_id)%radius
do ll = 0, max_sph_harm_degree
    do mm = -ll, ll
        R_surface = R_surface + sph_har_coeff(ll, mm)*real_ylm(...)
    end do
end do
! No guard: R_surface could be negative here
if (r <= R_surface ...) then

If coefficients are large enough to drive R_surface < 0, the condition r <= R_surface is always false and no cells are filled — a silent failure. The 2D counterpart correctly clips: R_boundary = max(R_boundary, 0._wp). The 3D patch should apply the same guard, or at minimum a runtime @:PROHIBIT in the checker.

🟡 MINOR — associated_legendre base case now unreachable / misleading condition

File: src/common/m_helper.fpp, associated_legendre (~line 620)

After adding the early-return guard if (l < 0 .or. m_order < 0 .or. m_order > l), the surviving path into the function body always has l >= 0 and m_order >= 0. The existing first elseif condition m_order <= 0 .and. l <= 0 is now equivalent to m_order == 0 .and. l == 0. Not a correctness bug but misleading.

🟡 MINOR — real_ylm removes Condon-Shortley prefactor for m > 0

File: src/common/m_helper.fpp, real_ylm (~line 584)

The old spherical_harmonic_func applied (-1)^m explicitly for m > 0:

Y = (-1._wp)**m_order * sqrt(2._wp) * prefactor * ...

The new real_ylm omits this:

else if (m > 0) then
    Y = prefac*sqrt(2._wp)*associated_legendre(x, l, m_abs)*cos(m*phi)

The docstring claims associated_legendre embeds the Condon-Shortley phase. This is true for the m == l seed ((-1)**l) and propagates via the recurrence, so mathematically this is likely correct. However the convention should be explicitly verified against a reference (e.g., DLMF 14.30.1) for the non-seed cases (m < l) before merging, as a sign error in Y_lm will silently produce wrong initial shapes.


Checklist against CLAUDE.md 4-location rule

Location Status
toolchain/mfc/params/definitions.py ✅ Added
src/pre_process/m_global_parameters.fpp ✅ Initialized
src/pre_process/m_start_up.fpp (namelist) Missing
toolchain/mfc/case_validator.py (constraints) ⚠️ Not added (physics constraints exist: radius > 0, modal_r_min > 0)

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

Claude Code Review

Head SHA: e85160c
Files changed: 21

Changed files
  • docs/documentation/case.md
  • examples/2D_patch_modal_shape/case.py (new)
  • examples/2D_patch_modal_shape_exp/case.py (new)
  • examples/3D_patch_spherical_harmonic/case.py (new)
  • src/common/m_constants.fpp
  • src/common/m_derived_types.fpp
  • src/common/m_helper.fpp
  • src/pre_process/m_check_patches.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/pre_process/m_icpp_patches.fpp
  • src/pre_process/m_mpi_proxy.fpp + 10 more (tests, toolchain)

Summary

  • Adds geometry 13 (2D Fourier modal patch) and replaces geometry 14 (3D spherical harmonic patch) with a well-defined real-Y_lm implementation.
  • New math helpers: real_ylm (replaces spherical_harmonic_func), improved associated_legendre with domain guards and safe sqrt(max(0,...)).
  • MPI broadcast, defaults, parameter registry, and docs are updated for the new fields.
  • Three new example cases and three new regression tests included.

Findings

[CRITICAL] src/pre_process/m_start_up.fpp not in the diff

Per CLAUDE.md and parameter-system.md, every new parameter must be added in at least 3 places, and location 3 is the Fortran namelist binding in src/pre_process/m_start_up.fpp. That file is not modified in this PR.

If the namelist is declared component-by-component (which is the common MFC pattern), the new fields fourier_cos, fourier_sin, modal_clip_r_to_min, modal_r_min, modal_use_exp_form, and sph_har_coeff will be silently ignored at runtime — geometry 13 will always produce a plain circle and geometry 14 a plain sphere regardless of the coefficients specified by the user. Please confirm that these fields are read correctly (e.g., by checking that the example cases actually produce non-circular/non-spherical patches) and add the namelist bindings if they are missing.

[HIGH] Missing p > 0 validation for geometry 14

src/pre_process/m_check_patches.fpps_check_3d_spherical_harmonic_patch_geometry

The new validator for geometry 14 checks centroid and radius but does not require p > 0. Geometry 14 is only dispatched in the p > 0 branch of m_icpp_patches.fpp, so using it in a 2D case will silently leave the patch unwritten with no error message. The old geometry-14 check had this guard and it should be preserved:

@:PROHIBIT(p == 0, "Spherical harmonic patch "//trim(iStr)//": requires 3D simulation (p > 0)")

[MEDIUM] Fortran namelist syntax for negative sph_har_coeff indices

toolchain/mfc/params/definitions.py — Python generates keys like patch_icpp(2)%sph_har_coeff(1,-1). Fortran namelist I/O requires the exact Fortran index syntax including negative subscripts, e.g. sph_har_coeff(1,-1). Please verify that toolchain/mfc/run/input.py writes these with the correct sign (not as unsigned integers), and that gfortran/nvfortran/ifx/CCE all accept this syntax in &user_inputs.

[LOW] small_radius constant comment is misleading

src/common/m_constants.fpp:18

The comment says !< Radius cutoff to avoid division by zero for 3D spherical harmonic patch (geometry 14) but small_radius is also used in s_icpp_2d_modal (geometry 13). Update the comment to reflect both uses.

[LOW] modal_r_min default uses a magic number instead of the existing constant

src/pre_process/m_global_parameters.fpp

The default is patch_icpp(i)%modal_r_min = 1.e-12_wp. The constant verysmall = 1.e-12_wp already exists in m_constants.fpp for exactly this purpose. Prefer verysmall for consistency.

[INFO] Golden-file metadata embeds a developer-local path

tests/1967AE06/golden-metadata.txt, tests/3F864CFF/golden-metadata.txt, tests/87FBC893/golden-metadata.txt

The metadata files reference /home/chris/source/MFC_modal_shapes/ and machine name GRAM. This is cosmetic and won't affect test results, but ideally golden files should be regenerated in CI (or at least on a neutral machine) before merging to keep the metadata meaningful.


Math correctness note (no action required)

The new real_ylm and associated_legendre implementation is mathematically consistent: the Condon–Shortley phase (−1)^l is embedded in associated_legendre via the m_order == l base case, and real_ylm applies the standard orthonormal normalization without a duplicate sign factor. The negative-m branch correctly uses sin(|m|φ). This is a valid and internally consistent convention.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

This pull request extends patch geometry support from indices 1–13 to 1–21. New geometries are introduced: geometry 13 for 2D modal patches using Fourier series representations, and geometry 14 for 3D spherical harmonic patches. Supporting changes include new module constants (small_radius, max_2d_fourier_modes, max_sph_harm_degree), extended derived type fields for Fourier coefficients and spherical harmonic coefficients, updated patch validation checks, refactored spherical harmonic computation functions, and MPI broadcast extensions. Example configuration files demonstrate the new geometries. Documentation and test metadata are updated accordingly.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main changes: adding a 2D modal patch (Geometry 13) and fixing/adding a 3D spherical harmonics patch (Geometry 14).
Description check ✅ Passed The PR description comprehensively covers the changes, motivation, type of change, testing approach with visual examples, and completes all required checklist items including tests and documentation updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
docs/documentation/case.md (1)

1105-1117: ⚠️ Potential issue | 🟡 Minor

Add matching Geometry 14 narrative block (still missing).

Geometry 13 now has detailed semantics, but Geometry 14 still lacks a comparable explanation (coordinate convention, real-(Y_l^m) basis, and sph_har_coeff(l,m) meaning), which makes configuration error-prone.

📘 Suggested doc addition
 **Geometry 13: 2D modal (Fourier):**  
 ...
 - **Exponential form** (`modal_use_exp_form` true):  
   \f$R_{\mathrm{boundary}} = \mathrm{radius} \times \exp\bigl( \sum_n [\ldots] \bigr)\f$.  
   Coefficients are relative (dimensionless); the sum scales the radius.
+
+**Geometry 14: 3D spherical harmonic:**  
+Boundary is defined relative to centroid using \f$\theta=\arccos(z/r)\f$ and \f$\phi=\mathrm{atan2}(y,x)\f$, with  
+\f$R_{\mathrm{boundary}}=\mathrm{radius}+\sum_{l=0}^{5}\sum_{m=-l}^{l}\mathtt{sph\_har\_coeff}(l,m)\,Y_l^m(\theta,\phi)\f$.  
+Uses the real-\f$Y_l^m\f$ basis; coefficients are absolute (length units).
src/pre_process/m_check_patches.fpp (1)

253-264: ⚠️ Potential issue | 🟡 Minor

Validate modal_r_min when clipping is enabled.

When modal_clip_r_to_min is true, modal_r_min should be explicitly validated (set and non-negative) to prevent ambiguous/invalid clipping behavior.

🛠️ Suggested validation patch
 impure subroutine s_check_2d_modal_patch_geometry(patch_id)
@@
         @:PROHIBIT(f_is_default(patch_icpp(patch_id)%x_centroid), "2D modal patch "//trim(iStr)//": x_centroid must be set")
         @:PROHIBIT(f_is_default(patch_icpp(patch_id)%y_centroid), "2D modal patch "//trim(iStr)//": y_centroid must be set")
+        if (patch_icpp(patch_id)%modal_clip_r_to_min) then
+            @:PROHIBIT(f_is_default(patch_icpp(patch_id)%modal_r_min), &
+                "2D modal patch "//trim(iStr)//": modal_r_min must be set when modal_clip_r_to_min is true")
+            @:PROHIBIT(patch_icpp(patch_id)%modal_r_min < 0._wp, &
+                "2D modal patch "//trim(iStr)//": modal_r_min must be non-negative")
+        end if
src/pre_process/m_icpp_patches.fpp (1)

1093-1096: ⚠️ Potential issue | 🟠 Major

Restore standard post-assignment hooks in the new modal/spherical-harmonic branches.

Line 1095 and Line 1155 assign primitives but skip the usual @:analytical()/@:Hardcoded*() override path used by other geometries, and skip the local patch ownership update pattern after full assignment. This makes geometries 13/14 behavior inconsistent with existing patch routines.

🛠️ Proposed fix
 subroutine s_icpp_2d_modal(patch_id, patch_id_fp, q_prim_vf)
+        @:HardcodedDimensionsExtrusion()
+        @:Hardcoded2DVariables()
@@
                 if ((r <= R_boundary .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, 0))) &
                     .or. patch_id_fp(i, j, 0) == smooth_patch_id) then
                     call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
+                    @:analytical()
+                    if (patch_icpp(patch_id)%hcid /= dflt_int) then
+                        @:Hardcoded2D()
+                    end if
+                    if (1._wp - eta < sgm_eps) patch_id_fp(i, j, 0) = patch_id
                 end if
             end do
         end do
+        @:HardcodedDellacation()
     end subroutine s_icpp_2d_modal
 
 subroutine s_icpp_3d_spherical_harmonic(patch_id, patch_id_fp, q_prim_vf)
+        @:HardcodedDimensionsExtrusion()
+        @:Hardcoded3DVariables()
@@
                     if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
                         .or. patch_id_fp(i, j, k) == smooth_patch_id) then
                         call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
+                        @:analytical()
+                        if (patch_icpp(patch_id)%hcid /= dflt_int) then
+                            @:Hardcoded3D()
+                        end if
+                        if (1._wp - eta_local < sgm_eps) patch_id_fp(i, j, k) = patch_id
                     end if
                 end do
             end do
         end do
+        @:HardcodedDellacation()
     end subroutine s_icpp_3d_spherical_harmonic

Also applies to: 1153-1156

🧹 Nitpick comments (2)
src/common/m_constants.fpp (1)

18-30: Please run full target tests for this src/common change.

Since these constants are shared broadly, run ./mfc.sh test -j 8 to cover pre_process, simulation, and post_process before merge.

Based on learnings: Code in src/common/ affects all three executables (pre_process, simulation, post_process) and must be tested thoroughly.

src/common/m_derived_types.fpp (1)

229-237: Please run the full 3-target test suite for this src/common schema change.

This type expansion impacts shared code paths across executables; run ./mfc.sh test -j 8 before merge.

As per coding guidelines: "For changes to src/common/, test ALL three targets by running ./mfc.sh test -j 8 since src/common/ is used by all three executables".


ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c806be and e85160c.

📒 Files selected for processing (21)
  • docs/documentation/case.md
  • examples/2D_patch_modal_shape/case.py
  • examples/2D_patch_modal_shape_exp/case.py
  • examples/3D_patch_spherical_harmonic/case.py
  • src/common/m_constants.fpp
  • src/common/m_derived_types.fpp
  • src/common/m_helper.fpp
  • src/pre_process/m_check_patches.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/pre_process/m_icpp_patches.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • tests/1967AE06/golden-metadata.txt
  • tests/1967AE06/golden.txt
  • tests/3F864CFF/golden-metadata.txt
  • tests/3F864CFF/golden.txt
  • tests/87FBC893/golden-metadata.txt
  • tests/87FBC893/golden.txt
  • toolchain/mfc/case.py
  • toolchain/mfc/params/definitions.py
  • toolchain/mfc/params/descriptions.py
  • toolchain/mfc/params_tests/test_registry.py

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a dedicated 2D modal (Fourier) patch (geometry 13) and replaces the previous 3D spherical-harmonic patch with an explicit real-spherical-harmonics implementation (geometry 14), wiring both through the parameter registry, MPI broadcast, defaults, examples, and documentation.

Changes:

  • Add geometry 13 (2D modal Fourier) with per-mode fourier_cos/sin and options for exponential form + radius clipping.
  • Replace geometry 14 implementation with explicit real spherical harmonics driven by sph_har_coeff(l,m) coefficients.
  • Update toolchain parameter registry/descriptions, pre_process patch validation/initialization, docs, and add example/test fixtures.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
toolchain/mfc/params_tests/test_registry.py Bumps registry-size expectation to account for newly registered params.
toolchain/mfc/params/descriptions.py Adds human-readable descriptions for the new geometry 13/14 inputs.
toolchain/mfc/params/definitions.py Registers new patch_icpp parameters for Fourier modes and spherical-harmonic coefficients.
toolchain/mfc/case.py Updates dimensional patch-type mapping so geometry 13 is 2D-only and 14 is 3D-only.
tests/87FBC893/golden-metadata.txt Adds generated golden metadata fixture for a new/updated test case.
tests/3F864CFF/golden-metadata.txt Adds generated golden metadata fixture for a new/updated test case.
tests/1967AE06/golden.txt Adds golden output data for a new/updated regression test.
tests/1967AE06/golden-metadata.txt Adds generated golden metadata fixture for the regression test.
src/pre_process/m_mpi_proxy.fpp Broadcasts the new patch components (fourier_*, sph_har_coeff, modal flags) over MPI.
src/pre_process/m_icpp_patches.fpp Adds geometry 13 initializer and replaces geometry 14 initializer routine.
src/pre_process/m_global_parameters.fpp Initializes defaults for the new patch parameters.
src/pre_process/m_check_patches.fpp Adds geometry 13 validation and updates geometry 14 validation entry point.
src/common/m_helper.fpp Introduces real_ylm and associated Legendre logic; removes old spherical-harmonic helpers.
src/common/m_derived_types.fpp Extends ic_patch_parameters with Fourier mode arrays and sph_har_coeff(l,m).
src/common/m_constants.fpp Adds max_2d_fourier_modes, max_sph_harm_degree, and small_radius.
examples/3D_patch_spherical_harmonic/case.py New example demonstrating geometry 14 with explicit (l,m) coefficients.
examples/2D_patch_modal_shape_exp/case.py New example demonstrating geometry 13 in exponential form.
examples/2D_patch_modal_shape/case.py New example demonstrating geometry 13 in additive + clipping form.
docs/documentation/case.md Updates patch-geometry docs and adds explicit documentation for geometry 13/14 inputs.

@:PROHIBIT(p > 0, "2D modal patch "//trim(iStr)//": p must be zero")
@:PROHIBIT(patch_icpp(patch_id)%radius <= 0._wp, "2D modal patch "//trim(iStr)//": radius must be greater than zero")
@:PROHIBIT(f_is_default(patch_icpp(patch_id)%x_centroid), "2D modal patch "//trim(iStr)//": x_centroid must be set")
@:PROHIBIT(f_is_default(patch_icpp(patch_id)%y_centroid), "2D modal patch "//trim(iStr)//": y_centroid must be set")
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s_check_2d_modal_patch_geometry validates radius/centroid and dimensions, but it doesn’t validate modal_r_min when modal_clip_r_to_min is enabled. Since s_icpp_2d_modal uses modal_r_min in a max() clamp, a non-positive value would make the option ineffective or confusing. Please add a PROHIBIT to require modal_r_min > 0 when modal_clip_r_to_min is true (and consider also rejecting modal_clip_r_to_min when modal_use_exp_form is true, since it has no effect in that mode).

Suggested change
@:PROHIBIT(f_is_default(patch_icpp(patch_id)%y_centroid), "2D modal patch "//trim(iStr)//": y_centroid must be set")
@:PROHIBIT(f_is_default(patch_icpp(patch_id)%y_centroid), "2D modal patch "//trim(iStr)//": y_centroid must be set")
@:PROHIBIT(patch_icpp(patch_id)%modal_clip_r_to_min .and. &
patch_icpp(patch_id)%modal_r_min <= 0._wp, &
"2D modal patch "//trim(iStr)//": modal_r_min must be greater than zero when modal_clip_r_to_min is enabled")
@:PROHIBIT(patch_icpp(patch_id)%modal_clip_r_to_min .and. &
patch_icpp(patch_id)%modal_use_exp_form, &
"2D modal patch "//trim(iStr)//": modal_clip_r_to_min is not allowed when modal_use_exp_form is enabled (it has no effect)")

Copilot uses AI. Check for mistakes.
end if
if ((r <= R_boundary .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, 0))) &
.or. patch_id_fp(i, j, 0) == smooth_patch_id) then
call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In s_icpp_2d_modal, after calling s_assign_patch_primitive_variables the routine does not run the usual @:analytical()/hardcoded IC hooks and does not update patch_id_fp when eta≈1. Other patch geometries do both (so later alter_patch/smoothing logic can work correctly). Please add the same post-assignment steps here (and base the patch_id_fp update on eta/sgm_eps as in other 2D patches).

Suggested change
call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
@:analytical()
@:hardcodedIC_2D()
if (eta > 1._wp - sgm_eps) then
patch_id_fp(i, j, 0) = patch_id
end if

Copilot uses AI. Check for mistakes.
Comment on lines +1151 to 1156
eta_local = 0.5_wp + 0.5_wp*tanh(smooth_coeff/min(dx, dy, dz)*(R_surface - r))
end if
if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
.or. patch_id_fp(i, j, k) == smooth_patch_id) then
call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
end if
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In s_icpp_3d_spherical_harmonic, after s_assign_patch_primitive_variables the routine currently skips the standard @:analytical()/hardcoded IC handling and never updates patch_id_fp when eta_local≈1. This differs from other 3D patch initializers and can break later patch alternation/smoothing behavior. Please add the same post-assignment hooks and patch_id_fp bookkeeping update (using eta_local and sgm_eps).

Copilot uses AI. Check for mistakes.
Comment on lines +1153 to 1156
if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
.or. patch_id_fp(i, j, k) == smooth_patch_id) then
call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
end if
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Sph-harm patch skips analytical ic 🐞 Bug ✓ Correctness

Geometry 14 (s_icpp_3d_spherical_harmonic) assigns patch primitive variables but never calls the
@:analytical() / @:Hardcoded3D() hooks used by other 3D patches, so analytical expressions (and HCID
overrides) generated by the toolchain won’t be applied for this geometry.
Agent Prompt
### Issue description
Geometry 14’s patch routine (`s_icpp_3d_spherical_harmonic`) assigns primitive variables but does not invoke the fypp hook `@:analytical()` (and does not provide the usual `hcid` hardcoded hook). This means toolchain-generated analytical IC expressions for geometry 14 are never applied.

### Issue Context
`toolchain/mfc/case.py` generates a fypp macro `#:def analytical()` that writes analytical expressions into `q_prim_vf` for a given `patch_id`. Existing 3D patch routines invoke this macro via `@:analytical()` immediately after `s_assign_patch_primitive_variables`.

### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1150-1156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@sbryngelson sbryngelson marked this pull request as draft March 3, 2026 02:43
@sbryngelson sbryngelson marked this pull request as ready for review March 11, 2026 19:14
@qodo-code-review
Copy link
Contributor

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Add 2D modal (Fourier) patch (geometry 13) and fix 3D spherical harmonic patch (geometry 14)

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add geometry 13: 2D modal (Fourier) patch with per-mode amplitudes
  - Supports additive and exponential forms with optional radius clipping
• Fix geometry 14: Replace buggy spherical harmonic with robust real Y_lm implementation
  - Uses explicit sph_har_coeff(l,m) coefficients (l=0..5, m=-l..l)
  - Adds safe theta/phi computation near patch centroid
• Refactor spherical harmonic math helpers with domain guards
  - Replace unassociated_legendre and spherical_harmonic_func with real_ylm
  - Improve associated_legendre with input validation and numerical safety
• Add constants, parameter registry, validation, and three example cases
Diagram
flowchart LR
  A["Geometry 13<br/>2D Modal Fourier"] -->|"Fourier series<br/>per mode"| B["Boundary radius<br/>R(theta)"]
  C["Geometry 14<br/>3D Spherical Harmonic"] -->|"Real Y_lm<br/>coefficients"| D["Surface radius<br/>R(theta,phi)"]
  B -->|"Additive or<br/>exponential form"| E["Patch shape"]
  D -->|"Safe theta/phi<br/>computation"| E
  F["Math helpers<br/>real_ylm, associated_legendre"] -->|"Domain guards<br/>numerical safety"| D
Loading

Grey Divider

File Changes

1. src/pre_process/m_icpp_patches.fpp ✨ Enhancement +101/-131

Implement geometry 13 and 14 patch routines

src/pre_process/m_icpp_patches.fpp


2. src/common/m_helper.fpp 🐞 Bug fix +47/-54

Replace spherical harmonic functions with real_ylm

src/common/m_helper.fpp


3. src/pre_process/m_check_patches.fpp ✨ Enhancement +20/-16

Add validation for geometry 13 and 14 patches

src/pre_process/m_check_patches.fpp


View more (18)
4. src/common/m_constants.fpp ✨ Enhancement +3/-0

Add max mode/degree limits and small radius cutoff

src/common/m_constants.fpp


5. src/pre_process/m_mpi_proxy.fpp ✨ Enhancement +7/-1

Broadcast new patch parameters for geometry 13 and 14

src/pre_process/m_mpi_proxy.fpp


6. src/common/m_derived_types.fpp ✨ Enhancement +10/-1

Add fourier and spherical harmonic coefficient arrays

src/common/m_derived_types.fpp


7. src/pre_process/m_global_parameters.fpp ✨ Enhancement +6/-0

Initialize new patch parameters to defaults

src/pre_process/m_global_parameters.fpp


8. examples/3D_patch_spherical_harmonic/case.py 🧪 Tests +145/-0

Add 3D spherical harmonic example with geometry 14

examples/3D_patch_spherical_harmonic/case.py


9. examples/2D_patch_modal_shape/case.py 🧪 Tests +124/-0

Add 2D modal additive form example with geometry 13

examples/2D_patch_modal_shape/case.py


10. examples/2D_patch_modal_shape_exp/case.py 🧪 Tests +127/-0

Add 2D modal exponential form example with geometry 13

examples/2D_patch_modal_shape_exp/case.py


11. toolchain/mfc/params/descriptions.py 📝 Documentation +6/-0

Add parameter descriptions for geometry 13 and 14

toolchain/mfc/params/descriptions.py


12. toolchain/mfc/params/definitions.py ✨ Enhancement +11/-0

Register Fourier and spherical harmonic parameters

toolchain/mfc/params/definitions.py


13. toolchain/mfc/case.py 🐞 Bug fix +2/-2

Update geometry dimension mapping for geometry 13

toolchain/mfc/case.py


14. toolchain/mfc/params_tests/test_registry.py 🧪 Tests +1/-1

Update parameter count assertion for new parameters

toolchain/mfc/params_tests/test_registry.py


15. docs/documentation/case.md 📝 Documentation +17/-4

Document geometry 13 and 14 with formulas and parameters

docs/documentation/case.md


16. tests/87FBC893/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for geometry 13 and 14 validation

tests/87FBC893/golden-metadata.txt


17. tests/3F864CFF/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for geometry 13 and 14 validation

tests/3F864CFF/golden-metadata.txt


18. tests/1967AE06/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for geometry 13 and 14 validation

tests/1967AE06/golden-metadata.txt


19. tests/1967AE06/golden.txt Additional files +14/-0

...

tests/1967AE06/golden.txt


20. tests/3F864CFF/golden.txt Additional files +14/-0

...

tests/3F864CFF/golden.txt


21. tests/87FBC893/golden.txt Additional files +16/-0

...

tests/87FBC893/golden.txt


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 11, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. real_ylm missing f_ prefix 📘 Rule violation ✓ Correctness ⭐ New
Description
A new public function real_ylm is introduced without the required f_ naming prefix for public
functions. This breaks the project’s Fortran public API naming convention and can cause inconsistent
interfaces.
Code

src/common/m_helper.fpp[38]

+              real_ylm, &
Evidence
PR Compliance ID 8 requires public functions to be named with an f_ prefix; the PR exports
real_ylm publicly and defines it as function real_ylm(...), violating that convention.

CLAUDE.md
src/common/m_helper.fpp[20-44]
src/common/m_helper.fpp[526-548]
src/pre_process/m_icpp_patches.fpp[1143-1148]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new public function `real_ylm` was added but does not follow the required `f_` prefix convention for public functions.

## Issue Context
`real_ylm` is exported from `m_helper` and is called from `m_icpp_patches`, so any rename must update both the public export list and all call sites.

## Fix Focus Areas
- src/common/m_helper.fpp[20-44]
- src/common/m_helper.fpp[526-548]
- src/pre_process/m_icpp_patches.fpp[1143-1148]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Sph-harm patch skips analytical IC 🐞 Bug ✓ Correctness
Description
Geometry 14 (s_icpp_3d_spherical_harmonic) assigns patch primitive variables but never calls the
@:analytical() / @:Hardcoded3D() hooks used by other 3D patches, so analytical expressions (and HCID
overrides) generated by the toolchain won’t be applied for this geometry.
Code

src/pre_process/m_icpp_patches.fpp[R1153-1156]

+                    if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
+                        .or. patch_id_fp(i, j, k) == smooth_patch_id) then
+                        call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
                   end if
Evidence
The toolchain emits the analytical() fypp macro to set q_prim_vf for analytical patches; 3D
patches generally call @:analytical() after s_assign_patch_primitive_variables. The new 3D
spherical harmonic routine omits this call, so analytical patch expressions (and optional HCID
overrides) are skipped for geometry 14.

src/pre_process/m_icpp_patches.fpp[1150-1156]
toolchain/mfc/case.py[219-227]
toolchain/mfc/case.py[145-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Geometry 14’s patch routine (`s_icpp_3d_spherical_harmonic`) assigns primitive variables but does not invoke the fypp hook `@:analytical()` (and does not provide the usual `hcid` hardcoded hook). This means toolchain-generated analytical IC expressions for geometry 14 are never applied.
### Issue Context
`toolchain/mfc/case.py` generates a fypp macro `#:def analytical()` that writes analytical expressions into `q_prim_vf` for a given `patch_id`. Existing 3D patch routines invoke this macro via `@:analytical()` immediately after `s_assign_patch_primitive_variables`.
### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1150-1156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Unbounded exp radius 🐞 Bug ⛯ Reliability ⭐ New
Description
In s_icpp_2d_modal, the exponential form computes R_boundary = radius*exp(sum_series) with no
bound/finite checks, so sufficiently large Fourier coefficients can overflow exp() and make the
modal patch cover the entire domain. The input validator for geometry 13 does not constrain Fourier
coefficients to prevent this failure mode.
Code

src/pre_process/m_icpp_patches.fpp[R1081-1083]

+                if (patch_icpp(patch_id)%modal_use_exp_form) then
+                    R_boundary = patch_icpp(patch_id)%radius*exp(sum_series)
+                else
Evidence
The exponential boundary radius is computed directly from exp(sum_series) without any guard, and
the geometry-13 check only enforces dimension/centroid/radius constraints (no coefficient bounds),
so overflow is reachable via user input.

src/pre_process/m_icpp_patches.fpp[1076-1083]
src/pre_process/m_check_patches.fpp[253-264]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`geometry=13` in exponential form computes `R_boundary = radius*exp(sum_series)` without guarding the exponent. Large user-provided Fourier coefficients can overflow `exp()`, producing an infinite boundary radius and causing the patch to be applied everywhere.

### Issue Context
This affects `s_icpp_2d_modal` when `patch_icpp(patch_id)%modal_use_exp_form` is enabled.

### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1076-1083]
- src/pre_process/m_check_patches.fpp[253-264]

### Suggested fix
- Compute a safe maximum exponent at runtime, e.g. `max_exp = log(huge(1._wp))` (and optionally a symmetric negative bound), then clamp: `sum_series = min(max(sum_series, -max_exp), max_exp)` before `exp(sum_series)`.
- Alternatively, detect `sum_series &gt; max_exp` and `call s_mpi_abort(...)` with a message explaining which patch and which parameter caused overflow.
- Consider adding a validation rule in `s_check_2d_modal_patch_geometry` when `modal_use_exp_form` is true (e.g., ensure all Fourier coefficients are finite and their absolute sum is below a conservative threshold).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Negative harmonic radius 🐞 Bug ✓ Correctness ⭐ New
Description
In s_icpp_3d_spherical_harmonic, R_surface is formed as radius + Σ coeff(l,m)*Y_lm and is used
directly in r <= R_surface and smoothing, but is never clamped to be non-negative. If coefficients
drive R_surface below zero, the patch can silently disappear (no cells satisfy r <= R_surface)
despite radius > 0 passing validation.
Code

src/pre_process/m_icpp_patches.fpp[R1143-1149]

+                    R_surface = patch_icpp(patch_id)%radius
+                    do ll = 0, max_sph_harm_degree
+                        do mm = -ll, ll
+                            if (patch_icpp(patch_id)%sph_har_coeff(ll, mm) == 0._wp) cycle
+                            R_surface = R_surface + patch_icpp(patch_id)%sph_har_coeff(ll, mm)*real_ylm(theta, phi, ll, mm)
+                        end do
+                    end do
Evidence
R_surface is accumulated from coefficients and then immediately used as the boundary radius. The
geometry-14 validator only requires a positive base radius and centroids, so negative effective
radii are not prevented or corrected.

src/pre_process/m_icpp_patches.fpp[1143-1155]
src/pre_process/m_check_patches.fpp[266-276]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`geometry=14` computes an effective surface radius `R_surface` from a spherical-harmonic sum and uses it directly in the inside/outside test. If the sum produces `R_surface &lt; 0`, the patch may apply to zero cells (silent disappearance) even though `radius &gt; 0` passes input validation.

### Issue Context
This is in `s_icpp_3d_spherical_harmonic`, after accumulating `R_surface` from `sph_har_coeff`.

### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1143-1155]
- src/pre_process/m_check_patches.fpp[266-276]

### Suggested fix
- After the `(ll,mm)` accumulation loop, add a defensive clamp such as:
 - `R_surface = max(R_surface, 0._wp)` (or `small_radius` if you want strictly-positive).
- Optionally (if you want to treat this as invalid input), detect `R_surface &lt; 0` and abort with a message including the patch id.
- Consider documenting the clamp behavior in docs/examples if implemented.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. ic_patch_parameters members indentation 📘 Rule violation ✓ Correctness
Description
New derived-type members for geometries 13/14 are indented with 8 spaces instead of the required
2-space indentation. This violates the Fortran style requirement for modified code blocks.
Code

src/common/m_derived_types.fpp[R229-236]

+        ! Geometry 13 (2D modal Fourier): fourier_cos(n), fourier_sin(n) for mode n
+        real(wp), dimension(1:max_2d_fourier_modes) :: fourier_cos, fourier_sin
+        logical :: modal_clip_r_to_min !< When true, clip boundary radius: R(theta) = max(R(theta), modal_r_min) (Non-exp form only)
+        real(wp) :: modal_r_min        !< Minimum boundary radius when modal_clip_r_to_min is true (Non-exp form only)
+        logical :: modal_use_exp_form  !< When true, boundary = radius*exp(Fourier series)
+
+        ! Geometry 14 (3D spherical harmonic): sph_har_coeff(l,m) for real Y_lm
+        real(wp), dimension(0:max_sph_harm_degree, -max_sph_harm_degree:max_sph_harm_degree) :: sph_har_coeff
Evidence
PR Compliance ID 9 requires 2-space indentation in new/modified Fortran code. The newly added
members under type ic_patch_parameters are indented with 8 spaces, as shown in the cited excerpt.

CLAUDE.md
src/common/m_derived_types.fpp[229-236]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New derived-type members for the geometry 13/14 patch parameters do not follow the required 2-space indentation.
## Issue Context
Compliance requires 2-space indentation for new/modified Fortran code.
## Fix Focus Areas
- src/common/m_derived_types.fpp[229-237]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

6. Duplicated mode limits 🐞 Bug ⛯ Reliability ⭐ New
Description
The maximum Fourier mode and spherical-harmonic degree limits are defined in Fortran constants but
are duplicated as hardcoded ranges in the Python parameter registry. If the Fortran limits change,
the toolchain registry will desynchronize and either reject valid inputs or fail to expose new
coefficients.
Code

toolchain/mfc/params/definitions.py[R1046-1056]

+        # 2D modal (geometry 13): Fourier modes and options
+        for j in range(1, 11):
+            _r(f"{px}fourier_cos({j})", REAL)
+            _r(f"{px}fourier_sin({j})", REAL)
+        _r(f"{px}modal_clip_r_to_min", LOG)
+        _r(f"{px}modal_r_min", REAL)
+        _r(f"{px}modal_use_exp_form", LOG)
+        # 3D spherical harmonic (geometry 14): coeffs (l, m), l=0..5, m=-l..l
+        for ll in range(0, 6):
+            for mm in range(-ll, ll + 1):
+                _r(f"{px}sph_har_coeff({ll},{mm})", REAL)
Evidence
The Fortran implementation relies on max_2d_fourier_modes and max_sph_harm_degree, while the
toolchain enumerates parameters using fixed numeric ranges (10 modes and degree 5) rather than
referencing a single shared source of truth.

src/common/m_constants.fpp[29-30]
toolchain/mfc/params/definitions.py[1046-1056]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Mode/degree limits are duplicated: Fortran defines `max_2d_fourier_modes` and `max_sph_harm_degree`, while the Python registry hardcodes `range(1, 11)` and `range(0, 6)`. Future edits can desync these and break input validation / discoverability.

### Issue Context
The Python toolchain defines which parameters are allowed/validated.

### Fix Focus Areas
- toolchain/mfc/params/definitions.py[1046-1056]
- src/common/m_constants.fpp[29-30]

### Suggested fix
- Define `MAX_2D_FOURIER_MODES = 10` and `MAX_SPH_HARM_DEGREE = 5` in a dedicated toolchain constants module and use those in loops.
- Add a small unit test asserting the registry exposes exactly the expected coefficient indices (so changes are caught intentionally).
- Add a comment pointing to `src/common/m_constants.fpp` to make synchronization explicit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@github-actions
Copy link

Claude Code Review

Head SHA: a53c1a8
Files changed: 21

Files
  • docs/documentation/case.md
  • examples/2D_patch_modal_shape/case.py (new)
  • examples/2D_patch_modal_shape_exp/case.py (new)
  • examples/3D_patch_spherical_harmonic/case.py (new)
  • src/common/m_constants.fpp
  • src/common/m_derived_types.fpp
  • src/common/m_helper.fpp
  • src/pre_process/m_check_patches.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/pre_process/m_icpp_patches.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • tests/1967AE06/, tests/3F864CFF/, tests/87FBC893/ (golden files, 3 new test cases)
  • toolchain/mfc/case.py, params/definitions.py, params/descriptions.py, params_tests/test_registry.py

Summary

  • Replaces the old (buggy, undocumented) geometry-14 spherical-harmonic routine with a clean real-Y_lm implementation using explicit sph_har_coeff(l,m) coefficients.
  • Adds geometry-13: 2D Fourier-modal patch with additive and exponential forms, optional radius clipping, and 10 Fourier modes.
  • New fields are correctly initialized, MPI-broadcast, and validated; new examples and three golden test cases are provided.

Findings

1. Missing lower-bound guard on R_surface in 3D path —

In s_icpp_2d_modal the boundary radius is clipped:

R_boundary = max(R_boundary, 0._wp)          ! additive form

But in s_icpp_3d_spherical_harmonic there is no such guard:

R_surface = patch_icpp(patch_id)%radius      ! then += sum sph_har_coeff*Y_lm

If large negative coefficients push R_surface < 0, the condition r <= R_surface is never true (since r >= 0) and the patch assigns no cells silently. This asymmetry with the 2D path seems unintentional. Recommend either clipping R_surface = max(R_surface, 0._wp) or adding a @:PROHIBIT in s_check_3d_spherical_harmonic_patch_geometry documenting the expected range.

2. Missing Condon-Shortley phase in real_ylm for m > 0

The old spherical_harmonic_func included (-1)^m_order explicitly:

! old code
Y = (-1._wp)**m_order * sqrt(2._wp) * prefactor * associated_legendre(...) * cos(m_order*phi)

The new real_ylm omits it:

! new code (m > 0 branch)
Y = prefac * sqrt(2._wp) * associated_legendre(x, l, m_abs) * cos(m*phi)

Whether this is correct depends on whether associated_legendre already encodes the full Condon-Shortley phase. The m_order == l base case does include (-1)**l but the general recurrence does not accumulate a separate (-1)^m factor. For Y_1^1, the code produces -sqrt(3/4π) sin θ cos φ (due to the (-1)^1 in P_1^1), which matches the standard real-harmonic convention. Please verify a few higher-(l,m) values against a known table (e.g., DLMF 14.30 or SciPy sph_harm) to confirm correctness across all modes, especially odd-m terms above l=1.

3. Memory footprint of sph_har_coeff

sph_har_coeff is declared as (0:5, -5:5) = 66 real(wp) values per patch. With num_patches_max = 1000 (line in m_constants.fpp), this adds ~528 KB per MPI rank for the patch array. Not a problem in practice but worth being aware of at extreme patch counts.

4. Base-case conditions in associated_legendre

After the new early-return guard (l < 0 .or. m_order < 0 .or. m_order > l), the first two base cases still read m_order <= 0 and l <= 0, which are now equivalent to == 0. These are not wrong, just slightly misleading. Consider tightening to == 0 for clarity.

5. mapped_weno absent from 3D example —

Both 2D examples set "mapped_weno": "T"; the 3D example does not. Minor stylistic inconsistency.


No issues found with

  • MPI broadcast coverage: fourier_cos, fourier_sin, sph_har_coeff, modal_clip_r_to_min, modal_r_min, modal_use_exp_form are all correctly broadcast in m_mpi_proxy.fpp.
  • All 4 required parameter-addition locations updated (definitions, derived types, global parameters, descriptions).
  • Smoothing (eta/eta_local) handled consistently with existing patch conventions.
  • Geometry-13 correctly guarded to 2D (p == 0) and geometry-14 to 3D (p > 0) in m_check_patches.fpp.
  • Division-by-zero near centroid guarded via small_radius in both new patch routines.

@github-actions
Copy link

Claude Code Review

Head SHA: a53c1a8
Files changed: 21 — docs, examples (3), src/common (3), src/pre_process (4), tests (6), toolchain (4)

Summary

  • Replaces the broken geometry 14 spherical-harmonic patch with a correct, coefficient-driven real Y_lm surface
  • Adds geometry 13 as a new 2D Fourier-modal patch (additive and exponential forms)
  • Cleans up associated_legendre / introduces real_ylm, adding domain guards against division-by-zero
  • New MPI broadcasts, parameter definitions, and input validation are included
  • Three new test golden files are committed

Findings

🔴 Critical — src/pre_process/m_start_up.fpp not updated (namelist bindings missing)

src/pre_process/m_start_up.fpp is absent from the changed-file list. The Fortran namelist declaration for every new parameter (fourier_cos, fourier_sin, modal_clip_r_to_min, modal_r_min, modal_use_exp_form, sph_har_coeff) must be added there per the 3-location rule in CLAUDE.md. Without this, Fortran silently ignores these fields when reading the .inp file — the patch becomes a plain circle/sphere regardless of what the user set. The example cases and tests will appear to pass (defaults kick in), but the feature does not actually work.

Fix: Add to the &user_inputs namelist in src/pre_process/m_start_up.fpp:

patch_icpp(i)%fourier_cos, patch_icpp(i)%fourier_sin,
patch_icpp(i)%modal_clip_r_to_min, patch_icpp(i)%modal_r_min,
patch_icpp(i)%modal_use_exp_form,
patch_icpp(i)%sph_har_coeff

(This also makes the MPI broadcasts in m_mpi_proxy.fpp meaningful — right now they broadcast default zeros.)


🟡 Medium — Test cases not registered in toolchain/mfc/test/cases.py

Three new golden files are committed (1967AE06, 3F864CFF, 87FBC893) but toolchain/mfc/test/cases.py is not in the diff. Golden-file UUIDs are CRC32 hashes of a test's trace string generated by cases.py. If matching entries don't exist there, ./mfc.sh test will never discover or run them, and the golden files are dead code.

Please confirm or add the corresponding cases.py entries so CI actually executes these tests.


🟡 Medium — R_surface can go negative for geometry 14 (no clipping)

In s_icpp_3d_spherical_harmonic (m_icpp_patches.fpp, new code ~line 1003–1013):

R_surface = patch_icpp(patch_id)%radius
do ll = 0, max_sph_harm_degree
    do mm = -ll, ll
        R_surface = R_surface + sph_har_coeff(ll,mm)*real_ylm(theta,phi,ll,mm)
    end do
end do

If the Y_lm sum exceeds radius, R_surface goes negative and every cell satisfies r <= R_surface is false everywhere, silently producing a zero-size patch. Geometry 13 clips to max(R_boundary, 0._wp). Geometry 14 should at minimum clip to zero or emit a @:PROHIBIT / warning. Add validation in m_check_patches.fpp or clip in the loop.


🟢 Low / Observation — Condon–Shortley phase removed from real_ylm

The old spherical_harmonic_func applied (-1)**m_order for m > 0; the new real_ylm omits it (m_helper.fpp, ~line 584). This is a deliberate break with the old (buggy) implementation and aligns with the standard real-spherical-harmonic convention (the phase absorbed into the polynomial convention). The comment says "Condon–Shortley phase" but the choice of convention isn't explicitly documented. Since the PR says the old behavior was undocumented/broken, this is acceptable, but a one-line comment in real_ylm noting which convention is used (e.g., "no Condon-Shortley sign on Y, consistent with standard real Y_lm") would prevent future confusion.


What's done well

  • acos(min(1._wp, max(-1._wp, dz_loc/r))) correctly clamps the argument (avoids NaN at poles)
  • small_radius guard prevents division-by-zero at the centroid in both geometries
  • New fields are properly initialized in m_global_parameters.fpp and broadcast in m_mpi_proxy.fpp
  • m_check_patches.fpp enforces dimensionality constraints (n > 0, p == 0 for geometry 13)
  • Parameter definitions, descriptions, and the parameter count test are all updated

unassociated_legendre, &
associated_legendre, &
spherical_harmonic_func, &
real_ylm, &
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. real_ylm missing f_ prefix 📘 Rule violation ✓ Correctness

A new public function real_ylm is introduced without the required f_ naming prefix for public
functions. This breaks the project’s Fortran public API naming convention and can cause inconsistent
interfaces.
Agent Prompt
## Issue description
A new public function `real_ylm` was added but does not follow the required `f_` prefix convention for public functions.

## Issue Context
`real_ylm` is exported from `m_helper` and is called from `m_icpp_patches`, so any rename must update both the public export list and all call sites.

## Fix Focus Areas
- src/common/m_helper.fpp[20-44]
- src/common/m_helper.fpp[526-548]
- src/pre_process/m_icpp_patches.fpp[1143-1148]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@sbryngelson sbryngelson marked this pull request as draft March 11, 2026 20:56
@sbryngelson sbryngelson marked this pull request as ready for review March 12, 2026 02:15
@qodo-code-review
Copy link
Contributor

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Add 2D modal Fourier patch (geometry 13) and fix 3D spherical harmonic patch (geometry 14)

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add geometry 13: 2D modal (Fourier) patch with cos/sin modes (1–10)
  - Supports additive and exponential forms
  - Optional radius clipping and minimum radius enforcement
• Replace geometry 14: Fix 3D spherical harmonic patch with real Y_lm coefficients
  - Uses explicit sph_har_coeff(l,m) for l=0..5, m=-l..l
  - Eliminates division-by-zero near centroid with small_radius guard
• Refactor spherical harmonic math helpers
  - Replace unassociated_legendre and spherical_harmonic_func with real_ylm
  - Improve associated_legendre with domain guards and numerical stability
• Add three example cases and update parameter registry and documentation
Diagram
flowchart LR
  A["Geometry 13<br/>2D Modal Fourier"] -->|"Fourier series<br/>cos/sin modes"| B["Boundary radius<br/>R(theta)"]
  B -->|"Additive or<br/>Exponential"| C["2D patch shape"]
  D["Geometry 14<br/>3D Spherical Harmonic"] -->|"Real Y_lm<br/>coefficients"| E["Surface radius<br/>R(theta,phi)"]
  E -->|"Sum l,m<br/>contributions"| F["3D patch shape"]
  G["Math helpers"] -->|"real_ylm<br/>associated_legendre"| H["Robust<br/>computation"]
  H -->|"small_radius<br/>guard"| I["Avoid<br/>division-by-zero"]
Loading

Grey Divider

File Changes

1. src/pre_process/m_icpp_patches.fpp ✨ Enhancement +101/-131

Implement 2D modal and 3D spherical harmonic patches

src/pre_process/m_icpp_patches.fpp


2. src/common/m_helper.fpp 🐞 Bug fix +47/-54

Replace spherical harmonic math with real_ylm function

src/common/m_helper.fpp


3. src/pre_process/m_check_patches.fpp ✨ Enhancement +20/-16

Add validation for geometry 13 and 14 patches

src/pre_process/m_check_patches.fpp


View more (18)
4. src/common/m_constants.fpp ✨ Enhancement +3/-0

Add constants for modal and spherical harmonic limits

src/common/m_constants.fpp


5. src/pre_process/m_mpi_proxy.fpp ✨ Enhancement +7/-1

Broadcast new patch parameters for MPI communication

src/pre_process/m_mpi_proxy.fpp


6. src/common/m_derived_types.fpp ✨ Enhancement +10/-1

Add Fourier and spherical harmonic coefficient arrays

src/common/m_derived_types.fpp


7. src/pre_process/m_global_parameters.fpp ✨ Enhancement +6/-0

Initialize new modal and spherical harmonic parameters

src/pre_process/m_global_parameters.fpp


8. examples/3D_patch_spherical_harmonic/case.py 📝 Documentation +145/-0

Add 3D spherical harmonic patch example case

examples/3D_patch_spherical_harmonic/case.py


9. examples/2D_patch_modal_shape/case.py 📝 Documentation +124/-0

Add 2D modal Fourier patch additive form example

examples/2D_patch_modal_shape/case.py


10. examples/2D_patch_modal_shape_exp/case.py 📝 Documentation +127/-0

Add 2D modal Fourier patch exponential form example

examples/2D_patch_modal_shape_exp/case.py


11. toolchain/mfc/params/descriptions.py 📝 Documentation +6/-0

Add parameter descriptions for modal and spherical modes

toolchain/mfc/params/descriptions.py


12. toolchain/mfc/params/definitions.py ✨ Enhancement +11/-0

Register Fourier and spherical harmonic coefficient parameters

toolchain/mfc/params/definitions.py


13. toolchain/mfc/case.py ✨ Enhancement +2/-2

Update geometry dimension mapping for new patches

toolchain/mfc/case.py


14. toolchain/mfc/params_tests/test_registry.py 🧪 Tests +1/-1

Update parameter count assertion for new registry entries

toolchain/mfc/params_tests/test_registry.py


15. docs/documentation/case.md 📝 Documentation +17/-4

Document geometry 13 and 14 patch types and parameters

docs/documentation/case.md


16. tests/87FBC893/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for 2D modal patch with clipping

tests/87FBC893/golden-metadata.txt


17. tests/3F864CFF/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for 2D modal exponential form patch

tests/3F864CFF/golden-metadata.txt


18. tests/1967AE06/golden-metadata.txt 🧪 Tests +185/-0

Test metadata for 3D spherical harmonic patch

tests/1967AE06/golden-metadata.txt


19. tests/1967AE06/golden.txt Additional files +14/-0

...

tests/1967AE06/golden.txt


20. tests/3F864CFF/golden.txt Additional files +14/-0

...

tests/3F864CFF/golden.txt


21. tests/87FBC893/golden.txt Additional files +16/-0

...

tests/87FBC893/golden.txt


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 12, 2026

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Modal patch skips IC hooks 🐞 Bug ✓ Correctness ⭐ New
Description
s_icpp_2d_modal never executes the @:analytical() / hcid hardcoded-IC hooks after cell
assignment, so analytical patch expressions and hardcoded ICs are silently ignored for geometry 13.
Code

src/pre_process/m_icpp_patches.fpp[R1090-1096]

+                if (patch_icpp(patch_id)%smoothen) then
+                    eta = 0.5_wp + 0.5_wp*tanh(smooth_coeff/min(dx, dy)*(R_boundary - r))
+                end if
+                if ((r <= R_boundary .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, 0))) &
+                    .or. patch_id_fp(i, j, 0) == smooth_patch_id) then
+                    call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
+                end if
Evidence
Other patch geometries invoke @:analytical() (and @:Hardcoded2D() when hcid is set)
immediately after s_assign_patch_primitive_variables, but the new geometry 13 routine ends after
assignment and never calls these hooks. The toolchain explicitly includes geometry 13 in the set of
patch types that can be analytically code-generated, which relies on those hooks being executed
during patch application.

src/pre_process/m_icpp_patches.fpp[1046-1099]
src/pre_process/m_icpp_patches.fpp[1232-1238]
toolchain/mfc/case.py[141-149]
src/common/m_derived_types.fpp[286-288]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`src/pre_process/m_icpp_patches.fpp::s_icpp_2d_modal` assigns patch primitive variables but never calls the standard `@:analytical()` and `hcid`-gated hardcoded IC hooks that other patch geometries call after assignment. As a result, toolchain-generated analytical IC expressions and `patch_icpp(patch_id)%hcid` hardcoded IC behavior will be ignored for geometry 13.

### Issue Context
Other 2D/3D patch routines call `@:analytical()` immediately after `s_assign_patch_primitive_variables` and conditionally call `@:Hardcoded2D()`/`@:Hardcoded3D()` when `hcid` is set. Geometry 13 should behave consistently.

### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1046-1099]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Sph-harm patch skips analytical IC 🐞 Bug ✓ Correctness
Description
Geometry 14 (s_icpp_3d_spherical_harmonic) assigns patch primitive variables but never calls the
@:analytical() / @:Hardcoded3D() hooks used by other 3D patches, so analytical expressions (and HCID
overrides) generated by the toolchain won’t be applied for this geometry.
Code

src/pre_process/m_icpp_patches.fpp[R1153-1156]

+                    if ((r <= R_surface .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, k))) &
+                        .or. patch_id_fp(i, j, k) == smooth_patch_id) then
+                        call s_assign_patch_primitive_variables(patch_id, i, j, k, eta_local, q_prim_vf, patch_id_fp)
                   end if
Evidence
The toolchain emits the analytical() fypp macro to set q_prim_vf for analytical patches; 3D
patches generally call @:analytical() after s_assign_patch_primitive_variables. The new 3D
spherical harmonic routine omits this call, so analytical patch expressions (and optional HCID
overrides) are skipped for geometry 14.

src/pre_process/m_icpp_patches.fpp[1150-1156]
toolchain/mfc/case.py[219-227]
toolchain/mfc/case.py[145-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Geometry 14’s patch routine (`s_icpp_3d_spherical_harmonic`) assigns primitive variables but does not invoke the fypp hook `@:analytical()` (and does not provide the usual `hcid` hardcoded hook). This means toolchain-generated analytical IC expressions for geometry 14 are never applied.
### Issue Context
`toolchain/mfc/case.py` generates a fypp macro `#:def analytical()` that writes analytical expressions into `q_prim_vf` for a given `patch_id`. Existing 3D patch routines invoke this macro via `@:analytical()` immediately after `s_assign_patch_primitive_variables`.
### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1150-1156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. real_ylm missing f_ prefix 📘 Rule violation ✓ Correctness
Description
A new public function real_ylm is introduced without the required f_ naming prefix for public
functions. This breaks the project’s Fortran public API naming convention and can cause inconsistent
interfaces.
Code

src/common/m_helper.fpp[38]

+              real_ylm, &
Evidence
PR Compliance ID 8 requires public functions to be named with an f_ prefix; the PR exports
real_ylm publicly and defines it as function real_ylm(...), violating that convention.

CLAUDE.md
src/common/m_helper.fpp[20-44]
src/common/m_helper.fpp[526-548]
src/pre_process/m_icpp_patches.fpp[1143-1148]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new public function `real_ylm` was added but does not follow the required `f_` prefix convention for public functions.
## Issue Context
`real_ylm` is exported from `m_helper` and is called from `m_icpp_patches`, so any rename must update both the public export list and all call sites.
## Fix Focus Areas
- src/common/m_helper.fpp[20-44]
- src/common/m_helper.fpp[526-548]
- src/pre_process/m_icpp_patches.fpp[1143-1148]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. ic_patch_parameters members indentation 📘 Rule violation ✓ Correctness
Description
New derived-type members for geometries 13/14 are indented with 8 spaces instead of the required
2-space indentation. This violates the Fortran style requirement for modified code blocks.
Code

src/common/m_derived_types.fpp[R229-236]

+        ! Geometry 13 (2D modal Fourier): fourier_cos(n), fourier_sin(n) for mode n
+        real(wp), dimension(1:max_2d_fourier_modes) :: fourier_cos, fourier_sin
+        logical :: modal_clip_r_to_min !< When true, clip boundary radius: R(theta) = max(R(theta), modal_r_min) (Non-exp form only)
+        real(wp) :: modal_r_min        !< Minimum boundary radius when modal_clip_r_to_min is true (Non-exp form only)
+        logical :: modal_use_exp_form  !< When true, boundary = radius*exp(Fourier series)
+
+        ! Geometry 14 (3D spherical harmonic): sph_har_coeff(l,m) for real Y_lm
+        real(wp), dimension(0:max_sph_harm_degree, -max_sph_harm_degree:max_sph_harm_degree) :: sph_har_coeff
Evidence
PR Compliance ID 9 requires 2-space indentation in new/modified Fortran code. The newly added
members under type ic_patch_parameters are indented with 8 spaces, as shown in the cited excerpt.

CLAUDE.md
src/common/m_derived_types.fpp[229-236]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New derived-type members for the geometry 13/14 patch parameters do not follow the required 2-space indentation.
## Issue Context
Compliance requires 2-space indentation for new/modified Fortran code.
## Fix Focus Areas
- src/common/m_derived_types.fpp[229-237]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Unbounded exp radius 🐞 Bug ⛯ Reliability
Description
In s_icpp_2d_modal, the exponential form computes R_boundary = radius*exp(sum_series) with no
bound/finite checks, so sufficiently large Fourier coefficients can overflow exp() and make the
modal patch cover the entire domain. The input validator for geometry 13 does not constrain Fourier
coefficients to prevent this failure mode.
Code

src/pre_process/m_icpp_patches.fpp[R1081-1083]

+                if (patch_icpp(patch_id)%modal_use_exp_form) then
+                    R_boundary = patch_icpp(patch_id)%radius*exp(sum_series)
+                else
Evidence
The exponential boundary radius is computed directly from exp(sum_series) without any guard, and
the geometry-13 check only enforces dimension/centroid/radius constraints (no coefficient bounds),
so overflow is reachable via user input.

src/pre_process/m_icpp_patches.fpp[1076-1083]
src/pre_process/m_check_patches.fpp[253-264]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`geometry=13` in exponential form computes `R_boundary = radius*exp(sum_series)` without guarding the exponent. Large user-provided Fourier coefficients can overflow `exp()`, producing an infinite boundary radius and causing the patch to be applied everywhere.
### Issue Context
This affects `s_icpp_2d_modal` when `patch_icpp(patch_id)%modal_use_exp_form` is enabled.
### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1076-1083]
- src/pre_process/m_check_patches.fpp[253-264]
### Suggested fix
- Compute a safe maximum exponent at runtime, e.g. `max_exp = log(huge(1._wp))` (and optionally a symmetric negative bound), then clamp: `sum_series = min(max(sum_series, -max_exp), max_exp)` before `exp(sum_series)`.
- Alternatively, detect `sum_series &amp;gt; max_exp` and `call s_mpi_abort(...)` with a message explaining which patch and which parameter caused overflow.
- Consider adding a validation rule in `s_check_2d_modal_patch_geometry` when `modal_use_exp_form` is true (e.g., ensure all Fourier coefficients are finite and their absolute sum is below a conservative threshold).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Negative harmonic radius 🐞 Bug ✓ Correctness
Description
In s_icpp_3d_spherical_harmonic, R_surface is formed as radius + Σ coeff(l,m)*Y_lm and is used
directly in r  0 passing validation.
Code

src/pre_process/m_icpp_patches.fpp[R1143-1149]

+                    R_surface = patch_icpp(patch_id)%radius
+                    do ll = 0, max_sph_harm_degree
+                        do mm = -ll, ll
+                            if (patch_icpp(patch_id)%sph_har_coeff(ll, mm) == 0._wp) cycle
+                            R_surface = R_surface + patch_icpp(patch_id)%sph_har_coeff(ll, mm)*real_ylm(theta, phi, ll, mm)
+                        end do
+                    end do
Evidence
R_surface is accumulated from coefficients and then immediately used as the boundary radius. The
geometry-14 validator only requires a positive base radius and centroids, so negative effective
radii are not prevented or corrected.

src/pre_process/m_icpp_patches.fpp[1143-1155]
src/pre_process/m_check_patches.fpp[266-276]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`geometry=14` computes an effective surface radius `R_surface` from a spherical-harmonic sum and uses it directly in the inside/outside test. If the sum produces `R_surface &amp;lt; 0`, the patch may apply to zero cells (silent disappearance) even though `radius &amp;gt; 0` passes input validation.
### Issue Context
This is in `s_icpp_3d_spherical_harmonic`, after accumulating `R_surface` from `sph_har_coeff`.
### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1143-1155]
- src/pre_process/m_check_patches.fpp[266-276]
### Suggested fix
- After the `(ll,mm)` accumulation loop, add a defensive clamp such as:
- `R_surface = max(R_surface, 0._wp)` (or `small_radius` if you want strictly-positive).
- Optionally (if you want to treat this as invalid input), detect `R_surface &amp;lt; 0` and abort with a message including the patch id.
- Consider documenting the clamp behavior in docs/examples if implemented.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

7. Duplicated mode limits 🐞 Bug ⛯ Reliability
Description
The maximum Fourier mode and spherical-harmonic degree limits are defined in Fortran constants but
are duplicated as hardcoded ranges in the Python parameter registry. If the Fortran limits change,
the toolchain registry will desynchronize and either reject valid inputs or fail to expose new
coefficients.
Code

toolchain/mfc/params/definitions.py[R1046-1056]

+        # 2D modal (geometry 13): Fourier modes and options
+        for j in range(1, 11):
+            _r(f"{px}fourier_cos({j})", REAL)
+            _r(f"{px}fourier_sin({j})", REAL)
+        _r(f"{px}modal_clip_r_to_min", LOG)
+        _r(f"{px}modal_r_min", REAL)
+        _r(f"{px}modal_use_exp_form", LOG)
+        # 3D spherical harmonic (geometry 14): coeffs (l, m), l=0..5, m=-l..l
+        for ll in range(0, 6):
+            for mm in range(-ll, ll + 1):
+                _r(f"{px}sph_har_coeff({ll},{mm})", REAL)
Evidence
The Fortran implementation relies on max_2d_fourier_modes and max_sph_harm_degree, while the
toolchain enumerates parameters using fixed numeric ranges (10 modes and degree 5) rather than
referencing a single shared source of truth.

src/common/m_constants.fpp[29-30]
toolchain/mfc/params/definitions.py[1046-1056]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Mode/degree limits are duplicated: Fortran defines `max_2d_fourier_modes` and `max_sph_harm_degree`, while the Python registry hardcodes `range(1, 11)` and `range(0, 6)`. Future edits can desync these and break input validation / discoverability.
### Issue Context
The Python toolchain defines which parameters are allowed/validated.
### Fix Focus Areas
- toolchain/mfc/params/definitions.py[1046-1056]
- src/common/m_constants.fpp[29-30]
### Suggested fix
- Define `MAX_2D_FOURIER_MODES = 10` and `MAX_SPH_HARM_DEGREE = 5` in a dedicated toolchain constants module and use those in loops.
- Add a small unit test asserting the registry exposes exactly the expected coefficient indices (so changes are caught intentionally).
- Add a comment pointing to `src/common/m_constants.fpp` to make synchronization explicit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@github-actions
Copy link

Claude Code Review

Head SHA: a53c1a8
Files changed: 21

Changed files
  • docs/documentation/case.md
  • examples/2D_patch_modal_shape/case.py (new)
  • examples/2D_patch_modal_shape_exp/case.py (new)
  • examples/3D_patch_spherical_harmonic/case.py (new)
  • src/common/m_constants.fpp
  • src/common/m_derived_types.fpp
  • src/common/m_helper.fpp
  • src/pre_process/m_check_patches.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/pre_process/m_icpp_patches.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • toolchain/mfc/case.py
  • toolchain/mfc/params/definitions.py
  • toolchain/mfc/params/descriptions.py
  • toolchain/mfc/params_tests/test_registry.py
  • 6 test golden files (3 new UUIDs)

Summary

  • Replaces geometry 13 (previously deprecated) with a 2D Fourier-modal patch and rewrites geometry 14 as a clean real-spherical-harmonic surface patch.
  • New real_ylm helper replaces the old spherical_harmonic_func; associated_legendre is improved with domain guards and real-exponent P_l^l.
  • New parameters (fourier_cos/sin, sph_har_coeff, modal_*) are added to m_derived_types, m_global_parameters, definitions.py, and broadcast via MPI — clean structure.
  • Three new test golden files are present; examples are well-documented.
  • Two significant issues identified below.

Findings

1. Critical — src/pre_process/m_start_up.fpp not updated (4-location checklist violation)

Per CLAUDE.md, every new parameter must be added in 3 required locations (4 if it has constraints). Location 3 — the Fortran namelist binding in src/pre_process/m_start_up.fpp — is missing from this PR (it is not in the 21 changed files).

The new parameters (fourier_cos, fourier_sin, modal_clip_r_to_min, modal_r_min, modal_use_exp_form, sph_har_coeff) are declared in m_global_parameters.fpp and registered in definitions.py, but without a namelist entry Fortran cannot read user-supplied values from the .inp file. gfortran will emit a runtime error if any of these appear in the input; other compilers may silently ignore them. Either way, the Fourier/harmonic perturbations specified in the example cases (e.g. fourier_cos(1) = 0.2) will never take effect.

Action required: Add all new patch fields to the &user_inputs namelist in src/pre_process/m_start_up.fpp.


2. Correctness — real_ylm sign error for odd positive m

File: src/common/m_helper.fpp

The associated_legendre function returns P_l^m with the Condon–Shortley (CS) phase (the (-1)**l seed in P_l^l propagates the factor (-1)^m through the recurrence). In real_ylm, the m > 0 branch is:

Y = prefac*sqrt(2._wp)*associated_legendre(x, l, m_abs)*cos(m*phi)

Because associated_legendre already carries the CS phase (-1)^m, the result is:

Y_new = prefac * sqrt(2) * (-1)^m * P_l^m_Ferrers(x) * cos(m*phi)

The standard real spherical harmonic (DLMF 14.30) requires only P_l^m_Ferrers (without CS phase). The extra (-1)^m flips the sign for all odd positive m (e.g. m = 1, 3, 5). The old code intentionally included (-1)**m_order as a correction factor that cancelled the CS phase — removing it while keeping the CS-convention associated Legendre reintroduces the sign error.

To fix: Either (a) change associated_legendre to return the Ferrers normalization (no CS phase), or (b) restore the (-1)**m correction in the m > 0 branch of real_ylm:

! m > 0: cancel the (-1)^m from the CS convention in associated_legendre
Y = (-1)**m * prefac*sqrt(2._wp)*associated_legendre(x, l, m_abs)*cos(m*phi)

This affects all Y_l^m with odd positive m (Y_1^1, Y_2^1, Y_3^1, Y_3^3, …). The example case 3D_patch_spherical_harmonic/case.py sets non-zero sph_har_coeff(1,1), sph_har_coeff(2,1), etc., so the output shapes will be incorrect until this is fixed.


3. Minor — New test UUIDs but toolchain/mfc/test/cases.py not in PR

Three new golden-file directories (1967AE06, 3F864CFF, 87FBC893) are added, but toolchain/mfc/test/cases.py is not changed. The golden-metadata comments show these were generated with --generate, which requires the test to already exist in cases.py. If the test definitions are missing, CI will not run the new regression tests and the golden files are unused. Please verify that cases.py includes definitions for these three UUIDs (or add them if missing).


4. Minor — associated_legendre base-case condition is overly broad

File: src/common/m_helper.fpp

if (m_order <= 0 .and. l <= 0) then
    result_P = 1._wp

The guard at the top of the function already rejects l < 0 and m_order < 0, so this condition is equivalent to l == 0 .and. m_order == 0. Using the tighter equality makes the intent clearer.

Comment on lines +1090 to +1096
if (patch_icpp(patch_id)%smoothen) then
eta = 0.5_wp + 0.5_wp*tanh(smooth_coeff/min(dx, dy)*(R_boundary - r))
end if
if ((r <= R_boundary .and. patch_icpp(patch_id)%alter_patch(patch_id_fp(i, j, 0))) &
.or. patch_id_fp(i, j, 0) == smooth_patch_id) then
call s_assign_patch_primitive_variables(patch_id, i, j, 0, eta, q_prim_vf, patch_id_fp)
end if
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Modal patch skips ic hooks 🐞 Bug ✓ Correctness

s_icpp_2d_modal never executes the @:analytical() / hcid hardcoded-IC hooks after cell
assignment, so analytical patch expressions and hardcoded ICs are silently ignored for geometry 13.
Agent Prompt
### Issue description
`src/pre_process/m_icpp_patches.fpp::s_icpp_2d_modal` assigns patch primitive variables but never calls the standard `@:analytical()` and `hcid`-gated hardcoded IC hooks that other patch geometries call after assignment. As a result, toolchain-generated analytical IC expressions and `patch_icpp(patch_id)%hcid` hardcoded IC behavior will be ignored for geometry 13.

### Issue Context
Other 2D/3D patch routines call `@:analytical()` immediately after `s_assign_patch_primitive_variables` and conditionally call `@:Hardcoded2D()`/`@:Hardcoded3D()` when `hcid` is set. Geometry 13 should behave consistently.

### Fix Focus Areas
- src/pre_process/m_icpp_patches.fpp[1046-1099]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Development

Successfully merging this pull request may close these issues.

3 participants