Skip to main content

geop_core_math/scalars/
mod.rs

1pub mod scal_in_f64;
2pub mod scal_in_fpa64;
3
4use core::fmt::Display;
5
6pub use scal_in_f64::ScalInF64;
7pub use scal_in_fpa64::ScalInFPA64;
8
9use crate::geop_error::GeopResult;
10
11// ── Algebraic traits ─────────────────────────────────────────────────────────
12
13pub trait Ring: Clone + core::fmt::Debug + Send + Sync + 'static {
14    fn add(self, other: Self) -> Self;
15    fn sub(self, other: Self) -> Self;
16    fn mul(self, other: Self) -> Self;
17    fn neg(self) -> Self;
18}
19
20pub trait Field: Ring {
21    fn div(self, other: Self) -> GeopResult<Self>;
22}
23
24// ── Core trait ────────────────────────────────────────────────────────────────
25pub trait Scalar: Field + Copy + Display + Default {
26    // Constants
27    const ZERO: Self;
28    const ONE: Self;
29    const TWO: Self;
30    const E: Self;
31    const PI: Self;
32    /// Saturation sentinel — set on overflow.
33    const INFINITY: Self;
34    /// The "entire" interval `(-inf, inf)` — the top element of the interval
35    /// lattice. `could_be_equal`/`could_be_greater`/`could_be_less` against
36    /// it are always `true`, and it never satisfies `definitely_*`. Used to
37    /// represent a value or a whole curve/surface whose position is not yet
38    /// known — an unsharp placeholder that automatically passes any
39    /// overlap/equality check made against it.
40    const ENTIRE: Self;
41
42    // Construction
43    fn from_i64(v: i64) -> Self;
44    fn from_f64(v: f64) -> Self;
45    fn from_ratio(num: i64, den: i64) -> GeopResult<Self>;
46
47    /// Approximate f64 midpoint. For point scalars returns the value; for
48    /// interval scalars returns (lo + hi) / 2. Used only for rendering/debugging.
49    fn to_f64(self) -> f64;
50
51    // Real-valued operations
52    fn abs(self) -> Self;
53    fn sqrt(self) -> GeopResult<Self>;
54    /// Outward-rounded enclosure of `sin`/`cos` over the whole interval
55    /// (radians). Total — never fails, even for [`Scalar::ENTIRE`] or an
56    /// [`Scalar::INFINITY`]-adjacent value, which just widen to `[-1, 1]`.
57    fn sin(self) -> Self;
58    fn cos(self) -> Self;
59
60    // Three-valued comparisons
61    fn could_be_equal(self, other: Self) -> bool;
62    fn definitely_not_equal(self, other: Self) -> bool;
63    fn could_be_greater(self, other: Self) -> bool;
64    fn definitely_greater(self, other: Self) -> bool;
65    fn could_be_less(self, other: Self) -> bool;
66    fn definitely_less(self, other: Self) -> bool;
67
68    // Finiteness
69    fn is_infinite(self) -> bool;
70    fn is_finite(self) -> bool;
71
72    // Set-valued helpers
73    fn midpoint(self) -> Self;
74
75    /// True iff this value carries no width — it's a single, exactly-known
76    /// point, not a genuine range of possibility.
77    fn is_sharp(self) -> bool;
78
79    /// How much possibility this enclosure carries: `hi - lo`, as a **sharp,
80    /// non-negative** value. Zero exactly when [`Scalar::is_sharp`].
81    ///
82    /// This is how much a computed quantity is *not* known. Being sharp
83    /// itself is what makes it usable as a threshold — comparing an uncertain
84    /// width against an uncertain bound could never be decided three-valuedly
85    /// (see `validation::numerical_accuracy`).
86    fn width(self) -> Self;
87
88    /// The sharp lower / upper endpoint of this enclosure. Every value
89    /// `self` could be is `>= lower()` and `<= upper()`, so these are the
90    /// *outer* bounds to cut at when a search restricts a domain to an
91    /// enclosure of its answer: a cut there never loses a solution (unlike
92    /// [`Scalar::sharpen`], which would cut through the enclosure).
93    fn lower(self) -> Self;
94    fn upper(self) -> Self;
95
96    /// Collapse to a single representative point (currently the midpoint,
97    /// like [`Scalar::midpoint`], but named for its distinct *purpose*: use
98    /// this only when you are free to pick *any* value within `self` and
99    /// don't need to preserve which one — e.g. choosing where to place a
100    /// new knot when subdividing a curve at an arbitrary interior point.
101    /// **Never** use this to compress a value that represents a genuinely
102    /// uncertain physical quantity (a search's converged bound, a measured
103    /// position) — that would silently discard real uncertainty rather than
104    /// making an arbitrary, harmless choice.
105    ///
106    /// Exists to break a specific class of interval blowup: repeatedly
107    /// re-deriving a split point as `(t0 + t1) / 2` from an already-widened
108    /// domain propagates and compounds that width forever, even though nothing
109    /// downstream actually cares *which* interior point was chosen — only
110    /// that some valid one was. Sharpening throws that unneeded width away
111    /// at the source instead of letting every later `alpha = (t - e) / (s - e)`
112    /// division amplify it further.
113    fn sharpen(self) -> Self {
114        self.midpoint()
115    }
116
117    /// Point a fraction `alpha` of the way from `a` to `b`: `a` at
118    /// `alpha=0`, `b` at `alpha=1`.
119    ///
120    /// Deliberately `a.add(alpha.mul(b.sub(a)))`, *not* the equally-valid
121    /// `a.mul(S::ONE.sub(alpha)).add(b.mul(alpha))` — both give the same
122    /// exact real result, but the latter computes `alpha` and `1-alpha` as
123    /// two *decorrelated* intervals before ever relating `a` and `b`, so
124    /// interval arithmetic can't recognize when they cancel. This form
125    /// computes `b.sub(a)` first: when `a` and `b` are honestly the same
126    /// value (e.g. a weight that should stay exactly `1.0` across many
127    /// subdivisions), that subtraction is exactly `0` regardless of
128    /// `alpha`'s own width, and the whole expression collapses to exactly
129    /// `a` instead of needlessly widening with every call.
130    fn interpolate(a: Self, b: Self, alpha: Self) -> Self {
131        // Two algebraically identical forms with *opposite* numerical
132        // strengths, so this evaluates both and keeps their intersection —
133        // both are honest enclosures of the same exact value, so the
134        // narrower parts of each are jointly valid, and no magic tolerance
135        // is involved in preferring them.
136        //
137        // - `a + alpha*(b - a)` mentions `a` twice (so `a`'s own width is
138        //   counted twice, decorrelated) but computes `b - a` first: when
139        //   `a` and `b` are honestly equal — a weight that should stay
140        //   exactly `1.0` across many subdivisions, say — that difference
141        //   is exactly zero and the whole thing collapses to exactly `a`,
142        //   no matter how wide `alpha` is.
143        // - `(1 - alpha)*a + alpha*b` mentions each of `a`/`b` once, so
144        //   wide control points don't get double-counted, but it splits
145        //   `alpha` into two decorrelated factors and so can't see the
146        //   `a == b` cancellation at all.
147        //
148        // Neither dominates: the first is what a repeatedly-split curve's
149        // weights need, the second is what a surface patch with genuinely
150        // wide control points needs.
151        let via_delta = a.add(alpha.mul(b.sub(a)));
152        let via_weights = Self::ONE.sub(alpha).mul(a).add(alpha.mul(b));
153        via_delta.intersect(via_weights)
154    }
155
156    /// The largest value contained in *both* `self` and `other` — the dual
157    /// of [`Scalar::union`]. Callers must only intersect two enclosures of
158    /// the same underlying exact value (as [`Scalar::interpolate`] does);
159    /// given that, the result is still an honest enclosure, just a tighter
160    /// one. Implementations may return either input if the two somehow
161    /// don't overlap, rather than fabricating an empty/inverted interval.
162    fn intersect(self, other: Self) -> Self;
163
164    /// The smallest value definitely containing both `self` and `other` —
165    /// the scalar-level analog of `Set::union`.
166    fn union(self, other: Self) -> Self;
167
168    /// True iff `self` is contained in `other` as sets: `other.lo <= self.lo`
169    /// and `self.hi <= other.hi`. This is the rigorous existence/uniqueness
170    /// test a Krawczyk-style contraction relies on (`K(X) ⊆ X`) — distinct
171    /// from [`Scalar::could_be_equal`], which only asks whether the two
172    /// enclosures *overlap*. `self.intersect(other).could_be_equal(self)`
173    /// would answer the same question but at the cost of rebuilding an
174    /// enclosure just to throw it away; implementations should compare
175    /// bounds directly.
176    fn is_subset_of(self, other: Self) -> bool;
177}
178
179// ── Test helper trait ─────────────────────────────────────────────────────────
180
181/// Invoke a generic test function once for each concrete scalar
182/// implementation.  Inside a `#[test]` function, write:
183///
184/// ```rust,ignore
185/// fn my_check<S: Scalar + ScalarTestHelper>() { /* … */ }
186/// #[test] fn my_test() { for_all_scalars!(my_check); }
187/// ```
188#[macro_export]
189macro_rules! for_all_scalars {
190    ($fn:ident) => {{
191        $fn::<$crate::scalars::ScalInF64>();
192        $fn::<$crate::scalars::ScalInFPA64>();
193    }};
194}