Skip to content
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 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
cfe6714
Fixed issue with cell volume calculations
danieljvickers Dec 4, 2025
a216ed4
Several major updates to the pressure and new golden files
danieljvickers Dec 4, 2025
2eedfe6
Found my 3D sphere error
danieljvickers Dec 5, 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
Copy link

Choose a reason for hiding this comment

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

Suggestion: Unused import: math is imported but never used anywhere; keep dead imports out of the codebase to avoid confusion and maintain clean dependencies — remove the import. [possible bug]

Severity Level: Critical 🚨

Suggested change
import math
# removed unused import 'math'
Why it matters? ⭐

The module imports math but never uses it anywhere in the current PR diff. Removing the import removes dead code and linter warnings and has no functional impact.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** examples/2D_mibm_cylinder_in_cross_flow/case.py
**Line:** 2:2
**Comment:**
	*Possible Bug: Unused import: `math` is imported but never used anywhere; keep dead imports out of the codebase to avoid confusion and maintain clean dependencies — remove the import.

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.


Mu = 1.84e-05
Copy link

Choose a reason for hiding this comment

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

Suggestion: Unused variable: Mu is assigned but never referenced; this is dead code and can be removed to avoid misleading readers about intended viscous computations. [possible bug]

Severity Level: Critical 🚨

Suggested change
Mu = 1.84e-05
# removed unused variable 'Mu'
Why it matters? ⭐

The variable Mu is defined but not referenced anywhere in the file after the PR. It's dead code and should be removed to avoid confusion; this is a harmless cleanup that doesn't change behavior.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** examples/2D_mibm_cylinder_in_cross_flow/case.py
**Line:** 4:4
**Comment:**
	*Possible Bug: Unused variable: `Mu` is assigned but never referenced; this is dead code and can be removed to avoid misleading readers about intended viscous computations.

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.

gam_a = 1.4

