Skip to content

Conversation

@danieljvickers
Copy link
Member

@danieljvickers danieljvickers commented Jan 15, 2026

User description

User description

Description

Adds a numeric method of computing the centroid of IBs where the center of mass is not well posed. In particular, I added hooks for STL and airfiol IBs.

This PR also includes support for parameterized IB motion. It allows one to analytically describe the velocity and rotation rate of any IB experiencing 1-way coupling.

Both of these features have been tested with IB's the move and rotate using the new analytic parameterization. I have also tested with an airfoil rotating about the correct center of mass. Videos available in my slides.

Fixes #1105
Fixes #1106

In addition to these issues being fixed, I also resolved a separate bug that is related in getting the 2-way force calculation to work with multi-fluid flow. I also included some minor refactoring to clean up the time stepper subroutine.

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Scope

  • This PR comprises a set of related changes with a common goal

That goal was enabling the ability to run a pitching airfoil case, which oscillates in time.

How Has This Been Tested?

  • I ran a pitching airfoil case which oscillates in time at a fixed frequency about the new correct center of mass
  • I ran a case of a cylinder moving in a circular pattern to "stir" a fluid

The best configuration can be summarized with this test case file for the pitching airfoil:

import json
import math

Mu = 1.84e-05
gam_a = 1.4
gam_b = 1.1

total_time = 5.0 
num_time_steps = 2000
num_saves = 500
dt = total_time / float(num_time_steps)
steps_per_save = int(num_time_steps / num_saves)

# Configuring case dictionary
print(
    json.dumps(
        {
            # Logistics
            "run_time_info": "T",
            # Computational Domain Parameters
            # axial direction
            "x_domain%beg": 0.0e00,
            "x_domain%end": 6.0,
            # r direction
            "y_domain%beg": 0.0e00,
            "y_domain%end": 3.0,
            "cyl_coord": "F",
            "m": 400,
            "n": 200,
            "p": 0,
            "dt": dt,
            "t_step_start": 0,
            "t_step_stop": num_time_steps,
            "t_step_save": steps_per_save,
            # Simulation Algorithm Parameters
            # Only one patches are necessary, the air tube
            "num_patches": 1,
            # Use the 5 equation model
            "model_eqns": 2,
            # 6 equations model does not need the K \div(u) term
            "alt_soundspeed": "F",
            # One fluids: air
            "num_fluids": 2,
            # time step
            "mpp_lim": "F",
            # Correct errors when computing speed of sound
            "mixture_err": "T",
            # Use TVD RK3 for time marching
            "time_stepper": 3,
            # Reconstruct the primitive variables to minimize spurious
            # Use WENO5
            "weno_order": 5,
            "weno_eps": 1.0e-16,
            "weno_Re_flux": "T",
            "weno_avg": "T",
            "avg_state": 2,
            "viscous": "T",
            # Use the mapped WENO weights to maintain monotinicity
            "mapped_weno": "T",
            "null_weights": "F",
            "mp_weno": "T",
            # Use the HLLC  Riemann solver
            "riemann_solver": 2,
            "wave_speeds": 1,
            # We use reflective boundary conditions at octant edges and
            # non-reflective boundary conditions at the domain edges
            "bc_x%beg": -3,
            "bc_x%end": -3,
            "bc_y%beg": -3,
            "bc_y%end": -3,
            # Set IB to True and add 1 patch
            "ib": "T",
            "num_ibs": 1,
            # Formatted Database Files Structure Parameters
            "format": 1,
            "precision": 2,
            "prim_vars_wrt": "T",
            "E_wrt": "T",
            "parallel_io": "T",
            # Patch: Constant Tube filled with air
            # Specify the cylindrical air tube grid geometry
            "patch_icpp(1)%geometry": 3,
            "patch_icpp(1)%x_centroid": 3.0,
            # Uniform medium density, centroid is at the center of the domain
            "patch_icpp(1)%y_centroid": 1.5,
            "patch_icpp(1)%length_x": 6.0,
            "patch_icpp(1)%length_y": 3.0,
            # Specify the patch primitive variables
            "patch_icpp(1)%vel(1)": 3.0e00,
            "patch_icpp(1)%vel(2)": 0.0e00,
            "patch_icpp(1)%pres": 1.0e00,
            "patch_icpp(1)%alpha_rho(1)": 0.8e00,
            "patch_icpp(1)%alpha(1)": 0.8e00,
            "patch_icpp(1)%alpha_rho(2)": 0.2e00,
            "patch_icpp(1)%alpha(2)": 0.2e00,
            # Patch: Airfoil Immersed Boundary
            "patch_ib(1)%geometry": 4,
            "patch_ib(1)%x_centroid": 1.5,
            "patch_ib(1)%y_centroid": 1.5,
            "patch_ib(1)%c": 1.0,
            "patch_ib(1)%t": 0.15,
            "patch_ib(1)%p": 0.4,
            "patch_ib(1)%m": 0.02,
            "patch_ib(1)%angular_vel(3)": "-10.0 * 2.0 * pi * sin(2.0 * pi * t) * pi / 180.",
            "patch_ib(1)%angles(3)": -5.0 * math.pi / 180.,
            "patch_ib(1)%moving_ibm": 1,
            "patch_ib(1)%slip": "F",
            # Fluids Physical Parameters
            # Use the same stiffness as the air bubble
            "fluid_pp(1)%gamma": 1.0e00 / (gam_a - 1.0e00),  # 2.50(Not 1.40)
            "fluid_pp(1)%pi_inf": 0,
            "fluid_pp(1)%Re(1)": 1000000,
            "fluid_pp(2)%gamma": 1.0e00 / (gam_b - 1.0e00),  # 2.50(Not 1.40)
            "fluid_pp(2)%pi_inf": 0,
            "fluid_pp(2)%Re(1)": 1000000,
        }
    )
)

Checklist

  • I have added comments for the new code
  • I added Doxygen docstrings to the new code
  • I have made corresponding changes to the documentation (docs/)
  • I have added regression tests to the test suite so that people can verify in the future that the feature is behaving as expected
  • I have added example cases in examples/ that demonstrate my new feature performing as expected.
    They run to completion and demonstrate "interesting physics"
  • I ran ./mfc.sh format before committing my code
  • New and existing tests pass locally with my changes, including with GPU capability enabled (both NVIDIA hardware with NVHPC compilers and AMD hardware with CRAY compilers) and disabled
  • This PR does not introduce any repeated code (it follows the DRY principle)
  • I cannot think of a way to condense this code and reduce any introduced additional line count

If your code changes any code source files (anything in src/simulation)

To make sure the code is performing as expected on GPU devices, I have:

  • Checked that the code compiles using NVHPC compilers
  • Checked that the code compiles using CRAY compilers
  • Ran the code on either V100, A100, or H100 GPUs and ensured the new feature performed as expected (the GPU results match the CPU results)
  • Ran the code on MI200+ GPUs and ensure the new features performed as expected (the GPU results match the CPU results)
  • Enclosed the new feature via nvtx ranges so that they can be identified in profiles
  • Ran a Nsight Systems profile using ./mfc.sh run XXXX --gpu -t simulation --nsys, and have attached the output file (.nsys-rep) and plain text results to this PR
  • Ran a Rocprof Systems profile using ./mfc.sh run XXXX --gpu -t simulation --rsys --hip-trace, and have attached the output file and plain text results to this PR.
  • Ran my code using various numbers of different GPUs (1, 2, and 8, for example) in parallel and made sure that the results scale similarly to what happens if you run without the new code/feature

PR Type

Enhancement

Description

  • Add ellipse immersed boundary patch geometry with levelset computation

  • Implement centroid offset calculation for complex IB shapes (airfoils, STL)

  • Support analytical parameterization of moving IB velocities and rotation rates

  • Enable 1-way coupling with analytically-defined IB motion in time-stepping

Diagram Walkthrough

flowchart LR
  A["IB Patch Definition"] -->|"geometry=6"| B["Ellipse Levelset"]
  A -->|"centroid_offset"| C["Centroid Computation"]
  C -->|"airfoil/STL"| D["Center of Mass Calculation"]
  D -->|"offset vector"| E["Levelset Adjustment"]
  F["Analytical Parameters"] -->|"vel, angular_vel"| G["1-Way Coupling"]
  G -->|"time-stepping"| H["IB Motion Update"]
Loading

File Walkthrough

