diff --git a/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs b/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs index 82f9dd1..0b22ca9 100644 --- a/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs +++ b/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs @@ -1,6 +1,8 @@ //! embedded3 S2-3: the free-ended flag with prescribed motion — the first //! honest 3D wake. The Turek–Hron channel (2.5 × 0.41) extruded to depth //! 0.41 with the cylinder (D 0.1 at (0.2, 0.2)) across the width, the flag +//! (`RTX_E3_FLAG_HEIGHT` / `RTX_E3_FLAG_DEPTH` grow the duct around the +//! unchanged body, re-centred: the 2026-09-22/23 explorations), the flag //! 0.35 × 0.02 × 0.2 centred in z (z 0.105–0.305), its centreline deflected //! as the first clamped-free beam mode with the 2D FSI2 flat-tip record's //! tip amplitude 84 mm at 1.930 Hz (motion prescribed, no structure), the @@ -139,7 +141,7 @@ fn flag_2d_recorded(rec: &Recorded, x: f64, y: f64, t: f64) -> (f64, (f64, f64)) POLY.with(|cell| { let mut c = cell.borrow_mut(); if c.0.to_bits() != t.to_bits() { - c.1 = rec.at(t, CY); + c.1 = rec.at(t, body_cy()); inset_last(&mut c.1, tip_inset()); c.0 = t; } @@ -179,7 +181,7 @@ fn flag_2d_analytic(x: f64, y: f64, t: f64) -> (f64, f64) { for (m, p) in c.1.iter_mut().enumerate() { let s = m as f64 / N as f64; let (d, v) = deflection(s, t); - *p = (FLAG_X0 + s * FLAG_LEN, CY + d, v); + *p = (FLAG_X0 + s * FLAG_LEN, body_cy() + d, v); } let inset = tip_inset(); if inset > 0.0 { @@ -226,6 +228,19 @@ fn duct_depth() -> f64 { env_f("RTX_E3_FLAG_DEPTH", H) } +/// The duct's height (the y extent): `RTX_E3_FLAG_HEIGHT` (default H). +/// The cell size stays `H / ny` (the benchmark's rung definition); the +/// body is re-centred in the taller duct (2026-09-23, the "big duct"). +fn duct_height() -> f64 { + env_f("RTX_E3_FLAG_HEIGHT", H) +} + +/// The cylinder's centre and the flag's rest centreline: `CY` in the +/// benchmark duct, lifted by half the added height otherwise. +fn body_cy() -> f64 { + CY + 0.5 * (duct_height() - H) +} + /// The flag in 3D: the extruded capsule cut to the span with edges /// rounded to radius `r` (no cut at the full width). fn flag_3d(x: f64, y: f64, z: f64, t: f64, r: f64) -> (f64, (f64, f64)) { @@ -242,8 +257,8 @@ fn flag_3d(x: f64, y: f64, z: f64, t: f64, r: f64) -> (f64, (f64, f64)) { } fn inflow(y: f64, z: f64) -> f64 { - let d = duct_depth(); - 16.0 * U_M * y * z * (H - y) * (d - z) / (H * H * d * d) + let (hd, d) = (duct_height(), duct_depth()); + 16.0 * U_M * y * z * (hd - y) * (d - z) / (hd * hd * d * d) } #[test] @@ -253,6 +268,8 @@ fn flag_wake_on_the_device() { let periods = env_f("RTX_E3_FLAG_PERIODS", 2.0); let h = H / ny as f64; let nx = (L / h).round() as usize; + // The grid's rows: the benchmark's `ny` unless the duct is taller. + let ny_grid = (duct_height() / h).round() as usize; // `RTX_E3_FLAG_NZ=4`: a thin slab periodic in z with the 2D inflow (Ū = 1) — the // flag as a 2D problem, minutes per rung: the instrument for the load routes' parts. let slab_nz = env_f("RTX_E3_FLAG_NZ", 0.0) as usize; @@ -321,7 +338,8 @@ fn flag_wake_on_the_device() { ); let inflow_at = move |y: f64, z: f64| { if slab_nz > 0 || inflow_2d { - 6.0 * y * (H - y) / (H * H) + let hd = duct_height(); + 6.0 * y * (hd - y) / (hd * hd) } else { inflow(y, z) } @@ -333,7 +351,8 @@ fn flag_wake_on_the_device() { (0.0, 0.0, 0.0) } }); - let cyl = move |x: f64, y: f64| ((x - CX).powi(2) + (y - CY).powi(2)).sqrt() - R_CYL; + let cy = body_cy(); + let cyl = move |x: f64, y: f64| ((x - CX).powi(2) + (y - cy).powi(2)).sqrt() - R_CYL; let r_fillet = root_fillet(); let body = Body::from_sdf(move |x, y, z, t| { fillet_union(cyl(x, y), flag_3d(x, y, z, t, r_edge).0, r_fillet) @@ -347,10 +366,10 @@ fn flag_wake_on_the_device() { } }); solver.set_moving_body(body); - let g = Grid::cubic(nx, ny, nz, h); + let g = Grid::cubic(nx, ny_grid, nz, h); let mut field = Field::new(g); for k in 0..nz { - for j in 0..ny { + for j in 0..ny_grid { let u0 = inflow_at((j as f64 + 0.5) * h, (k as f64 + 0.5) * h); for i in 0..=nx { field.u[g.uface(k, j, i)] = u0; @@ -359,8 +378,10 @@ fn flag_wake_on_the_device() { } solver.initialize(&mut field); println!( - " flag wake ny {ny} (span {}; inflow {}, z sides {}; root fillet {:.4} m, tip inset {:.4} m): {nx}×{ny}×{nz} = {} cells, h {h:.4e}, dt {dt:.3e}, {periods} periods = {t_end:.3} s, {} steps", + " flag wake ny {ny} (span {}; duct {:.2} × {:.2} m, inflow {}, z sides {}; root fillet {:.4} m, tip inset {:.4} m): {nx}×{ny_grid}×{nz} = {} cells, h {h:.4e}, dt {dt:.3e}, {periods} periods = {t_end:.3} s, {} steps", flag_span(), + duct_height(), + duct_depth(), if slab_nz > 0 || inflow_2d { "2d" } else { "3d" }, if slab_nz > 0 { "periodic" @@ -443,7 +464,7 @@ fn flag_wake_on_the_device() { // recorded mode (until 2026-09-21 this column held the analytic // first mode even then — R2's fits use the record directly). let tip = match recorded() { - Some(rec) => rec.at(t, CY).last().map_or(0.0, |p| p.1 - CY), + Some(rec) => rec.at(t, body_cy()).last().map_or(0.0, |p| p.1 - body_cy()), None => deflection(1.0, t).0, }; if sample {