forked from 1b-agent/fieldcraft-upstream
* predicates: LNC points, generic insphere and its perturbed form (Diazzi et al. 2023 §4.3) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * mesh: constrained Delaunay tetrahedralization (segment recovery with LNC Steiner points, gift-wrapping facet recovery, labelling) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * cdt: rounding repair, mesh.tet.generate, corpus CDT column; review cleanups (shared perturbation rule, one facet scan, cached coordinates, worklist segment recovery) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * mesh.tet.generate is ready in the catalogue; corpus table with the CDT column Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --------- Co-authored-by: jyviko <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
117 lines
3.3 KiB
Rust
117 lines
3.3 KiB
Rust
//! Interval arithmetic, the dynamic filter of the indirect predicates (Attene 2020, §5.2;
|
|
//! Brönnimann, Burnikel & Pion 1998).
|
|
//!
|
|
//! Attene switches the FPU to directed rounding; Rust has no portable control of the rounding
|
|
//! mode (and wasm none at all), so each bound is computed with the default round-to-nearest and
|
|
//! then moved outward by one unit in the last place (`next_down` / `next_up`). A round-to-nearest
|
|
//! result is within half an ulp of the exact value, so the widened interval always contains it,
|
|
//! subnormals included. Overflow gives infinite bounds and `inf - inf` gives NaN; both make the
|
|
//! sign uncertain, which sends the predicate on to the exact stage.
|
|
|
|
use std::ops::{Add, Mul, Sub};
|
|
|
|
use super::Sign;
|
|
use super::ring::{Ring, Signed};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub(crate) struct Interval {
|
|
lo: f64,
|
|
hi: f64,
|
|
}
|
|
|
|
impl Interval {
|
|
const NAN: Interval = Interval { lo: f64::NAN, hi: f64::NAN };
|
|
|
|
#[inline(always)]
|
|
fn widened(lo: f64, hi: f64) -> Self {
|
|
if lo.is_nan() || hi.is_nan() { Self::NAN } else { Self { lo: lo.next_down(), hi: hi.next_up() } }
|
|
}
|
|
|
|
/// The lower bound.
|
|
pub(crate) fn lo(&self) -> f64 {
|
|
self.lo
|
|
}
|
|
|
|
/// The upper bound.
|
|
pub(crate) fn hi(&self) -> f64 {
|
|
self.hi
|
|
}
|
|
|
|
/// The midpoint, an approximation of the value.
|
|
pub(crate) fn mid(&self) -> f64 {
|
|
0.5 * self.lo + 0.5 * self.hi
|
|
}
|
|
}
|
|
|
|
impl Add for Interval {
|
|
type Output = Self;
|
|
|
|
#[inline(always)]
|
|
fn add(self, rhs: Self) -> Self {
|
|
Self::widened(self.lo + rhs.lo, self.hi + rhs.hi)
|
|
}
|
|
}
|
|
|
|
impl Sub for Interval {
|
|
type Output = Self;
|
|
|
|
#[inline(always)]
|
|
fn sub(self, rhs: Self) -> Self {
|
|
Self::widened(self.lo - rhs.hi, self.hi - rhs.lo)
|
|
}
|
|
}
|
|
|
|
impl Mul for Interval {
|
|
type Output = Self;
|
|
|
|
#[inline(always)]
|
|
fn mul(self, rhs: Self) -> Self {
|
|
let p = [self.lo * rhs.lo, self.lo * rhs.hi, self.hi * rhs.lo, self.hi * rhs.hi];
|
|
// `f64::min`/`max` skip NaN (from `0 * inf`), which would drop a bound: check first.
|
|
if p.iter().any(|x| x.is_nan()) {
|
|
return Self::NAN;
|
|
}
|
|
let lo = p.iter().copied().fold(f64::INFINITY, f64::min);
|
|
let hi = p.iter().copied().fold(f64::NEG_INFINITY, f64::max);
|
|
Self::widened(lo, hi)
|
|
}
|
|
}
|
|
|
|
impl Ring for Interval {
|
|
#[inline(always)]
|
|
fn from_f64(x: f64) -> Self {
|
|
Self { lo: x, hi: x }
|
|
}
|
|
}
|
|
|
|
impl Signed for Interval {
|
|
#[inline(always)]
|
|
fn sign(&self) -> Option<Sign> {
|
|
if self.lo > 0.0 {
|
|
Some(Sign::Positive)
|
|
} else if self.hi < 0.0 {
|
|
Some(Sign::Negative)
|
|
} else if self.lo == 0.0 && self.hi == 0.0 {
|
|
Some(Sign::Zero)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn intervals_enclose_and_flag_uncertainty() {
|
|
let third = Interval::from_f64(1.0) * Interval::from_f64(1.0 / 3.0);
|
|
let x = third + third + third - Interval::from_f64(1.0);
|
|
assert!(x.lo <= 0.0 && x.hi >= 0.0);
|
|
assert_eq!(x.sign(), None);
|
|
let big = Interval::from_f64(f64::MAX) * Interval::from_f64(2.0);
|
|
assert_eq!((big - big).sign(), None);
|
|
assert_eq!((Interval::from_f64(2.0) * Interval::from_f64(3.0)).sign(), Some(Sign::Positive));
|
|
}
|
|
}
|