Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5c3b79a
Adding changes from original two way coupling branch manually
danieljvickers Nov 13, 2025
1f39342
Initial volume integration method for invicid surface integral calcul…
danieljvickers Nov 13, 2025
9b687b8
Added time step integration code to support new coupling scheme
danieljvickers Nov 13, 2025
22dba76
Changed torque calculation to output torque in local IB coordinates
danieljvickers Nov 13, 2025
42512af
syntax error correction
danieljvickers Nov 13, 2025
85ea1b1
Resolved compilation errors
danieljvickers Nov 14, 2025
b38796e
Running ok-looking tests
danieljvickers Nov 14, 2025
0cbe4b9
This is actually doing quite a good job, but two problems. One is it …
danieljvickers Nov 14, 2025
9ea3020
Important update to simulation case inputs
danieljvickers Nov 16, 2025
3f68040
found my error resulting from improper movement of the data from one …
danieljvickers Nov 22, 2025
676aa73
initial 2_way
danieljvickers Dec 2, 2025
8094216
Merge with master
danieljvickers Dec 2, 2025
5d2fc37
spelling and formatting
danieljvickers Dec 2, 2025
b7f00d1
Back to working after merge:
danieljvickers Dec 2, 2025
f25e61d
Finished invisid case
danieljvickers Dec 3, 2025
c82a623
Resolved some MPI errors and added a test of a particle in a cross flow
danieljvickers Dec 3, 2025
25ee314
Added rotation back
danieljvickers Dec 3, 2025
7c70fd4
Spelling and formatting
danieljvickers Dec 3, 2025
0c221ae
Added atomic updates and some code structure
danieljvickers Dec 4, 2025
ec0ccf6
Spelling:
danieljvickers Dec 4, 2025
a4967c9
Replaced variable in numeric moment of inertia calculation
danieljvickers Dec 4, 2025
8531946
Added feedback for if variables are not the same in non-MPI
danieljvickers Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
331 changes: 170 additions & 161 deletions docs/documentation/case.md

Large diffs are not rendered by default.

105 changes: 105 additions & 0 deletions examples/2D_mibm_cylinder_in_cross_flow/case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import json
import math

Mu = 1.84e-05
gam_a = 1.4

