Record immersed-boundary forces and kinematics every time step - #1846
Record immersed-boundary forces and kinematics every time step#1846sbryngelson wants to merge 2 commits into
Conversation
`ib_state_wrt` writes the force, torque and kinematic state of each immersed boundary only at snapshot intervals, so the force history is sampled at the field-output cadence. That is far too coarse to compare a transient load against an experiment or a reference computation, or to drive a reduced-order model from a force signal: typical cases here write a field every few hundred steps. Write one record per time step to `D/ib<id>_forces.dat` (time step, time, force, torque, velocity, angular velocity, angles, centroid), under the existing `ib_state_wrt` flag. Records are buffered per rank and flushed in batches rather than opened per body per step: opening a file per body per step is a metadata operation per step on a parallel filesystem and does not scale, and a particle bed of a thousand bodies would issue on the order of a hundred million of them over a long run. Each buffered row carries its own global body id, so a body changing owner mid-run needs no special handling, and `ib_force_stride` subsamples runs long enough for the record itself to become large. Claude-Session: https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Adds per-time-step immersed-boundary force/kinematics logging (buffered + flushed in batches) so IB force histories are recorded at high temporal resolution, with optional subsampling via ib_force_stride.
Changes:
- Introduces
ib_force_strideparameter (docs + toolchain registration + validation). - Writes buffered per-step IB force/torque/kinematics rows and flushes at shutdown.
- Hooks per-step recording into the time-step loop when
ib_state_wrtis enabled.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| toolchain/mfc/params/descriptions.py | Adds description string for ib_force_stride. |
| toolchain/mfc/params/definitions.py | Registers ib_force_stride in parameter definitions. |
| toolchain/mfc/case_validator.py | Validates ib_force_stride value in IBM checks. |
| src/simulation/m_time_steppers.fpp | Calls per-step IB force logging routine. |
| src/simulation/m_start_up.fpp | Flushes buffered IB force records at shutdown. |
| src/simulation/m_global_parameters.fpp | Sets default ib_force_stride = 1. |
| src/simulation/m_data_output.fpp | Implements buffered IB force record writing + flush to D/ib<id>_forces.dat. |
| docs/documentation/case.md | Documents ib_force_stride and new per-step IB force record behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| write (file_loc, '(A,I0,A)') '/D/ib', ib_id, '_forces.dat' | ||
| file_loc = trim(case_dir) // trim(file_loc) | ||
| inquire (file=trim(file_loc), exist=file_exist) | ||
| if (file_exist) then | ||
| open (newunit=file_unit, file=trim(file_loc), form='formatted', status='old', position='append') | ||
| else | ||
| open (newunit=file_unit, file=trim(file_loc), form='formatted', status='new') | ||
| write (file_unit, '(A)') '# t_step time Fx Fy Fz Tx Ty Tz vx vy vz wx wy wz ax ay az xc yc zc' |
| !> Buffered immersed-boundary force records: (id, t_step, time, force, torque, vel, angular_vel, angles, centroid) | ||
| integer, parameter :: ib_force_buf_len = 1024 | ||
| real(wp), dimension(21, ib_force_buf_len) :: ib_force_buf |
| $:GPU_UPDATE(host='[patch_ib(1:num_ibs)]') | ||
|
|
||
| do i = 1, n_write | ||
| ib_idx = i | ||
| if (num_procs > 1) ib_idx = local_ib_patch_ids(i) |
| ) | ||
| self.prohibit(not ib and num_ibs > 0, "num_ibs is set, but ib is not enabled") | ||
| self.prohibit(ib_state_wrt and not ib, "ib_state_wrt requires ib to be enabled") | ||
| ib_force_stride = self.get("ib_force_stride", 1) or 1 |
| integer :: i, j, ib_id, file_unit | ||
| logical :: file_exist | ||
|
|
||
| do i = 1, ib_force_buf_n |
| write (file_unit, '(A)') '# t_step time Fx Fy Fz Tx Ty Tz vx vy vz wx wy wz ax ay az xc yc zc' | ||
| end if | ||
|
|
||
| do j = i, ib_force_buf_n ! all rows for this body, in time order |
ES18.10E3 is exactly wide enough for a negative value with a three-digit exponent (-1.2345678901E+003), so adjacent values were written with no space between them and the file could not be read as whitespace-separated columns. Use an explicit 1X separator. Claude-Session: https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG
|
Claude Code Review Head SHA: 0d79d0e Files changed:
Findings:
|
Lines of Code
|
Why
ib_state_wrtrecords the force, torque and kinematic state of each immersed boundary only at snapshot intervals, so the force history is sampled at the field-output cadence — typically every few hundred steps. That is too coarse to compare a transient load against an experiment or a reference computation, or to drive a reduced-order model from a force signal. Every force comparison in the verification work behind this PR needed a per-step record.What
One record per time step in
D/ib<id>_forces.dat: time step, time, force, torque, velocity, angular velocity, angles, centroid. Gated by the existingib_state_wrt, so no new switch to enable it.Scaling
The obvious implementation — open, append, close, per body, per step — is a metadata operation per step on a parallel filesystem and does not scale. A particle bed of a thousand bodies over a long run would issue on the order of a hundred million of them. Instead records accumulate in a small fixed rank-local buffer (1024 rows) and are flushed in batches, with a final flush at shutdown. Each buffered row carries its own global body id, so a body changing owner mid-run needs no special handling: the previous owner's pending rows still reach the right file.
ib_force_stride(default 1) subsamples runs long enough for the record itself to become large. At the default the record is about 350 bytes per step per body — 25 MB for a 50k-step case, which is nothing next to the field output, but a thousand-body bed would want a stride.Testing
Exercised on Frontier across 2D and 3D immersed-boundary cases, static and moving, on 4, 8 and 64 ranks. The per-step forces were checked against an independent control-volume momentum balance and, for a static cylinder at Re 40, against the published drag coefficient.
./mfc.sh precheckpasses.https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG