Skip to content

Add prescribed kinematics for immersed boundaries - #1847

Open
sbryngelson wants to merge 9 commits into
masterfrom
feat/ib-prescribed-kinematics
Open

Add prescribed kinematics for immersed boundaries#1847
sbryngelson wants to merge 9 commits into
masterfrom
feat/ib-prescribed-kinematics

Conversation

@sbryngelson

Copy link
Copy Markdown
Member

Why

Moving immersed boundaries are driven either by two-way coupling or by analytic velocity expressions in the case file. For a parameter sweep or an ensemble the analytic path has two drawbacks:

  • the expressions are compiled into case.fpp, so every distinct motion forces a rebuild of the simulation binary — a three-point Strouhal sweep is three builds;
  • the motion is obtained by integrating the supplied rates, so a restart does not reproduce the same trajectory.

What

patch_ib(i)%kin_model sets the body state directly from the current time at each Runge-Kutta stage:

  • 1, hinged flapping — roll about the lab x axis through the hinge and pitch about the body spanwise axis through the same hinge. Inputs: hinge, body-frame hinge-to-centroid offset, roll and pitch amplitudes, mean pitch, frequency, pitch phase lead, onset time, raised-cosine onset ramp.
  • 2, smoothed pitch ramp and hold — the AIAA low-Reynolds-number canonical maneuver (Eldredge et al. 2009, Ol et al. 2010), with nominal pitch rate and the log-cosh smoothing parameter as inputs.

Nothing is integrated, so restarts are exact and one case-optimized binary serves every parameter value and every ensemble member.

A correctness fix that comes with it

The existing path advances the three angles componentwise from angular_vel, and then uses that same array as a vector in omega x r for the ghost-point velocity. Those two uses only agree when a single angle is changing. With the rotation composed as R = Rx(phi) Ry(theta), the lab-frame angular velocity is phi' e_x + theta' Rx(phi) e_y. The new routine sets that vector directly. On a plate rolling 30 degrees while pitching 20 degrees the difference reaches 10 percent of the surface velocity.

Testing

Against closed-form kinematics on a 3D plate: angles, centroid, centroid velocity and lab-frame angular velocity all match to 1e-15 at every step. The velocity carried by body cells adjacent to the fluid matches the rigid-body velocity to 8e-13, versus 4e-3 to 1e-1 if the Euler-angle rates are used as the angular velocity vector — so the check distinguishes the two. Also exercised on a 3D pitch-ramp validation case against published DNS. ./mfc.sh precheck passes.

Restricted to 3D by the validator, since the in-plane 2D rotation goes through a different path.

https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG

Moving immersed boundaries are driven either by two-way coupling or by analytic velocity
expressions in the case file. The analytic path has two drawbacks for a parameter sweep or
an ensemble: the expressions are compiled into `case.fpp`, so every distinct motion forces
a rebuild of the simulation binary, and the motion is obtained by integrating the supplied
rates, so a restart does not reproduce the same trajectory.

Add `patch_ib(i)%kin_model`, which sets the body state directly from the current time at
each Runge-Kutta stage:

  1  hinged flapping: roll about the lab x axis through the hinge and pitch about the body
     spanwise axis through the same hinge, with amplitudes, mean pitch, frequency, pitch
     phase lead, onset time and a raised-cosine onset ramp as inputs
  2  the smoothed linear pitch ramp and hold of the AIAA low-Reynolds-number canonical
     cases (Eldredge et al. 2009, Ol et al. 2010), with nominal pitch rate and smoothing
     parameter as inputs

Because nothing is integrated, restarts are exact and a single case-optimized binary serves
every parameter value and every ensemble member.

This also corrects the angular velocity used for the surface condition. The existing path
advances the three angles componentwise from `angular_vel` and then uses that same array as
a vector in `omega x r` for the ghost-point velocity. Those two uses only agree when a
single angle changes; with the rotation composed as R = Rx(phi) Ry(theta) the lab-frame
angular velocity is phi' e_x + theta' Rx(phi) e_y. The new routine sets that vector
directly. Measured on a plate rolling 30 degrees while pitching 20 degrees, the difference
reaches 10 percent of the surface velocity.