# Configuring case dictionary
print(
json.dumps(
{
# Logistics
"run_time_info": "T",
# Computational Domain Parameters
# For these computations, the cylinder is placed at the (0,0,0)
# domain origin.
# axial direction
"x_domain%beg": 0.0e00,
"x_domain%end": 6.0e-03,
# r direction
"y_domain%beg": 0.0e00,
"y_domain%end": 6.0e-03,
"cyl_coord": "F",
"m": 200,
"n": 200,
"p": 0,
"dt": 6.0e-6,
"t_step_start": 0,
"t_step_stop": 10000, # 10000,
"t_step_save": 100,
# Simulation Algorithm Parameters
# Only one patches are necessary, the air tube
"num_patches": 1,
# Use the 5 equation model
"model_eqns": 2,
"alt_soundspeed": "F",
# One fluids: air
"num_fluids": 1,
# time step
"mpp_lim": "F",
# Correct errors when computing speed of sound
"mixture_err": "T",
# Use TVD RK3 for time marching
"time_stepper": 3,
# Use WENO5
"weno_order": 5,
"weno_eps": 1.0e-16,
"weno_Re_flux": "T",
"weno_avg": "T",
"avg_state": 2,
"mapped_weno": "T",
"null_weights": "F",
"mp_weno": "T",
"riemann_solver": 2,
"wave_speeds": 1,
# We use ghost-cell
"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,
"viscous": "T",
# 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.0e-03,
# Uniform medium density, centroid is at the center of the domain
"patch_icpp(1)%y_centroid": 3.0e-03,
"patch_icpp(1)%length_x": 6.0e-03,
"patch_icpp(1)%length_y": 6.0e-03,
# Specify the patch primitive variables
"patch_icpp(1)%vel(1)": 0.05e00,
"patch_icpp(1)%vel(2)": 0.0e00,
"patch_icpp(1)%pres": 1.0e00,
"patch_icpp(1)%alpha_rho(1)": 1.0e00,
"patch_icpp(1)%alpha(1)": 1.0e00,
# Patch: Cylinder Immersed Boundary
"patch_ib(1)%geometry": 2,
"patch_ib(1)%x_centroid": 1.5e-03,
"patch_ib(1)%y_centroid": 4.5e-03,
"patch_ib(1)%radius": 0.3e-03,
"patch_ib(1)%slip": "F",
"patch_ib(1)%moving_ibm": 2,
"patch_ib(1)%vel(2)": -0.1,
"patch_ib(1)%angles(1)": 0.0, # x-axis rotation in radians
"patch_ib(1)%angles(2)": 0.0, # y-axis rotation
"patch_ib(1)%angles(3)": 0.0, # z-axis rotation
"patch_ib(1)%angular_vel(1)": 0.0, # x-axis rotational velocity in radians per second
"patch_ib(1)%angular_vel(2)": 0.0, # y-axis rotation
"patch_ib(1)%angular_vel(3)": 0.0, # z-axis rotation
"patch_ib(1)%mass": 0.0001, # z-axis rotation
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

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

P2: Incorrect comment: Comment says "z-axis rotation" but this line sets the mass parameter. This appears to be a copy-paste error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/2D_mibm_cylinder_in_cross_flow/case.py, line 98:

<comment>Incorrect comment: Comment says &quot;z-axis rotation&quot; but this line sets the `mass` parameter. This appears to be a copy-paste error.</comment>

<file context>
@@ -0,0 +1,105 @@
+            &quot;patch_ib(1)%angular_vel(1)&quot;: 0.0,  # x-axis rotational velocity in radians per second
+            &quot;patch_ib(1)%angular_vel(2)&quot;: 0.0,  # y-axis rotation
+            &quot;patch_ib(1)%angular_vel(3)&quot;: 0.0,  # z-axis rotation
+            &quot;patch_ib(1)%mass&quot;: 0.0001,  # z-axis rotation
+            # Fluids Physical Parameters
+            &quot;fluid_pp(1)%gamma&quot;: 1.0e00 / (gam_a - 1.0e00),  # 2.50(Not 1.40)
</file context>
Suggested change
"patch_ib(1)%mass": 0.0001, # z-axis rotation
"patch_ib(1)%mass": 0.0001, # mass of the immersed boundary object
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

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

P2: Incorrect comment: The comment # z-axis rotation is wrong for the mass parameter. This appears to be a copy-paste error from the angular velocity comments above.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/2D_mibm_cylinder_in_cross_flow/case.py, line 98:

<comment>Incorrect comment: The comment `# z-axis rotation` is wrong for the `mass` parameter. This appears to be a copy-paste error from the angular velocity comments above.</comment>

<file context>
@@ -0,0 +1,105 @@
+            &quot;patch_ib(1)%angular_vel(1)&quot;: 0.0,  # x-axis rotational velocity in radians per second
+            &quot;patch_ib(1)%angular_vel(2)&quot;: 0.0,  # y-axis rotation
+            &quot;patch_ib(1)%angular_vel(3)&quot;: 0.0,  # z-axis rotation
+            &quot;patch_ib(1)%mass&quot;: 0.0001,  # z-axis rotation
+            # Fluids Physical Parameters
+            &quot;fluid_pp(1)%gamma&quot;: 1.0e00 / (gam_a - 1.0e00),  # 2.50(Not 1.40)
</file context>
Suggested change
"patch_ib(1)%mass": 0.0001, # z-axis rotation
"patch_ib(1)%mass": 0.0001, # mass of the immersed boundary
Fix with Cubic

# Fluids Physical Parameters
"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)": 2500000,
}
)
)
2 changes: 2 additions & 0 deletions src/common/m_derived_types.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ module m_derived_types

!! Patch conditions for moving imersed boundaries
integer :: moving_ibm ! 0 for no moving, 1 for moving, 2 for moving on forced path
real(wp) :: mass, moment ! mass and moment of inertia of object used to compute forces in 2-way coupling
real(wp), dimension(1:3) :: force, torque ! vectors for the computed force and torque values applied to an IB
real(wp), dimension(1:3) :: vel
real(wp), dimension(1:3) :: step_vel ! velocity array used to store intermediate steps in the time_stepper module
real(wp), dimension(1:3) :: angular_vel
Expand Down
26 changes: 26 additions & 0 deletions src/common/m_mpi_common.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,32 @@ contains
end subroutine s_mpi_allreduce_sum
!> This subroutine follows the behavior of the s_mpi_allreduce_sum subroutine
!> with the additional feature that it reduces an array of vectors.
impure subroutine s_mpi_allreduce_vectors_sum(var_loc, var_glb, num_vectors, vector_length)
integer, intent(in) :: num_vectors, vector_length
real(wp), dimension(:, :), intent(in) :: var_loc
real(wp), dimension(:, :), intent(out) :: var_glb
#ifdef MFC_MPI
integer :: ierr !< Generic flag used to identify and report MPI errors
! Performing the reduction procedure
if (loc(var_loc) == loc(var_glb)) then
call MPI_Allreduce(MPI_IN_PLACE, var_glb, num_vectors*vector_length, &
mpi_p, MPI_SUM, MPI_COMM_WORLD, ierr)
else
call MPI_Allreduce(var_loc, var_glb, num_vectors*vector_length, &
mpi_p, MPI_SUM, MPI_COMM_WORLD, ierr)
end if
#else
var_glb = var_loc
#endif
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

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

