diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/cutwall.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/cutwall.rs index f7c8a4c..e7db24b 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/cutwall.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/cutwall.rs @@ -223,6 +223,7 @@ impl Mask { wall_order2_centroid: false, wall_exchange_foot: false, conv_sides_exact: false, + wall_flux_true_normal: false, grad_weights: None, diffusion_centroid: false, face_shifts: None, @@ -699,6 +700,18 @@ impl Mask { (j as f64 + 0.5) * g.dy, (k as f64 + 0.5) * g.dz, ]; + // S2-7b A1: the flux through the TRUE surface at the wall foot — + // `A_w (u_b · n)` with n the body's own normal into the body. + if self.wall_flux_true_normal { + let (s, n) = self.interpolant_distance_and_normal(cut, x); + let foot = [x[0] - s * n[0], x[1] - s * n[1], x[2] - s * n[2]]; + let v = body.surface_velocity(foot[0], foot[1], foot[2], t); + let eps = 1e-6 * g.dx.min(g.dy).min(g.dz); + let (n1, n2, n3) = body.normal(foot[0], foot[1], foot[2], t, eps); + let a_w = (w[0] * w[0] + w[1] * w[1] + w[2] * w[2]).sqrt(); + // `normal` points out of the solid: into the body is its negative. + return -a_w * (v.0 * n1 + v.1 * n2 + v.2 * n3); + } let mut flux = 0.0; for (c, wc) in w.iter().enumerate() { flux += self.surface_velocity_at(body, x, c, t) * wc; 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 d9715bf..2802033 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 @@ -178,7 +178,9 @@ impl DeviceStep { "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.wall_exchange_foot && !solver.params.conv_sides_exact, + !solver.params.wall_exchange_foot + && !solver.params.conv_sides_exact + && !solver.params.wall_flux_true_normal, "the axis-foot exchange / exact convective sides (S2-7b) are host prototypes: the device kernels do not carry them" ); assert!( 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 b3b46d2..d057df6 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 @@ -174,6 +174,13 @@ pub struct Parameters { /// faces across, the cell-centre planes along) instead of the averages /// of whole-face fluxes. `RTX_E3_CONV_SIDES=exact`; the device refuses it. pub conv_sides_exact: bool, + /// HOST PROTOTYPE (S2-7b, A1): a moving wall's mass flux from the body's + /// TRUE normal at the wall foot, `A_w (u_b · n_true)` (zero for a + /// tangentially moving wall), instead of `u_b · W_c` with the + /// interpolant's facet vector, which pumps `U_b h/R` through every facet + /// of a rotating or flexing wall. `RTX_E3_WALL_FLUX=true`; the device + /// refuses it. + pub wall_flux_true_normal: 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 @@ -218,6 +225,7 @@ impl Default for Parameters { wall_exchange_axis: std::env::var("RTX_E3_WALL_EXCHANGE").map_or(true, |v| v != "h"), wall_exchange_foot: std::env::var("RTX_E3_WALL_EXCHANGE").is_ok_and(|v| v == "axisfoot"), conv_sides_exact: std::env::var("RTX_E3_CONV_SIDES").is_ok_and(|v| v == "exact"), + wall_flux_true_normal: std::env::var("RTX_E3_WALL_FLUX").is_ok_and(|v| v == "true"), 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"), @@ -366,6 +374,7 @@ impl Solver { m.wall_order2_centroid = self.params.wall_order2_centroid; m.wall_exchange_foot = self.params.wall_exchange_foot; m.conv_sides_exact = self.params.conv_sides_exact; + m.wall_flux_true_normal = self.params.wall_flux_true_normal; m.diffusion_centroid = self.params.diffusion_centroid; if self.params.diffusion_centroid { m.compute_face_shifts(); diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/wall.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/wall.rs index 3bebfb3..0bcdd7c 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/wall.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/wall.rs @@ -118,6 +118,7 @@ pub struct Mask { /// exchange; the convective sides from the sides' own apertures. pub(super) wall_exchange_foot: bool, pub(super) conv_sides_exact: bool, + pub(super) wall_flux_true_normal: 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, @@ -548,6 +549,7 @@ impl Mask { wall_order2_centroid: false, wall_exchange_foot: false, conv_sides_exact: false, + wall_flux_true_normal: false, grad_weights: None, diffusion_centroid: false, face_shifts: None,