1use 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
22const EPSILON: f64 = f64::EPSILON / 2.0;
24
25const INCIRCLE_ERROR_FACTOR: f64 = (10.0 + 96.0 * EPSILON) * EPSILON;
27
28const INSPHERE_ERROR_FACTOR: f64 = (16.0 + 224.0 * EPSILON) * EPSILON;
30
31#[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#[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#[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#[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#[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#[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#[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#[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 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#[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#[must_use]
222fn insphere_expansion(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> Sign {
223 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 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#[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 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 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#[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
340const DIAMETRAL_MIN_EXPONENT: i32 = -100;
343
344const DIAMETRAL_MAX_EXPONENT: i32 = 100;
346
347#[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#[derive(Clone, Copy)]
399struct Bounded {
400 value: f64,
401 error: f64,
402}
403
404const 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#[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 let bound = power.error * (1.0 + 2f64.powi(-40)) + tiny;
494 Certified::from_filter(-power.value, bound, Precision::F64)
495}
496
497#[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 #[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 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}