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}