R8-a harness: save/load of the coupled state (continue on the same grid, or extrude the slab onto the full duct)

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-25 18:14:16 -05:00
co-authored by Claude Opus 5.5
parent 12a27c6fca
commit 9867a8b831
3 changed files with 263 additions and 14 deletions
@@ -28,13 +28,15 @@
mod fluid;
#[path = "fsi2_harness/mod.rs"]
mod fsi2_harness;
#[path = "fsi2_embedded3/state.rs"]
mod state;
use std::cell::RefCell;
use std::io::Write as _;
use fluid::{CX, CY, Contribution, E3Fluid, HALF, Line, R_CYL};
use fsi2_harness::{FSI2, Interface, clamp_left, flag_mesh, median, mid_amp};
use nalgebra::Vector3;
use nalgebra::{DVector, Vector3};
use rtx_fea::analysis::{
AnalysisConfig, ConvergenceCriteria, DynamicState, NonlinearDynamicAnalysis,
};
@@ -251,7 +253,26 @@ fn fsi2_on_embedded3() {
let flag_geo = Flag::build(&mesh);
let zero_c = vec![0.0; 2 * STATIONS];
let rest = line_of(0.0, &zero_c, &zero_c, 1.0);
let mut fl = E3Fluid::build(ny, nz, speed, rest.clone());
// `RTX_E3FSI_LOAD=<dir>`: continue from a saved coupled state (extruded
// onto the full duct when the saved nz differs); `RTX_E3FSI_SAVE=<dir>`
// saves the state every `RTX_E3FSI_SAVE_EVERY` coupled steps (2000) and at the end.
let saved = std::env::var("RTX_E3FSI_LOAD")
.ok()
.map(|d| state::Saved::load(&d).expect("load the saved state"));
let save_dir = std::env::var("RTX_E3FSI_SAVE").ok();
let save_every = env_f("RTX_E3FSI_SAVE_EVERY", 2000.0) as usize;
let mut fl = E3Fluid::build(ny, nz, speed, rest.clone(), saved.as_ref());
if let Some(s) = &saved {
println!(
" loaded the coupled state at t {:.4} from {} ({}×{}×{} → nz {})",
s.t,
std::env::var("RTX_E3FSI_LOAD").unwrap(),
s.dims[0],
s.dims[1],
s.dims[2],
fl.grid.nz
);
}
let dt = fl.dt;
println!(
" R8-a FSI2 on embedded3: rigid to {t_rigid} s, coupled to {t_end} s; Aitken rtol {rtol:.1e} floor {floor:.1e} max {max_subit} stall accept {stall_accept}; Newmark γ {gamma}; the body's 2D counterpart = the overset SEMICIRCLE line (ny 62: 94.8 mm, 1.914 Hz)"
@@ -268,7 +289,11 @@ fn fsi2_on_embedded3() {
let start = std::time::Instant::now();
// Phase 1: the rigid flag (target 1: the rest state vs CFD2 136.7 / 10.53).
let rigid_steps = (t_rigid / dt).round() as usize;
let rigid_steps = if saved.is_some() {
0
} else {
(t_rigid / dt).round() as usize
};
let mut last = ([0.0; 3], Vec::new());
for step in 0..rigid_steps {
let r = fl.step();
@@ -318,7 +343,7 @@ fn fsi2_on_embedded3() {
tot0[1],
start.elapsed().as_secs_f64()
);
if rigid_only {
if rigid_only && saved.is_none() {
return;
}
@@ -363,16 +388,34 @@ fn fsi2_on_embedded3() {
c
};
let (nodal0, _, _, _) = distribute(&flag_geo, &rest, &contrib0, fl.width);
flag.borrow_mut().set_nodal_forces(&nodal0);
let mut flag_state = flag.borrow_mut().rest_state().unwrap();
let mut committed_nodal = nodal0;
// The fluid's own previous line and centreline (its geometry's history).
let mut line_n = Line {
t: fl.time(),
..rest.clone()
let (mut flag_state, mut committed_nodal, mut line_n, mut c_fluid_n) = match &saved {
Some(s) => {
let nodal: Vec<(NodeId, Vector3<f64>)> = flag_geo
.interface
.wetted
.iter()
.enumerate()
.map(|(k, &id)| (id, Vector3::new(s.nodal[2 * k], s.nodal[2 * k + 1], 0.0)))
.collect();
let state = DynamicState {
displacement: DVector::from_vec(s.disp.clone()),
velocity: DVector::from_vec(s.vel.clone()),
acceleration: DVector::from_vec(s.acc.clone()),
};
(state, nodal, s.line.clone(), s.c_fluid.clone())
}
None => {
let (nodal0, _, _, _) = distribute(&flag_geo, &rest, &contrib0, fl.width);
flag.borrow_mut().set_nodal_forces(&nodal0);
let state = flag.borrow_mut().rest_state().unwrap();
// The fluid's own previous line and centreline (its geometry's history).
let line = Line {
t: fl.time(),
..rest.clone()
};
(state, nodal0, line, zero_c.clone())
}
};
let mut c_fluid_n = zero_c.clone();
let coupled_steps = ((t_end - fl.time()) / dt).round() as usize;
let fl = RefCell::new(fl);
@@ -537,6 +580,34 @@ fn fsi2_on_embedded3() {
)
.unwrap();
}
let at_end = step + 1 == coupled_steps;
if let Some(dir) = save_dir
.as_ref()
.filter(|_| at_end || (step + 1) % save_every == 0)
{
let f = fl.borrow();
let g = f.grid;
state::Saved {
t: t_new,
dims: [g.nx, g.ny, g.nz],
h: f.h,
u: f.field.u.clone(),
v: f.field.v.clone(),
w: f.field.w.clone(),
p: f.field.p.clone(),
disp: flag_state.displacement.as_slice().to_vec(),
vel: flag_state.velocity.as_slice().to_vec(),
acc: flag_state.acceleration.as_slice().to_vec(),
line: line_n.clone(),
c_fluid: c_fluid_n.clone(),
nodal: committed_nodal
.iter()
.flat_map(|(_, v)| [v.x, v.y])
.collect(),
}
.save(dir)
.expect("save the coupled state");
}
if (step + 1) % 250 == 0 {
let w = &uy_series[uy_series.len().saturating_sub(600)..];
let (mid, amp) = mid_amp(w);