1use axiolid_core::{Point2, Point3};
30use axiolid_guarantees::{Certified, Precision, Sign};
31
32use crate::arithmetic::{expansion_product, expansion_sign, expansion_sum, negate_expansion};
33use crate::expansion::two_diff;
34use crate::orient3::{highest_bit_exponent, least_significant_bit_exponent};
35use crate::sphere_dyadic::{incircle_exact_dyadic, insphere_exact_dyadic};
36
37const EPSILON: f64 = f64::EPSILON / 2.0;
39
40const INCIRCLE_ERROR_FACTOR: f64 = (10.0 + 96.0 * EPSILON) * EPSILON;
42
43const INSPHERE_ERROR_FACTOR: f64 = (16.0 + 224.0 * EPSILON) * EPSILON;
45
46const INCIRCLE_MIN: f64 = 1.0e-60;
52const INCIRCLE_MAX: f64 = 1.0e60;
53
54const INSPHERE_MIN: f64 = 1.0e-45;
56const INSPHERE_MAX: f64 = 1.0e45;
57
58fn all_in_range(values: &[f64], min: f64, max: f64) -> bool {
61 values.iter().fold(true, |ok, v| {
63 let magnitude = v.abs();
64 ok & ((magnitude == 0.0) | ((magnitude >= min) & (magnitude <= max)))
65 })
66}
67
68fn differences_expandable(pairs: &[(f64, f64)], min: f64, max: f64) -> bool {
72 pairs.iter().all(|&(p, q)| {
73 let (head, tail) = two_diff(p, q);
74 all_in_range(&[head], min, max) && (tail == 0.0 || tail.abs() >= min)
75 })
76}
77
78const FILTER_EXPONENT_LOW: u64 = 1023 - 128;
83
84fn exponents_in_range(values: &[f64]) -> bool {
89 let offsets = values.iter().fold(0_u64, |or, v| {
90 let bits = v.to_bits();
91 let nonzero = u64::from(bits << 1 != 0).wrapping_neg();
92 or | ((((bits >> 52) & 0x7ff).wrapping_sub(FILTER_EXPONENT_LOW)) & nonzero)
93 });
94 offsets >> 8 == 0
95}
96
97#[must_use]
106pub fn incircle(a: Point2, b: Point2, c: Point2, d: Point2) -> Certified {
107 match incircle_filter(a, b, c, d) {
108 Certified::Certain { sign, .. } => Certified::exact_sign(sign),
109 _ => incircle_exact(a, b, c, d).map_or(
110 Certified::Uncertain {
111 attempted: Precision::Exact,
112 },
113 Certified::exact_sign,
114 ),
115 }
116}
117
118#[must_use]
123pub fn incircle_filter(a: Point2, b: Point2, c: Point2, d: Point2) -> Certified {
124 let (adx, ady) = (a.x - d.x, a.y - d.y);
125 let (bdx, bdy) = (b.x - d.x, b.y - d.y);
126 let (cdx, cdy) = (c.x - d.x, c.y - d.y);
127 if !exponents_in_range(&[adx, ady, bdx, bdy, cdx, cdy]) {
128 return Certified::Uncertain {
129 attempted: Precision::F64,
130 };
131 }
132 let bdxcdy = bdx * cdy;
133 let cdxbdy = cdx * bdy;
134 let alift = adx * adx + ady * ady;
135
136 let cdxady = cdx * ady;
137 let adxcdy = adx * cdy;
138 let blift = bdx * bdx + bdy * bdy;
139
140 let adxbdy = adx * bdy;
141 let bdxady = bdx * ady;
142 let clift = cdx * cdx + cdy * cdy;
143
144 let determinant =
145 alift * (bdxcdy - cdxbdy) + blift * (cdxady - adxcdy) + clift * (adxbdy - bdxady);
146
147 let permanent = (bdxcdy.abs() + cdxbdy.abs()) * alift
148 + (cdxady.abs() + adxcdy.abs()) * blift
149 + (adxbdy.abs() + bdxady.abs()) * clift;
150
151 Certified::from_filter(
152 determinant,
153 INCIRCLE_ERROR_FACTOR * permanent,
154 Precision::F64,
155 )
156}
157
158#[must_use]
171fn incircle_exact(a: Point2, b: Point2, c: Point2, d: Point2) -> Option<Sign> {
172 let pairs = [
175 (a.x, d.x),
176 (a.y, d.y),
177 (b.x, d.x),
178 (b.y, d.y),
179 (c.x, d.x),
180 (c.y, d.y),
181 ];
182 if differences_expandable(&pairs, INCIRCLE_MIN, INCIRCLE_MAX) {
183 Some(incircle_expansion(a, b, c, d))
184 } else {
185 incircle_exact_dyadic(a, b, c, d)
186 }
187}
188
189#[must_use]
191fn incircle_expansion(a: Point2, b: Point2, c: Point2, d: Point2) -> Sign {
192 let (adx, ady) = (diff(a.x, d.x), diff(a.y, d.y));
193 let (bdx, bdy) = (diff(b.x, d.x), diff(b.y, d.y));
194 let (cdx, cdy) = (diff(c.x, d.x), diff(c.y, d.y));
195
196 let bc = minor(&bdx, &cdy, &cdx, &bdy);
197 let ca = minor(&cdx, &ady, &adx, &cdy);
198 let ab = minor(&adx, &bdy, &bdx, &ady);
199
200 let total = expansion_sum(
201 &expansion_sum(&lift(&bc, &adx, &ady), &lift(&ca, &bdx, &bdy)),
202 &lift(&ab, &cdx, &cdy),
203 );
204 expansion_sign(&total)
205}
206
207#[must_use]
209fn diff(p: f64, q: f64) -> Vec<f64> {
210 let (d, err) = two_diff(p, q);
211 let mut e = Vec::with_capacity(2);
212 if err != 0.0 {
213 e.push(err);
214 }
215 if d != 0.0 || e.is_empty() {
216 e.push(d);
217 }
218 e
219}
220
221#[must_use]
223fn minor(p: &[f64], q: &[f64], r: &[f64], s: &[f64]) -> Vec<f64> {
224 expansion_sum(
225 &expansion_product(p, q),
226 &negate_expansion(&expansion_product(r, s)),
227 )
228}
229
230#[must_use]
232fn lift(e: &[f64], x: &[f64], y: &[f64]) -> Vec<f64> {
233 let square = expansion_sum(&expansion_product(x, x), &expansion_product(y, y));
234 expansion_product(e, &square)
235}
236
237#[must_use]
246pub fn insphere(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Certified {
247 match insphere_filter(a, b, c, d, e) {
248 Certified::Certain { sign, .. } => Certified::exact_sign(sign),
249 _ => insphere_exact(a, b, c, d, e).map_or(
250 Certified::Uncertain {
251 attempted: Precision::Exact,
252 },
253 Certified::exact_sign,
254 ),
255 }
256}
257
258#[must_use]
263pub fn insphere_filter(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Certified {
264 let v = |p: Point3| (p.x - e.x, p.y - e.y, p.z - e.z);
265 let (ax, ay, az) = v(a);
266 let (bx, by, bz) = v(b);
267 let (cx, cy, cz) = v(c);
268 let (dx, dy, dz) = v(d);
269 if !exponents_in_range(&[ax, ay, az, bx, by, bz, cx, cy, cz, dx, dy, dz]) {
270 return Certified::Uncertain {
271 attempted: Precision::F64,
272 };
273 }
274 let (axby, bxay) = (ax * by, bx * ay);
275 let (bxcy, cxby) = (bx * cy, cx * by);
276 let (cxdy, dxcy) = (cx * dy, dx * cy);
277 let (dxay, axdy) = (dx * ay, ax * dy);
278 let (axcy, cxay) = (ax * cy, cx * ay);
279 let (bxdy, dxby) = (bx * dy, dx * by);
280 let ab = axby - bxay;
281 let bc = bxcy - cxby;
282 let cd = cxdy - dxcy;
283 let da = dxay - axdy;
284 let ac = axcy - cxay;
285 let bd = bxdy - dxby;
286
287 let abc = az * bc - bz * ac + cz * ab;
288 let bcd = bz * cd - cz * bd + dz * bc;
289 let cda = cz * da + dz * ac + az * cd;
290 let dab = dz * ab + az * bd + bz * da;
291
292 let alift = ax * ax + ay * ay + az * az;
293 let blift = bx * bx + by * by + bz * bz;
294 let clift = cx * cx + cy * cy + cz * cz;
295 let dlift = dx * dx + dy * dy + dz * dz;
296
297 let determinant = (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
298
299 let (az, bz, cz, dz) = (az.abs(), bz.abs(), cz.abs(), dz.abs());
305 let ab_plus = axby.abs() + bxay.abs();
306 let bc_plus = bxcy.abs() + cxby.abs();
307 let cd_plus = cxdy.abs() + dxcy.abs();
308 let da_plus = dxay.abs() + axdy.abs();
309 let ac_plus = axcy.abs() + cxay.abs();
310 let bd_plus = bxdy.abs() + dxby.abs();
311 let permanent = (cd_plus * bz + bd_plus * cz + bc_plus * dz) * alift
312 + (da_plus * cz + ac_plus * dz + cd_plus * az) * blift
313 + (ab_plus * dz + bd_plus * az + da_plus * bz) * clift
314 + (bc_plus * az + ac_plus * bz + ab_plus * cz) * dlift;
315
316 Certified::from_filter(
317 determinant,
318 INSPHERE_ERROR_FACTOR * permanent,
319 Precision::F64,
320 )
321}
322
323#[must_use]
333fn insphere_exact(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Option<Sign> {
334 if let Some(sign) = insphere_small_integer(a, b, c, d, e) {
337 return Some(sign);
338 }
339 let pairs = [a, b, c, d].map(|p| [(p.x, e.x), (p.y, e.y), (p.z, e.z)]);
340 if differences_expandable(pairs.as_flattened(), INSPHERE_MIN, INSPHERE_MAX) {
341 Some(insphere_expansion(a, b, c, d, e))
342 } else {
343 insphere_exact_dyadic(a, b, c, d, e)
344 }
345}
346
347#[must_use]
349fn insphere_expansion(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Sign {
350 let v = |p: Point3| [diff(p.x, e.x), diff(p.y, e.y), diff(p.z, e.z)];
352 let (a3, b3, c3, d3) = (v(a), v(b), v(c), v(d));
353
354 let minor3 = |p: &[Vec<f64>; 3], q: &[Vec<f64>; 3], r: &[Vec<f64>; 3]| {
355 let qr = minor(&q[0], &r[1], &r[0], &q[1]);
356 let rp = minor(&r[0], &p[1], &p[0], &r[1]);
357 let pq = minor(&p[0], &q[1], &q[0], &p[1]);
358 expansion_sum(
359 &expansion_sum(
360 &expansion_product(&qr, &p[2]),
361 &expansion_product(&rp, &q[2]),
362 ),
363 &expansion_product(&pq, &r[2]),
364 )
365 };
366
367 let bcd = minor3(&b3, &c3, &d3);
368 let cda = minor3(&c3, &d3, &a3);
369 let dab = minor3(&d3, &a3, &b3);
370 let abc = minor3(&a3, &b3, &c3);
371
372 let total = expansion_sum(
374 &expansion_sum(&lift3(&abc, &d3), &negate_expansion(&lift3(&dab, &c3))),
375 &expansion_sum(&lift3(&cda, &b3), &negate_expansion(&lift3(&bcd, &a3))),
376 );
377 expansion_sign(&total)
378}
379
380#[must_use]
390fn insphere_small_integer(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Option<Sign> {
391 let mut differences = [[0.0f64; 3]; 4];
392 for (row, p) in [a, b, c, d].into_iter().enumerate() {
393 for (axis, (x, y)) in [(p.x, e.x), (p.y, e.y), (p.z, e.z)].into_iter().enumerate() {
394 let (difference, error) = two_diff(x, y);
395 if error != 0.0 || !difference.is_finite() {
396 return None;
397 }
398 differences[row][axis] = difference;
399 }
400 }
401 let nonzero = || differences.iter().flatten().filter(|v| **v != 0.0);
402 let Some(lowest) = nonzero().map(|v| least_significant_bit_exponent(*v)).min() else {
403 return Some(Sign::Zero);
404 };
405 let highest = nonzero().map(|v| highest_bit_exponent(*v)).max()?;
406 if highest - lowest >= 20 {
407 return None;
408 }
409 let scaled = |v: f64| -> i128 {
410 if v == 0.0 {
411 return 0;
412 }
413 let bits = v.abs().to_bits();
415 let encoded = ((bits >> 52) & 0x7ff) as i32;
416 let fraction = bits & ((1u64 << 52) - 1);
417 let (significand, exponent) = if encoded == 0 {
418 (fraction, -1074)
419 } else {
420 (fraction | (1u64 << 52), encoded - 1075)
421 };
422 let shift = exponent - lowest;
423 let magnitude = if shift >= 0 {
424 i128::from(significand) << shift
425 } else {
426 i128::from(significand >> -shift)
427 };
428 if v < 0.0 {
429 -magnitude
430 } else {
431 magnitude
432 }
433 };
434 let row = |r: usize| {
435 let [x, y, z] = differences[r].map(scaled);
436 [x, y, z, x * x + y * y + z * z]
437 };
438 let m = [row(0), row(1), row(2), row(3)];
439 let det3 = |p: [i128; 4], q: [i128; 4], r: [i128; 4]| {
440 p[0] * (q[1] * r[2] - r[1] * q[2]) - p[1] * (q[0] * r[2] - r[0] * q[2])
441 + p[2] * (q[0] * r[1] - r[0] * q[1])
442 };
443 let total = -m[0][3] * det3(m[1], m[2], m[3]) + m[1][3] * det3(m[0], m[2], m[3])
445 - m[2][3] * det3(m[0], m[1], m[3])
446 + m[3][3] * det3(m[0], m[1], m[2]);
447 Some(match total.signum() {
448 1 => Sign::Positive,
449 -1 => Sign::Negative,
450 _ => Sign::Zero,
451 })
452}
453
454#[must_use]
456fn lift3(e: &[f64], p: &[Vec<f64>; 3]) -> Vec<f64> {
457 let square = expansion_sum(
458 &expansion_sum(
459 &expansion_product(&p[0], &p[0]),
460 &expansion_product(&p[1], &p[1]),
461 ),
462 &expansion_product(&p[2], &p[2]),
463 );
464 expansion_product(e, &square)
465}
466
467const DIAMETRAL_MIN_EXPONENT: i32 = -100;
470
471const DIAMETRAL_MAX_EXPONENT: i32 = 100;
473
474#[must_use]
502pub fn in_diametral_sphere(a: Point3, b: Point3, c: Point3, d: Point3) -> Certified {
503 match in_diametral_sphere_filter(a, b, c, d) {
504 Certified::Certain { sign, .. } => Certified::exact_sign(sign),
505 _ => {
506 if [a, b, c, d].iter().all(|p| {
507 [p.x, p.y, p.z].iter().all(|&x| {
508 x == 0.0
509 || (x.is_finite()
510 && x.abs() >= 2f64.powi(DIAMETRAL_MIN_EXPONENT)
511 && x.abs() <= 2f64.powi(DIAMETRAL_MAX_EXPONENT))
512 })
513 }) {
514 Certified::exact_sign(in_diametral_sphere_exact(a, b, c, d))
515 } else {
516 Certified::Uncertain {
517 attempted: Precision::Exact,
518 }
519 }
520 }
521 }
522}
523
524#[derive(Clone, Copy)]
526struct Bounded {
527 value: f64,
528 error: f64,
529}
530
531const ROUNDOFF: f64 = f64::EPSILON;
534
535impl Bounded {
536 fn difference(p: f64, q: f64) -> Self {
537 let value = p - q;
538 Self {
539 value,
540 error: ROUNDOFF * value.abs(),
541 }
542 }
543
544 fn add(self, other: Self) -> Self {
545 let value = self.value + other.value;
546 Self {
547 value,
548 error: self.error + other.error + ROUNDOFF * value.abs(),
549 }
550 }
551
552 fn sub(self, other: Self) -> Self {
553 let value = self.value - other.value;
554 Self {
555 value,
556 error: self.error + other.error + ROUNDOFF * value.abs(),
557 }
558 }
559
560 fn mul(self, other: Self) -> Self {
561 let value = self.value * other.value;
562 Self {
563 value,
564 error: self.value.abs() * other.error
565 + other.value.abs() * self.error
566 + self.error * other.error
567 + ROUNDOFF * value.abs(),
568 }
569 }
570}
571
572#[must_use]
581pub fn in_diametral_sphere_filter(a: Point3, b: Point3, c: Point3, d: Point3) -> Certified {
582 let vector = |p: Point3| {
583 [
584 Bounded::difference(p.x, a.x),
585 Bounded::difference(p.y, a.y),
586 Bounded::difference(p.z, a.z),
587 ]
588 };
589 let (u, v, w) = (vector(b), vector(c), vector(d));
590 let cross = |p: [Bounded; 3], q: [Bounded; 3]| {
591 [
592 p[1].mul(q[2]).sub(p[2].mul(q[1])),
593 p[2].mul(q[0]).sub(p[0].mul(q[2])),
594 p[0].mul(q[1]).sub(p[1].mul(q[0])),
595 ]
596 };
597 let dot =
598 |p: [Bounded; 3], q: [Bounded; 3]| p[0].mul(q[0]).add(p[1].mul(q[1])).add(p[2].mul(q[2]));
599 let n = cross(u, v);
600 let power = dot(n, n)
601 .mul(dot(w, w))
602 .sub(dot(u, u).mul(dot(cross(w, v), n)))
603 .sub(dot(v, v).mul(dot(cross(u, w), n)));
604
605 let tiny = 2f64.powi(-900);
606 let operands = [u, v, w].into_iter().flatten().map(|x| x.value.abs());
607 let representable = operands.clone().all(f64::is_finite)
608 && operands
609 .filter(|x| *x != 0.0)
610 .all(|x| x >= 2f64.powi(-150) && x <= 2f64.powi(150))
611 && power.value.is_finite()
612 && power.error.is_finite();
613 if !representable {
614 return Certified::Uncertain {
615 attempted: Precision::F64,
616 };
617 }
618 let bound = power.error * (1.0 + 2f64.powi(-40)) + tiny;
621 Certified::from_filter(-power.value, bound, Precision::F64)
622}
623
624#[must_use]
626fn in_diametral_sphere_exact(a: Point3, b: Point3, c: Point3, d: Point3) -> Sign {
627 let vector = |p: Point3| [diff(p.x, a.x), diff(p.y, a.y), diff(p.z, a.z)];
628 let (u, v, w) = (vector(b), vector(c), vector(d));
629 let cross = |p: &[Vec<f64>; 3], q: &[Vec<f64>; 3]| {
630 [
631 minor(&p[1], &q[2], &p[2], &q[1]),
632 minor(&p[2], &q[0], &p[0], &q[2]),
633 minor(&p[0], &q[1], &p[1], &q[0]),
634 ]
635 };
636 let dot = |p: &[Vec<f64>; 3], q: &[Vec<f64>; 3]| {
637 expansion_sum(
638 &expansion_sum(
639 &expansion_product(&p[0], &q[0]),
640 &expansion_product(&p[1], &q[1]),
641 ),
642 &expansion_product(&p[2], &q[2]),
643 )
644 };
645 let n = cross(&u, &v);
646 let first = expansion_product(&dot(&n, &n), &dot(&w, &w));
647 let second = expansion_product(&dot(&u, &u), &dot(&cross(&w, &v), &n));
648 let third = expansion_product(&dot(&v, &v), &dot(&cross(&u, &w), &n));
649 let power = expansion_sum(&first, &negate_expansion(&expansion_sum(&second, &third)));
650 expansion_sign(&power).flip()
651}
652
653#[cfg(test)]
654mod tests {
655 use super::{insphere_expansion, insphere_small_integer};
656 use axiolid_core::Point3;
657
658 fn next(state: &mut u64) -> u64 {
659 *state ^= *state << 13;
660 *state ^= *state >> 7;
661 *state ^= *state << 17;
662 *state
663 }
664
665 #[test]
670 fn the_integer_tier_agrees_with_the_expansions() {
671 let mut state = 0x2545_F491_4F6C_DD1Du64;
672 let (mut answered, mut declined) = (0usize, 0usize);
673 for round in 0..40_000u32 {
674 let bits = 2 + round % 29;
676 let unit = 2f64.powi((next(&mut state) % 17) as i32 - 8);
677 let mut coordinate = || {
678 let span = 1u64 << bits;
679 ((next(&mut state) % (2 * span)) as f64 - span as f64) * unit
680 };
681 let mut point = || Point3::new(coordinate(), coordinate(), coordinate());
682 let (a, b, c, d, e) = (point(), point(), point(), point(), point());
683 let expected = insphere_expansion(a, b, c, d, e);
684 match insphere_small_integer(a, b, c, d, e) {
685 Some(sign) => {
686 assert_eq!(sign, expected, "{a:?} {b:?} {c:?} {d:?} {e:?}");
687 answered += 1;
688 }
689 None => declined += 1,
690 }
691 }
692 assert!(
693 answered > 10_000 && declined > 10_000,
694 "{answered} / {declined}"
695 );
696 }
697}