# Configuring case dictionary
print(
Copy link

Choose a reason for hiding this comment

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

Suggestion: Module-level side effect: the code prints the JSON at import time which causes unexpected output whenever this module is imported; wrap the printing in an if __name__ == "__main__": guard so the JSON is only printed when the script is executed directly. [logic error]

Severity Level: Minor ⚠️

Suggested change
print(
if __name__ == "__main__":
Why it matters? ⭐

The module currently performs a print at import time which is a side effect and can surprise callers who import this module. Wrapping the print in if name == "main": is the conventional, low-risk fix to ensure the JSON is only emitted when the script is executed directly.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** examples/2D_mibm_cylinder_in_cross_flow/case.py
**Line:** 8:8
**Comment:**
	*Logic Error: Module-level side effect: the code prints the JSON at import time which causes unexpected output whenever this module is imported; wrap the printing in an `if __name__ == "__main__":` guard so the JSON is only printed when the script is executed directly.

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.

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": 1.0e-6, # z-axis rotation
Copy link
Contributor

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

Choose a reason for hiding this comment

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

P2: Misleading comment: The comment says # z-axis rotation but this parameter sets the mass of the immersed boundary, not rotation. This appears to be a copy-paste error from the previous line.

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>Misleading comment: The comment says `# z-axis rotation` but this parameter sets the mass of the immersed boundary, not rotation. This appears to be a copy-paste error from the previous line.</comment>

<file context>
@@ -95,7 +95,7 @@
             &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
+            &quot;patch_ib(1)%mass&quot;: 1.0e-6,  # 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": 1.0e-6, # z-axis rotation
"patch_ib(1)%mass": 1.0e-6, # 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
Copy link

Choose a reason for hiding this comment

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

Suggestion: The new force and torque arrays use an explicit lower bound notation dimension(1:3) while many other 3-component vectors in this file use dimension(3) or L(3)/R(3) notation; this inconsistency can confuse callers and increase the risk of indexing mistakes. Use the canonical dimension(3) form for 3-element vectors to match the rest of the module and avoid subtle off-by-bound assumptions. [logic error]

Severity Level: Minor ⚠️

Suggested change
real(wp), dimension(1:3) :: force, torque ! vectors for the computed force and torque values applied to an IB
real(wp), dimension(3) :: force, torque ! vectors for the computed force and torque values applied to an IB
Why it matters? ⭐

Changing dimension(1:3) to dimension(3) makes the declaration consistent with other 3-component vectors in the module and avoids accidental reliance on a non-unit lower bound. It's a cosmetic/clarity improvement with no behavioral impact.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/common/m_derived_types.fpp
**Line:** 336:336
**Comment:**
	*Logic Error: The new `force` and `torque` arrays use an explicit lower bound notation `dimension(1:3)` while many other 3-component vectors in this file use `dimension(3)` or `L(3)/R(3)` notation; this inconsistency can confuse callers and increase the risk of indexing mistakes. Use the canonical `dimension(3)` form for 3-element vectors to match the rest of the module and avoid subtle off-by-bound assumptions.

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.

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
Comment on lines +479 to +481
Copy link

Choose a reason for hiding this comment

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

Suggestion: Intent mismatch for in-place reduction: var_glb is declared with INTENT(OUT) but the subroutine uses MPI_IN_PLACE when the caller passes the same buffer for send and receive; INTENT(OUT) allows the compiler to undefine or reallocate the actual argument before the call which makes in-place MPI operations unsafe. Change var_glb to INTENT(INOUT) so the caller's data is preserved on entry for MPI_IN_PLACE use. [logic error]

Severity Level: Minor ⚠️

Suggested change
integer, intent(in) :: num_vectors, vector_length
real(wp), dimension(:, :), intent(in) :: var_loc
real(wp), dimension(:, :), intent(out) :: var_glb
real(wp), dimension(:, :), intent(inout) :: var_glb
Why it matters? ⭐

This is a valid correctness issue. INTENT(OUT) permits the compiler to treat the actual argument as undefined on entry (and even reallocate/erase it), which makes using MPI_IN_PLACE unsafe when the caller passes the same buffer for send and receive. Changing var_glb to INTENT(INOUT) accurately documents and guarantees the value is preserved on entry and is the correct contract for potential in-place MPI calls.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/common/m_mpi_common.fpp
**Line:** 479:481
**Comment:**
	*Logic Error: Intent mismatch for in-place reduction: `var_glb` is declared with INTENT(OUT) but the subroutine uses MPI_IN_PLACE when the caller passes the same buffer for send and receive; INTENT(OUT) allows the compiler to undefine or reallocate the actual argument before the call which makes in-place MPI operations unsafe. Change `var_glb` to INTENT(INOUT) so the caller's data is preserved on entry for MPI_IN_PLACE use.

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.


#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(1:num_vectors, 1:vector_length) = var_loc(1:num_vectors, 1:vector_length)
#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
169 changes: 168 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,19 @@ contains
type(ghost_point) :: gp
type(ghost_point) :: innerp

! set the Moving IBM interior Pressure Values
$:GPU_PARALLEL_LOOP(private='[i,j,k]', copyin='[E_idx]', collapse=3)
do l = 0, p
do k = 0, n
do j = 0, m
if (ib_markers%sf(j, k, l) /= 0) then
q_prim_vf(E_idx)%sf(j, k, l) = 1._wp
end if
end do
end do
end do
$:END_GPU_PARALLEL_LOOP()

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 +256,18 @@ contains
if (surface_tension) then
q_prim_vf(c_idx)%sf(j, k, l) = c_IP
end if

! set the pressure
if (patch_ib(patch_id)%moving_ibm == 0) then
q_prim_vf(E_idx)%sf(j, k, l) = pres_IP
else
q_prim_vf(E_idx)%sf(j, k, l) = 0._wp
$:GPU_LOOP(parallelism='[seq]')
do q = 1, num_fluids
q_prim_vf(E_idx)%sf(j, k, l) = 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 do
end if

if (model_eqns /= 4) then
! If in simulation, use acc mixture subroutines
if (elasticity) then
Expand Down Expand Up @@ -969,6 +993,78 @@ 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, dx, dy, dz

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, dx, dy, dz]', 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
dx = x_cc(i + 1) - x_cc(i)
dy = y_cc(j + 1) - y_cc(j)

! 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*dx)
pressure_divergence(2) = (pressure(i, j + 1, k) - pressure(i, j - 1, k))/(2._wp*dy)
cell_volume = dx*dy

! add the 3D component, if we are working in 3 dimensions
if (num_dims == 3) then
dz = z_cc(k + 1) - z_cc(k)
pressure_divergence(3) = (pressure(i, j, k + 1) - pressure(i, j, k - 1))/(2._wp*dz)
cell_volume = cell_volume*dz
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

print *, forces(1, 1:2)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove debug print statement.

This debug output will produce excessive noise in production runs. Remove or guard with a debug flag.

-        print *, forces(1, 1:2)
📝 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
print *, forces(1, 1:2)
🤖 Prompt for AI Agents
In src/simulation/m_ibm.fpp around line 1064, there is a debug print statement
"print *, forces(1, 1:2)" that will produce excessive runtime output; remove
this line or wrap it in a conditional that checks a debug/logging flag (e.g., if
(debug) then ... end if) so it only prints when debugging is enabled, and ensure
any added flag is initialized/controlled by existing logging or
compilation/debug configuration.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

P2: Debug print statement should be removed before merging. This will pollute output logs and may cause performance issues in parallel execution.

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

<comment>Debug print statement should be removed before merging. This will pollute output logs and may cause performance issues in parallel execution.</comment>

<file context>
@@ -1059,6 +1061,8 @@ contains
             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
 
+        print *, forces(1, 1:2)
+
     end subroutine s_compute_ib_forces
</file context>
Fix with Cubic


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 +1074,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 +1087 to +1095
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(ib_marker)%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

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 = dot_product(vector_to_axis, vector_to_axis) ! saves the distance to the axis squared

! 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