Merge the FSI2 b4 prescribed-mode added-mass instrument (fsi2_b4_added_mass.rs, #[ignore]; RTX_FSI2O_STILL default off, byte-identical when unset)
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
CI / Format Check (push) Failing after 3s
CI / Clippy Check (push) Failing after 4s
Documentation / Build User Guide (push) Successful in 4s
CI / Build (ubuntu-latest) (push) Failing after 4s
Performance Benchmarks / Run Benchmarks (push) Failing after 5s
CI / Build CPU-Only (Explicit) (push) Failing after 11s
Documentation / Build API Documentation (push) Failing after 1m23s
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
CI / Format Check (push) Failing after 3s
CI / Clippy Check (push) Failing after 4s
Documentation / Build User Guide (push) Successful in 4s
CI / Build (ubuntu-latest) (push) Failing after 4s
Performance Benchmarks / Run Benchmarks (push) Failing after 5s
CI / Build CPU-Only (Explicit) (push) Failing after 11s
Documentation / Build API Documentation (push) Failing after 1m23s
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -0,0 +1,201 @@
|
|||||||
|
//! The FSI2 flag's 4th bending mode b4 as a PRESCRIBED motion on the
|
||||||
|
//! overset fluid (track 1 round 5, `b4am`): the fluid-side test of the
|
||||||
|
//! round-4 verdict that FSI2's 5f lift excess is the wet b4 resonating
|
||||||
|
//! near 5f = 9.67 Hz. The flag's wetted surface moves as
|
||||||
|
//! `d(t) = q(t) φ` with `φ` the in-vacuo b4 shape at the wetted DoFs
|
||||||
|
//! (M-orthonormal, from `fsi2_flag_modes`' operators) and
|
||||||
|
//! `q(t) = q0 r(t) sin(ω (t − t0))` (`r` a smooth start ramp); per fluid
|
||||||
|
//! step the CSV records `q`, `q̇`, `q̈` and the generalised fluid force
|
||||||
|
//! `F = Σ_k f_k · φ_k` over the wetted nodes (the march's own nodal load,
|
||||||
|
//! `sample_load`), from which the analysis fits the part in phase with
|
||||||
|
//! the acceleration (the modal added mass) and with the velocity (the
|
||||||
|
//! modal fluid damping).
|
||||||
|
//!
|
||||||
|
//! `#[ignore]`d instrument; test-only code, nothing in the solver or the
|
||||||
|
//! march changes. Knobs:
|
||||||
|
//!
|
||||||
|
//! * `B4_MODE` (required): lines `node_id φx φy` for the wetted nodes
|
||||||
|
//! (other nodes are ignored; missing wetted nodes are zero).
|
||||||
|
//! * `B4_Q0` modal amplitude (1e-3), `B4_FREQ` Hz (9.67), `B4_PERIODS`
|
||||||
|
//! (10), `B4_RAMP_PERIODS` (2).
|
||||||
|
//! * `B4_DT`: override the fluid step (to run still fluid at the mean
|
||||||
|
//! flow's step; unset = the case's CFL step).
|
||||||
|
//! * `RTX_FSI2O_LOAD` (optional): the rigid state to start from (the mean
|
||||||
|
//! flow); unset = start from rest (with `RTX_FSI2O_UMEAN=0`: still fluid).
|
||||||
|
//! * `B4_CSV` (required), `B4_NODE_CSV` (optional: t + fx, fy per wetted
|
||||||
|
//! node, for projections onto other modes).
|
||||||
|
//! * `RTX_FSI2O_NY` (62), and every `RTX_FSI2O_*` fluid knob the overset
|
||||||
|
//! builder reads (tip corner, warm sweeps, red-black, …).
|
||||||
|
|
||||||
|
mod fsi2_harness;
|
||||||
|
|
||||||
|
use std::io::Write as _;
|
||||||
|
|
||||||
|
use fsi2_harness::overset::OversetFluid;
|
||||||
|
use fsi2_harness::replay::ramp;
|
||||||
|
use fsi2_harness::{FSI2, case_from_env};
|
||||||
|
|
||||||
|
fn env_f(k: &str, d: f64) -> f64 {
|
||||||
|
std::env::var(k)
|
||||||
|
.ok()
|
||||||
|
.map(|v| v.parse().unwrap_or_else(|_| panic!("{k}")))
|
||||||
|
.unwrap_or(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "instrument: prescribed b4 motion on the overset (needs B4_MODE, B4_CSV)"]
|
||||||
|
fn fsi2_b4_prescribed_mode() {
|
||||||
|
let mode_path = std::env::var("B4_MODE").expect("B4_MODE");
|
||||||
|
let csv_path = std::env::var("B4_CSV").expect("B4_CSV");
|
||||||
|
let ny = env_f("RTX_FSI2O_NY", 62.0) as usize;
|
||||||
|
let q0 = env_f("B4_Q0", 1e-3);
|
||||||
|
let freq = env_f("B4_FREQ", 9.67);
|
||||||
|
let periods = env_f("B4_PERIODS", 10.0);
|
||||||
|
let ramp_periods = env_f("B4_RAMP_PERIODS", 2.0);
|
||||||
|
let max_rounds = env_f("RTX_FSI2O_MAX_ROUNDS", 3.0) as usize;
|
||||||
|
let case = case_from_env("FSI2O", FSI2);
|
||||||
|
|
||||||
|
let mut fluid = OversetFluid::build_case(case, ny, 35, 100, max_rounds).expect("overset fluid");
|
||||||
|
if let Ok(v) = std::env::var("B4_DT") {
|
||||||
|
let dt: f64 = v.parse().expect("B4_DT");
|
||||||
|
println!(
|
||||||
|
" fluid step OVERRIDDEN {:.6e} → {dt:.6e} (B4_DT)",
|
||||||
|
fluid.dt_fluid
|
||||||
|
);
|
||||||
|
fluid.dt_fluid = dt;
|
||||||
|
}
|
||||||
|
let t0 = match std::env::var("RTX_FSI2O_LOAD") {
|
||||||
|
Ok(dir) => fluid.load(&dir).expect("load"),
|
||||||
|
Err(_) => {
|
||||||
|
println!(
|
||||||
|
" no RTX_FSI2O_LOAD: starting from rest (u_mean {})",
|
||||||
|
fluid.case.u_mean
|
||||||
|
);
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fluid.commit_base();
|
||||||
|
fluid.solver.set_time(t0);
|
||||||
|
|
||||||
|
// φ at the wetted DoFs, in the interface's order.
|
||||||
|
let wetted = fluid.interface.wetted.clone();
|
||||||
|
let n = 2 * wetted.len();
|
||||||
|
let mut phi = vec![0.0; n];
|
||||||
|
let mut matched = 0usize;
|
||||||
|
for line in std::fs::read_to_string(&mode_path)
|
||||||
|
.expect("B4_MODE")
|
||||||
|
.lines()
|
||||||
|
{
|
||||||
|
let f: Vec<f64> = line
|
||||||
|
.split_whitespace()
|
||||||
|
.filter_map(|t| t.parse().ok())
|
||||||
|
.collect();
|
||||||
|
if f.len() < 3 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(k) = wetted.iter().position(|id| id.0 == f[0] as usize) {
|
||||||
|
phi[2 * k] = f[1];
|
||||||
|
phi[2 * k + 1] = f[2];
|
||||||
|
matched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
matched,
|
||||||
|
wetted.len(),
|
||||||
|
"B4_MODE must cover every wetted node"
|
||||||
|
);
|
||||||
|
let peak_y = phi
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.step_by(2)
|
||||||
|
.fold(0.0f64, |a, &b| a.max(b.abs()));
|
||||||
|
|
||||||
|
let dt = fluid.dt_fluid;
|
||||||
|
let omega = 2.0 * std::f64::consts::PI * freq;
|
||||||
|
let period = 1.0 / freq;
|
||||||
|
let t_end = t0 + periods * period;
|
||||||
|
let ramp_w = ramp_periods * period;
|
||||||
|
let (drag0, lift0) = fluid.measure_force();
|
||||||
|
println!(
|
||||||
|
" B4 PRESCRIBED ny = {ny}: {} wetted nodes, q0 {q0:.4e} (peak lateral {:.4e} m), f {freq} Hz, {periods} periods from t0 = {t0:.4} (ramp {ramp_periods} periods), dt {dt:.6e} ({:.1} steps/period), u_mean {}, tip corner {} m, rounds cap {max_rounds}; start drag {drag0:.3} lift {lift0:.3}",
|
||||||
|
wetted.len(),
|
||||||
|
q0 * peak_y,
|
||||||
|
period / dt,
|
||||||
|
fluid.case.u_mean,
|
||||||
|
fsi2_harness::overset::tip_corner(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut csv = std::fs::File::create(&csv_path).expect("B4_CSV");
|
||||||
|
writeln!(
|
||||||
|
csv,
|
||||||
|
"t,q,qd,qdd,F,Fx_sum,Fy_sum,drag,lift,rounds_max,schwarz_ok,conservation"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut node_csv = std::env::var("B4_NODE_CSV").ok().map(|p| {
|
||||||
|
let mut f = std::fs::File::create(p).expect("B4_NODE_CSV");
|
||||||
|
let mut head = String::from("t");
|
||||||
|
for id in &wetted {
|
||||||
|
head.push_str(&format!(",fx{},fy{}", id.0, id.0));
|
||||||
|
}
|
||||||
|
writeln!(f, "{head}").unwrap();
|
||||||
|
f
|
||||||
|
});
|
||||||
|
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
let mut step = 0usize;
|
||||||
|
let mut t_now = t0;
|
||||||
|
while t_now < t_end - 0.5 * dt {
|
||||||
|
let t_new = t_now + dt;
|
||||||
|
let s = omega * (t_new - t0);
|
||||||
|
let (r, rd) = ramp(t_new, t0, ramp_w);
|
||||||
|
let q = q0 * r * s.sin();
|
||||||
|
let qd = q0 * (r * omega * s.cos() + rd * s.sin());
|
||||||
|
// q̈ of the steady part (the ramp's own terms are dropped: the fit
|
||||||
|
// reads only the post-ramp window).
|
||||||
|
let qdd = -q0 * r * omega * omega * s.sin();
|
||||||
|
let d: Vec<f64> = phi.iter().map(|p| q * p).collect();
|
||||||
|
let v: Vec<f64> = phi.iter().map(|p| qd * p).collect();
|
||||||
|
if let Err(e) = fluid.set_geometry(&d, &v) {
|
||||||
|
panic!("set_geometry died at step {step}, t = {t_new:.5}: {e:?}");
|
||||||
|
}
|
||||||
|
let res = fluid
|
||||||
|
.step()
|
||||||
|
.unwrap_or_else(|e| panic!("fluid step died at step {step}, t = {t_new:.5}: {e:?}"));
|
||||||
|
fluid.commit_base();
|
||||||
|
let (nodal, conservation, _) = fluid.sample_load(&d);
|
||||||
|
let mut gen_f = 0.0;
|
||||||
|
let (mut fx, mut fy) = (0.0, 0.0);
|
||||||
|
for (k, (_, f)) in nodal.iter().enumerate() {
|
||||||
|
gen_f += f.x * phi[2 * k] + f.y * phi[2 * k + 1];
|
||||||
|
fx += f.x;
|
||||||
|
fy += f.y;
|
||||||
|
}
|
||||||
|
let (drag, lift) = fluid.measure_force();
|
||||||
|
writeln!(
|
||||||
|
csv,
|
||||||
|
"{t_new:.9},{q:.9e},{qd:.9e},{qdd:.9e},{gen_f:.9e},{fx:.9e},{fy:.9e},{drag:.6},{lift:.6},{},{},{conservation:.3e}",
|
||||||
|
res.rounds.iter().copied().max().unwrap_or(0),
|
||||||
|
res.schwarz_converged as u8
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
if let Some(f) = node_csv.as_mut() {
|
||||||
|
let mut line = format!("{t_new:.9}");
|
||||||
|
for (_, fv) in &nodal {
|
||||||
|
line.push_str(&format!(",{:.6e},{:.6e}", fv.x, fv.y));
|
||||||
|
}
|
||||||
|
writeln!(f, "{line}").unwrap();
|
||||||
|
}
|
||||||
|
step += 1;
|
||||||
|
if step % 500 == 0 {
|
||||||
|
println!(
|
||||||
|
" t = {t_new:.4} ({step} steps): q {q:+.3e} F {gen_f:+.4e} drag {drag:.2} lift {lift:.2}, {:.0} s wall",
|
||||||
|
start.elapsed().as_secs_f64()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
t_now = t_new;
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
" B4 PRESCRIBED DONE: {step} steps to t = {t_now:.5} in {:.0} s wall → {csv_path}",
|
||||||
|
start.elapsed().as_secs_f64()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -343,8 +343,18 @@ impl OversetFluid {
|
|||||||
" multigrid V-cycle: CUDA DEVICE, f32 red-black (PERF-2 regime, RTX_FSI2O_MG_DEVICE)"
|
" multigrid V-cycle: CUDA DEVICE, f32 red-black (PERF-2 regime, RTX_FSI2O_MG_DEVICE)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// `RTX_FSI2O_STILL=1` (b4am): no inflow — still fluid — with the
|
||||||
|
// case's u_mean kept for the CFL step and the solvers' reference
|
||||||
|
// flux (their inner stops), so a still-fluid run differs from the
|
||||||
|
// mean-flow run in the inflow alone. Unset = the benchmark inflow.
|
||||||
|
let still = std::env::var("RTX_FSI2O_STILL").is_ok_and(|v| v == "1");
|
||||||
|
if still {
|
||||||
|
println!(
|
||||||
|
" STILL FLUID: inflow off (RTX_FSI2O_STILL), u_mean {u_mean} kept for dt and references"
|
||||||
|
);
|
||||||
|
}
|
||||||
background.set_boundary_velocity(move |x, y, t| {
|
background.set_boundary_velocity(move |x, y, t| {
|
||||||
if x <= 0.0 {
|
if x <= 0.0 && !still {
|
||||||
(inflow_for(u_mean, y, t), 0.0)
|
(inflow_for(u_mean, y, t), 0.0)
|
||||||
} else {
|
} else {
|
||||||
(0.0, 0.0)
|
(0.0, 0.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user