P2: Missing non-MPI fallback: when MFC_MPI is not defined, var_glb is never assigned, leaving it uninitialized. Similar subroutine s_mpi_allreduce_integer_sum includes an #else block to copy var_loc to var_glb for serial execution.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/common/m_mpi_common.fpp, line 495:

<comment>Missing non-MPI fallback: when `MFC_MPI` is not defined, `var_glb` is never assigned, leaving it uninitialized. Similar subroutine `s_mpi_allreduce_integer_sum` includes an `#else` block to copy `var_loc` to `var_glb` for serial execution.</comment>

<file context>
@@ -472,6 +472,30 @@ contains
+                               mpi_p, MPI_SUM, MPI_COMM_WORLD, ierr)
+        end if
+
+#endif
+
+    end subroutine s_mpi_allreduce_vectors_sum
</file context>
Suggested change
#endif
#else
var_glb = var_loc
#endif

✅ Addressed in 8531946

end subroutine s_mpi_allreduce_vectors_sum
!> The following subroutine takes the input local variable
!! from all processors and reduces to the sum of all
!! values. The reduced variable is recorded back onto the
Expand Down
2 changes: 2 additions & 0 deletions src/pre_process/m_global_parameters.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,8 @@ contains
patch_ib(i)%vel(:) = 0._wp
patch_ib(i)%angles(:) = 0._wp
patch_ib(i)%angular_vel(:) = 0._wp
patch_ib(i)%mass = dflt_real
patch_ib(i)%moment = dflt_real

! sets values of a rotation matrix which can be used when calculating rotations
patch_ib(i)%rotation_matrix = 0._wp
Expand Down
165 changes: 164 additions & 1 deletion src/simulation/m_ibm.fpp
Copy link
Contributor

Choose a reason for hiding this comment

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

High-level Suggestion

The force and torque calculations in s_compute_ib_forces are incorrect because the pressure gradient and cell volume are computed using cell coordinates instead of grid spacing and cell dimensions. This invalidates the fluid-structure interaction results. [High-level, importance: 9]

Solution Walkthrough:

Before:

subroutine s_compute_ib_forces(pressure)
    ...
    do i = 0, m
        do j = 0, n
            do k = 0, p
                ...
                ! Incorrect pressure gradient using cell-center coordinates in denominator
                pressure_divergence(1) = (pressure(i+1,j,k) - pressure(i-1,j,k)) / (2._wp * x_cc(i))
                pressure_divergence(2) = (pressure(i,j+1,k) - pressure(i,j-1,k)) / (2._wp * y_cc(j))
                
                ! Incorrect cell volume calculation using coordinates
                cell_volume = x_cc(i) * y_cc(j)
                if (num_dims == 3) then
                    pressure_divergence(3) = (pressure(i,j,k+1) - pressure(i,j,k-1)) / (2._wp * z_cc(k))
                    cell_volume = cell_volume * z_cc(k)
                end if

                forces(ib_idx, :) = forces(ib_idx, :) - (pressure_divergence * cell_volume)
                ...
            end do
        end do
    end do
end subroutine

After:

subroutine s_compute_ib_forces(pressure)
    ...
    ! Grid spacings (dx, dy, dz) should be available or computed
    ! e.g., dx = x_cc(1) - x_cc(0) for a uniform grid
    
    do i = 0, m
        do j = 0, n
            do k = 0, p
                ...
                ! Correct pressure gradient using grid spacing
                pressure_divergence(1) = (pressure(i+1,j,k) - pressure(i-1,j,k)) / (2._wp * dx)
                pressure_divergence(2) = (pressure(i,j+1,k) - pressure(i,j-1,k)) / (2._wp * dy)
                
                ! Correct cell volume calculation
                cell_volume = dx * dy
                if (num_dims == 3) then
                    pressure_divergence(3) = (pressure(i,j,k+1) - pressure(i,j,k-1)) / (2._wp * dz)
                    cell_volume = cell_volume * dz
                end if

                forces(ib_idx, :) = forces(ib_idx, :) - (pressure_divergence * cell_volume)
                ...
            end do
        end do
    end do
end subroutine

Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ contains
end if
call s_update_ib_rotation_matrix(i)
end do