Verified against closed-form kinematics: angles, centroid, centroid velocity and angular
velocity all match to 1e-15 every step, and the velocity carried by the body cells adjacent
to the fluid matches the rigid-body velocity to 8e-13, against 4e-3 to 1e-1 if the
Euler-angle rates are used as the angular velocity.

Claude-Session: https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG
Copilot AI lite review requested due to automatic review settings September 11, 2026 14:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 runtime-evaluated prescribed kinematics for immersed boundary (IB) patches, enabling parameter sweeps without recompiling and making restarts reproduce identical trajectories.

Changes:

  • Introduces new patch_ib(i)%kin_* parameters (toolchain schema, validation, docs) for two prescribed kinematics models.
  • Updates simulation to evaluate prescribed kinematics per Runge–Kutta stage (and at initialization) instead of integrating rates.
  • Broadcasts new IB kinematics parameters via MPI and initializes them in global parameter setup.

Reviewed changes

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

Show a summary per file
File Description
toolchain/mfc/params/descriptions.py Adds human-readable descriptions for new kin_* IB parameters.
toolchain/mfc/params/definitions.py Registers new kin_* parameters in the toolchain registry.
toolchain/mfc/case_validator.py Validates kin_model and required inputs; restricts to moving IB in 3D.
src/simulation/m_time_steppers.fpp Calls prescribed kinematics during RK propagation using a computed stage time.
src/simulation/m_mpi_proxy.fpp Adds MPI broadcasts for the new kinematics parameters.
src/simulation/m_ibm.fpp Evaluates kinematics at initialization and implements the kinematics routines.
src/simulation/m_global_parameters.fpp Initializes kinematics parameters for simulation.
src/pre_process/m_global_parameters.fpp Initializes kinematics parameters for preprocessing.
src/common/m_derived_types.fpp Extends IB patch derived type with kinematics parameters.
docs/documentation/case.md Documents new IB kinematics parameters and models.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +792 to +795
t_stage = mytime + dt
if (time_stepper == time_stepper_rk3 .and. s == 2) t_stage = mytime + 0.5_wp*dt

$:GPU_PARALLEL_LOOP(private='[i, gbl_id]', copyin='[s, t_stage]')
Comment on lines +1001 to +1002
_ib_attrs[f"kin_hinge({j})"] = (REAL, _ib_tags)
_ib_attrs[f"kin_offset({j})"] = (REAL, _ib_tags)
Comment thread src/simulation/m_ibm.fpp Outdated

end function f_log_cosh

!> Prescribed hinged flapping kinematics (kin_model = 1). Roll phi about the lab x axis through the hinge and pitch theta about
@github-actions

Copy link
Copy Markdown

Claude Code Review

Head SHA: b9fed79

Files changed:

  • 10
  • docs/documentation/case.md
  • src/common/m_derived_types.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_ibm.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/simulation/m_time_steppers.fpp
  • toolchain/mfc/case_validator.py
  • toolchain/mfc/params/definitions.py
  • toolchain/mfc/params/descriptions.py

Findings:

  • src/simulation/m_ibm.fpp: In s_ibm_setup, the new t_init used to seed prescribed kinematics is reconstructed via t_step_start*dt (fixed dt) or t_save*n_start (cfl_dt) instead of reading the existing global mytime, which s_propagate_immersed_boundaries in src/simulation/m_time_steppers.fpp uses directly for the same purpose (t_stage = mytime + dt). If mytime is already restored from the checkpoint by the time s_ibm_setup runs, t_save*n_start is only a nominal approximation of the actual restart time under adaptive (cfl_dt) stepping, so a restarted flapping/pitching case could silently start its prescribed-kinematics phase at the wrong time. Worth confirming mytime isn't yet valid at this call site (which would justify the reconstruction) rather than assuming it.
  • src/simulation/m_ibm.fpp: The new f_log_cosh and s_prescribed_kinematics are inserted directly after the pre-existing Doxygen comment !> Compute pressure and viscous forces and torques on immersed bodies via volume integration, which previously documented s_compute_ib_forces. That comment now documents f_log_cosh instead, and s_compute_ib_forces is left with no leading doc comment.

