From 91fbf86026d471daa2ad2259fbb3a07aba92a28f Mon Sep 17 00:00:00 2001 From: Omar Sobh Date: Thu, 24 Sep 2026 18:08:50 -0500 Subject: [PATCH] =?UTF-8?q?embedded3=20R6-1:=20the=20device=20geometry=20d?= =?UTF-8?q?efault=20ON=20(RTX=5FE3=5FGEOM=5FDEVICE=3D0=20restores=20the=20?= =?UTF-8?q?host=20build);=20a=20body=20without=20a=20device=20form=20of=20?= =?UTF-8?q?=CF=86=20falls=20back=20to=20the=20host,=20logged=20once,=20not?= =?UTF-8?q?hing=20allocated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- .../solvers/incompressible/embedded3/body.rs | 13 +++++-- .../incompressible/embedded3/step/device.rs | 2 +- .../embedded3/step/device/cut.rs | 18 +++++++--- .../embedded3/step/device/geom.rs | 35 +++++++++++++++++-- .../incompressible/embedded3/step/mod.rs | 2 +- .../rtx-cfd/tests/embedded3_flag_wake.rs | 2 +- 6 files changed, 59 insertions(+), 13 deletions(-) diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/body.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/body.rs index 2662ac5..3a8fae7 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/body.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/body.rs @@ -153,9 +153,10 @@ impl Body { self } - /// R6-1: attach the device form of φ (`RTX_E3_GEOM_DEVICE=1` evaluates - /// it on the band instead of the host closure; the caller guarantees - /// the two are the same arithmetic). + /// R6-1: attach the device form of φ (the device geometry, default ON, + /// evaluates it on the band instead of the host closure; the caller + /// guarantees the two are the same arithmetic). A body without one + /// falls back to the host build. #[must_use] pub fn with_device_sdf(mut self, f: F) -> Self where @@ -165,6 +166,12 @@ impl Body { self } + /// Whether the body carries a device form of φ. + #[must_use] + pub fn has_device_sdf(&self) -> bool { + self.device_sdf.is_some() + } + /// The device form of φ at `t`, when the body has one. #[must_use] pub fn device_sdf(&self, t: f64) -> Option { diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device.rs index acc1359..61faa37 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device.rs @@ -163,7 +163,7 @@ pub struct DeviceStep { cut: Option, /// Steps since the multigrid hierarchy was last rebuilt (moving bodies). steps_since_hierarchy: usize, - /// R6-1: the persistent device cut geometry (`RTX_E3_GEOM_DEVICE=1`). + /// R6-1: the persistent device cut geometry (default ON; `RTX_E3_GEOM_DEVICE=0` off). geom: Option, } diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs index 2c1687c..002de5e 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/cut.rs @@ -746,10 +746,20 @@ impl DeviceStep { let mut field = self.host_field.take().unwrap_or_else(|| Field::new(g)); self.download_for_rebuild(&mut field); let l_down = lap.elapsed(); - // R6-1 (`RTX_E3_GEOM_DEVICE=1`): the end-of-step cut geometry on - // the device, its face tables written into the persistent - // predictor set; the host classifies the mirror. - if super::geom::enabled() { + // R6-1 (default ON, `RTX_E3_GEOM_DEVICE=0` off): the end-of-step + // cut geometry on the device, its face tables written into the + // persistent predictor set; the host classifies the mirror. A + // body the device cannot build falls back to the host (logged + // once, nothing allocated). + let geom_on = super::geom::enabled() + && match super::geom::unsupported(&self.solver) { + Some(reason) => { + super::geom::log_fallback(reason); + false + } + None => true, + }; + if geom_on { if let Some(dc) = self.cut.as_mut() { let geom = self .geom diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/geom.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/geom.rs index 3eb1b5b..7cec3e5 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/geom.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/device/geom.rs @@ -1,5 +1,7 @@ //! R6-1: the body's φ and the cut geometry on the device (`e3_geom.cu`), -//! behind `RTX_E3_GEOM_DEVICE=1`. The corner φ (the narrow band's +//! default ON since 2026-09-24 (`RTX_E3_GEOM_DEVICE=0` restores the host +//! build; a body without a device form of φ falls back to the host, logged +//! once). The corner φ (the narrow band's //! keep-or-evaluate, φ from the body's `DeviceSdf` polyline), the face //! apertures and face-centre φ (written straight into the persistent //! `DeviceCut` predictor apertures and distances), the cell volumes and @@ -82,9 +84,36 @@ struct GeomGrid { unsafe impl DeviceRepr for GeomGrid {} unsafe impl ValidAsZeroBits for GeomGrid {} -/// `RTX_E3_GEOM_DEVICE=1`. +/// The device geometry: default ON (R6-1's two-period 3D gate held +/// 2026-09-24); `RTX_E3_GEOM_DEVICE=0` restores the host build. pub(super) fn enabled() -> bool { - std::env::var("RTX_E3_GEOM_DEVICE").is_ok_and(|v| v == "1") + !std::env::var("RTX_E3_GEOM_DEVICE").is_ok_and(|v| v == "0") +} + +/// Why the device geometry cannot build this solver's body (the host +/// builds it instead), or `None` when it can. +pub(super) fn unsupported(solver: &Solver) -> Option<&'static str> { + if solver.params.wall_scheme != WallScheme::CutCell { + return Some("the wall scheme is not the cut cell"); + } + if solver.params.aperture_substeps != 0 { + return Some("aperture substeps are on"); + } + match solver.body() { + None => Some("no body"), + Some(b) if !b.has_device_sdf() => Some("the body has no device form of φ"), + Some(_) => None, + } +} + +/// The host fallback, logged once per process. +pub(super) fn log_fallback(reason: &str) { + static LOGGED: std::sync::Once = std::sync::Once::new(); + LOGGED.call_once(|| { + eprintln!( + " R6-1 device geometry: host fallback ({reason}); the cut geometry is built on the host" + ); + }); } /// The persistent device geometry: corner φ and bound, the evaluated diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/mod.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/mod.rs index ca230af..a2e68f9 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/mod.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/mod.rs @@ -297,7 +297,7 @@ pub struct Solver { /// `(setup ns, iterate ns, solves, CG iterations)` summed. poisson_profile: (u64, u64, u64, u64), /// R6-1: the next moving rebuild's cut geometry, built on the device - /// (`RTX_E3_GEOM_DEVICE=1`); `build_mask` classifies it instead of + /// (default ON, `RTX_E3_GEOM_DEVICE=0` off); `build_mask` classifies it instead of /// evaluating φ on the host. pub(super) pending_cut: Option, /// R6-1: retired device-built geometry arrays (empty on the host path) diff --git a/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs b/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs index 36d9dba..f45a2bd 100644 --- a/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs +++ b/crates/specialized/rtx-cfd/tests/embedded3_flag_wake.rs @@ -396,7 +396,7 @@ 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 + // R6-1: the same φ in the device's form (the device geometry, default ON): 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],