$:GPU_ENTER_DATA(copyin='[patch_ib]')

! Allocating the patch identities bookkeeping variable
Expand Down Expand Up @@ -197,6 +196,21 @@ contains
type(ghost_point) :: gp
type(ghost_point) :: innerp

! set the Moving IBM interior Pressure Values
do patch_id = 1, num_ibs
if (patch_ib(patch_id)%moving_ibm == 2) then
do j = 0, m
do k = 0, n
do l = 0, p
if (ib_markers%sf(j, k, l) == patch_id) then
q_prim_vf(E_idx)%sf(j, k, l) = 1._wp
end if
end do
end do
end do
end if
end do
Comment on lines +199 to +212
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add GPU parallelization for nested loop over grid cells.

This triple-nested loop over the grid lacks GPU parallelization. In src/simulation/**/*.fpp files, such loops should be wrapped with the $:GPU_PARALLEL_LOOP macro for performance.

Apply this diff to add GPU parallelization:

+        $:GPU_PARALLEL_LOOP(private='[patch_id,j,k,l]', collapse=3)
         do patch_id = 1, num_ibs
             if (patch_ib(patch_id)%moving_ibm == 2) then
                 do j = 0, m
                     do k = 0, n
                         do l = 0, p
                             if (ib_markers%sf(j, k, l) == patch_id) then
                                 q_prim_vf(E_idx)%sf(j, k, l) = 1._wp
                             end if
                         end do
                     end do
                 end do
             end if
         end do
+        $:END_GPU_PARALLEL_LOOP()

As per coding guidelines for GPU code in simulation modules.

Committable suggestion skipped: line range outside the PR's diff.


if (num_gps > 0) then
$:GPU_PARALLEL_LOOP(private='[i,physical_loc,dyn_pres,alpha_rho_IP, alpha_IP,pres_IP,vel_IP,vel_g,vel_norm_IP,r_IP, v_IP,pb_IP,mv_IP,nmom_IP,presb_IP,massv_IP,rho, gamma,pi_inf,Re_K,G_K,Gs,gp,innerp,norm,buf, radial_vector, rotation_velocity, j,k,l,q,qv_K,c_IP,nbub,patch_id]')
do i = 1, num_gps
Expand Down Expand Up @@ -244,6 +258,17 @@ contains
if (surface_tension) then
q_prim_vf(c_idx)%sf(j, k, l) = c_IP
end if

! set the pressure
do q = 1, num_fluids
if (patch_ib(patch_id)%moving_ibm == 0) then
q_prim_vf(E_idx)%sf(j, k, l) = pres_IP
else
! TODO :: improve for two fluid
q_prim_vf(E_idx)%sf(j, k, l) = pres_IP/(1._wp - 2._wp*abs(levelset%sf(j, k, l, patch_id)*alpha_rho_IP(q)/pres_IP)*dot_product(patch_ib(patch_id)%force/patch_ib(patch_id)%mass, levelset_norm%sf(j, k, l, patch_id, :)))
end if
end do
Comment on lines +261 to +270
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

Redundant loop overwrites the same pressure value.

The loop do q = 1, num_fluids repeatedly overwrites q_prim_vf(E_idx)%sf(j, k, l) with each iteration, but only the final value (when q == num_fluids) is retained. This appears to be a logic error—either:

  1. Remove the loop if a single pressure value is intended, or
  2. The formula should accumulate contributions from each fluid.

The TODO comment suggests this needs revision for multi-fluid cases.