Relevant files
Enhancement
9 files
m_compute_levelset.fpp
Add ellipse levelset computation and centroid offset         
+60/-1   
m_derived_types.fpp
Add centroid offset field to IB patch type                             
+1/-0     
m_ib_patches.fpp
Add ellipse IB patch initialization and marker setup         
+47/-0   
m_check_ib_patches.fpp
Add validation for ellipse IB patch geometry                         
+2/-0     
m_global_parameters.fpp
Initialize centroid offset to zero for all patches             
+1/-0     
m_ibm.fpp
Add centroid offset computation and 1-way coupling support
+56/-1   
m_time_steppers.fpp
Integrate analytical velocity parameterization for 1-way coupling
+5/-1     
case.py
Add code generation for analytical moving IB velocities   
+91/-3   
case_dicts.py
Mark IB velocity and angular velocity as analytically-parameterizable
+2/-4     
Documentation
1 files
case.py
Add example case configuration for ellipse IB simulation 
+97/-0   

CodeAnt-AI Description

Add analytic moving IB velocities, centroid offset for airfoils, and multi-fluid IB force handling

What Changed

  • Immersed boundaries can be driven by analytic (time-dependent) velocity and rotation inputs; the case generator now accepts analytic expressions for IB vel and angular_vel and embeds them into generated input code so 1‑way prescribed IB motion runs without manual code edits.
  • For irregular IB shapes (NACA airfoils, STL, ellipses) the code computes the true center of mass at startup and applies a centroid offset so rotations and positions use the physically correct center rather than the input "centroid".
  • IB force calculation now supports multiple fluids: pressure and viscous contributions are computed per-fluid, the local dynamic viscosity is weighted by local phase fraction, and Reynolds-number handling avoids divide-by-zero for zero-Re cases.
  • Time-step handling for moving IBs was refactored so IB propagation (analytic motion, two‑way forcing, rotation, and position updates) is centralized and ensures forces are computed once per stage when needed.

Impact

✅ Can run pitching or rotating airfoils about correct center
✅ Clearer analytic IB motion setup in generated case files
✅ Correct IB forces for multi-fluid viscous flows

💡 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.

Summary by CodeRabbit

  • New Features

    • Ellipse-shaped immersed boundary patches supported
    • Centroid-offset correction improves handling of irregular immersed boundaries
    • Analytical moving immersed boundaries (prescribed MIB motion) supported
    • Multi‑fluid immersed boundary interactions and per‑fluid viscous handling
  • Chores

    • Consolidated immersed-boundary propagation during time stepping and minor example/comment updates

✏️ Tip: You can customize this high-level summary in your review settings.

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 15, 2026

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds centroid_offset handling for IB patches, applies centroid corrections in airfoil levelsets, introduces ellipse patch geometry, refactors IBM force computation for per-fluid viscosities and centroid computation, and centralizes IB state propagation with a new RK-stage helper.

Changes

Cohort / File(s) Summary
Type & Initialization
src/common/m_derived_types.fpp, src/pre_process/m_global_parameters.fpp
Add centroid_offset(1:3) to ib_patch_parameters and initialize it to 0._wp for all patches.
Levelset & Patch Geometry
src/common/m_compute_levelset.fpp, src/common/m_ib_patches.fpp
Subtract patch_ib(... )%centroid_offset after rotating into IB frame for 2D/3D airfoils; add ellipse geometry (geometry code 6) via new internal s_ib_ellipse and dispatch to s_ellipse_levelset.
IBM Forces & Centroid Computation
src/simulation/m_ibm.fpp
Change s_compute_ib_forces signature to accept fluid_pp array, compute per-fluid dynamic viscosities and volume-weighted viscosity, add s_compute_centroid_offset (MPI reductions) and adapt force accumulation to multiple fluids.
Time Integration / IB Propagation
src/simulation/m_time_steppers.fpp
Extract per-IB RK-stage propagation into new s_propagate_immersed_boundaries(s) that updates velocities, angular momentum, angles, centroids and invokes s_update_mib.
Analytic MIB / Toolchain
toolchain/mfc/case.py, toolchain/mfc/run/case_dicts.py, src/common/include/case.fpp
Add MIB analytic support: MIBM_ANALYTIC_VARS, __is_mib_analytical, __get_analytic_mib_fpp, wrapper __get_pre_fpp; add mib_analytical() macro and switch velocity/angular_vel params to analytic where applicable.
Examples & Build
examples/2D_ibm_ellipse/case.py, toolchain/dependencies/CMakeLists.txt
Update example comment to "Ellipse" and bump Silo git tag.
sequenceDiagram
    rect rgba(200,200,255,0.5)
    participant TS as TimeStepper
    end
    rect rgba(200,255,200,0.5)
    participant IBM as IBM Module (m_ibm)
    participant PCH as Patch Data (patch_ib)
    end
    rect rgba(255,200,200,0.5)
    participant MPI as MPI/Domain
    participant GRID as Grid/Levelset
    end

    TS->>IBM: call s_propagate_immersed_boundaries(stage s)
    IBM->>PCH: read patch params (centroid_offset, geometry)
    IBM->>IBM: if needed call s_compute_centroid_offset(ib_marker)
    IBM->>MPI: reduce centroid data across ranks
    IBM->>GRID: compute levelset / forces per cell (uses centroid-corrected coords)
    GRID-->>IBM: cell-level forces/volumes
    IBM->>TS: update IB velocities/angles/centroids (RK update)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • #1075: Modifies IBM core modules and per-patch mass/force handling—closely related to the m_ibm and time-step refactors in this PR.
  • #1029: Touches the same levelset and airfoil geometry paths (s_airfoil_levelset / s_3D_airfoil_levelset), overlapping the centroid corrections and patch dispatch changes.

Suggested labels

size:XL

Suggested reviewers

  • sbryngelson
  • wilfonba

Poem

🐰 I nudge the centroid, hop by hop,

