prescribed-motion replay: the fluid's power on the flag split face by face (normal vs tangential traction; root / middle / tip thirds) beside the nodal Σ F·ḋ, and replay instants (RTX_FSI2O_SAVE + SAVE_EVERY or SAVE_AT=t1,t2,… — the step that first reaches each time) for the per-region pressure audit at one physical instant across h
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0116sg1Qz1gMv9hdcKP1XUam
This commit is contained in:
Omar Sobh
2026-09-09 15:49:27 -07:00
co-authored by Claude Fable 5.1
parent f61c427b6d
commit 1562b7cbb5
@@ -409,13 +409,22 @@ fn fsi2_overset_prescribed_motion() {
let mut csv = std::env::var("RTX_FSI2O_CSV").ok().map(|p| {
use std::io::Write as _;
let mut f = std::fs::File::create(p).expect("csv");
writeln!(f, "t,uy_tip,drag,lift,power,wall_flux,area_rate").unwrap();
writeln!(
f,
"t,uy_tip,drag,lift,power,wall_flux,area_rate,p_faces,p_normal,p_tangential,p_root,p_mid,p_tip"
)
.unwrap();
f
});
let tip = fluid.interface.tip.clone();
let start = std::time::Instant::now();
let mut prev_area = fluid.shared.read().unwrap().area();
let mut window: Vec<(f64, f64, f64, f64)> = Vec::new();
let mut faces_window: Vec<(f64, f64, f64, [f64; 3])> = Vec::new();
let save_at: Vec<f64> = std::env::var("RTX_FSI2O_SAVE_AT")
.ok()
.map(|v| v.split(',').filter_map(|x| x.trim().parse().ok()).collect())
.unwrap_or_default();
let mut step = 0usize;
let mut t_now = t0;
while t_now < t_end - 0.5 * dt {
@@ -437,17 +446,83 @@ fn fsi2_overset_prescribed_motion() {
.enumerate()
.map(|(k, (_, f))| f.x * v_r[2 * k] + f.y * v_r[2 * k + 1])
.sum();
// The same power face by face: normal vs tangential traction, and
// the flag's root / middle / tip thirds (x < 0.3667 / < 0.4833 /
// the rest, the tip arc included); the cylinder's faces excluded.
let (mut p_faces, mut p_normal, mut p_tangential) = (0.0, 0.0, 0.0);
let mut p_thirds = [0.0; 3];
{
let wall = fluid.shared.read().unwrap();
for (centre, normal, len, traction) in fluid.solver.patch().wall_tractions(
&fluid.field.patch,
rtx_cfd::mesh::PatchSide::Inner,
fluid.solver.time(),
) {
let on_cylinder =
((centre[0] - 0.2).powi(2) + (centre[1] - 0.2).powi(2)).sqrt() < 0.05 + 1e-9;
if on_cylinder {
continue;
}
let (u, v) = wall.velocity_at(centre[0], centre[1]);
let tn = traction[0] * normal[0] + traction[1] * normal[1];
let vn = u * normal[0] + v * normal[1];
let pf = (traction[0] * u + traction[1] * v) * len;
let pn = tn * vn * len;
p_faces += pf;
p_normal += pn;
p_tangential += pf - pn;
let third = if centre[0] < 0.3667 {
0
} else if centre[0] < 0.4833 {
1
} else {
2
};
p_thirds[third] += pf;
}
}
let (drag, lift) = fluid.measure_force();
let (_, wall_flux, _, _, poly_area) = fluid.level_and_wall_flux();
// `RTX_FSI2O_SAVE=dir` + `RTX_FSI2O_SAVE_EVERY=N` or
// `RTX_FSI2O_SAVE_AT=t1,t2,…` (the step that first reaches each
// time): replay instants for the per-region pressure audit at one
// physical instant across h.
if let Ok(dir) = std::env::var("RTX_FSI2O_SAVE") {
let hit = save_at.iter().any(|&ts| t_now < ts && ts <= t_new + 1e-12);
if hit {
fluid
.save_instant(&dir, step + 1, &d_r, &v_r)
.expect("save instant");
}
}
if let (Ok(dir), Some(every)) = (
std::env::var("RTX_FSI2O_SAVE"),
std::env::var("RTX_FSI2O_SAVE_EVERY")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.filter(|&n| n > 0),
) {
if (step + 1) % every == 0 {
fluid
.save_instant(&dir, step + 1, &d_r, &v_r)
.expect("save instant");
}
}
let area_rate = (poly_area - prev_area) / dt;
prev_area = poly_area;
let uy_tip = tip.iter().map(|&k| d_r[2 * k + 1]).sum::<f64>() / tip.len().max(1) as f64;
if let Some(f) = csv.as_mut() {
use std::io::Write as _;
writeln!(f, "{t_new:.6},{uy_tip:.6e},{drag:.4},{lift:.4},{power:.6e},{wall_flux:.6e},{area_rate:.6e}").unwrap();
writeln!(
f,
"{t_new:.6},{uy_tip:.6e},{drag:.4},{lift:.4},{power:.6e},{wall_flux:.6e},{area_rate:.6e},{p_faces:.6e},{p_normal:.6e},{p_tangential:.6e},{:.6e},{:.6e},{:.6e}",
p_thirds[0], p_thirds[1], p_thirds[2]
)
.unwrap();
}
if t_new >= 13.0 {
window.push((t_new, drag, lift, power));
faces_window.push((p_faces, p_normal, p_tangential, p_thirds));
}
step += 1;
if step % 500 == 0 {
@@ -474,4 +549,17 @@ fn fsi2_overset_prescribed_motion() {
0.5 * (lmax - lmin),
start.elapsed().as_secs_f64()
);
let nf = faces_window.len().max(1) as f64;
let mean = |f: &dyn Fn(&(f64, f64, f64, [f64; 3])) -> f64| {
faces_window.iter().map(f).sum::<f64>() / nf
};
println!(
" PRESCRIBED ny = {ny} power by face over [13, 16] s: faces {:+.3} W/m (nodal {mean_power:+.3}) = normal {:+.3} + tangential {:+.3}; root {:+.3} / middle {:+.3} / tip {:+.3}",
mean(&|w| w.0),
mean(&|w| w.1),
mean(&|w| w.2),
mean(&|w| w.3[0]),
mean(&|w| w.3[1]),
mean(&|w| w.3[2])
);
}