-! set the pressure
-do q = 1, num_fluids
-    if (patch_ib(patch_id)%moving_ibm == 0) then
-        q_prim_vf(E_idx)%sf(j, k, l) = pres_IP
-    else
-        ! TODO :: improve for two fluid
-        q_prim_vf(E_idx)%sf(j, k, l) = pres_IP/(1._wp - 2._wp*abs(levelset%sf(j, k, l, patch_id)*alpha_rho_IP(q)/pres_IP)*dot_product(patch_ib(patch_id)%force/patch_ib(patch_id)%mass, levelset_norm%sf(j, k, l, patch_id, :)))
-    end if
-end do
+! set the pressure
+if (patch_ib(patch_id)%moving_ibm == 0) then
+    q_prim_vf(E_idx)%sf(j, k, l) = pres_IP
+else
+    ! TODO :: improve for two fluid - currently uses first fluid's properties
+    if (abs(pres_IP) > 1.e-16_wp) then
+        buf = 1._wp - 2._wp*abs(levelset%sf(j, k, l, patch_id)*alpha_rho_IP(1)/pres_IP) * &
+              dot_product(patch_ib(patch_id)%force/patch_ib(patch_id)%mass, levelset_norm%sf(j, k, l, patch_id, :))
+        if (abs(buf) > 1.e-16_wp) then
+            q_prim_vf(E_idx)%sf(j, k, l) = pres_IP/buf
+        else
+            q_prim_vf(E_idx)%sf(j, k, l) = pres_IP
+        end if
+    else
+        q_prim_vf(E_idx)%sf(j, k, l) = pres_IP
+    end if
+end if

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/simulation/m_ibm.fpp around lines 261 to 270, the loop over q (do q = 1,
num_fluids) repeatedly overwrites q_prim_vf(E_idx)%sf(j, k, l) leaving only the
last fluid’s value; either remove the loop and set the single intended pressure
once, or implement a proper accumulation over q. Fix by choosing the intended
behavior: if a single pressure is intended, remove the loop and assign
q_prim_vf(E_idx)%sf(j,k,l) once (use the appropriate representative fluid index
or a fluid-independent formula); if multi-fluid contributions are required,
introduce a local accumulator (initialize to zero), add each fluid’s
contribution inside the loop, then assign the accumulated result to
q_prim_vf(E_idx)%sf(j,k,l); also update or remove the TODO comment accordingly.


if (model_eqns /= 4) then
! If in simulation, use acc mixture subroutines
if (elasticity) then
Expand Down Expand Up @@ -969,6 +994,73 @@ contains
end subroutine s_update_mib
! compute the surface integrals of the IB via a volume integraion method described in
! "A coupled IBM/Euler-Lagrange framework for simulating shock-induced particle size segregation"
! by Archana Sridhar and Jesse Capecelatro
subroutine s_compute_ib_forces(pressure)
real(wp), dimension(0:m, 0:n, 0:p), intent(in) :: pressure
integer :: i, j, k, ib_idx
real(wp), dimension(num_ibs, 3) :: forces, torques
real(wp), dimension(1:3) :: pressure_divergence, radial_vector
real(wp) :: cell_volume
forces = 0._wp
torques = 0._wp
! TODO :: This is currently only valid inviscid, and needs to be extended to add viscocity
$:GPU_PARALLEL_LOOP(private='[ib_idx,radial_vector,pressure_divergence,cell_volume]', copy='[forces,torques]', copyin='[ib_markers]', collapse=3)
do i = 0, m
do j = 0, n
do k = 0, p
ib_idx = ib_markers%sf(i, j, k)
if (ib_idx /= 0) then ! only need to compute the gradient for cells inside a IB
if (patch_ib(ib_idx)%moving_ibm == 2) then ! make sure that this IB has 2-way coupling enabled
! get the vector pointing to the grid cell from the IB centroid
if (num_dims == 3) then
radial_vector = [x_cc(i), y_cc(j), z_cc(k)] - [patch_ib(ib_idx)%x_centroid, patch_ib(ib_idx)%y_centroid, patch_ib(ib_idx)%z_centroid]
else
radial_vector = [x_cc(i), y_cc(j), 0._wp] - [patch_ib(ib_idx)%x_centroid, patch_ib(ib_idx)%y_centroid, 0._wp]
end if
! use a finite difference to compute the 2D components of the gradient of the pressure and cell volume
pressure_divergence(1) = (pressure(i + 1, j, k) - pressure(i - 1, j, k))/(2._wp*x_cc(i))
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

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

P1: Pressure gradient calculation uses x_cc(i) (cell center coordinate) instead of grid spacing dx(i) in denominator. For central difference: (P[i+1]-P[i-1])/(2*dx). Similarly, cell_volume should use grid spacings dx(i)*dy(j), not coordinates.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/simulation/m_ibm.fpp, line 1025:

<comment>Pressure gradient calculation uses `x_cc(i)` (cell center coordinate) instead of grid spacing `dx(i)` in denominator. For central difference: `(P[i+1]-P[i-1])/(2*dx)`. Similarly, `cell_volume` should use grid spacings `dx(i)*dy(j)`, not coordinates.</comment>

<file context>
@@ -969,6 +994,67 @@ contains
+                            else
+                                radial_vector = [x_cc(i), y_cc(j), 0._wp] - [patch_ib(ib_idx)%x_centroid, patch_ib(ib_idx)%y_centroid, 0._wp] ! get the vector pointing to the grid cell
+                            end if
+                            pressure_divergence(1) = (pressure(i + 1, j, k) - pressure(i - 1, j, k))/(2._wp*x_cc(i))
+                            pressure_divergence(2) = (pressure(i, j + 1, k) - pressure(i, j - 1, k))/(2._wp*y_cc(j))
+                            cell_volume = x_cc(i)*y_cc(j)
</file context>
Fix with Cubic