Airfoils spin around their true heart—no more tip-stop.
Ellipses join the immersed ball, fluids swirl and play,
RK hops forward, forces blend, to simulate the day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Analytic mibm velocities and airfoil centroid' clearly and concisely summarizes the main changes: analytical parameterization of moving IB velocities and correction of airfoil centroid computation.
Description check ✅ Passed The pull request description comprehensively addresses the template requirements: it summarizes changes (centroid computation and analytic motion), links fixed issues (#1105, #1106), describes testing with pitching airfoil and moving cylinder cases, and provides a detailed configuration example. However, most checklist items remain unchecked, including documentation updates, regression tests, GPU testing, and code formatting verification.
Linked Issues check ✅ Passed The code changes comprehensively address both linked issues: #1105 is resolved through analytic parametrization of IB velocities (case.py, case_dicts.py, m_time_steppers.fpp, case.fpp support time-dependent vel/angular_vel), and #1106 is resolved through centroid_offset computation (m_derived_types.fpp, m_ibm.fpp, m_compute_levelset.fpp, m_ib_patches.fpp implement center-of-mass calculation and offset application for airfoils and other irregular shapes).
Out of Scope Changes check ✅ Passed Changes to CMakeLists.txt (Silo Git tag update) and multi-fluid force handling (s_compute_ib_forces refactoring) appear tangentially related but are justified: the Silo dependency may be required for advanced geometry, and multi-fluid force support is necessary for correct 2-way coupling with the new features. The ellipse geometry (geometry code 6) and new helper routines are all directly tied to the stated objectives.

✏️ 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.

@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jan 15, 2026
@qodo-code-review
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

1106 - Partially compliant

Compliant requirements:

  • Fix the "centroid" used for airfoils so it is the true centroid/center of mass rather than the airfoil tip.
  • Ensure rotations in 1-way coupling occur about the center of mass.

Non-compliant requirements:

  • Ensure torque calculations in 2-way coupling use the correct centroid/center of mass reference.

Requires further human verification:

  • Works for 2D and 3D airfoils across all environments.
  • Ensure torque calculations in 2-way coupling use the correct centroid/center of mass reference.

1105 - Partially compliant

Compliant requirements:

  • Support parametrizing 1-way MIBM motion (velocity and rotation rate) as a function of time in the case file.
  • Provide a toolchain/codegen addition that injects analytic functions into a case file (similar to analytic patches/ICs).
  • Apply the prescribed motion inside the time-step update logic using a conditional statement.

Non-compliant requirements:

  • ""

Requires further human verification:

  • ""
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

s_compute_centroid_offset appears to be called during s_ibm_setup before ib_markers%sf is allocated/populated (and before the IB patch marker routines run), yet it iterates over ib_markers%sf(i,j,k) to find cells. This risks invalid memory access or always computing a zero-count COM depending on initialization order.

! do all set up for moving immersed boundaries
moving_immersed_boundary_flag = .false.
do i = 1, num_ibs
    if (patch_ib(i)%moving_ibm /= 0) then
        moving_immersed_boundary_flag = .true.
    end if
    call s_update_ib_rotation_matrix(i)
    call s_compute_centroid_offset(i)
end do
$:GPU_ENTER_DATA(copyin='[patch_ib]')

! Allocating the patch identities bookkeeping variable
allocate (patch_id_fp(0:m, 0:n, 0:p))

$:GPU_UPDATE(device='[ib_markers%sf]')
$:GPU_UPDATE(device='[levelset%sf]')
$:GPU_UPDATE(device='[levelset_norm%sf]')

! Get neighboring IB variables from other processors
call s_populate_ib_buffers()

$:GPU_UPDATE(host='[ib_markers%sf]')

! find the number of ghost points and set them to be the maximum total across ranks
call s_find_num_ghost_points(num_gps, num_inner_gps)
call s_mpi_allreduce_integer_sum(num_gps, max_num_gps)
call s_mpi_allreduce_integer_sum(num_inner_gps, max_num_inner_gps)

! set the size of the ghost point arrays to be the amount of points total, plus a factor of 2 buffer
$:GPU_UPDATE(device='[num_gps, num_inner_gps]')
@:ALLOCATE(ghost_points(1:int((max_num_gps + max_num_inner_gps) * 2.0)))
@:ALLOCATE(inner_points(1:int((max_num_gps + max_num_inner_gps) * 2.0)))

$:GPU_ENTER_DATA(copyin='[ghost_points,inner_points]')

call s_find_ghost_points(ghost_points, inner_points)
$:GPU_UPDATE(device='[ghost_points, inner_points]')

call s_compute_image_points(ghost_points, levelset, levelset_norm)
$:GPU_UPDATE(device='[ghost_points]')
Possible Issue

The MPI reduction logic computes global sums into center_of_mass and global cell count into num_cells, but then divides by num_cells_local (local count) instead of the global count. This will yield incorrect centroids in parallel runs. Also verify 2D handling: the accumulation uses [x_cc(i), y_cc(j), 0] but still loops over k; in 2D this may overweight if p spans more than a single plane.

!> Computes the center of mass for IB patch types where we are unable to determine their center of mass analytically.
!> These patches include things like NACA airfoils and STL models
subroutine s_compute_centroid_offset(ib_marker)

    integer, intent(in) :: ib_marker

    integer :: i, j, k, num_cells, num_cells_local
    real(wp), dimension(1:3) :: center_of_mass

    ! Offset only needs to be computes for specific geometries
    if (patch_ib(ib_marker)%geometry == 4 .or. &
        patch_ib(ib_marker)%geometry == 5 .or. &
        patch_ib(ib_marker)%geometry == 11 .or. &
        patch_ib(ib_marker)%geometry == 12) then

        center_of_mass = [0._wp, 0._wp, 0._wp]
        num_cells_local = 0

        ! get the summed mass distribution and number of cells to divide by
        do i = 0, m
            do j = 0, n
                do k = 0, p
                    if (ib_markers%sf(i, j, k) == ib_marker) then
                      num_cells_local = num_cells_local + 1
                        center_of_mass = center_of_mass + [x_cc(i), y_cc(j), 0._wp]
                        if (num_dims == 3) center_of_mass(3) = center_of_mass(3) + z_cc(k)
                    end if
                end do
            end do
        end do

        ! reduce the mass contribution over all MPI ranks and compute COM
        ! print *, "Before reduction ", center_of_mass, num_cells_local
        call s_mpi_allreduce_sum(center_of_mass(1), center_of_mass(1))
        call s_mpi_allreduce_sum(center_of_mass(2), center_of_mass(2))
        call s_mpi_allreduce_sum(center_of_mass(3), center_of_mass(3))
        call s_mpi_allreduce_integer_sum(num_cells_local, num_cells)
        center_of_mass =  center_of_mass / real(num_cells_local, wp)

        ! assign the centroid offset as a vector pointing from the true COM to the "centroid" in the input file and replace the current centroid
        patch_ib(ib_marker)%centroid_offset = [patch_ib(ib_marker)%x_centroid, patch_ib(ib_marker)%y_centroid, patch_ib(ib_marker)%z_centroid] &
            - center_of_mass
        patch_ib(ib_marker)%x_centroid = center_of_mass(1)
        patch_ib(ib_marker)%y_centroid = center_of_mass(2)
        patch_ib(ib_marker)%z_centroid = center_of_mass(3)

        ! rotate the centroid offset back into the local coords of the IB
        patch_ib(ib_marker)%centroid_offset = matmul(patch_ib(ib_marker)%rotation_matrix_inverse, patch_ib(ib_marker)%centroid_offset)
    else
        patch_ib(ib_marker)%centroid_offset(:) = [0._wp, 0._wp, 0._wp]
    end if
Possible Issue

s_ellipse_levelset only assigns levelset/levelset_norm when a point is inside the ellipse; outside points are left untouched, which can leave uninitialized/stale values in the levelset fields and break downstream logic that assumes the full field is defined. Consider explicitly setting an outside value and normal (or at least levelset) for all cells.

subroutine s_ellipse_levelset(ib_patch_id, levelset, levelset_norm)

    type(levelset_field), intent(INOUT), optional :: levelset
    type(levelset_norm_field), intent(INOUT), optional :: levelset_norm

    integer, intent(in) :: ib_patch_id
    real(wp) :: ellipse_coeffs(2) ! a and b in the ellipse equation
    real(wp) :: quadratic_coeffs(3) ! A, B, C in the quadratic equation to compute levelset

    real(wp) :: length_x, length_y
    real(wp), dimension(1:3) :: xy_local, normal_vector !< x and y coordinates in local IB frame
    real(wp), dimension(2) :: center !< x and y coordinates in local IB frame
    real(wp), dimension(1:3, 1:3) :: rotation, inverse_rotation

    integer :: i, j, k !< Loop index variables
    integer :: idx !< Shortest path direction indicator

    length_x = patch_ib(ib_patch_id)%length_x
    length_y = patch_ib(ib_patch_id)%length_y
    center(1) = patch_ib(ib_patch_id)%x_centroid
    center(2) = patch_ib(ib_patch_id)%y_centroid
    inverse_rotation(:, :) = patch_ib(ib_patch_id)%rotation_matrix_inverse(:, :)
    rotation(:, :) = patch_ib(ib_patch_id)%rotation_matrix(:, :)

    ellipse_coeffs(1) = 0.5_wp * length_x
    ellipse_coeffs(2) = 0.5_wp * length_y

    $:GPU_PARALLEL_LOOP(private='[i,j,k,idx,quadratic_coeffs,xy_local,normal_vector]', &
              & copyin='[ib_patch_id,center,ellipse_coeffs,inverse_rotation,rotation]', collapse=2)
    do i = 0, m
        do j = 0, n
            xy_local = [x_cc(i) - center(1), y_cc(j) - center(2), 0._wp]
            xy_local = matmul(inverse_rotation, xy_local)

            ! we will get NaNs in the levelset if we compute this outside the ellipse
            if ((xy_local(1)/ellipse_coeffs(1))**2 + (xy_local(2)/ellipse_coeffs(2))**2 <= 1._wp) then

                normal_vector = xy_local
                normal_vector(2) = normal_vector(2) * (ellipse_coeffs(1)/ellipse_coeffs(2))**2._wp ! get the normal direction via the coordinate transofmration method
                normal_vector = normal_vector / sqrt(dot_product(normal_vector, normal_vector)) ! normalize the vector
                levelset_norm%sf(i, j, 0, ib_patch_id, :) = matmul(rotation, normal_vector) ! save after rotating the vector to the global frame

                ! use the normal vector to set up the quadratic equation for the levelset, using A, B, and C in indices 1, 2, and 3
                quadratic_coeffs(1) = (normal_vector(1)/ellipse_coeffs(1))**2 + (normal_vector(2)/ellipse_coeffs(2))**2
                quadratic_coeffs(2) = 2._wp * ((xy_local(1)*normal_vector(1)/(ellipse_coeffs(1)**2)) + (xy_local(2)*normal_vector(2)/(ellipse_coeffs(2)**2)))
                quadratic_coeffs(3) = (xy_local(1)/ellipse_coeffs(1))**2._wp + (xy_local(2)/ellipse_coeffs(2))**2._wp - 1._wp

                ! compute the levelset with the quadratic equation [ -B + sqrt(B^2 - 4AC) ] / 2A
                levelset%sf(i, j, 0, ib_patch_id) = -0.5_wp * (-quadratic_coeffs(2) + sqrt(quadratic_coeffs(2)**2._wp - 4._wp * quadratic_coeffs(1)*quadratic_coeffs(3))) / quadratic_coeffs(1)
            end if
        end do
    end do
    $:END_GPU_PARALLEL_LOOP()

end subroutine s_ellipse_levelset

do i = 0, m
xyz_local = [x_cc(i) - center(1), y_cc(j) - center(2), z_cc(l) - center(3)] ! get coordinate frame centered on IB
xyz_local = matmul(inverse_rotation, xyz_local) ! rotate the frame into the IB's coordinates
xyz_local = xyz_local - patch_ib(patch_id)%centroid_offset ! airfoils are a patch that require a centroid offset
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: The 3D airfoil routine subtracts patch_ib(patch_id)%centroid_offset from xyz_local after applying inverse_rotation; if centroid_offset is expressed in the global/model frame this subtraction is done in the wrong basis. Rotate the centroid offset into the IB/local frame with the same inverse_rotation before subtracting to ensure the offset is applied consistently in the same coordinate system. [logic error]

Severity Level: Critical 🚨
- ❌ 3D airfoil marker placement incorrect in s_ib_3D_airfoil.
- ⚠️ 3D pitching/rotating airfoil simulations show wrong geometry.
Suggested change
xyz_local = xyz_local - patch_ib(patch_id)%centroid_offset ! airfoils are a patch that require a centroid offset
xyz_local = xyz_local - matmul(inverse_rotation, patch_ib(patch_id)%centroid_offset) ! rotate centroid_offset into IB/local frame before subtracting
Steps of Reproduction ✅
1. Prepare a 3D case with an airfoil patch (patch_ib(... )%geometry == 11) so
s_apply_ib_patches calls s_ib_3D_airfoil during marker generation.

2. Set patch_ib(patch_id)%centroid_offset to a non-zero vector and set non-zero rotation
angles so rotation_matrix_inverse is non-trivial; run to the IB marker generation step.

3. In s_ib_3D_airfoil the code computes xyz_local and applies inverse_rotation at
src/common/m_ib_patches.fpp:435-436, then subtracts patch_ib(patch_id)%centroid_offset at
line 437 without rotating that offset into the local frame.

4. As a result the z/y/x cell-inclusion tests (for example the z_min/z_max check at the
surrounding block) and the later assignments to ib_markers_sf(i,j,l) are done with an
inconsistent offset and the 3D airfoil appears shifted. Reproduces when centroid_offset ≠
0 and rotation ≠ identity.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/common/m_ib_patches.fpp
**Line:** 437:437
**Comment:**
	*Logic Error: The 3D airfoil routine subtracts `patch_ib(patch_id)%centroid_offset` from `xyz_local` after applying `inverse_rotation`; if `centroid_offset` is expressed in the global/model frame this subtraction is done in the wrong basis. Rotate the centroid offset into the IB/local frame with the same `inverse_rotation` before subtracting to ensure the offset is applied consistently in the same coordinate system.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

call s_mpi_allreduce_sum(center_of_mass(2), center_of_mass(2))
call s_mpi_allreduce_sum(center_of_mass(3), center_of_mass(3))
call s_mpi_allreduce_integer_sum(num_cells_local, num_cells)
center_of_mass = center_of_mass / real(num_cells_local, wp)
Copy link
Contributor

@codeant-ai codeant-ai bot Jan 15, 2026

Choose a reason for hiding this comment

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

Suggestion: Logic error: the code divides the reduced (global) sum of coordinates by the local rank cell count (num_cells_local) instead of the global cell count (num_cells), which yields an incorrect centroid and can cause a division-by-zero when the local rank has zero cells; change the divisor to the globally reduced num_cells and guard against zero. [logic error]

Severity Level: Critical 🚨
- ❌ Centroid miscomputed for airfoil/STL IBs (s_compute_centroid_offset)
- ❌ Possible runtime crash on ranks with zero local IB cells
- ⚠️ Affects s_ibm_setup and subsequent IB motion calculations
- ⚠️ Breaks pitching-airfoil test cases (user-reported)
Suggested change
center_of_mass = center_of_mass / real(num_cells_local, wp)
if (num_cells == 0) then
! No cells globally for this IB -- leave offsets at zero and exit safely
patch_ib(ib_marker)%centroid_offset(:) = [0._wp, 0._wp, 0._wp]
return
end if
center_of_mass = center_of_mass / real(num_cells, wp)
Steps of Reproduction ✅
1. Build and run the code with MPI (>=2 ranks) and a case that registers an IB of geometry
type handled here (geometry == 4/5/11/12). The initialization path runs s_ibm_setup
(src/simulation/m_ibm.fpp: around lines 93-105) which calls s_compute_centroid_offset.

2. Ensure the IB lies entirely within one MPI rank's subdomain such that other ranks have
zero cells for that IB. During s_compute_centroid_offset (subroutine start at
src/simulation/m_ibm.fpp: ~1086) each rank counts local cells into num_cells_local and
sums coordinates into center_of_mass.

3. The code calls s_mpi_allreduce_integer_sum(num_cells_local, num_cells) (line 1120)
which returns global num_cells, but then divides by the local num_cells_local at line
1121. If the local rank had num_cells_local == 0, this division triggers a divide-by-zero
on that rank causing a crash; otherwise the centroid is computed incorrectly (using local
count instead of global).

4. Observe either a floating-point exception / program abort on ranks with zero local
cells or an incorrect centroid value being written to patch_ib(...)%x_centroid etc.
Confirm by inspecting outputs written after s_compute_centroid_offset or by adding a debug
print near lines 1115-1121.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/simulation/m_ibm.fpp
**Line:** 1121:1121
**Comment:**
	*Logic Error: Logic error: the code divides the reduced (global) sum of coordinates by the local rank cell count (`num_cells_local`) instead of the global cell count (`num_cells`), which yields an incorrect centroid and can cause a division-by-zero when the local rank has zero cells; change the divisor to the globally reduced `num_cells` and guard against zero.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Copy link
Member Author

Choose a reason for hiding this comment

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

This comment actually doesn't stink

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 15, 2026

CodeAnt AI finished reviewing your PR.

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: 4

Caution

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

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

65-70: Add validation for ellipse geometry and update error message to include geometry 6.

The ellipse patch (geometry == 6) branch lacks parameter validation that exists for all other geometry types. Additionally, the error message on line 70 must be updated to include geometry 6 in the list of valid geometries to match the explicit handling at line 65.

🔧 Suggested fixes
  1. Update the error message to include geometry 6:
                 call s_prohibit_abort("Invalid IB patch", &
                                       "patch_ib("//trim(iStr)//")%geometry must be "// &
-                                      "2-4, 8-10, 11 or 12.")
+                                      "2-6, 8-10, 11 or 12.")
  1. Add a validation subroutine for ellipse geometry and call it:
