embedded3 R6-1: the body's φ and the cut geometry on the device (RTX_E3_GEOM_DEVICE=1)
CI / Format Check (push) Failing after 5s
Performance Benchmarks / Run Benchmarks (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 6s
Documentation / Build User Guide (push) Successful in 5s
CI / Clippy Check (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 15s
CI / Build CPU-Only (Explicit) (push) Failing after 24s
CI / Build (macos-latest) (push) Failing after 5s
CI / Test (macos-latest) (push) Skipped
CI / Test (ubuntu-latest) (push) Skipped
CI / Python Bindings (maturin) (macos-latest) (push) Skipped
CI / Python Bindings (maturin) (ubuntu-latest) (push) Skipped
CI / WASM Build + Size Check (push) Skipped
CI / Distributed Training Tests (push) Skipped
CI / CI Success (push) Failing after 0s

- e3_geom.cu: corner φ on the narrow band (the flag test's circle + capsule
  around the step's centreline polyline + span cuts + fillet union), face
  apertures / face-centre φ by the Kuhn triangles, cell volumes by the six
  Kuhn tets and wall vectors by closure — CutGeometry::build_from in fp64,
  host operation order, FMA contraction off.
- Body::with_device_sdf / DeviceSdf: the device form of φ (the host passes the
  polyline per step); the flag wake test attaches it (same arithmetic as its
  closure; the polylines factored out unchanged).
- step/device/geom.rs DeviceGeom: persistent φ / bound / volume / wall buffers;
  the face tables written straight into DeviceCut's predictor apertures and
  distances (update skips their scatters); the host mirror = the band's entries
  gathered compactly onto recycled arrays of retired device generations
  (GeomPool; band-list history) — Mask::from_cut classifies it.
- RTX_E3_BAND_CHECK=1 with the knob: mirror AND device tables against the host
  build_from bit for bit on every refresh.
- Knob off: byte-identical (slab ny 62 one period CSV = main's); host suite 21/21.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-24 08:15:34 -05:00
co-authored by Claude Opus 5.5
parent c4a29b557d
commit f5f0ffdd2a
11 changed files with 1180 additions and 33 deletions
@@ -26,7 +26,8 @@ use embedded3_flag_kinematics::{Recorded, recorded};
use rtx_cfd::solvers::incompressible::ConvectionScheme;
use rtx_cfd::solvers::incompressible::embedded3::step::device::DeviceStep;
use rtx_cfd::solvers::incompressible::embedded3::{
Body, Boundaries, Field, Fluid, Grid, Parameters, Side, Solver, WallScheme, write_vtk,
Body, Boundaries, DeviceSdf, Field, Fluid, Grid, Parameters, Side, Solver, WallScheme,
write_vtk,
};
use std::io::Write as _;
@@ -141,8 +142,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, body_cy());
inset_last(&mut c.1, tip_inset());
c.1 = recorded_polyline(rec, t);
c.0 = t;
}
let pts = &c.1;
@@ -165,8 +165,36 @@ fn flag_2d_recorded(rec: &Recorded, x: f64, y: f64, t: f64) -> (f64, (f64, f64))
})
}
/// The recorded centreline at `t` with the tip inset.
fn recorded_polyline(rec: &Recorded, t: f64) -> Vec<(f64, f64, f64, f64)> {
let mut pts = rec.at(t, body_cy());
inset_last(&mut pts, tip_inset());
pts
}
/// The analytic centreline's segments.
const N: usize = 40;
/// The analytic centreline at `t` (x, y, transverse velocity), the tip inset.
fn analytic_polyline(t: f64) -> [(f64, f64, f64); N + 1] {
let mut p = [(0.0, 0.0, 0.0); N + 1];
for (m, q) in p.iter_mut().enumerate() {
let s = m as f64 / N as f64;
let (d, v) = deflection(s, t);
*q = (FLAG_X0 + s * FLAG_LEN, body_cy() + d, v);
}
let inset = tip_inset();
if inset > 0.0 {
let (ax, ay, _) = p[N - 1];
let (bx, by, bv) = p[N];
let len = ((bx - ax).powi(2) + (by - ay).powi(2)).sqrt();
let f = (1.0 - inset / len).max(0.0);
p[N] = (ax + f * (bx - ax), ay + f * (by - ay), bv);
}
p
}
fn flag_2d_analytic(x: f64, y: f64, t: f64) -> (f64, f64) {
const N: usize = 40;
// The centreline polyline at `t`, once per thread and time (PERF-3
// P1-2): the solver asks for the surface velocity at ~10⁶ faces per
// step and each call rebuilt the 41 points (four hyperbolic / trigonometric
@@ -178,19 +206,7 @@ fn flag_2d_analytic(x: f64, y: f64, t: f64) -> (f64, f64) {
let pts = POLYLINE.with(|cell| {
let mut c = cell.borrow_mut();
if c.0.to_bits() != t.to_bits() {
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, body_cy() + d, v);
}
let inset = tip_inset();
if inset > 0.0 {
let (ax, ay, _) = c.1[N - 1];
let (bx, by, bv) = c.1[N];
let len = ((bx - ax).powi(2) + (by - ay).powi(2)).sqrt();
let f = (1.0 - inset / len).max(0.0);
c.1[N] = (ax + f * (bx - ax), ay + f * (by - ay), bv);
}
c.1 = analytic_polyline(t);
c.0 = t;
}
c.1
@@ -380,6 +396,26 @@ fn flag_wake_on_the_device() {
(0.0, 0.0, 0.0)
}
});
// R6-1: the same φ in the device's form (`RTX_E3_GEOM_DEVICE=1`): the
// circle, the capsule around the step's centreline, the span cuts.
let body = body.with_device_sdf(move |t| DeviceSdf {
cyl: [CX, cy, R_CYL],
cyl_cut: !(flag_span() >= duct_depth()
|| !std::env::var("RTX_E3_FLAG_CYL_SPAN").is_ok_and(|v| v == "flag")),
flag_cut: flag_span() < duct_depth(),
zc: 0.5 * duct_depth(),
span: flag_span(),
r_edge,
half: FLAG_HALF,
fillet: r_fillet,
poly: match recorded() {
Some(rec) => recorded_polyline(rec, t)
.iter()
.map(|p| [p.0, p.1])
.collect(),
None => analytic_polyline(t).iter().map(|p| [p.0, p.1]).collect(),
},
});
solver.set_moving_body(body);
let g = Grid::cubic(nx, ny_grid, nz, h);
let mut field = Field::new(g);