pressure_divergence(2) = (pressure(i, j + 1, k) - pressure(i, j - 1, k))/(2._wp*y_cc(j))
cell_volume = x_cc(i)*y_cc(j)
! add the 3D component, if we are working in 3 dimensions
if (num_dims == 3) then
pressure_divergence(3) = (pressure(i, j, k + 1) - pressure(i, j, k - 1))/(2._wp*z_cc(k))
cell_volume = cell_volume*z_cc(k)
else
pressure_divergence(3) = 0._wp
end if
! Update the force values atomically to prevent race conditions
$:GPU_ATOMIC(atomic='update')
forces(ib_idx, :) = forces(ib_idx, :) - (pressure_divergence*cell_volume)
$:GPU_ATOMIC(atomic='update')
torques(ib_idx, :) = torques(ib_idx, :) - (cross_product(radial_vector, pressure_divergence)*cell_volume)
end if
end if
end do
end do
end do
$:END_GPU_PARALLEL_LOOP()
! reduce the forces across all MPI ranks
call s_mpi_allreduce_vectors_sum(forces, forces, num_ibs, 3)
call s_mpi_allreduce_vectors_sum(torques, torques, num_ibs, 3)
! apply the summed forces
do i = 1, num_ibs
patch_ib(i)%force(:) = forces(i, :)
patch_ib(i)%torque(:) = matmul(patch_ib(i)%rotation_matrix_inverse, torques(i, :)) ! torques must be computed in the local coordinates of the IB
end do
end subroutine s_compute_ib_forces
!> Subroutine to deallocate memory reserved for the IBM module
impure subroutine s_finalize_ibm_module()
Expand All @@ -978,6 +1070,77 @@ contains
end subroutine s_finalize_ibm_module
subroutine s_compute_moment_of_inertia(ib_marker, axis)
real(wp), dimension(3), optional :: axis !< the axis about which we compute the moment. Only required in 3D.
integer, intent(in) :: ib_marker
real(wp) :: moment, distance_to_axis, cell_volume
real(wp), dimension(3) :: position, closest_point_along_axis, vector_to_axis
integer :: i, j, k, count
if (p == 0) then
axis = [0, 1, 0]
else if (sqrt(sum(axis**2)) == 0) then
! if the object is not actually rotating at this time, return a dummy value and exit
patch_ib(ib_marker)%moment = 1._wp
return
else
axis = axis/sqrt(sum(axis))
end if
Comment on lines +1082 to +1090
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

Optional argument handling issue in 2D case.

When p == 0 (2D), the code assigns to axis without checking if it was provided. Since axis is declared optional, assigning to it when not present is undefined behavior. Additionally, the axis parameter should have intent(inout) if it's being modified.

-subroutine s_compute_moment_of_inertia(ib_marker, axis)
+subroutine s_compute_moment_of_inertia(ib_marker, axis_in)
 
-    real(wp), dimension(3), optional :: axis !< the axis about which we compute the moment. Only required in 3D.
+    real(wp), dimension(3), intent(in), optional :: axis_in !< the axis about which we compute the moment. Only required in 3D.
     integer, intent(in) :: ib_marker
 
     real(wp) :: moment, distance_to_axis, cell_volume
-    real(wp), dimension(3) :: position, closest_point_along_axis, vector_to_axis
+    real(wp), dimension(3) :: position, closest_point_along_axis, vector_to_axis, axis
     integer :: i, j, k, count
 
     if (p == 0) then
-        axis = [0, 1, 0]
-    else if (sqrt(sum(axis**2)) == 0) then
+        axis = [0._wp, 0._wp, 1._wp]
+    else if (.not. present(axis_in) .or. sqrt(sum(axis_in**2)) < 1.e-16_wp) then
         ! if the object is not actually rotating at this time, return a dummy value and exit
         patch_ib(ib_marker)%moment = 1._wp
         return
     else
-        axis = axis/sqrt(sum(axis))
+        axis = axis_in/sqrt(sum(axis_in**2))
     end if

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/simulation/m_ibm.fpp around lines 1076 to 1084, the branch for p == 0
assigns to the optional argument axis (undefined if not present) and axis should
be declared intent(inout) if modified; fix by (a) guarding any write/read of
axis with present(axis) (e.g., only assign axis = [0,1,0] when present(axis) is
true), (b) avoid touching axis when not present (use a local dummy variable
instead), and (c) update the routine signature to declare axis as intent(inout)
if the caller is expected to receive the modified axis. Ensure other uses in
this block also check present(axis) before reading or normalizing it.

