Skip to main content

axiolid_predicates/
sphere.rs

1//! `incircle` and `insphere`: is a point inside a circumscribed ball?
2//!
3//! These are the Delaunay predicates. `incircle(a, b, c, d)` asks whether `d`
4//! lies inside the circle through `a`, `b`, `c`; `insphere` is the 3D analogue.
5//! A zero means `d` lies exactly on the ball -- the cocircular/cospherical case
6//! that makes a Delaunay triangulation non-unique and, if misjudged, produces
7//! inverted or overlapping cells.
8//!
9//! Both are lifted determinants: adding a coordinate equal to the squared
10//! distance from the origin turns "inside a ball" into "below a hyperplane".
11//! That lift squares the operand magnitudes, so the error bound grows faster
12//! than `orient*`'s and the filter fails sooner -- which is why the escalation
13//! rate is measured rather than assumed.
14
15use axiolid_core::{Point2, Point3};
16use axiolid_guarantees::{Certified, Precision, Sign};
17
18use crate::arithmetic::{expansion_product, expansion_sign, expansion_sum, negate_expansion};
19use crate::expansion::two_diff;
20use crate::orient3::{highest_bit_exponent, least_significant_bit_exponent};
21
22/// Machine epsilon for binary64.
23const EPSILON: f64 = f64::EPSILON / 2.0;
24
25/// Relative error bound for the lifted 3x3 `incircle` determinant.
26const INCIRCLE_ERROR_FACTOR: f64 = (10.0 + 96.0 * EPSILON) * EPSILON;
27
28/// Relative error bound for the lifted 4x4 `insphere` determinant.
29const INSPHERE_ERROR_FACTOR: f64 = (16.0 + 224.0 * EPSILON) * EPSILON;
30
31/// Is `d` inside the circle through `a`, `b`, `c`?
32///
33/// [`Sign::Positive`] means inside when `a, b, c` are counter-clockwise.
34/// Callers that cannot guarantee that orientation must normalise it first with
35/// `orient2d`, because the sign of this determinant flips with it.
36///
37/// Always [`Certified::Certain`].
38#[must_use]
39pub fn incircle(a: Point2, b: Point2, c: Point2, d: Point2) -> Certified {
40    match incircle_filter(a, b, c, d) {
41        Certified::Certain { sign, .. } => Certified::exact_sign(sign),
42        _ => Certified::exact_sign(incircle_exact(a, b, c, d)),
43    }
44}
45
46/// The fast filter alone, exposed so escalation can be measured.
47#[must_use]
48pub fn incircle_filter(a: Point2, b: Point2, c: Point2, d: Point2) -> Certified {
49    let (adx, ady) = (a.x - d.x, a.y - d.y);
50    let (bdx, bdy) = (b.x - d.x, b.y - d.y);
51    let (cdx, cdy) = (c.x - d.x, c.y - d.y);
52
53    let bdxcdy = bdx * cdy;
54    let cdxbdy = cdx * bdy;
55    let alift = adx * adx + ady * ady;
56
57    let cdxady = cdx * ady;
58    let adxcdy = adx * cdy;
59    let blift = bdx * bdx + bdy * bdy;
60
61    let adxbdy = adx * bdy;
62    let bdxady = bdx * ady;
63    let clift = cdx * cdx + cdy * cdy;
64
65    let determinant =
66        alift * (bdxcdy - cdxbdy) + blift * (cdxady - adxcdy) + clift * (adxbdy - bdxady);
67
68    let permanent = (bdxcdy.abs() + cdxbdy.abs()) * alift
69        + (cdxady.abs() + adxcdy.abs()) * blift
70        + (adxbdy.abs() + bdxady.abs()) * clift;
71
72    Certified::from_filter(
73        determinant,
74        INCIRCLE_ERROR_FACTOR * permanent,
75        Precision::F64,
76    )
77}
78
79/// Exact sign of the lifted `incircle` determinant.
80///
81/// Every coordinate difference is kept exactly, as a two-term expansion
82/// (`two_diff`), and every product and sum after it is an expansion. The
83/// differences used to be rounded first, which made this "exact" fallback
84/// wrong exactly where the filter hands over -- nearly cocircular points
85/// whose differences do not fit an `f64` -- and Delaunay flips driven by it
86/// could cycle for ever (#190).
87#[must_use]
88fn incircle_exact(a: Point2, b: Point2, c: Point2, d: Point2) -> Sign {
89    let (adx, ady) = (diff(a.x, d.x), diff(a.y, d.y));
90    let (bdx, bdy) = (diff(b.x, d.x), diff(b.y, d.y));
91    let (cdx, cdy) = (diff(c.x, d.x), diff(c.y, d.y));
92
93    let bc = minor(&bdx, &cdy, &cdx, &bdy);
94    let ca = minor(&cdx, &ady, &adx, &cdy);
95    let ab = minor(&adx, &bdy, &bdx, &ady);
96
97    let total = expansion_sum(
98        &expansion_sum(&lift(&bc, &adx, &ady), &lift(&ca, &bdx, &bdy)),
99        &lift(&ab, &cdx, &cdy),
100    );
101    expansion_sign(&total)
102}
103
104/// `p - q`, exactly, as an expansion.
105#[must_use]
106fn diff(p: f64, q: f64) -> Vec<f64> {
107    let (d, err) = two_diff(p, q);
108    let mut e = Vec::with_capacity(2);
109    if err != 0.0 {
110        e.push(err);
111    }
112    if d != 0.0 || e.is_empty() {
113        e.push(d);
114    }
115    e
116}
117
118/// `p q - r s` over expansions, exactly.
119#[must_use]
120fn minor(p: &[f64], q: &[f64], r: &[f64], s: &[f64]) -> Vec<f64> {
121    expansion_sum(
122        &expansion_product(p, q),
123        &negate_expansion(&expansion_product(r, s)),
124    )
125}
126
127/// Multiply an expansion by `x*x + y*y`, exactly.
128#[must_use]
129fn lift(e: &[f64], x: &[f64], y: &[f64]) -> Vec<f64> {
130    let square = expansion_sum(&expansion_product(x, x), &expansion_product(y, y));
131    expansion_product(e, &square)
132}
133
134/// Is `e` inside the sphere through `a`, `b`, `c`, `d`?
135///
136/// [`Sign::Positive`] means inside when `a, b, c, d` are positively oriented
137/// (`orient3d(a, b, c, d) > 0`). As with [`incircle`], the sign flips with the
138/// base orientation, so a caller must normalise it.
139///
140/// Always [`Certified::Certain`].
141#[must_use]
142pub fn insphere(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Certified {
143    match insphere_filter(a, b, c, d, e) {
144        Certified::Certain { sign, .. } => Certified::exact_sign(sign),
145        _ => Certified::exact_sign(insphere_exact(a, b, c, d, e)),
146    }
147}
148
149/// The fast filter alone, exposed so escalation can be measured.
150#[must_use]
151pub fn insphere_filter(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Certified {
152    let v = |p: Point3| (p.x - e.x, p.y - e.y, p.z - e.z);
153    let (ax, ay, az) = v(a);
154    let (bx, by, bz) = v(b);
155    let (cx, cy, cz) = v(c);
156    let (dx, dy, dz) = v(d);
157
158    let (axby, bxay) = (ax * by, bx * ay);
159    let (bxcy, cxby) = (bx * cy, cx * by);
160    let (cxdy, dxcy) = (cx * dy, dx * cy);
161    let (dxay, axdy) = (dx * ay, ax * dy);
162    let (axcy, cxay) = (ax * cy, cx * ay);
163    let (bxdy, dxby) = (bx * dy, dx * by);
164    let ab = axby - bxay;
165    let bc = bxcy - cxby;
166    let cd = cxdy - dxcy;
167    let da = dxay - axdy;
168    let ac = axcy - cxay;
169    let bd = bxdy - dxby;
170
171    let abc = az * bc - bz * ac + cz * ab;
172    let bcd = bz * cd - cz * bd + dz * bc;
173    let cda = cz * da + dz * ac + az * cd;
174    let dab = dz * ab + az * bd + bz * da;
175
176    let alift = ax * ax + ay * ay + az * az;
177    let blift = bx * bx + by * by + bz * bz;
178    let clift = cx * cx + cy * cy + cz * cz;
179    let dlift = dx * dx + dy * dy + dz * dz;
180
181    let determinant = (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
182
183    // Shewchuk's permanent: the same expression over the ABSOLUTE values of
184    // every elementary product. Bounding by |abc| and friends instead -- the
185    // rounded minors -- is not a bound at all: when a minor cancels, its
186    // rounding error is not proportional to its value, and the filter
187    // certified a non-zero sign for exactly cospherical points (#126).
188    let (az, bz, cz, dz) = (az.abs(), bz.abs(), cz.abs(), dz.abs());
189    let ab_plus = axby.abs() + bxay.abs();
190    let bc_plus = bxcy.abs() + cxby.abs();
191    let cd_plus = cxdy.abs() + dxcy.abs();
192    let da_plus = dxay.abs() + axdy.abs();
193    let ac_plus = axcy.abs() + cxay.abs();
194    let bd_plus = bxdy.abs() + dxby.abs();
195    let permanent = (cd_plus * bz + bd_plus * cz + bc_plus * dz) * alift
196        + (da_plus * cz + ac_plus * dz + cd_plus * az) * blift
197        + (ab_plus * dz + bd_plus * az + da_plus * bz) * clift
198        + (bc_plus * az + ac_plus * bz + ab_plus * cz) * dlift;
199
200    Certified::from_filter(
201        determinant,
202        INSPHERE_ERROR_FACTOR * permanent,
203        Precision::F64,
204    )
205}
206
207/// Exact sign of the lifted 4x4 `insphere` determinant.
208///
209/// Expands along the lifted column: each 3x3 minor is built from exact 2x2
210/// cofactors, scaled by the remaining z difference, then by the squared
211/// distance. Nothing is rounded between those steps.
212#[must_use]
213fn insphere_exact(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Sign {
214    if let Some(sign) = insphere_small_integer(a, b, c, d, e) {
215        return sign;
216    }
217    insphere_expansion(a, b, c, d, e)
218}
219
220/// The expansion-arithmetic tier of [`insphere_exact`].
221#[must_use]
222fn insphere_expansion(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Sign {
223    // Differences exact, as for `incircle_exact` (#190).
224    let v = |p: Point3| [diff(p.x, e.x), diff(p.y, e.y), diff(p.z, e.z)];
225    let (a3, b3, c3, d3) = (v(a), v(b), v(c), v(d));
226
227    let minor3 = |p: &[Vec<f64>; 3], q: &[Vec<f64>; 3], r: &[Vec<f64>; 3]| {
228        let qr = minor(&q[0], &r[1], &r[0], &q[1]);
229        let rp = minor(&r[0], &p[1], &p[0], &r[1]);
230        let pq = minor(&p[0], &q[1], &q[0], &p[1]);
231        expansion_sum(
232            &expansion_sum(
233                &expansion_product(&qr, &p[2]),
234                &expansion_product(&rp, &q[2]),
235            ),
236            &expansion_product(&pq, &r[2]),
237        )
238    };
239
240    let bcd = minor3(&b3, &c3, &d3);
241    let cda = minor3(&c3, &d3, &a3);
242    let dab = minor3(&d3, &a3, &b3);
243    let abc = minor3(&a3, &b3, &c3);
244
245    // Cofactor expansion signs alternate: +d -c +b -a.
246    let total = expansion_sum(
247        &expansion_sum(&lift3(&abc, &d3), &negate_expansion(&lift3(&dab, &c3))),
248        &expansion_sum(&lift3(&cda, &b3), &negate_expansion(&lift3(&bcd, &a3))),
249    );
250    expansion_sign(&total)
251}
252
253/// Exact `insphere` in `i128`, for differences on a narrow dyadic grid.
254///
255/// Exactly degenerate inputs -- a cubic lattice, where every insphere test
256/// is an exact zero -- reach the exact path on every call, and the
257/// expansion arithmetic allocates throughout. When every coordinate
258/// difference is itself exact in `f64` and all are integer multiples of one
259/// power of two `2^k` with magnitude below `2^(k + 20)`, the determinant of
260/// the scaled integers is at most `72 * 2^100` in magnitude and fits `i128`
261/// exactly. Returns `None` (use the expansions) otherwise.
262#[must_use]
263fn insphere_small_integer(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Option<Sign> {
264    let mut differences = [[0.0f64; 3]; 4];
265    for (row, p) in [a, b, c, d].into_iter().enumerate() {
266        for (axis, (x, y)) in [(p.x, e.x), (p.y, e.y), (p.z, e.z)].into_iter().enumerate() {
267            let (difference, error) = two_diff(x, y);
268            if error != 0.0 || !difference.is_finite() {
269                return None;
270            }
271            differences[row][axis] = difference;
272        }
273    }
274    let nonzero = || differences.iter().flatten().filter(|v| **v != 0.0);
275    let Some(lowest) = nonzero().map(|v| least_significant_bit_exponent(*v)).min() else {
276        return Some(Sign::Zero);
277    };
278    let highest = nonzero().map(|v| highest_bit_exponent(*v)).max()?;
279    if highest - lowest >= 20 {
280        return None;
281    }
282    let scaled = |v: f64| -> i128 {
283        if v == 0.0 {
284            return 0;
285        }
286        // |v| = significand * 2^exponent exactly; shift onto the 2^lowest grid.
287        let bits = v.abs().to_bits();
288        let encoded = ((bits >> 52) & 0x7ff) as i32;
289        let fraction = bits & ((1u64 << 52) - 1);
290        let (significand, exponent) = if encoded == 0 {
291            (fraction, -1074)
292        } else {
293            (fraction | (1u64 << 52), encoded - 1075)
294        };
295        let shift = exponent - lowest;
296        let magnitude = if shift >= 0 {
297            i128::from(significand) << shift
298        } else {
299            i128::from(significand >> -shift)
300        };
301        if v < 0.0 {
302            -magnitude
303        } else {
304            magnitude
305        }
306    };
307    let row = |r: usize| {
308        let [x, y, z] = differences[r].map(scaled);
309        [x, y, z, x * x + y * y + z * z]
310    };
311    let m = [row(0), row(1), row(2), row(3)];
312    let det3 = |p: [i128; 4], q: [i128; 4], r: [i128; 4]| {
313        p[0] * (q[1] * r[2] - r[1] * q[2]) - p[1] * (q[0] * r[2] - r[0] * q[2])
314            + p[2] * (q[0] * r[1] - r[0] * q[1])
315    };
316    // The lifted determinant with the sign convention of `insphere`.
317    let total = -m[0][3] * det3(m[1], m[2], m[3]) + m[1][3] * det3(m[0], m[2], m[3])
318        - m[2][3] * det3(m[0], m[1], m[3])
319        + m[3][3] * det3(m[0], m[1], m[2]);
320    Some(match total.signum() {
321        1 => Sign::Positive,
322        -1 => Sign::Negative,
323        _ => Sign::Zero,
324    })
325}
326
327/// Multiply an expansion by `x*x + y*y + z*z`, exactly.
328#[must_use]
329fn lift3(e: &[f64], p: &[Vec<f64>; 3]) -> Vec<f64> {
330    let square = expansion_sum(
331        &expansion_sum(
332            &expansion_product(&p[0], &p[0]),
333            &expansion_product(&p[1], &p[1]),
334        ),
335        &expansion_product(&p[2], &p[2]),
336    );
337    expansion_product(e, &square)
338}
339
340/// Smallest exponent `k` with `2^k` a coordinate magnitude the exact path of
341/// [`in_diametral_sphere`] accepts; zero is always accepted.
342const DIAMETRAL_MIN_EXPONENT: i32 = -100;
343
344/// Largest such exponent.
345const DIAMETRAL_MAX_EXPONENT: i32 = 100;
346
347/// Is `d` inside the diametral sphere of the triangle `a`, `b`, `c`?
348///
349/// The diametral sphere is the smallest sphere through `a`, `b`, `c`: its
350/// centre is the triangle's circumcentre and its equator the circumcircle.
351/// For `d` coplanar with the triangle this is therefore the coplanar
352/// `incircle` test in 3D -- the question a 3D Delaunay triangulation asks of
353/// a point lying exactly in the plane of a convex-hull face -- and, unlike
354/// [`insphere`], it needs no orientation: [`Sign::Positive`] means strictly
355/// inside, [`Sign::Negative`] strictly outside, [`Sign::Zero`] on the sphere.
356///
357/// The sign is that of
358///
359/// ```text
360/// -( |n|^2 (w.w) - |u|^2 ((w x v).n) - |v|^2 ((u x w).n) )
361/// ```
362///
363/// with `u = b - a`, `v = c - a`, `w = d - a` and `n = u x v`: `|n|^2` times
364/// the power of `d` with respect to the sphere, a degree-6 polynomial in the
365/// coordinates.
366///
367/// A degenerate triangle (collinear `a`, `b`, `c`) has no diametral sphere and
368/// returns `Zero`. A forward running-error filter decides almost every case;
369/// the exact expansion path decides the rest. The exact path needs every
370/// non-zero coordinate magnitude in `[2^-100, 2^100]`, so that no product in
371/// the degree-6 determinant leaves binary64's normal range; outside it, and
372/// for non-finite input, the result is [`Certified::Uncertain`] rather than a
373/// guess.
374#[must_use]
375pub fn in_diametral_sphere(a: Point3, b: Point3, c: Point3, d: Point3) -> Certified {
376    match in_diametral_sphere_filter(a, b, c, d) {
377        Certified::Certain { sign, .. } => Certified::exact_sign(sign),
378        _ => {
379            if [a, b, c, d].iter().all(|p| {
380                [p.x, p.y, p.z].iter().all(|&x| {
381                    x == 0.0
382                        || (x.is_finite()
383                            && x.abs() >= 2f64.powi(DIAMETRAL_MIN_EXPONENT)
384                            && x.abs() <= 2f64.powi(DIAMETRAL_MAX_EXPONENT))
385                })
386            }) {
387                Certified::exact_sign(in_diametral_sphere_exact(a, b, c, d))
388            } else {
389                Certified::Uncertain {
390                    attempted: Precision::Exact,
391                }
392            }
393        }
394    }
395}
396
397/// A value and a bound on its absolute error, for a running-error filter.
398#[derive(Clone, Copy)]
399struct Bounded {
400    value: f64,
401    error: f64,
402}
403
404/// Unit roundoff, taken at twice its size so the `1 / (1 - eps)` factor of
405/// the backward bound is absorbed.
406const ROUNDOFF: f64 = f64::EPSILON;
407
408impl Bounded {
409    fn difference(p: f64, q: f64) -> Self {
410        let value = p - q;
411        Self {
412            value,
413            error: ROUNDOFF * value.abs(),
414        }
415    }
416
417    fn add(self, other: Self) -> Self {
418        let value = self.value + other.value;
419        Self {
420            value,
421            error: self.error + other.error + ROUNDOFF * value.abs(),
422        }
423    }
424
425    fn sub(self, other: Self) -> Self {
426        let value = self.value - other.value;
427        Self {
428            value,
429            error: self.error + other.error + ROUNDOFF * value.abs(),
430        }
431    }
432
433    fn mul(self, other: Self) -> Self {
434        let value = self.value * other.value;
435        Self {
436            value,
437            error: self.value.abs() * other.error
438                + other.value.abs() * self.error
439                + self.error * other.error
440                + ROUNDOFF * value.abs(),
441        }
442    }
443}
444
445/// The running-error filter alone, exposed so escalation can be measured.
446///
447/// Every operation carries a bound on its absolute error; the bound itself is
448/// computed in rounded arithmetic, so the final bound is inflated by a
449/// relative margin far larger than the rounding the bound's own evaluation
450/// can incur. A non-zero coordinate difference outside `[2^-150, 2^150]`
451/// (whose degree-6 products could leave the normal range) or a non-finite
452/// value is left [`Certified::Uncertain`].
453#[must_use]
454pub fn in_diametral_sphere_filter(a: Point3, b: Point3, c: Point3, d: Point3) -> Certified {
455    let vector = |p: Point3| {
456        [
457            Bounded::difference(p.x, a.x),
458            Bounded::difference(p.y, a.y),
459            Bounded::difference(p.z, a.z),
460        ]
461    };
462    let (u, v, w) = (vector(b), vector(c), vector(d));
463    let cross = |p: [Bounded; 3], q: [Bounded; 3]| {
464        [
465            p[1].mul(q[2]).sub(p[2].mul(q[1])),
466            p[2].mul(q[0]).sub(p[0].mul(q[2])),
467            p[0].mul(q[1]).sub(p[1].mul(q[0])),
468        ]
469    };
470    let dot =
471        |p: [Bounded; 3], q: [Bounded; 3]| p[0].mul(q[0]).add(p[1].mul(q[1])).add(p[2].mul(q[2]));
472    let n = cross(u, v);
473    let power = dot(n, n)
474        .mul(dot(w, w))
475        .sub(dot(u, u).mul(dot(cross(w, v), n)))
476        .sub(dot(v, v).mul(dot(cross(u, w), n)));
477
478    let tiny = 2f64.powi(-900);
479    let operands = [u, v, w].into_iter().flatten().map(|x| x.value.abs());
480    let representable = operands.clone().all(f64::is_finite)
481        && operands
482            .filter(|x| *x != 0.0)
483            .all(|x| x >= 2f64.powi(-150) && x <= 2f64.powi(150))
484        && power.value.is_finite()
485        && power.error.is_finite();
486    if !representable {
487        return Certified::Uncertain {
488            attempted: Precision::F64,
489        };
490    }
491    // The bound's own evaluation rounds a few dozen times; 2^-40 relative
492    // dwarfs that, and the absolute term covers underflow in the bound.
493    let bound = power.error * (1.0 + 2f64.powi(-40)) + tiny;
494    Certified::from_filter(-power.value, bound, Precision::F64)
495}
496
497/// Exact sign of the diametral-sphere determinant, over expansions.
498#[must_use]
499fn in_diametral_sphere_exact(a: Point3, b: Point3, c: Point3, d: Point3) -> Sign {
500    let vector = |p: Point3| [diff(p.x, a.x), diff(p.y, a.y), diff(p.z, a.z)];
501    let (u, v, w) = (vector(b), vector(c), vector(d));
502    let cross = |p: &[Vec<f64>; 3], q: &[Vec<f64>; 3]| {
503        [
504            minor(&p[1], &q[2], &p[2], &q[1]),
505            minor(&p[2], &q[0], &p[0], &q[2]),
506            minor(&p[0], &q[1], &p[1], &q[0]),
507        ]
508    };
509    let dot = |p: &[Vec<f64>; 3], q: &[Vec<f64>; 3]| {
510        expansion_sum(
511            &expansion_sum(
512                &expansion_product(&p[0], &q[0]),
513                &expansion_product(&p[1], &q[1]),
514            ),
515            &expansion_product(&p[2], &q[2]),
516        )
517    };
518    let n = cross(&u, &v);
519    let first = expansion_product(&dot(&n, &n), &dot(&w, &w));
520    let second = expansion_product(&dot(&u, &u), &dot(&cross(&w, &v), &n));
521    let third = expansion_product(&dot(&v, &v), &dot(&cross(&u, &w), &n));
522    let power = expansion_sum(&first, &negate_expansion(&expansion_sum(&second, &third)));
523    expansion_sign(&power).flip()
524}
525
526#[cfg(test)]
527mod tests {
528    use super::{insphere_expansion, insphere_small_integer};
529    use axiolid_core::Point3;
530
531    fn next(state: &mut u64) -> u64 {
532        *state ^= *state << 13;
533        *state ^= *state >> 7;
534        *state ^= *state << 17;
535        *state
536    }
537
538    /// The integer tier agrees with the expansion tier wherever it answers,
539    /// non-zero signs included (the filter usually settles those, so the
540    /// public predicate alone would not exercise them), and it declines
541    /// every input whose scaled integers could overflow `i128`.
542    #[test]
543    fn the_integer_tier_agrees_with_the_expansions() {
544        let mut state = 0x2545_F491_4F6C_DD1Du64;
545        let (mut answered, mut declined) = (0usize, 0usize);
546        for round in 0..40_000u32 {
547            // Magnitudes from a few units up to 2^30, on grids of 2^-8..2^8.
548            let bits = 2 + round % 29;
549            let unit = 2f64.powi((next(&mut state) % 17) as i32 - 8);
550            let mut coordinate = || {
551                let span = 1u64 << bits;
552                ((next(&mut state) % (2 * span)) as f64 - span as f64) * unit
553            };
554            let mut point = || Point3::new(coordinate(), coordinate(), coordinate());
555            let (a, b, c, d, e) = (point(), point(), point(), point(), point());
556            let expected = insphere_expansion(a, b, c, d, e);
557            match insphere_small_integer(a, b, c, d, e) {
558                Some(sign) => {
559                    assert_eq!(sign, expected, "{a:?} {b:?} {c:?} {d:?} {e:?}");
560                    answered += 1;
561                }
562                None => declined += 1,
563            }
564        }
565        assert!(
566            answered > 10_000 && declined > 10_000,
567            "{answered} / {declined}"
568        );
569    }
570}