else if (patch_ib(i)%geometry == 6) then
    call s_check_ellipse_ib_patch_geometry(i)

Then implement s_check_ellipse_ib_patch_geometry to validate centroid and radii parameters similarly to the circle patch validation.

src/simulation/m_time_steppers.fpp (1)

629-643: The @:mib_analytical() macro at line 631 is undefined and will cause a preprocessing error.

The macro is invoked for 1-way coupling (moving_ibm == 1) but is not defined in case.fpp, macros.fpp, or any other .fpp file in the codebase. Only an empty analytical() macro (without the mib_ prefix) exists in case.fpp.

The macro must be properly defined to specify how analytic velocities are applied to the IB patches in 1-way coupling mode. Add the implementation to case.fpp or the appropriate macro file.

🤖 Fix all issues with AI agents
In `@examples/2D_ibm_ellipse/case.py`:
- Around line 84-90: Update the misleading comment "Cylinder Immersed Boundary"
to describe the actual geometry being configured (an ellipse) where the patch
keys like "patch_ib(1)%geometry" are set to 6; change the comment near the block
that sets "patch_ib(1)%geometry", "patch_ib(1)%x_centroid",
"patch_ib(1)%y_centroid", "patch_ib(1)%length_x", "patch_ib(1)%length_y", and
"patch_ib(1)%slip" so it accurately states "Ellipse Immersed Boundary" (or
equivalent) to match geometry=6.

In `@src/common/m_compute_levelset.fpp`:
- Line 380: The inline comment on the line assigning normal_vector(2) contains a
typo "transofmration"; update the comment text to "transformation" in the same
statement so it reads "... via the coordinate transformation method" while
leaving the code (normal_vector(2) = normal_vector(2) *
(ellipse_coeffs(1)/ellipse_coeffs(2))**2._wp) and surrounding whitespace
unchanged.