! if the IB is in 2D or a 3D sphere, we can compute this exactly
if (patch_ib(ib_marker)%geometry == 2) then ! circle
patch_ib(ib_marker)%moment = 0.5*patch_ib(ib_marker)%mass*(patch_ib(ib_marker)%radius)**2
elseif (patch_ib(ib_marker)%geometry == 3) then ! rectangle
patch_ib(ib_marker)%moment = patch_ib(i)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._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 | 🔴 Critical

Variable name typo: Uses i instead of ib_marker.

Line 1090 references patch_ib(i) but i is a loop iterator declared later. This should be patch_ib(ib_marker).

 elseif (patch_ib(ib_marker)%geometry == 3) then ! rectangle
-    patch_ib(ib_marker)%moment = patch_ib(i)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._wp
+    patch_ib(ib_marker)%moment = patch_ib(ib_marker)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._wp
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
patch_ib(ib_marker)%moment = patch_ib(i)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._wp
patch_ib(ib_marker)%moment = patch_ib(ib_marker)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._wp
🤖 Prompt for AI Agents
In src/simulation/m_ibm.fpp around line 1090, the assignment uses
patch_ib(i)%mass but the loop index there should be ib_marker; change the
reference from patch_ib(i) to patch_ib(ib_marker) so the mass and lengths come
from the same element (patch_ib(ib_marker)). Ensure the variable ib_marker is in
scope at this line and remove any accidental dependence on i.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

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

P0: Wrong index variable: patch_ib(i)%mass should be patch_ib(ib_marker)%mass. Variable i is undefined at this point in the code path - it's only used as a loop variable in the numerical approximation section below.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/simulation/m_ibm.fpp, line 1090:

<comment>Wrong index variable: `patch_ib(i)%mass` should be `patch_ib(ib_marker)%mass`. Variable `i` is undefined at this point in the code path - it&#39;s only used as a loop variable in the numerical approximation section below.</comment>

<file context>
@@ -978,6 +1064,77 @@ contains
+        if (patch_ib(ib_marker)%geometry == 2) then ! circle
+            patch_ib(ib_marker)%moment = 0.5*patch_ib(ib_marker)%mass*(patch_ib(ib_marker)%radius)**2
+        elseif (patch_ib(ib_marker)%geometry == 3) then ! rectangle
+            patch_ib(ib_marker)%moment = patch_ib(i)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._wp
+        elseif (patch_ib(ib_marker)%geometry == 8) then ! sphere
+            patch_ib(ib_marker)%moment = 0.4*patch_ib(ib_marker)%mass*(patch_ib(ib_marker)%radius)**2
</file context>
Suggested change
patch_ib(ib_marker)%moment = patch_ib(i)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._wp
patch_ib(ib_marker)%moment = patch_ib(ib_marker)%mass*(patch_ib(ib_marker)%length_x**2 + patch_ib(ib_marker)%length_y**2)/6._wp
Fix with Cubic

elseif (patch_ib(ib_marker)%geometry == 8) then ! sphere
patch_ib(ib_marker)%moment = 0.4*patch_ib(ib_marker)%mass*(patch_ib(ib_marker)%radius)**2
else ! we do not have an analytic moment of inertia calculation and need to approximate it directly
count = 0
moment = 0._wp
cell_volume = (x_cc(1) - x_cc(0))*(y_cc(1) - y_cc(0)) ! computed without grid stretching. Update in the loop to perform with stretching
if (p /= 0) then
cell_volume = cell_volume*(z_cc(1) - z_cc(0))
end if
$:GPU_PARALLEL_LOOP(private='[position,closest_point_along_axis,vector_to_axis,distance_to_axis]', copy='[moment,count]', copyin='[ib_marker,cell_volume,axis]', collapse=3)
do i = 0, m
do j = 0, n
do k = 0, p
if (ib_markers%sf(i, j, k) == ib_marker) then
$:GPU_ATOMIC(atomic='update')
count = count + 1 ! increment the count of total cells in the boundary
! get the position in local coordinates so that the axis passes through 0, 0, 0
if (p == 0) then
position = [x_cc(i), y_cc(j), 0._wp] - [patch_ib(ib_marker)%x_centroid, patch_ib(ib_marker)%y_centroid, 0._wp]
else
position = [x_cc(i), y_cc(j), z_cc(k)] - [patch_ib(ib_marker)%x_centroid, patch_ib(ib_marker)%y_centroid, patch_ib(ib_marker)%z_centroid]
end if
! project the position along the axis to find the closest distance to the rotation axis
closest_point_along_axis = axis*dot_product(axis, position)
vector_to_axis = position - closest_point_along_axis
distance_to_axis = sum(vector_to_axis) ! saves the distance to the axis squared
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 | 🔴 Critical