sbryngelson and others added 3 commits September 11, 2026 12:22
Two immersed-boundary examples exercising the prescribed-kinematics option, both of which
can be checked without any reference data:

  examples/3D_ibm_flapping_plate  a wing in glide begins to flap (kin_model = 1). The
    motion has a closed form, so angles, centroid, centroid velocity and the lab-frame
    angular velocity can all be compared directly against it, and the glide lift can be
    compared with lifting-line theory for the aspect ratio.

  examples/3D_ibm_pitchup_plate   the AIAA low-Reynolds-number canonical pitch-up
    (kin_model = 2), validated against the DNS of Jantzen et al. (2014), Phys. Fluids 26,
    053606. Lift reaches 76 percent of the reference, consistent with modelling a finite
    square-edged section against their infinitely thin plate.

Both readmes record what agrees and what does not, including the streamwise-force defect
for thin inclined plates reported in #1849, for which the pitch-up case is a reproducer
that needs no reference data: the ratio of drag to lift on a plate held at 45 degrees
should be about 1 and instead collapses after the ramp.

They also document why the section is four cells thick, which a companion 2D grid study
showed to be the point where the lift through the ramp converges; two cells appears to
agree better with the reference only because two errors cancel.

Claude-Session: https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG
Every directory under examples/ is enumerated as a golden test, so the two
example cases added here arrived without goldens and failed on every lane.

Generating them exposed a second problem. The Example suite caps the grid at
25 cells per direction, which puts the pitch-up case's 5-percent-chord plate
at a third of a cell: the body occupies no cells, the golden is a uniform
field identical at step 0 and step 50, and no code change could ever perturb
it. That case is skipped with the reason recorded, and kin_model = 2 is
instead covered by a purpose-built test whose plate is four cells thick --
the minimum at which the body has an interior. The flapping example keeps its
golden: at the capped resolution its section is still 1.25 cells thick, so
the body exists and the field responds to it.

Claude-Session: https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG
@sbryngelson

Copy link
Copy Markdown
Member Author

Pushed 467a91525 for the reldebug lanes. The failure is not from this PR:

At line 1143 of file src/simulation/m_viscous.fpp
Fortran runtime error: Index '-2' of dimension 2 of array 'fd_coeff_y' below lower bound of 0

s_compute_ib_forces samples the viscous stress fd_number cells out from an interior cell, and s_compute_viscous_stress_tensor then looks up fd_coeff_y at that sample index, but the coefficients are allocated 0:n. A body within fd_number of a domain boundary therefore reads past the array. That path is master's, and this PR touches neither m_viscous.fpp nor the force integration - 3D_ibm_flapping_plate is just the first viscous IB case in the suite with a body near a boundary. Written up as #1856, same class as #1792. Verified with ./mfc.sh test --no-gpu --debug: the error reproduces on 86A2C59B without the change and all three cases pass with it.

The Pitch Ramp goldens are a separate matter and are not fixed yet. 18f123747 cut kin_theta0 and kin_pitch_rate by a hundred (holding t_p = theta0/rate = 0.025, so the Eldredge ramp keeps its shape and its a*t_p = 5 smoothing) because at the original amplitude the tip sweeps 1.5 cells over the 50 steps and perturbing kin_smooth by 5e-13 moved the step-50 field by 5e-3; at the reduced amplitude the same perturbation moves it by 2e-15. That was not enough. ubuntu no-debug GNU still reports

Max abs among failing: Candidate 2.5   Golden 1.30556685791813   (91% relative)

A candidate of exactly 2.5 against a golden of 1.31 is a cell still at its initial condition in one run and evolved in the other, i.e. solid in one and fluid in the other - a classification flip, not drift. The plate's y faces sit at exactly 0.30 and 0.70, which are cell centres on this 25-cell grid, and f_is_inside_cuboid decides with <=; the rotated coordinate reaching that comparison differs in its last bit between compilers. Perturbing the geometry does not test this, because it moves the face off the tie rather than changing how the tie is broken - which is why my earlier probe came back clean and I wrongly concluded the amplitude was the whole story.

