R8-c: drop zero summands from cut_wall_loads; extrapolation diagnostics beyond FAR_OUTSIDE with the worst point

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-25 20:45:04 -05:00
co-authored by Claude Opus 5.5
parent 9c2ae32061
commit 05bf4c74a0
3 changed files with 33 additions and 18 deletions
@@ -79,18 +79,17 @@ impl Mask {
if self.grad_weights.is_some() {
return None;
}
// The loops visit every fluid cell and face; only the non-zero
// summands are loads (the zero ones change no sum).
let mut raw: Vec<(LoadKind, [f64; 3], [f64; 3])> = Vec::new();
let (p, s) = self
.cut_wall_force_parts_sink(body, f, mu, t, None, &mut |k, x, v| raw.push((k, x, v)))?;
let (d, c) = self.cut_wall_exchange_parts_sink(
body,
f,
mu,
self.density,
t,
None,
&mut |k, x, v| raw.push((k, x, v)),
)?;
let mut keep = |k: LoadKind, x: [f64; 3], v: [f64; 3]| {
if v != [0.0; 3] {
raw.push((k, x, v));
}
};
let (p, s) = self.cut_wall_force_parts_sink(body, f, mu, t, None, &mut keep)?;
let (d, c) =
self.cut_wall_exchange_parts_sink(body, f, mu, self.density, t, None, &mut keep)?;
let xch = [d[0] + c[0], d[1] + c[1], d[2] + c[2]];
let total = [
p[0] + s[0] + xch[0],
@@ -209,6 +208,10 @@ pub struct Location {
pub outside: f64,
}
/// The natural-coordinate distance outside an element from which a load
/// counts as extrapolated in [`PlateTransfer`] (0.02 of the half-element).
pub const FAR_OUTSIDE: f64 = 0.02;
/// The result of one load transfer (see [`HexPlate::transfer`]).
#[derive(Debug, Clone)]
pub struct PlateTransfer {
@@ -223,9 +226,13 @@ pub struct PlateTransfer {
/// The largest Newton residual (m) and the largest outside-ness.
pub max_residual: f64,
pub max_outside: f64,
/// Loads located outside every element (extrapolated), and their Σ|F|.
/// Loads located outside every element by more than [`FAR_OUTSIDE`]
/// in natural coordinates (a foot off the plate by more than a few %
/// of an element — the surfaces' round-off-level mismatch is excluded),
/// their Σ|F|, and the worst one's point.
pub extrapolated: usize,
pub extrapolated_load: f64,
pub worst_point: [f64; 3],
/// The share of Σ|f_a| on nodes off the wetted surface (the interior
/// layers and the clamped root face).
pub interior_share: f64,
@@ -481,6 +488,7 @@ impl HexPlate {
let (mut force_in, mut moment_in) = ([0.0f64; 3], [0.0f64; 3]);
let (mut max_residual, mut max_outside) = (0.0f64, f64::NEG_INFINITY);
let (mut extrapolated, mut extrapolated_load) = (0usize, 0.0f64);
let mut worst_point = [0.0f64; 3];
for (&(p, f), loc) in loads.iter().zip(&located) {
for (&n, &w) in loc.nodes.iter().zip(&loc.weights) {
for c in 0..3 {
@@ -493,8 +501,11 @@ impl HexPlate {
moment_in[c] += m[c];
}
max_residual = max_residual.max(loc.residual);
max_outside = max_outside.max(loc.outside);
if loc.outside > 1e-9 {
if loc.outside > max_outside {
max_outside = loc.outside;
worst_point = p;
}
if loc.outside > FAR_OUTSIDE {
extrapolated += 1;
extrapolated_load += norm(f);
}
@@ -524,6 +535,7 @@ impl HexPlate {
max_outside,
extrapolated,
extrapolated_load,
worst_point,
interior_share: if total_abs > 0.0 {
interior_abs / total_abs
} else {
@@ -29,7 +29,7 @@ pub use cut::CutGeometry;
pub use export_vtk::write_vtk;
pub use field::Field;
pub use grid::Grid;
pub use interface::{HexPlate, LoadKind, PlateTransfer, WallLoad};
pub use interface::{FAR_OUTSIDE, HexPlate, LoadKind, PlateTransfer, WallLoad};
pub use loads::SurfaceForce;
pub use plate::PlateSurface;
pub use step::{Boundaries, Fluid, Parameters, Side, Solver, StepResult};
@@ -26,8 +26,8 @@ use embedded3_flag_kinematics::{Recorded, recorded};
use rtx_cfd::solvers::incompressible::ConvectionScheme;
use rtx_cfd::solvers::incompressible::embedded3::step::device::DeviceStep;
use rtx_cfd::solvers::incompressible::embedded3::{
Body, Boundaries, DeviceSdf, Field, Fluid, Grid, HexPlate, Parameters, PlateSurface, Side,
Solver, WallScheme, write_vtk,
Body, Boundaries, DeviceSdf, FAR_OUTSIDE, Field, Fluid, Grid, HexPlate, Parameters,
PlateSurface, Side, Solver, WallScheme, write_vtk,
};
use std::io::Write as _;
@@ -735,7 +735,7 @@ fn flag_wake_on_the_device() {
let extrap_share = tr.extrapolated_load / abs_f.max(1e-300);
let ms = lap.elapsed().as_secs_f64() * 1e3;
println!(
" transfer t {t:.4}: {} loads ({} flag); route − Σ {:.1e} N; flag F {:+.4} {:+.4} {:+.4} N, M {:+.5} {:+.5} {:+.5} N m; |ΔF|/Σ|F| {rel_f:.1e}, |ΔM|/(Σ|F| L) {rel_m:.1e}; Newton ≤ {:.1e} m, outside ≤ {:.2e} ({} extrapolated, {:.1e} of Σ|F|), interior share {:.3}; {ms:.0} ms",
" transfer t {t:.4}: {} loads ({} flag); route − Σ {:.1e} N; flag F {:+.4} {:+.4} {:+.4} N, M {:+.5} {:+.5} {:+.5} N m; |ΔF|/Σ|F| {rel_f:.1e}, |ΔM|/(Σ|F| L) {rel_m:.1e}; Newton ≤ {:.1e} m, outside ≤ {:.2e} at ({:.4}, {:.4}, {:.4}) ({} beyond {FAR_OUTSIDE}, {:.1e} of Σ|F|), interior share {:.3}; {ms:.0} ms",
loads.len(),
flag.len(),
nrm([route[0] - sum[0], route[1] - sum[1], route[2] - sum[2]]),
@@ -747,6 +747,9 @@ fn flag_wake_on_the_device() {
tr.moment_in[2],
tr.max_residual,
tr.max_outside,
tr.worst_point[0],
tr.worst_point[1],
tr.worst_point[2],
tr.extrapolated,
extrap_share,
tr.interior_share