embedded3 S2-7: oblique instrument gains registration sweep / slope subset / z-flow skip / merged-cell count (RTX_E3_OBLIQUE_{C0_SHIFTS,SLOPES,ZFLOW,NS}) and the operator probe (oblique_operator_probe: predictor acceleration on the exact centroid-valued field by aperture band, cut-cell divergence under four valuations, spurious pressure; z-flow control); two host prototypes, default off, device refuses them: RTX_E3_CV_SIDES=exact (the momentum CV's side apertures from the interpolant on the half faces / cell-centre planes) and RTX_E3_WALL_ORDER2=centroid (the quadratic wall gradient's second point at the neighbour's centroid distance); quad_fraction / tri_area_fraction lifted to module fns (default path digit-identical: oblique record, host suite 21/21, device cut tests)
CI / Build CPU-Only (Explicit) (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 6s
Documentation / Build User Guide (push) Successful in 5s
CI / Format Check (push) Failing after 10s
CI / Build (ubuntu-latest) (push) Failing after 1m30s
CI / Clippy Check (push) Failing after 1m50s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m15s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (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
CI / Build CPU-Only (Explicit) (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 6s
Documentation / Build User Guide (push) Successful in 5s
CI / Format Check (push) Failing after 10s
CI / Build (ubuntu-latest) (push) Failing after 1m30s
CI / Clippy Check (push) Failing after 1m50s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m15s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (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
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0cf9d20afb
commit
b80af59ca4
@@ -160,7 +160,17 @@ impl Mask {
|
||||
let f = self.lattice().face(c, q).expect("open face");
|
||||
let h = [self.grid.dx, self.grid.dy, self.grid.dz];
|
||||
let d1 = cv.distance;
|
||||
let d2 = d1 + h[d] * n[d].abs();
|
||||
// S2-7: the second point's value lives at ITS OWN open-part centroid
|
||||
// (its wall distance), not one lattice step along the axis from
|
||||
// this face's centroid — the two differ by ½h(1 − α)n_t.
|
||||
let d2 = if self.wall_order2_centroid {
|
||||
self.cv_geometry(c, q).distance
|
||||
} else {
|
||||
d1 + h[d] * n[d].abs()
|
||||
};
|
||||
if d2 <= d1 {
|
||||
return linear;
|
||||
}
|
||||
(d2 / (d1 * (d2 - d1)), -d1 / (d2 * (d2 - d1)), Some(f))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,29 +90,7 @@ impl CutGeometry {
|
||||
// (0, 0) to its (1, 1) corner in the face's own (a, b) order — the
|
||||
// Kuhn split's diagonals: for an x-face (y, z), a y-face (x, z), a
|
||||
// z-face (x, y); the same triangles seen from either cell.
|
||||
let tri_area_fraction = |p0: f64, p1: f64, p2: f64| -> f64 {
|
||||
let v = [p0, p1, p2];
|
||||
let pos = v.iter().filter(|&&q| q >= 0.0).count();
|
||||
match pos {
|
||||
0 => 0.0,
|
||||
3 => 1.0,
|
||||
1 => {
|
||||
let a = v.iter().position(|&q| q >= 0.0).unwrap();
|
||||
let (b, c) = ((a + 1) % 3, (a + 2) % 3);
|
||||
(v[a] / (v[a] - v[b])) * (v[a] / (v[a] - v[c]))
|
||||
}
|
||||
_ => {
|
||||
let a = v.iter().position(|&q| q < 0.0).unwrap();
|
||||
let (b, c) = ((a + 1) % 3, (a + 2) % 3);
|
||||
1.0 - (v[a] / (v[a] - v[b])) * (v[a] / (v[a] - v[c]))
|
||||
}
|
||||
}
|
||||
};
|
||||
// Quad corners in (a, b) order: q00, q10, q01, q11; triangles
|
||||
// (q00, q10, q11) and (q00, q11, q01).
|
||||
let quad_fraction = |q00: f64, q10: f64, q01: f64, q11: f64| -> f64 {
|
||||
0.5 * (tri_area_fraction(q00, q10, q11) + tri_area_fraction(q00, q11, q01))
|
||||
};
|
||||
let quad_fraction = quad_fraction;
|
||||
let mut a_u = vec![0.0; (nx + 1) * ny * nz];
|
||||
let mut a_v = vec![0.0; nx * (ny + 1) * nz];
|
||||
let mut a_w = vec![0.0; nx * ny * (nz + 1)];
|
||||
@@ -221,6 +199,13 @@ impl CutGeometry {
|
||||
}
|
||||
}
|
||||
|
||||
/// φ at the corner `(k, j, i)` of the corner lattice.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn corner_phi(&self, k: usize, j: usize, i: usize) -> f64 {
|
||||
self.phi[Self::node(self.grid, k, j, i)]
|
||||
}
|
||||
|
||||
/// Total fluid volume.
|
||||
#[must_use]
|
||||
pub fn fluid_volume(&self) -> f64 {
|
||||
@@ -244,6 +229,34 @@ impl CutGeometry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The fluid fraction of a triangle from its three corner values of φ
|
||||
/// (the linear interpolant; fluid where φ ≥ 0).
|
||||
pub(super) fn tri_area_fraction(p0: f64, p1: f64, p2: f64) -> f64 {
|
||||
let v = [p0, p1, p2];
|
||||
let pos = v.iter().filter(|&&q| q >= 0.0).count();
|
||||
match pos {
|
||||
0 => 0.0,
|
||||
3 => 1.0,
|
||||
1 => {
|
||||
let a = v.iter().position(|&q| q >= 0.0).unwrap();
|
||||
let (b, c) = ((a + 1) % 3, (a + 2) % 3);
|
||||
(v[a] / (v[a] - v[b])) * (v[a] / (v[a] - v[c]))
|
||||
}
|
||||
_ => {
|
||||
let a = v.iter().position(|&q| q < 0.0).unwrap();
|
||||
let (b, c) = ((a + 1) % 3, (a + 2) % 3);
|
||||
1.0 - (v[a] / (v[a] - v[b])) * (v[a] / (v[a] - v[c]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The fluid fraction of a quad from its corner values in (a, b) order
|
||||
/// (q00, q10, q01, q11): the two triangles along the (0, 0)–(1, 1)
|
||||
/// diagonal (the Kuhn split's).
|
||||
pub(super) fn quad_fraction(q00: f64, q10: f64, q01: f64, q11: f64) -> f64 {
|
||||
0.5 * (tri_area_fraction(q00, q10, q11) + tri_area_fraction(q00, q11, q01))
|
||||
}
|
||||
|
||||
fn det3(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
|
||||
a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0])
|
||||
+ a[2] * (b[0] * c[1] - b[1] * c[0])
|
||||
|
||||
@@ -219,6 +219,8 @@ impl Mask {
|
||||
wall_advancing: false,
|
||||
exchange_convection_off: false,
|
||||
wall_exchange_axis: false,
|
||||
cv_sides_exact: false,
|
||||
wall_order2_centroid: false,
|
||||
grad_weights: None,
|
||||
diffusion_centroid: false,
|
||||
face_shifts: None,
|
||||
@@ -375,6 +377,12 @@ impl Mask {
|
||||
ap[d] = [minus, plus];
|
||||
}
|
||||
}
|
||||
// S2-7: the sides' own apertures instead of the whole-face averages.
|
||||
if self.cv_sides_exact {
|
||||
if let Some(exact) = self.cut.as_ref().and_then(|cut| self.exact_cv_sides(cut, c, p)) {
|
||||
ap = exact;
|
||||
}
|
||||
}
|
||||
let mut wall = [0.0; 3];
|
||||
for d in 0..3 {
|
||||
wall[d] = -(ap[d][1] - ap[d][0]) * area[d];
|
||||
@@ -415,6 +423,86 @@ impl Mask {
|
||||
}
|
||||
}
|
||||
|
||||
/// S2-7: the control volume's side apertures from the interpolant on the
|
||||
/// sides' OWN corners. An unknown face's control volume is the tile
|
||||
/// between the two adjacent cells' centres: across `c` its sides are
|
||||
/// two HALF faces (the far half of `cell_minus`'s face, the near half
|
||||
/// of `cell_plus`'s), in the own direction the two cells' centre
|
||||
/// planes. φ is linear along every edge, so the mid-edge values are
|
||||
/// exact for the interpolant; each half face / centre plane is a quad
|
||||
/// through the faces' own `quad_fraction`. The averages of whole-face
|
||||
/// apertures the default takes are wrong by O(1) wherever the wall
|
||||
/// crosses a side (the in-plane momentum residual on oblique walls).
|
||||
/// `None` at a domain side (the default stays).
|
||||
fn exact_cv_sides(&self, cut: &CutGeometry, c: usize, p: [i64; 3]) -> Option<[[f64; 2]; 3]> {
|
||||
let lat = self.lattice();
|
||||
let g = self.grid;
|
||||
let mut pm = p;
|
||||
pm[c] -= 1;
|
||||
let cells = [g.kji(lat.cell(pm)?), g.kji(lat.cell(p)?)];
|
||||
// The corner of cell (k, j, i) at unit offsets `o = [di, dj, dk]`.
|
||||
let corner = |cell: (usize, usize, usize), o: [usize; 3]| {
|
||||
cut.corner_phi(cell.0 + o[2], cell.1 + o[1], cell.2 + o[0])
|
||||
};
|
||||
// The value at a cell's corner or, with `half`, at the mid-point of
|
||||
// its edge along `c` (the interpolant's mean of the two corners).
|
||||
let value = |cell: (usize, usize, usize), mut o: [usize; 3], half: bool| -> f64 {
|
||||
if half {
|
||||
o[c] = 0;
|
||||
let a = corner(cell, o);
|
||||
o[c] = 1;
|
||||
0.5 * (a + corner(cell, o))
|
||||
} else {
|
||||
corner(cell, o)
|
||||
}
|
||||
};
|
||||
let mut ap = [[1.0; 2]; 3];
|
||||
for d in 0..3 {
|
||||
if d == c {
|
||||
// The two cells' centre planes across `c`: corners at the
|
||||
// mid-points of the cells' `c` edges.
|
||||
let (d1, d2) = ((c + 1) % 3, (c + 2) % 3);
|
||||
for (side, cell) in cells.iter().enumerate() {
|
||||
let mid = |o1: usize, o2: usize| {
|
||||
let mut o = [0; 3];
|
||||
o[d1] = o1;
|
||||
o[d2] = o2;
|
||||
value(*cell, o, true)
|
||||
};
|
||||
ap[d][side] = super::cut::quad_fraction(mid(0, 0), mid(1, 0), mid(0, 1), mid(1, 1));
|
||||
}
|
||||
} else {
|
||||
let e = 3 - c - d;
|
||||
for side in 0..2 {
|
||||
// cell_minus's `d` face at `side`, its half nearer the
|
||||
// unknown face (c from ½ to 1); cell_plus's, c from 0 to ½.
|
||||
let half = |cell: (usize, usize, usize), far: bool| -> f64 {
|
||||
let at = |oc: u8, oe: usize| {
|
||||
let mut o = [0; 3];
|
||||
o[d] = side;
|
||||
o[e] = oe;
|
||||
match oc {
|
||||
0 => value(cell, o, false),
|
||||
1 => value(cell, o, true),
|
||||
_ => {
|
||||
o[c] = 1;
|
||||
value(cell, o, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
if far {
|
||||
super::cut::quad_fraction(at(1, 0), at(2, 0), at(1, 1), at(2, 1))
|
||||
} else {
|
||||
super::cut::quad_fraction(at(0, 0), at(1, 0), at(0, 1), at(1, 1))
|
||||
}
|
||||
};
|
||||
ap[d][side] = 0.5 * (half(cells[0], true) + half(cells[1], false));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(ap)
|
||||
}
|
||||
|
||||
/// The surface velocity component `c` at the foot of the normal from
|
||||
/// the face centre `x`. With a cut geometry the signed distance and
|
||||
/// the normal come from the geometry's own corner values (the
|
||||
|
||||
@@ -173,6 +173,14 @@ impl DeviceStep {
|
||||
!solver.params.momentum_volume_tiled,
|
||||
"the tiled momentum volume (A1-b) is a host prototype: the device kernels do not carry it"
|
||||
);
|
||||
assert!(
|
||||
!solver.params.wall_order2_centroid,
|
||||
"the order-2 second point at the neighbour's centroid (S2-7) is a host prototype: the device kernels do not carry it"
|
||||
);
|
||||
assert!(
|
||||
!solver.params.cv_sides_exact,
|
||||
"the exact control-volume sides (S2-7) are a host prototype: the device kernels do not carry it"
|
||||
);
|
||||
let rt = runtime();
|
||||
let nu = (grid.nx + 1) * grid.ny * grid.nz;
|
||||
let nv = grid.nx * (grid.ny + 1) * grid.nz;
|
||||
|
||||
@@ -147,6 +147,19 @@ pub struct Parameters {
|
||||
/// layer — the sharpened S2-7 hypothesis. The device path refuses it.
|
||||
/// `RTX_E3_MOMENTUM_VOLUME=tiled`.
|
||||
pub momentum_volume_tiled: bool,
|
||||
/// HOST PROTOTYPE (S2-7, 2026-09-19): the momentum control volumes' side
|
||||
/// apertures from the interpolant on the sides' own corners (the half
|
||||
/// faces across the face's direction, the cell-centre planes along it)
|
||||
/// instead of the averages of whole-face apertures, which are wrong by
|
||||
/// O(1) wherever the wall crosses a side. Static bodies; the device
|
||||
/// refuses it. `RTX_E3_CV_SIDES=exact`.
|
||||
pub cv_sides_exact: bool,
|
||||
/// HOST PROTOTYPE (S2-7): the quadratic wall gradient's (order 2) second
|
||||
/// point at the neighbour face's OWN centroid distance instead of one
|
||||
/// lattice step along the dominant axis from this face's centroid (the
|
||||
/// two differ by ½h(1 − α)n_t, O(1) of the step on cut faces). The
|
||||
/// device refuses it. `RTX_E3_WALL_ORDER2=centroid`.
|
||||
pub wall_order2_centroid: bool,
|
||||
/// S2-5: the cross-direction diffusion between two faces over the
|
||||
/// distance between their OPEN-PART CENTROIDS (a cut face's velocity
|
||||
/// is its open part's mean, ½h(1 − α) off the face centre along the
|
||||
@@ -191,6 +204,8 @@ impl Default for Parameters {
|
||||
pressure_centroid: std::env::var("RTX_E3_PRESSURE_CENTROID").is_ok_and(|v| v == "1"),
|
||||
momentum_volume_tiled: std::env::var("RTX_E3_MOMENTUM_VOLUME")
|
||||
.is_ok_and(|v| v == "tiled"),
|
||||
cv_sides_exact: std::env::var("RTX_E3_CV_SIDES").is_ok_and(|v| v == "exact"),
|
||||
wall_order2_centroid: std::env::var("RTX_E3_WALL_ORDER2").is_ok_and(|v| v == "centroid"),
|
||||
// ON by default since S2-5 (`=0` reproduces the records before it).
|
||||
diffusion_centroid: std::env::var("RTX_E3_DIFFUSION_CENTROID")
|
||||
.map_or(true, |v| v != "0"),
|
||||
@@ -330,6 +345,8 @@ impl Solver {
|
||||
m.distance_floor_fine = self.params.distance_floor_fine;
|
||||
m.wall_advancing = self.params.wall_advancing;
|
||||
m.exchange_convection_off = self.params.exchange_convection_off;
|
||||
m.cv_sides_exact = self.params.cv_sides_exact;
|
||||
m.wall_order2_centroid = self.params.wall_order2_centroid;
|
||||
m.diffusion_centroid = self.params.diffusion_centroid;
|
||||
if self.params.diffusion_centroid {
|
||||
m.compute_face_shifts();
|
||||
|
||||
@@ -108,6 +108,12 @@ pub struct Mask {
|
||||
pub(super) exchange_convection_off: bool,
|
||||
/// The axis-distance implicit wall exchange (S2-5).
|
||||
pub(super) wall_exchange_axis: bool,
|
||||
/// S2-7: the momentum control volumes' side apertures from the
|
||||
/// interpolant on the sides' own corners (host prototype).
|
||||
pub(super) cv_sides_exact: bool,
|
||||
/// S2-7: the quadratic wall gradient's second point at the neighbour's
|
||||
/// own centroid distance (host prototype).
|
||||
pub(super) wall_order2_centroid: bool,
|
||||
/// The centroid prototype's pressure-gradient weights per u / v / w face.
|
||||
/// The centroid-distance cross diffusion (S2-5).
|
||||
pub(super) diffusion_centroid: bool,
|
||||
@@ -534,6 +540,8 @@ impl Mask {
|
||||
wall_advancing: false,
|
||||
exchange_convection_off: false,
|
||||
wall_exchange_axis: false,
|
||||
cv_sides_exact: false,
|
||||
wall_order2_centroid: false,
|
||||
grad_weights: None,
|
||||
diffusion_centroid: false,
|
||||
face_shifts: None,
|
||||
|
||||
Reference in New Issue
Block a user