In `@src/common/m_ib_patches.fpp`:
- Around line 789-792: The comment in the m_ib_patches module that reads
"Checking whether the rectangle covers..." is incorrect for the ellipse-handling
subroutine; update the comment to say "ellipse" (and adjust surrounding phrasing
if needed, e.g., "ellipse covers a particular cell") so it accurately reflects
the geometry handled by the subroutine in this file.

In `@src/simulation/m_ibm.fpp`:
- Around line 1115-1122: The center_of_mass calculation divides by the local
count; after calling s_mpi_allreduce_sum(center_of_mass) and
s_mpi_allreduce_integer_sum(num_cells_local, num_cells) replace the division by
real(num_cells_local, wp) with real(num_cells, wp) so the globally reduced
center_of_mass is normalized by the global cell count (ensure you use the
reduced integer num_cells, not num_cells_local, in the division and keep the wp
real-kind conversion).
🧹 Nitpick comments (1)
toolchain/mfc/case.py (1)

223-256: Consider binding pid explicitly to avoid closure capture issue.

The static analyzer flagged that rhs_replace captures the loop variable pid by reference. While this is safe here since the function is called immediately within the same iteration, it's a potential maintenance hazard if the code is refactored.

♻️ Optional fix using default argument binding
         for pid, items in ib_patches.items():
 
             # function that defines how we will replace variable names with
             # values from the case file
-            def rhs_replace(match):
+            def rhs_replace(match, pid=pid):
                 return {
                     'x': 'x_cc(i)', 'y': 'y_cc(j)', 'z': 'z_cc(k)',
                     't': 'mytime',
 
                     'r':     f'patch_ib({pid})%radius',
 
                     'e' : f'{math.e}',
                 }.get(match.group(), match.group())
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31c938f and 7bd28cd.

📒 Files selected for processing (10)
  • examples/2D_ibm_ellipse/case.py
  • src/common/m_compute_levelset.fpp
  • src/common/m_derived_types.fpp
  • src/common/m_ib_patches.fpp
  • src/pre_process/m_check_ib_patches.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/simulation/m_ibm.fpp
  • src/simulation/m_time_steppers.fpp
  • toolchain/mfc/case.py
  • toolchain/mfc/run/case_dicts.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and `!! `@return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/common/m_derived_types.fpp
  • src/pre_process/m_check_ib_patches.fpp
  • src/simulation/m_ibm.fpp
  • src/simulation/m_time_steppers.fpp
  • src/common/m_compute_levelset.fpp
  • src/common/m_ib_patches.fpp
  • src/pre_process/m_global_parameters.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/common/m_derived_types.fpp
  • src/pre_process/m_check_ib_patches.fpp
  • src/simulation/m_ibm.fpp
  • src/simulation/m_time_steppers.fpp
  • src/common/m_compute_levelset.fpp
  • src/common/m_ib_patches.fpp
  • src/pre_process/m_global_parameters.fpp
src/simulation/**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/simulation/**/*.{fpp,f90}: Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up
Avoid stop/error stop inside GPU device code
Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

src/simulation/**/*.{fpp,f90}: Mark GPU-callable helpers with $:GPU_ROUTINE(function_name='...', parallelism='[seq]') immediately after declaration
Do not use OpenACC or OpenMP directives directly; use Fypp macros from src/common/include/parallel_macros.fpp instead
Wrap tight loops with $:GPU_PARALLEL_FOR(private='[...]', copy='[...]') macro; add collapse=n for safe nested loop merging
Declare loop-local variables with private='[...]' in GPU parallel loop macros
Allocate large arrays with managed or move them into a persistent $:GPU_ENTER_DATA(...) region at start-up
Do not place stop or error stop inside device code

Files:

  • src/simulation/m_ibm.fpp
  • src/simulation/m_time_steppers.fpp
🧠 Learnings (7)
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up

Applied to files:

  • src/simulation/m_ibm.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large arrays with `managed` or move them into a persistent `$:GPU_ENTER_DATA(...)` region at start-up

Applied to files:

  • src/simulation/m_ibm.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight loops with `$:GPU_PARALLEL_FOR(private='[...]', copy='[...]')` macro; add `collapse=n` for safe nested loop merging

Applied to files:

  • src/simulation/m_ibm.fpp
  • src/simulation/m_time_steppers.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Do not use OpenACC or OpenMP directives directly; use Fypp macros from `src/common/include/parallel_macros.fpp` instead

Applied to files:

  • src/simulation/m_time_steppers.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

Applied to files:

  • src/simulation/m_time_steppers.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)

Applied to files:

  • src/simulation/m_time_steppers.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `private` declaration followed by explicit `public` exports in modules

Applied to files:

  • src/common/m_compute_levelset.fpp
🧬 Code graph analysis (3)
src/simulation/m_ibm.fpp (1)
toolchain/mfc/test/cases.py (1)
  • alter_ib (377-422)
examples/2D_ibm_ellipse/case.py (1)
toolchain/mfc/test/cases.py (8)
  • alter_ib (377-422)
  • alter_3d (322-365)
  • alter_2d (293-320)
  • alter_hypoelasticity (590-652)
  • alter_num_fluids (224-291)
  • alter_mixlayer_perturb (686-717)
  • alter_viscosity (788-839)
  • alter_bc_patches (908-955)
toolchain/mfc/case.py (1)
toolchain/mfc/common.py (1)
  • is_number (190-201)
🪛 GitHub Actions: Spell Check
src/common/m_compute_levelset.fpp

[error] 380-380: Typos found. 'transofmration' should be 'transformation'.

🪛 Ruff (0.14.11)
toolchain/mfc/case.py

232-232: Function definition does not bind loop variable pid