Incorrect distance calculation - missing square root and squaring.

The distance_to_axis should be the squared distance (for moment of inertia) or the actual distance. Currently it uses sum(vector_to_axis) which sums the components, not their squares. The moment of inertia formula requires .

-distance_to_axis = sum(vector_to_axis) ! saves the distance to the axis squared
+distance_to_axis = sum(vector_to_axis**2) ! distance squared to the rotation axis
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
distance_to_axis = sum(vector_to_axis) ! saves the distance to the axis squared
distance_to_axis = sum(vector_to_axis**2) ! distance squared to the rotation axis
🤖 Prompt for AI Agents
In src/simulation/m_ibm.fpp around line 1120, the assignment uses
sum(vector_to_axis) which incorrectly sums components instead of computing r or
r^2; replace this with the squared distance computed as the sum of each
component squared (e.g., sum of vector_to_axis[i]*vector_to_axis[i]) so
distance_to_axis holds r^2 for moment-of-inertia calculations; if you actually
need the linear distance elsewhere, compute sqrt(r^2) and use a different
variable name to avoid confusion.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

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

P1: Incorrect distance calculation: sum(vector_to_axis) sums components, not computing squared distance. Should be sum(vector_to_axis**2) per the comment.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/simulation/m_ibm.fpp, line 1121:

<comment>Incorrect distance calculation: `sum(vector_to_axis)` sums components, not computing squared distance. Should be `sum(vector_to_axis**2)` per the comment.</comment>

<file context>
@@ -978,6 +1064,78 @@ contains
+                            ! project the position along the axis to find the closest distance to the rotation axis
+                            closest_point_along_axis = axis*dot_product(axis, position)
+                            vector_to_axis = position - closest_point_along_axis
+                            distance_to_axis = sum(vector_to_axis) ! saves the distance to the axis squared
+
+                            ! compute the position component of the moment
</file context>
Suggested change
distance_to_axis = sum(vector_to_axis) ! saves the distance to the axis squared
distance_to_axis = sum(vector_to_axis**2) ! saves the distance to the axis squared
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

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

P1: Incorrect distance calculation: sum(vector_to_axis) computes the sum of components, not the squared distance. For moment of inertia, this should be sum(vector_to_axis**2) to get the squared perpendicular distance from the rotation axis.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/simulation/m_ibm.fpp, line 1120:

<comment>Incorrect distance calculation: `sum(vector_to_axis)` computes the sum of components, not the squared distance. For moment of inertia, this should be `sum(vector_to_axis**2)` to get the squared perpendicular distance from the rotation axis.</comment>

<file context>
@@ -978,6 +1064,77 @@ contains
+                            ! project the position along the axis to find the closest distance to the rotation axis
+                            closest_point_along_axis = axis*dot_product(axis, position)
+                            vector_to_axis = position - closest_point_along_axis
+                            distance_to_axis = sum(vector_to_axis) ! saves the distance to the axis squared
+
+                            ! compute the position component of the moment
</file context>
Suggested change
distance_to_axis = sum(vector_to_axis) ! saves the distance to the axis squared
distance_to_axis = sum(vector_to_axis**2) ! saves the distance to the axis squared
Fix with Cubic

! compute the position component of the moment
$:GPU_ATOMIC(atomic='update')
moment = moment + distance_to_axis
end if
end do
end do
end do
$:END_GPU_PARALLEL_LOOP()
! write the final moment assuming the points are all uniform density
patch_ib(ib_marker)%moment = moment*patch_ib(ib_marker)%mass/(count*cell_volume)
$:GPU_UPDATE(device='[patch_ib(ib_marker)%moment]')
end if
end subroutine s_compute_moment_of_inertia
function cross_product(a, b) result(c)
implicit none
real(wp), intent(in) :: a(3), b(3)
Expand Down
2 changes: 1 addition & 1 deletion src/simulation/m_mpi_proxy.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ contains
do i = 1, num_ibs
#:for VAR in [ 'radius', 'length_x', 'length_y', 'length_z', &
& 'x_centroid', 'y_centroid', 'z_centroid', 'c', 'm', 'p', 't', 'theta', 'slip']
& 'x_centroid', 'y_centroid', 'z_centroid', 'c', 'm', 'p', 't', 'theta', 'slip', 'mass']
call MPI_BCAST(patch_ib(i)%${VAR}$, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)
#:endfor
#:for VAR in ['vel', 'angular_vel', 'angles']
Expand Down
Loading
Loading