P6-b: the compensating load uses the candidate's own Newmark acceleration a(d) = (d − u_pred)/(β Δt²) (consistent with the relaxed candidate the fluid saw; cancels exactly at the fixed point) — the lagged structure-output acceleration of the first try made the loop slower (1.8 → 5.2 subit/step at α 0 → 2)
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
Documentation / Build API Documentation (push) Failing after 6s
CI / Format Check (push) Failing after 10s
Documentation / Build User Guide (push) Successful in 6s
CI / Build CPU-Only (Explicit) (push) Failing after 21s
CI / Build (ubuntu-latest) (push) Failing after 1m26s
CI / Clippy Check (push) Failing after 2m1s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m36s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YJPeT6WA2e7YvAnS875AHL
This commit is contained in:
Omar Sobh
2026-09-15 08:37:35 -05:00
co-authored by Claude Fable 5.1
parent a2086a59de
commit c144a5f733
@@ -199,6 +199,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
// of the flag's high-frequency modes (the thickness breathing the
// body-fitted wall couples to at s = 1); the benchmark's average
// acceleration (γ = ½) has none.
let newmark_beta: f64;
let analysis = match std::env::var("RTX_FSI2O_NEWMARK_GAMMA")
.ok()
.and_then(|v| v.parse::<f64>().ok())
@@ -206,9 +207,13 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
Some(g) => {
let b = (g + 0.5).powi(2) / 4.0;
println!(" Newmark γ = {g}, β = {b:.4}");
newmark_beta = b;
analysis.with_newmark_parameters(g, b)
}
None => analysis,
None => {
newmark_beta = 0.25;
analysis
}
};
let flag = RefCell::new(analysis.stepper().unwrap());
let wetted_dofs: Vec<[usize; 2]> = fluid
@@ -269,6 +274,27 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
fict_per_node * wetted_dofs.len() as f64
);
}
// The Newmark acceleration a CANDIDATE displacement implies at the wetted
// DoFs, a(d) = (d u_pred) / (β Δt²) with u_pred from the committed
// state — the acceleration the structure itself would report at d, so
// the compensating load M_f a(d) is consistent with the relaxed candidate
// the fluid was evaluated at, and cancels exactly at the fixed point
// (a lagged structure-output acceleration does not: 09-16's first try
// made the loop SLOWER, 1.8 → 5.2 subit/step at α 0 → 2).
let accel_of = |d_candidate: &[f64], committed: &DynamicState| -> Vec<f64> {
let inv = 1.0 / (newmark_beta * dt * dt);
let mut a = vec![0.0; 2 * wetted_dofs.len()];
for (k, dofs) in wetted_dofs.iter().enumerate() {
for c in 0..2 {
let dof = dofs[c];
let u_pred = committed.displacement[dof]
+ dt * committed.velocity[dof]
+ dt * dt * (0.5 - newmark_beta) * committed.acceleration[dof];
a[2 * k + c] = inv * (d_candidate[2 * k + c] - u_pred);
}
}
a
};
// The load with the compensating term for a given previous-subiterate acceleration.
let with_fict =
|nodal: &[(NodeId, Vector3<f64>)], accel: &[f64]| -> Vec<(NodeId, Vector3<f64>)> {
@@ -345,11 +371,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
.unwrap_or(0);
let phase_start = std::time::Instant::now();
// The previous subiterate's interface acceleration (the fictitious mass's
// compensating load): the committed state's at each step's start.
let last_accel: RefCell<Vec<f64>> = RefCell::new(extract_accel(&flag_state));
for step in 0..coupled_steps {
last_accel.replace(extract_accel(&flag_state));
let d_n = extract(&flag_state);
let v_n: Option<Vec<f64>> = cfg.c1_interface.then(|| extract_velocity(&flag_state));
let d_predicted = if cfg.predictor == "kinematic" {
@@ -359,7 +381,6 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
flag.borrow_mut()
.set_nodal_forces(&with_fict(&committed_nodal, &extract_accel(&flag_state)));
let (predicted, _) = flag.borrow_mut().step(&flag_state).unwrap();
last_accel.replace(extract_accel(&predicted));
extract(&predicted)
};
let saved = fluid.borrow().snapshot();
@@ -375,9 +396,8 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
t_fluid.set(t_fluid.get() + fs.elapsed().as_secs_f64());
let ss = std::time::Instant::now();
let mut flag_ref = flag.borrow_mut();
flag_ref.set_nodal_forces(&with_fict(&nodal, &last_accel.borrow()));
flag_ref.set_nodal_forces(&with_fict(&nodal, &accel_of(d_candidate, &flag_state)));
let (candidate_state, _) = flag_ref.step(&flag_state).unwrap();
last_accel.replace(extract_accel(&candidate_state));
t_structure.set(t_structure.get() + ss.elapsed().as_secs_f64());
let d_new = extract(&candidate_state);
if step < cfg.trace_steps {