(B023)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Github (macos, mpi, no-debug, false)
  • GitHub Check: Github (macos, mpi, debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Build & Publish
🔇 Additional comments (20)
toolchain/mfc/run/case_dicts.py (1)

368-371: LGTM! Analytic type support for IB velocity parameters.

The change to ParamType.REAL.analytic() for vel and angular_vel correctly enables time-parameterized motion expressions for moving immersed boundaries, consistent with how other analytic initial conditions are handled elsewhere in the codebase.

toolchain/mfc/case.py (5)

15-18: LGTM! Well-structured constant for MIB analytic variables.

The MIBM_ANALYTIC_VARS list correctly enumerates all velocity and angular velocity components that can be analytically defined for moving immersed boundaries.


101-111: LGTM! Proper regex escaping for variable matching.

Good use of re.escape(variable) to handle parentheses in variable names like vel(1). The method correctly mirrors the pattern established by __is_ic_analytical.


350-358: LGTM! Clean refactoring for FPP generation.

The separation of __get_analytic_ic_fpp and __get_analytic_mib_fpp with proper concatenation in __get_sim_fpp ensures both IC and MIB analytic macros are available during simulation compilation.


54-57: LGTM! Proper handling of MIB analytic expressions in input generation.

Correctly extends the analytic placeholder logic to MIB parameters, ensuring string expressions are replaced with 0d0 in the .inp file while the actual expressions are handled via generated Fypp macros.


258-265: No action needed — mytime is properly available in scope.

The generated #:def mib_analytical() macro correctly maps the time variable t to mytime, which is defined as a module-level variable in m_global_parameters.fpp and is initialized and maintained throughout the simulation lifecycle. It is accessible wherever the macro is expanded.

src/common/m_derived_types.fpp (1)

299-299: LGTM! Well-documented centroid offset field.

The centroid_offset field correctly addresses issue #1106 by allowing proper tracking of the center of mass offset for asymmetric immersed boundaries like airfoils. The field type and dimensioning are appropriate.

src/pre_process/m_global_parameters.fpp (1)

565-565: LGTM! Proper default initialization for centroid_offset.

The initialization to 0._wp is consistent with other array initializations in the IB patch defaults and ensures a clean starting state before the actual centroid offset is computed during simulation setup.

examples/2D_ibm_ellipse/case.py (1)

1-97: LGTM - New 2D ellipse example case.

The case file correctly demonstrates the new ellipse IB patch (geometry=6) with appropriate domain parameters, boundary conditions, and fluid properties for viscous flow simulation.

src/simulation/m_time_steppers.fpp (1)

5-6: LGTM - Include case.fpp for analytic MIB macros.

Adding the include enables the @:mib_analytical() macro for prescribing analytic velocities in 1-way MIBM coupling.

src/simulation/m_ibm.fpp (2)

96-104: LGTM - Added centroid offset computation in IBM setup.

The call to s_compute_centroid_offset(i) is correctly placed after the rotation matrix update and before GPU data transfer, enabling proper centroid calculations for airfoil and STL geometries.


1103-1113: Use the more direct if (p > 0) check for consistency with the rest of the codebase. Line 1109 should be if (p > 0) instead of if (num_dims == 3), matching the pattern used elsewhere (e.g., lines 224–227) and avoiding reliance on a global parameter that should inherently correspond to the grid dimensions.

src/common/m_compute_levelset.fpp (4)

26-27: LGTM - Added ellipse levelset to public exports.

The new s_ellipse_levelset subroutine is correctly added to the module's public interface.


97-97: LGTM - Centroid offset adjustment for 2D airfoil levelset.

Correctly subtracts centroid_offset from local coordinates after rotation, aligning the airfoil's geometric center with its true center of mass for proper levelset computation.


193-193: LGTM - Centroid offset adjustment for 3D airfoil levelset.

Consistent with the 2D implementation, correctly applies centroid offset in the local IB frame.


342-396: Resolve the sign discrepancy between the formula comment and implementation on line 390.

The comment on line 389 states to compute [ -B + sqrt(B^2 - 4AC) ] / 2A (the plus root), but the code on line 390 implements -0.5_wp * (-quadratic_coeffs(2) + sqrt(...)) / quadratic_coeffs(1), which mathematically evaluates to (B - sqrt(...)) / (2A) (the minus root with a sign flip). Verify whether this negation is intentional (e.g., for inward-facing signed distance) and update the comment to reflect the actual formula being computed.

Regarding the other points: centroid_offset is correctly omitted since ellipses have a well-defined geometric center (unlike airfoils which require explicit offset correction, as noted in s_airfoil_levelset). Minor style: replace **2._wp exponents on lines 380, 387 with **2 per coding guidelines.

src/common/m_ib_patches.fpp (4)

122-125: LGTM - Added ellipse geometry dispatch in 2D IB patch application.

The new geometry type 6 for ellipse is correctly routed to both the marker function (s_ib_ellipse) and levelset computation (s_ellipse_levelset).


282-282: LGTM - Centroid offset for 2D airfoil patch markers.

Correctly applies centroid offset in the local IB frame, consistent with the levelset computation in m_compute_levelset.fpp.


437-437: LGTM - Centroid offset for 3D airfoil patch markers.

Consistent implementation with the 2D version.


771-810: LGTM - New ellipse IB patch subroutine.

The implementation correctly:

  • Extracts ellipse semi-axes from length_x/2 and length_y/2
  • Transforms coordinates to local IB frame using rotation matrix
  • Uses the standard ellipse equation (x/a)² + (y/b)² ≤ 1 for interior check
  • Follows the same pattern as other 2D patch subroutines

The GPU parallel loop is properly configured with appropriate private variables and data movement.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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.

6 issues found across 10 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/simulation/m_ibm.fpp">

<violation number="1" location="src/simulation/m_ibm.fpp:1121">
P1: After the MPI reductions, the centroid is divided by `num_cells_local` (the pre-reduction local count) instead of the globally reduced `num_cells`, which can produce NaNs or incorrect centers of mass. Divide by the global count returned by `s_mpi_allreduce_integer_sum` (and guard against zero) to compute a valid centroid.</violation>
</file>

<file name="src/pre_process/m_check_ib_patches.fpp">

<violation number="1" location="src/pre_process/m_check_ib_patches.fpp:65">
P2: Geometry 6 IB patches bypass all validation: the new branch just prints “Ellipse Patch” and never enforces required parameters (centroid, radii, dimensionality). This lets malformed ellipse IB definitions pass unchecked and can cause runtime failures when s_ib_ellipse later uses unset data. Replace the print with proper validation mirroring other geometries.</violation>
</file>

<file name="toolchain/mfc/run/case_dicts.py">

<violation number="1" location="toolchain/mfc/run/case_dicts.py:369">
P1: Assigning `ParamType.REAL.analytic()` (which returns a plain dict) directly into `SIMULATION` is inconsistent with other entries that use `ParamType` Enum members. Downstream code that builds `_properties = {k: v.value for k, v in ALL.items()}` will fail with an `AttributeError` because dicts don't have a `.value` attribute. Use `ParamType.REAL` instead to maintain consistency.</violation>

<violation number="2" location="toolchain/mfc/run/case_dicts.py:371">
P1: Same issue as with `vel`: assigning `ParamType.REAL.analytic()` (a plain dict) will cause `AttributeError` when building `_properties`. Use `ParamType.REAL` instead.</violation>
</file>

<file name="src/common/m_compute_levelset.fpp">

<violation number="1" location="src/common/m_compute_levelset.fpp:377">
P1: levelset and normal fields are never set for cells outside the ellipse, leaving undefined geometry data for most of the domain. Add an `else` branch that computes the signed distance/normal outside the ellipse instead of skipping the assignment.</violation>
</file>

<file name="toolchain/mfc/case.py">

<violation number="1" location="toolchain/mfc/case.py:227">
P1: Analytic IB codegen replaces `x`, `y`, `z` with grid-center arrays using undefined indices (`j`, `k`) inside the time-stepper loop, causing compilation failures or wrong values when users reference spatial variables.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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

🤖 Fix all issues with AI agents
In `@src/common/m_ib_patches.fpp`:
- Line 781: The inline comment "! Transferring the rectangle's centroid and
length information" is a copy-paste error; update it to refer to the ellipse
instead (e.g., "! Transferring the ellipse's centroid and axes/length
information") so it accurately describes the code handling ellipse centroid and
length/axes values; locate and change this exact comment string in
m_ib_patches.fpp (search for "Transferring the rectangle's centroid") and
replace "rectangle" with "ellipse" (optionally adjust "length" to "axes" for
clarity).
♻️ Duplicate comments (2)
src/common/m_ib_patches.fpp (1)

788-791: Fix copy-paste comment: "rectangle" should be "ellipse".

📝 Proposed fix
-        ! Checking whether the rectangle covers a particular cell in the
+        ! Checking whether the ellipse covers a particular cell in the
         ! domain and verifying whether the current patch has the permission
         ! to write to that cell. If both queries check out, the primitive
         ! variables of the current patch are assigned to this cell.
src/common/m_compute_levelset.fpp (1)

376-391: Level set only computed inside ellipse - exterior cells remain uninitialized.

The condition at line 377 restricts computation to points inside the ellipse. Cells outside will retain their initial values (likely zero from the caller's initialization), which may cause incorrect ghost point interpolation for cells near but outside the ellipse boundary.

Compare to s_circle_levelset (lines 49-62) which computes dist - radius for ALL grid points, providing a valid signed distance everywhere.

🐛 Proposed fix: Compute signed distance for all points

For points outside the ellipse, you could use an algebraic distance approximation:

-                if ((xy_local(1)/ellipse_coeffs(1))**2 + (xy_local(2)/ellipse_coeffs(2))**2 <= 1._wp) then
+                ! Algebraic distance approximation valid everywhere
+                levelset%sf(i, j, 0, ib_patch_id) = sqrt(ellipse_coeffs(1) * ellipse_coeffs(2)) * &
+                    (sqrt((xy_local(1)/ellipse_coeffs(1))**2 + (xy_local(2)/ellipse_coeffs(2))**2) - 1._wp)
 
+                ! Normal vector computation only valid inside/near surface
+                if ((xy_local(1)/ellipse_coeffs(1))**2 + (xy_local(2)/ellipse_coeffs(2))**2 <= 1._wp) then
                     normal_vector = xy_local
                     normal_vector(2) = normal_vector(2)*(ellipse_coeffs(1)/ellipse_coeffs(2))**2._wp
                     normal_vector = normal_vector/sqrt(dot_product(normal_vector, normal_vector))
                     levelset_norm%sf(i, j, 0, ib_patch_id, :) = matmul(rotation, normal_vector)
-
-                    quadratic_coeffs(1) = ...
-                    ...
-                    levelset%sf(i, j, 0, ib_patch_id) = ...
                 end if

Alternatively, compute the exact closest-point-on-ellipse distance for exterior points, though this requires iterative root finding.

🧹 Nitpick comments (2)
src/simulation/m_ibm.fpp (1)

1115-1121: Previous critical bug is fixed, but consider guarding against zero cell count.

The division now correctly uses the globally reduced num_cells instead of num_cells_local. However, if an IB patch has no cells (e.g., misconfigured geometry), num_cells could be zero, causing a division-by-zero crash.

🛡️ Optional: Add zero-guard for robustness
             call s_mpi_allreduce_sum(center_of_mass(1), center_of_mass(1))
             call s_mpi_allreduce_sum(center_of_mass(2), center_of_mass(2))
             call s_mpi_allreduce_sum(center_of_mass(3), center_of_mass(3))
             call s_mpi_allreduce_integer_sum(num_cells_local, num_cells)
+            if (num_cells == 0) then
+                call s_mpi_abort("s_compute_centroid_offset: No cells found for IB patch")
+            end if
             center_of_mass = center_of_mass/real(num_cells, wp)
src/common/m_compute_levelset.fpp (1)

356-357: Remove unused variables k and idx.

These variables are declared but never used in the subroutine.

♻️ Proposed fix
-        integer :: i, j, k !< Loop index variables
-        integer :: idx !< Shortest path direction indicator
+        integer :: i, j !< Loop index variables
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bd28cd and 541e727.

📒 Files selected for processing (3)
  • src/common/m_compute_levelset.fpp
  • src/common/m_ib_patches.fpp
  • src/simulation/m_ibm.fpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and `!! `@return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/common/m_ib_patches.fpp
  • src/simulation/m_ibm.fpp
  • src/common/m_compute_levelset.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/common/m_ib_patches.fpp
  • src/simulation/m_ibm.fpp
  • src/common/m_compute_levelset.fpp
src/simulation/**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/simulation/**/*.{fpp,f90}: Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up
Avoid stop/error stop inside GPU device code
Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

src/simulation/**/*.{fpp,f90}: Mark GPU-callable helpers with $:GPU_ROUTINE(function_name='...', parallelism='[seq]') immediately after declaration
Do not use OpenACC or OpenMP directives directly; use Fypp macros from src/common/include/parallel_macros.fpp instead
Wrap tight loops with $:GPU_PARALLEL_FOR(private='[...]', copy='[...]') macro; add collapse=n for safe nested loop merging
Declare loop-local variables with private='[...]' in GPU parallel loop macros
Allocate large arrays with managed or move them into a persistent $:GPU_ENTER_DATA(...) region at start-up
Do not place stop or error stop inside device code

Files:

  • src/simulation/m_ibm.fpp
🧠 Learnings (4)
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up

Applied to files:

  • src/simulation/m_ibm.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large arrays with `managed` or move them into a persistent `$:GPU_ENTER_DATA(...)` region at start-up

Applied to files:

  • src/simulation/m_ibm.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight loops with `$:GPU_PARALLEL_FOR(private='[...]', copy='[...]')` macro; add `collapse=n` for safe nested loop merging

Applied to files:

  • src/simulation/m_ibm.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `private` declaration followed by explicit `public` exports in modules

Applied to files:

  • src/common/m_compute_levelset.fpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Build & Publish
🔇 Additional comments (8)
src/simulation/m_ibm.fpp (2)

1084-1136: LGTM! Well-structured centroid offset computation.

The subroutine correctly:

  • Targets only geometry types requiring offset computation (airfoils, STL models)
  • Uses MPI reduction for distributed computation
  • Transforms the offset back to local IB coordinates for consistent usage
  • Initializes offset to zero for inapplicable geometries

96-105: LGTM! Correct integration of centroid offset computation.

The call ordering is correct: rotation matrix is computed first (required for the offset rotation at line 1131), and the centroid offset is computed before the GPU data transfer.

src/common/m_compute_levelset.fpp (2)

95-97: LGTM! Correct coordinate frame for centroid offset subtraction.

The centroid_offset is stored in local IB coordinates (transformed at m_ibm.fpp:1131), and here it's correctly subtracted after xy_local has been rotated into the same local frame.


191-193: LGTM! Consistent with 2D airfoil implementation.

src/common/m_ib_patches.fpp (4)

280-282: LGTM! Centroid offset correctly applied in local coordinates.

The centroid_offset is pre-rotated into local IB coordinates during initialization (m_ibm.fpp:1131). Here it's correctly subtracted after xy_local is transformed to the same local frame. Both vectors share the same coordinate basis.


435-437: LGTM! Consistent centroid offset handling for 3D airfoil.


770-809: LGTM! Clean ellipse IB marker implementation.

The subroutine correctly:

  • Extracts ellipse parameters from patch data
  • Transforms coordinates to local IB frame
  • Uses standard ellipse membership test (x/a)² + (y/b)² ≤ 1

122-124: LGTM! Correct dispatch for ellipse geometry.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 16, 2026

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Jan 16, 2026
@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 16, 2026

CodeAnt AI Incremental review completed.

@codecov
Copy link

codecov bot commented Jan 16, 2026

Codecov Report

❌ Patch coverage is 70.51282% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.16%. Comparing base (6264d3f) to head (de62c66).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/simulation/m_ibm.fpp 71.73% 11 Missing and 2 partials ⚠️
src/simulation/m_time_steppers.fpp 70.37% 5 Missing and 3 partials ⚠️
src/common/m_compute_levelset.fpp 50.00% 1 Missing ⚠️
src/common/m_ib_patches.fpp 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1111      +/-   ##
==========================================
+ Coverage   44.07%   44.16%   +0.09%     
==========================================
  Files          71       71              
  Lines       20369    20417      +48     
  Branches     1986     1991       +5     
==========================================
+ Hits         8978     9018      +40     
- Misses      10246    10252       +6     
- Partials     1145     1147       +2     

☔ 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.

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 20, 2026

CodeAnt AI Incremental review completed.

sbryngelson
sbryngelson previously approved these changes Jan 20, 2026
@danieljvickers
Copy link
Member Author

@sbryngelson I am getting desperate and tried passing the error to chatGPT, and it pointed out that the failure is a SIGTERM. The build is being terminated externally.

It says the following:


⏱️ Walltime limit exceeded

💾 Out-of-memory (OOM) kill

🧵 Node-level job preemption

🔁 Workflow cancellation (e.g., newer job superseded the old one)

🧹 Runner cleanup / resource reclaim

I confirmed this with some googling. Is it possible that we are running out of memory? It would explain why we keep failing at the same spot consistently and why master is the one failing. I would expect variable time if it were the walltime or workflow cancellation. Could be the problem. I will see if the other branch build successfully.

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 21, 2026

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Jan 21, 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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/simulation/m_ibm.fpp">

<violation number="1" location="src/simulation/m_ibm.fpp:1080">
P2: dynamic_viscosities entries are not initialized for fluids with Re(1)=0, but are used in the viscosity sum, leading to undefined values in force/torque calculations. Initialize the array or set zero in the Re==0 case before using it.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 21, 2026

CodeAnt AI Incremental review completed.

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

🤖 Fix all issues with AI agents
In `@src/simulation/m_ibm.fpp`:
- Around line 1076-1081: The spacing in the local dynamic viscosity accumulation
loop is inconsistent; re-run the code formatter or normalize spacing around
operators in the block that updates dynamic_viscosity inside the loop (variable
dynamic_viscosity, array q_prim_vf, field sf, and dynamic_viscosities used in
the do loop over fluid_idx). Ensure consistent spaces around assignment and
arithmetic operators (e.g., "dynamic_viscosity = dynamic_viscosity + ( ... )")
and align continuation/indentation to match the file's formatting conventions,
then save and re-run CI.
♻️ Duplicate comments (2)
src/simulation/m_ibm.fpp (2)

1056-1066: Pressure gradient indexing may be over-counting in multi-fluid cases.
This still uses q_prim_vf(E_idx + fluid_idx). If E_idx is the single shared pressure (as in the usual MFC layout), then E_idx + fluid_idx points to non-pressure fields and will double-count or miscompute forces. Please confirm.

🔧 If pressure is shared, consider this fix
-                        local_force_contribution(:) = 0._wp
-                        do fluid_idx = 0, num_fluids - 1
-                            local_force_contribution(1) = local_force_contribution(1) - (q_prim_vf(E_idx + fluid_idx)%sf(i + 1, j, k) - q_prim_vf(E_idx + fluid_idx)%sf(i - 1, j, k))/(2._wp*dx)
-                            local_force_contribution(2) = local_force_contribution(2) - (q_prim_vf(E_idx + fluid_idx)%sf(i, j + 1, k) - q_prim_vf(E_idx + fluid_idx)%sf(i, j - 1, k))/(2._wp*dy)
-                            cell_volume = abs(dx*dy)
-                            if (num_dims == 3) then
-                                dz = z_cc(k + 1) - z_cc(k)
-                                local_force_contribution(3) = local_force_contribution(3) - (q_prim_vf(E_idx + fluid_idx)%sf(i, j, k + 1) - q_prim_vf(E_idx + fluid_idx)%sf(i, j, k - 1))/(2._wp*dz)
-                                cell_volume = abs(cell_volume*dz)
-                            end if
-                        end do
+                        local_force_contribution(:) = 0._wp
+                        local_force_contribution(1) = local_force_contribution(1) - (q_prim_vf(E_idx)%sf(i + 1, j, k) - q_prim_vf(E_idx)%sf(i - 1, j, k))/(2._wp*dx)
+                        local_force_contribution(2) = local_force_contribution(2) - (q_prim_vf(E_idx)%sf(i, j + 1, k) - q_prim_vf(E_idx)%sf(i, j - 1, k))/(2._wp*dy)
+                        cell_volume = abs(dx*dy)
+                        if (num_dims == 3) then
+                            dz = z_cc(k + 1) - z_cc(k)
+                            local_force_contribution(3) = local_force_contribution(3) - (q_prim_vf(E_idx)%sf(i, j, k + 1) - q_prim_vf(E_idx)%sf(i, j, k - 1))/(2._wp*dz)
+                            cell_volume = abs(cell_volume*dz)
+                        end if

1035-1039: Initialize dynamic_viscosities for Re==0 fluids to avoid undefined viscous forces.
As written, entries stay uninitialized when Re == 0, but they’re still used in the weighted sum, which can inject garbage into viscous forces/torques.

🛠️ Proposed fix
-        if (viscous) then
-            do fluid_idx = 1, num_fluids
-                if (fluid_pp(fluid_idx)%Re(1) /= 0._wp) dynamic_viscosities(fluid_idx) = 1._wp / fluid_pp(fluid_idx)%Re(1)
-            end do
-        end if
+        dynamic_viscosities = 0._wp
+        if (viscous) then
+            do fluid_idx = 1, num_fluids
+                if (fluid_pp(fluid_idx)%Re(1) /= 0._wp) then
+                    dynamic_viscosities(fluid_idx) = 1._wp / fluid_pp(fluid_idx)%Re(1)
+                end if
+            end do
+        end if

@danieljvickers
Copy link
Member Author

Any chance I can get a branchmark run @sbryngelson or @wilfonba ?

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 22, 2026

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

wilfonba
wilfonba previously approved these changes Jan 22, 2026
Copy link
Contributor

@wilfonba wilfonba left a comment

Choose a reason for hiding this comment

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

benchmark

@codeant-ai codeant-ai bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Jan 22, 2026
Comment on lines +1061 to +1069
do fluid_idx = 0, num_fluids - 1
! Get the pressure contribution to force via a finite difference to compute the 2D components of the gradient of the pressure and cell volume
local_force_contribution(1) = local_force_contribution(1) - (q_prim_vf(E_idx + fluid_idx)%sf(i + 1, j, k) - q_prim_vf(E_idx + fluid_idx)%sf(i - 1, j, k))/(2._wp*dx) ! force is the negative pressure gradient
local_force_contribution(2) = local_force_contribution(2) - (q_prim_vf(E_idx + fluid_idx)%sf(i, j + 1, k) - q_prim_vf(E_idx + fluid_idx)%sf(i, j - 1, k))/(2._wp*dy)
cell_volume = abs(dx*dy)
! add the 3D component of the pressure gradient, if we are working in 3 dimensions
if (num_dims == 3) then
dz = z_cc(k + 1) - z_cc(k)
local_force_contribution(3) = local_force_contribution(3) - (q_prim_vf(E_idx + fluid_idx)%sf(i, j, k + 1) - q_prim_vf(E_idx + fluid_idx)%sf(i, j, k - 1))/(2._wp*dz)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: Off-by-one indexing: the pressure-gradient loop uses do fluid_idx = 0, num_fluids - 1 but other code uses 1..num_fluids; this mixes 0-based and 1-based offsets when indexing q_prim_vf and is likely wrong or will index the wrong fields; change the loop to do fluid_idx = 1, num_fluids and index q_prim_vf(E_idx + fluid_idx - 1) so indexing is consistent with the rest of the code. [off-by-one]

Severity Level: Critical 🚨
- ❌ IB force computation for multi-fluid flows incorrect.
- ⚠️ Multi-fluid pressure-gradient selection inconsistent.
- ⚠️ Affects s_compute_ib_forces force aggregation path.
Suggested change
do fluid_idx = 0, num_fluids - 1
! Get the pressure contribution to force via a finite difference to compute the 2D components of the gradient of the pressure and cell volume
local_force_contribution(1) = local_force_contribution(1) - (q_prim_vf(E_idx + fluid_idx)%sf(i + 1, j, k) - q_prim_vf(E_idx + fluid_idx)%sf(i - 1, j, k))/(2._wp*dx) ! force is the negative pressure gradient
local_force_contribution(2) = local_force_contribution(2) - (q_prim_vf(E_idx + fluid_idx)%sf(i, j + 1, k) - q_prim_vf(E_idx + fluid_idx)%sf(i, j - 1, k))/(2._wp*dy)
cell_volume = abs(dx*dy)
! add the 3D component of the pressure gradient, if we are working in 3 dimensions
if (num_dims == 3) then
dz = z_cc(k + 1) - z_cc(k)
local_force_contribution(3) = local_force_contribution(3) - (q_prim_vf(E_idx + fluid_idx)%sf(i, j, k + 1) - q_prim_vf(E_idx + fluid_idx)%sf(i, j, k - 1))/(2._wp*dz)
do fluid_idx = 1, num_fluids
! Get the pressure contribution to force via a finite difference to compute the 2D components of the gradient of the pressure and cell volume
local_force_contribution(1) = local_force_contribution(1) - (q_prim_vf(E_idx + fluid_idx - 1)%sf(i + 1, j, k) - q_prim_vf(E_idx + fluid_idx - 1)%sf(i - 1, j, k))/(2._wp*dx) ! force is the negative pressure gradient
local_force_contribution(2) = local_force_contribution(2) - (q_prim_vf(E_idx + fluid_idx - 1)%sf(i, j + 1, k) - q_prim_vf(E_idx + fluid_idx - 1)%sf(i, j - 1, k))/(2._wp*dy)
cell_volume = abs(dx*dy)
! add the 3D component of the pressure gradient, if we are working in 3 dimensions
if (num_dims == 3) then
dz = z_cc(k + 1) - z_cc(k)
local_force_contribution(3) = local_force_contribution(3) - (q_prim_vf(E_idx + fluid_idx - 1)%sf(i, j, k + 1) - q_prim_vf(E_idx + fluid_idx - 1)%sf(i, j, k - 1))/(2._wp*dz)
Steps of Reproduction ✅
1. Run a multi-fluid case (num_fluids >= 2) that exercises immersed boundary force
accumulation; s_compute_ib_forces is defined in src/simulation/m_ibm.fpp (subroutine
s_compute_ib_forces, start around line 1017).

2. When the triple loop over grid points hits an IB cell (ib_idx /= 0) the code executes
the pressure-gradient accumulation loop at src/simulation/m_ibm.fpp lines ~1060-1072 (the
shown local_force_contribution block).

3. The loop uses do fluid_idx = 0, num_fluids-1 and indexes q_prim_vf(E_idx + fluid_idx) —
the rest of the codebase consistently uses 1..num_fluids indexing for per-fluid fields
(see multiple occurrences of loops using 1..num_fluids, e.g., s_ibm_correct_state and
earlier sections).

4. On a multi-fluid run this off-by-one selection will pick the wrong q_prim_vf field for
at least one fluid component, producing incorrect pressure gradients and therefore
incorrect IB force contributions; this is observable in differing IB forces/torques vs.
expected values when comparing single- vs multi-fluid runs.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/simulation/m_ibm.fpp
**Line:** 1061:1069
**Comment:**
	*Off By One: Off-by-one indexing: the pressure-gradient loop uses `do fluid_idx = 0, num_fluids - 1` but other code uses 1..num_fluids; this mixes 0-based and 1-based offsets when indexing `q_prim_vf` and is likely wrong or will index the wrong fields; change the loop to `do fluid_idx = 1, num_fluids` and index `q_prim_vf(E_idx + fluid_idx - 1)` so indexing is consistent with the rest of the code.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 22, 2026

CodeAnt AI Incremental review completed.

@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 23, 2026

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Jan 23, 2026
@codeant-ai
Copy link
Contributor

codeant-ai bot commented Jan 23, 2026

CodeAnt AI Incremental review completed.

@sbryngelson sbryngelson merged commit 944aa2f into MFlowCode:master Jan 24, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Review effort 4/5 size:L This PR changes 100-499 lines, ignoring generated files

Development

Successfully merging this pull request may close these issues.

Airfoil Centroid in Wrong Location Parametrized 1-Way MIBM Movement

3 participants