rtx-cfd: indexed polygon SDF — bit-identical queries, the fluid's measured hot function cut
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
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
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (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
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s

The 2026-08-30 fluid profile (symbolized samples, rigid AND coupled
phases of the FSI3 default) attributed the fluid step to the function:
polygon_signed_distance 51% rigid / 35% coupled — the embedded mask
rebuild and its ghost reconstruction walk every edge of the ~150-vertex
interface polygon for every cell-centre and face query, every step.
(Also measured, refuting the parked consolidation: Level::new — the MG
hierarchy build — is 0.5-0.7% in BOTH phases; caching it would buy
nothing. The MG smoother at 34-40% is the honest remaining fluid cost.)

PolygonSdf (solvers/incompressible/polygon_sdf.rs): a binned edge
index whose query is BIT-IDENTICAL to polygon_signed_distance by
construction — per-edge distances use the same float ops, the ring
search provably visits a superset of the argmin (convex-projection
lower bound sqrt(d_out^2 + ((r-1)b)^2)), and parity XORs the same ray
tests over exactly the straddling edges (y-binned). Equality is
ASSERTED, not assumed: tests compare to_bits against the brute force
over ~40k adversarial points (flag-like walks, random polygons with
degenerate zero-length edges, horizontal-edge/vertex-y rays). Wired
into EmbeddedBody::polygon and the FSI harness's shared geometry
(rebuilt per set_geometry, ~microseconds for 150 edges).

Verification — the bar for a bit-exact change is digit identity, and
it holds: FSI2 and FSI3 committed defaults reproduce EVERY printed
digit of the banded-LU baseline logs (uy 3.7732±3.7920 / 6.0229±
25.2190 mm, conservation 8.26e-12 / 1.49e-12, rigid drags 121.4 /
426.9); rtx-cfd full suite 0 failures; rtx-fsi lib/piston/transfer/
FSI1 green. The study pins need no re-run: the trajectories are
unchanged by construction and confirmed by measurement.

Wall clock: FSI2 rigid 323 -> 167 s (1.93x), whole default 400 -> 225 s;
FSI3 rigid 420 -> 250 s (1.68x), whole default 539 -> 343 s. Cumulative
with the banded LU this session: FSI3 default 944 -> 343 s (2.75x),
FSI2 524 -> 225 s (2.33x).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
This commit is contained in:
Omar Sobh
2026-08-30 07:47:42 -05:00
co-authored by Claude Fable 5
parent 0b4f306ed1
commit 6c48e53998
4 changed files with 353 additions and 10 deletions
@@ -19,7 +19,7 @@ use nalgebra::Vector3;
use rtx_cfd::CfdConfig;
use rtx_cfd::solvers::incompressible::{
AleBoundaries, ConvectionScheme, EmbeddedBody, EmbeddedParameters, EmbeddedPisoSolver,
FlowField, PoissonSolverKind, SideBoundary, polygon_interface_velocity,
FlowField, PoissonSolverKind, PolygonSdf, SideBoundary, polygon_interface_velocity,
polygon_signed_distance,
};
use rtx_fea::assembly::dof_mapping::DofComponent;
@@ -334,7 +334,7 @@ pub struct Fsi2Harness {
/// The deformable geometry AND its velocity, behind one lock: the
/// fluid's per-step mask rebuild reads the polygon; the no-slip
/// closure reads both.
pub shared: Arc<RwLock<(Vec<(f64, f64)>, Vec<(f64, f64)>)>>,
pub shared: Arc<RwLock<(PolygonSdf, Vec<(f64, f64)>)>>,
pub spiked_total: Cell<usize>,
}
@@ -377,8 +377,11 @@ impl Fsi2Harness {
.expect("point A");
let zero_d = vec![0.0; 2 * interface.wetted.len()];
// The polygon lives behind the lock as an INDEXED SDF
// (bit-identical query; the brute-force walk was measured at
// 51% of the fluid step, called for every mask cell and ghost).
let shared = Arc::new(RwLock::new((
interface.polygon(&zero_d),
PolygonSdf::new(interface.polygon(&zero_d)),
interface.walk_velocities(&zero_d),
)));
let sdf_shared = shared.clone();
@@ -418,14 +421,14 @@ impl Fsi2Harness {
solver.set_moving_body(
EmbeddedBody::from_sdf(move |x, y, _| {
let geometry = sdf_shared.read().unwrap();
circle_sdf(x, y).min(polygon_signed_distance(&geometry.0, x, y))
circle_sdf(x, y).min(geometry.0.signed_distance(x, y))
})
.with_surface_velocity(move |x, y, _| {
let geometry = vel_shared.read().unwrap();
if circle_sdf(x, y) <= polygon_signed_distance(&geometry.0, x, y) {
if circle_sdf(x, y) <= geometry.0.signed_distance(x, y) {
(0.0, 0.0)
} else {
polygon_interface_velocity(&geometry.0, &geometry.1, x, y)
polygon_interface_velocity(geometry.0.vertices(), &geometry.1, x, y)
}
}),
);
@@ -454,7 +457,7 @@ impl Fsi2Harness {
/// Publish an interface geometry (+ velocity) to the fluid.
pub fn set_geometry(&self, d: &[f64], ddot: &[f64]) {
let mut geometry = self.shared.write().unwrap();
geometry.0 = self.interface.polygon(d);
geometry.0 = PolygonSdf::new(self.interface.polygon(d));
geometry.1 = self.interface.walk_velocities(ddot);
}
@@ -462,7 +465,7 @@ impl Fsi2Harness {
pub fn measure_force(&self, solver: &EmbeddedPisoSolver, field: &FlowField) -> (f64, f64) {
let mask = solver.mask().unwrap();
let body = solver.body().unwrap();
let vertices = self.shared.read().unwrap().0.clone();
let vertices = self.shared.read().unwrap().0.vertices().to_vec();
// Collect, then clamp, then integrate: the same 20x-median spike
// clamp the coupling loads carry. Without it the REPORTED
// drag/lift at large deformation are dominated by the rare wild