The fix I would make next is to move the body off the grid so no face lands on a cell centre or boundary, then regenerate. Holding off until you say whether you want that or would rather drop the Pitch Ramp case, since 86A2C59B already covers kin_model = 1 and passes everywhere.

@github-actions

Copy link
Copy Markdown

Lines of Code

File Lines Diff
src/simulation/m_ibm.fpp 1317 +64
src/pre_process/m_global_parameters.fpp 462 +12
src/simulation/m_global_parameters.fpp 783 +12
src/common/m_derived_types.fpp 475 +6
src/simulation/m_mpi_proxy.fpp 528 +5
src/simulation/m_time_steppers.fpp 866 +5
src/simulation/m_viscous.fpp 1025 +4
src/simulation/m_particle_cloud.fpp 412 +1
Directory Lines Diff
common 10359 +6
pre_process 4525 +12
simulation 28177 +91
total 46449 +109

@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.15663% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.41%. Comparing base (dc0aec1) to head (3be50af).

Files with missing lines Patch % Lines
src/simulation/m_ibm.fpp 84.44% 3 Missing and 4 partials ⚠️
src/simulation/m_time_steppers.fpp 60.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1847      +/-   ##
==========================================
+ Coverage   61.26%   61.41%   +0.15%     
==========================================
  Files          84       84              
  Lines       22330    22409      +79     
  Branches     3265     3271       +6     
==========================================
+ Hits        13680    13763      +83     
+ Misses       6207     6195      -12     
- Partials     2443     2451       +8     

☔ View full report in Codecov by Harness.
📢 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.

@sbryngelson

Copy link
Copy Markdown
Member Author

Worked through the three review comments. One is fixed, two do not hold - details so the reasoning is checkable rather than just asserted.

Docstrings (m_ibm.fpp:993) - fixed in 32e85546. The brief did say kin_model = 1 while the routine serves both models; it now names both and says the ramp holds phi at zero. Inserting the two new routines had also orphaned the !> Compute pressure and viscous forces ... brief, which was left attached to f_log_cosh and had taken s_compute_ib_forces's away; that is restored.

kin_hinge/kin_offset registered as REAL rather than A_REAL - not a mismatch. A_REAL is ParamType.ANALYTIC_REAL (definitions.py:583), which is "may be given as an analytic expression", not "array real": schema.py:38 maps it to {"type": ["number", "string"]}, the string being the expression. vel(j)/angular_vel(j) use it because a moving IB's velocity can be written as an expression of time. A hinge point and a body-frame offset are static geometry, so plain REAL is the right type, and x_centroid/length_x are registered the same way for the same reason.

There is no scalar-versus-array mismatch either: both sit inside the same for j in range(1, 4) loop, so kin_hinge(1..3) are three registered components exactly as vel(1..3) are. The Fortran side is dimension(1:3) and m_mpi_proxy broadcasts it as length 3, which matches.

t_stage hard-coded for RK3 stage 2 - checked against the coefficients, and it is right. Reading rk_coef (m_time_steppers.fpp:426-433): stage 1 produces u1 at t^n + dt; stage 2 produces (3/4)u^n + (1/4)(u1 + dt L) at t^n + dt/2; stage 3 lands at t^n + dt. RK2's two stages both land at t^n + dt, and RK1's single stage at t^n + dt. So the s == 2 special case and the mytime + dt fall-through are each correct for every time_stepper in the tree.

mytime is also the right base to add to: it holds t^n throughout s_tvd_rk and is advanced only afterwards, at m_start_up.fpp:639. And the first stage of the first step is covered separately by s_ibm_setup's t_init, which reproduces p_main.fpp:46-56 for both the cfl_dt and fixed-dt paths.

Deriving the stage time from a stored abscissa table would be more robust against a future integrator, and I would take that change if the stage times were ever wrong today - but they are not, and a table would have to be added and kept in step with rk_coef, so it trades one coupling for another. Leaving it, with the reasoning recorded here.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants