mesh: one transfinite interpolation for mapped hex and mesh.block; stable oracle report (#52)

* mesh: one transfinite interpolation for mapped hex and mesh.block; stable oracle report

mesh.block's lattice is now built from the structured hex mesher's
transfinite code: faces by quad::transfinite_grid, the interior by the new
quad::transfinite_volume (Gordon-Thiel, blended by the block's graded
parameters), which hex::mapped now calls too. 2D patches are the
transfinite_grid of their sides. Edge curves return their corners exactly.

Oracle: a zero-reference quantity (rounding noise) keeps its absolute
tolerance as its fixture band, and docs/oracle.md shows such noise and
relative errors inside the fixture band as bounds, so the report no longer
changes with the order of floating-point operations (it failed the
'docs/oracle.md is current' check on main after #50). Scordelis-Lo moved by
1.4e-9 relative (an ill-conditioned thin shell of solids: sub-ulp node
changes) and is re-blessed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* simplify: shared face filling and lattice index; faces blended like the volume; exact ends for every edge

quad::transfinite_faces fills a block's six faces (with a per-face hook
that hex::mapped uses to project onto its B-rep faces) and
quad::lattice_index fixes the lattice layout for all three users.
transfinite_grid_blended blends faces by the block's graded parameters, so
faces and interior use one parametrisation and mesh.block reproduces its
earlier Gordon-Hall lattice (the Scordelis-Lo fixture is back to its
blessed value). Straight edges also return their corners exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 22:50:20 -04:00
1 parent cd5e1e9df9
commit 85ba526663
16 files changed
+331 -239

No files matched your search

+30 -7
View File
@@ -178,8 +178,13 @@ pub struct Frozen {
pub band: f64,
}
/// The band a fixture value gets when it is blessed.
/// The band a fixture value gets when it is blessed. A quantity whose reference is zero (a
/// rigid-body frequency, an equilibrium residual, an invalid-element count) is rounding noise
/// inside its tolerance: its band is that tolerance, as freezing noise would only flag rounding.
pub fn fixture_band(value: f64, reference: f64, tolerance: Tolerance) -> f64 {
if reference == 0.0 {
return tolerance.absolute;
}
FIXTURE_RELATIVE * value.abs().max(reference.abs()) + 1e-3 * tolerance.absolute
}
@@ -474,15 +479,33 @@ fn show(x: Option<f64>) -> String {
x.map(number).unwrap_or_else(|| "—".into())
}
/// The error against the reference: relative where the reference is not zero.
/// Below this fraction of its absolute tolerance, a quantity whose reference is zero is shown as
/// zero: it is rounding noise, and its digits change with any reordering of the arithmetic
/// (docs/oracle.md must not).
const NOISE: f64 = 1e-2;
fn is_noise(r: &Row, value: f64) -> bool {
r.quantity.reference == Some(0.0) && value.abs() < NOISE * r.quantity.tolerance.absolute
}
/// Our value as the report shows it.
fn ours_text(r: &Row) -> String {
match r.ours {
Some(v) if is_noise(r, v) => "≈ 0".into(),
other => show(other),
}
}
/// The error against the reference: relative where the reference is not zero. Errors inside the
/// fixture band are rounding and are shown as a bound.
fn error_of(r: &Row) -> String {
let (Some(ours), Some(reference)) = (r.ours, r.quantity.reference) else { return "—".into() };
let d = ours - reference;
if reference != 0.0 && r.quantity.tolerance.relative > 0.0 {
let rel = d / reference.abs();
if rel.abs() < 1e-12 { "< 1e-10%".into() } else { percent(rel) }
} else if d.abs() < 1e-12 {
"< 1e-12".into()
if rel.abs() < FIXTURE_RELATIVE { "< 1e-7%".into() } else { percent(rel) }
} else if is_noise(r, d) || d == 0.0 {
"≈ 0".into()
} else {
format!("{d:.1e}")
}
@@ -565,7 +588,7 @@ benchmark, {} experiment; {} pending cases wait for capabilities still being bui
"| [{case}](../tests/oracle/{case}/) | {} | {} | {} | {} | {} | {} | {} |",
cell(&r.quantity.description),
show(r.quantity.reference),
show(r.ours),
ours_text(r),
error_of(r),
r.quantity.tolerance.describe(),
cell(&result),
@@ -635,7 +658,7 @@ mod tests {
// Zero references use the absolute tolerance, and the band keeps a floor from it.
let a = Tolerance { relative: 0.0, absolute: 1e-9 };
assert!(a.allows(0.0, 5e-10) && !a.allows(0.0, 2e-9));
assert!(fixture_band(1e-16, 0.0, a) >= 1e-12);
assert_eq!(fixture_band(1e-16, 0.0, a), 1e-9, "a zero reference keeps its tolerance as the band");
}
#[test]