R8-c: the 3D flag's interface — deformed-plate body (host + device φ/ub) and the conservative load transfer onto a Hex20 plate

- embedded3/plate.rs: PlateSurface (mid-surface on span stations) and the
  host evaluation of DeviceSdf (phi_host / velocity_host / is_flag_host),
  expression for expression e3_geom.cu; a span-uniform plate is the
  polyline capsule to the bit; first-order spanwise-slope correction.
- e3_geom.cu / device/geom.rs: plate branch of geom_phi_at and
  body_velocity (nst = 0 keeps the polyline path unchanged).
- cutwall.rs / exchange.rs: the load loops observed through a sink (sums
  unchanged); interface.rs: Mask::cut_wall_loads (every summand of
  cut_wall_force with its foot), HexPlate (R8-b's Hex20 lattice numbering),
  consistent point-force transfer conserving force and moment to round-off,
  locate() for the transpose, mid_surface() for the fluid body.
- flag test: RTX_E3_FLAG_BODY=plate, _STATIONS, _TWIST, _TRANSFER(_EVERY,
  _CSV, _NODAL); all default off.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-25 18:03:26 -05:00
co-authored by Claude Opus 5.5
parent d63806c0e6
commit 9c2ae32061
9 changed files with 1639 additions and 56 deletions
@@ -92,6 +92,33 @@ impl Mask {
t: f64,
planes: Option<(usize, usize)>,
) -> Option<([f64; 3], [f64; 3])> {
self.cut_wall_exchange_parts_sink(body, f, mu, rho, t, planes, &mut |_, _, _| {})
}
/// `cut_wall_exchange_parts` with every summand also handed to `sink`
/// (R8-c: each prescribed neighbour's diffusive and convective exchange
/// at the fluid face's position, as a force on the body; the sums are
/// computed exactly as before — the sink only observes them).
#[allow(clippy::too_many_arguments)]
pub(super) fn cut_wall_exchange_parts_sink<S>(
&self,
body: &Body,
f: &Field,
mu: f64,
rho: f64,
t: f64,
planes: Option<(usize, usize)>,
sink: &mut S,
) -> Option<([f64; 3], [f64; 3])>
where
S: FnMut(super::interface::LoadKind, [f64; 3], [f64; 3]),
{
use super::interface::LoadKind;
let comp = |c: usize, v: f64| {
let mut out = [0.0; 3];
out[c] = v;
out
};
let _ = body;
let _ = t;
self.cut.as_ref()?;
@@ -139,6 +166,7 @@ impl Mask {
continue;
}
let cv = self.cv_geometry(c, p);
let xf = lat.face_position(c, p);
let u0 = vals[c][idx];
let shift0 = self.face_shift(c, p);
let cell_minus = add(p, ec, -1);
@@ -192,10 +220,13 @@ impl Mask {
};
let u_face = upwind(m_plus, u0, un) + delta;
if !self.exchange_convection_off {
convective[c] -= -rho * m_plus * (u_face - u0);
let v = -rho * m_plus * (u_face - u0);
convective[c] -= v;
sink(LoadKind::ExchangeConvective, xf, comp(c, -v));
}
force[c] -=
mu * cv.ap[d][1] * a_d * (un - u0) / solid_spacing(1.0);
let v = mu * cv.ap[d][1] * a_d * (un - u0) / solid_spacing(1.0);
force[c] -= v;
sink(LoadKind::ExchangeDiffusive, xf, comp(c, -v));
}
}
// Minus side.
@@ -211,10 +242,14 @@ impl Mask {
};
let u_face = upwind(m_minus, ud, u0) + delta;
if !self.exchange_convection_off {
convective[c] -= rho * m_minus * (u_face - u0);
let v = rho * m_minus * (u_face - u0);
convective[c] -= v;
sink(LoadKind::ExchangeConvective, xf, comp(c, -v));
}
force[c] -=
let v =
mu * cv.ap[d][0] * a_d * (ud - u0) / solid_spacing(-1.0);
force[c] -= v;
sink(LoadKind::ExchangeDiffusive, xf, comp(c, -v));
}
}
}