geometry_predicates/
lib.rs

1//! A safe Rust port of the [robust adaptive floating-point geometric predicates](https://www.cs.cmu.edu/~quake/robust.html).
2//!
3//! This crate provides a Rust solution to efficient exact geometry predicates
4//! used widely for computational geometry.
5//!
6//! In addition, the building blocks of these predicates, namely the adaptive precision
7//! floating-point arithmetic primitives, are also exposed in [`predicates`] to allow for extensions
8//! to other predicates or exact geometric constructions.
9//!
10//! ## Background
11//!
12//! These predicates have been a staple in computational geometry for many years now
13//! and are widely used in industry.   In the context of geometry algorithms, it is
14//! often essential to determine the orientation of a point with respect to a line (or a
15//! plane) and whether a point lies inside a circle (or a sphere) or not.  The reason
16//! why these tests often need to be exact is because many geometry algorithms
17//! ask questions (to determine orientation or in-circle/sphere) about point
18//! configurations that require consistent answers. For instance, if `a`, `b`, and
19//! `c` are three points on a 2D plane, to ask where `b` with respect to the line
20//! through `a` and `c` (left-of, right-of, or coincident) is the same as asking where
21//! `a` lies with respect to the line through `c` and `b`.
22//! In Rust, this condition can be written as
23//! ```
24//! # use geometry_predicates::orient2d;
25//! # let a = [1.0, 2.0];
26//! # let b = [3.0, 4.0];
27//! # let c = [5.0, 6.0];
28//! assert_eq!(orient2d(a,c,b).signum(), orient2d(c,b,a).signum());
29//! ```
30//!
31//! Mathematically, predicates like `orient2d` are
32//! defined as
33//!```verbatim
34//!                                        ⎛⎡ax ay 1⎤⎞
35//!orient2d([ax,ay],[bx,by],[cx,cy]) := det⎜⎢bx by 1⎥⎟
36//!                                        ⎝⎣cx cy 1⎦⎠
37//!```
38//!
39//! It's easy to see that these predicates solve the problem of
40//! computing the determinant of small matrices with the correct sign, regardless of how
41//! close the matrix is to being singular.
42//!
43//! For instance to compute the determinant of a matrix `[a b; c d]` with the
44//! correct sign, we can invoke
45//! ```
46//! # use geometry_predicates::orient2d;
47//! # let a = 1.0;
48//! # let b = 2.0;
49//! # let c = 3.0;
50//! # let d = 4.0;
51//! assert_eq!(orient2d([a,b], [c,d], [0.0,0.0]), a*d - b*c);
52//! ```
53//!
54//! For more details please refer to the [original
55//! webpage](https://www.cs.cmu.edu/~quake/robust.html) for these predicates.
56//!
57//! ## Caveats
58//!
59//! These predicates do NOT handle exponent overflow [\[1\]], which means inputs with floats smaller than
60//! `1e-142` or larger than `1e201` may not produce accurate results. This is true for the original
61//! predicates in `predicates.c` as well as other Rust ports and bindings for these predicates.
62//!
63//! ## References
64//!
65//!  - [\[1\] Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates][\[1\]],
66//!    Discrete & Computational Geometry 18(3):305–363, October 1997.
67//!  - [\[2\] Robust Adaptive Floating-Point Geometric Predicates Proceedings of the Twelfth Annual][\[2\]],
68//!    Symposium on Computational Geometry (Philadelphia, Pennsylvania), pages 141–150, Association for
69//!    Computing Machinery, May 1996
70//!
71//! [\[1\]]: http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf
72//! [\[2\]]: http://www.cs.berkeley.edu/~jrs/papers/robust-predicates.pdf
73#![no_std]
74pub mod predicates;
75
76#[cfg(feature = "transpiled")]
77mod transpiled;
78
79// The following predicates are exposed at the top level.
80// There are other alternative robust implementations (but typically slower) of these predicates.
81// We use those to check the adaptive implementations.
82pub use predicates::{
83    // Adaptive robust predicates.
84    incircle,
85    // Fast inexact predicates.
86    incircle_fast,
87    insphere,
88    insphere_fast,
89    orient2d,
90    orient2d_fast,
91    orient3d,
92    orient3d_fast,
93};
94
95#[cfg(test)]
96mod tests {
97    extern crate rand;
98    use self::rand::{Rng, SeedableRng, StdRng};
99    use super::*;
100
101    use predicates::{
102        incircle_exact, incircle_slow, insphere_exact, insphere_slow, orient2d_exact,
103        orient2d_slow, orient3d_exact, orient3d_slow,
104    };
105
106    const SEED: &[usize] = &[1, 2, 3, 4];
107
108    /*
109     * Note on robustness testing.
110     *
111     * These predicates do NOT handle overflow or underflowo of the exponent.
112     * Quoting Shechuk's predicates paper directly:
113     *
114     * > The four predicates implemented [in this library] will not overflow nor underflow if their
115     * > inputs have exponents in the range [-142, 201] and IEEE 754 double precision arithmetic
116     * > is used.
117     * - Jonathan Shechuk 1997
118     *
119     * We will therefore be careful to test for inputs with exponents in that range and not beyond.
120     */
121
122    const EXP_BOUNDS: [i32; 2] = [-142, 201];
123
124    /// Generate a tolerance value with exponent no less than -142.
125    fn tol(rng: &mut StdRng) -> f64 {
126        let max_exp = (EXP_BOUNDS[0] + 1) as f64;
127        10.0_f64.powi((max_exp * rng.gen::<f64>()).round() as i32) * (rng.gen::<f64>() - 0.5)
128    }
129
130    /*
131     * Many of the tests below ensure that the predicates produce expected results for all
132     * permutations of the inputs. Thus, below we write an adhoc QuickPerm implementation to
133     * generate swap based permutations.
134     *
135     * The following is a basic countdown implementation of QuickPerm (see quickperm.org).
136     * This implementation can be refactored to use const generics when those stabilize.
137     */
138    macro_rules! quick_perm_impl {
139        ($n:expr, $struct_name:ident) => {
140            struct $struct_name<T> {
141                perm: [T; $n],
142                p: [usize; $n + 1],
143                index: usize,
144            }
145
146            impl<T> $struct_name<T> {
147                fn new(v: [T; $n]) -> Self {
148                    let mut p = [0; $n + 1];
149                    for i in 0..$n + 1 {
150                        p[i] = i;
151                    }
152                    $struct_name {
153                        perm: v,
154                        p,
155                        index: 0,
156                    }
157                }
158            }
159
160            impl<T: Clone> Iterator for $struct_name<T> {
161                type Item = [T; $n];
162                fn next(&mut self) -> Option<Self::Item> {
163                    let $struct_name { perm, p, index } = self;
164                    let mut i = *index;
165                    let n = p.len() - 1;
166                    if i == 0 {
167                        *index += 1;
168                        return Some(perm.clone());
169                    } else if i >= n {
170                        return None;
171                    }
172                    p[i] -= 1;
173                    let j = if i % 2 == 0 { 0 } else { p[i] };
174                    perm.swap(j, i);
175                    i = 1;
176                    while p[i] == 0 {
177                        p[i] = i;
178                        i += 1;
179                    }
180                    *index = i;
181                    Some(perm.clone())
182                }
183            }
184        };
185    }
186
187    quick_perm_impl!(3, QuickPerm3);
188    quick_perm_impl!(4, QuickPerm4);
189    quick_perm_impl!(5, QuickPerm5);
190
191    /*
192     * The tests below have specific purposes.
193     *
194     * - _robustness_ tests check that the adaptive predicates work in degenerate or close to degenerate
195     *   configurations
196     * - _regression_ tests verify the adpative predicates against their _slow and _exact variants.
197     * - _transpiled_regression_ tests verify the predicates against the directly transpiled
198     *   (unrefactored) version of the library.
199     */
200
201    #[test]
202    fn orient2d_test() {
203        let a = [0.0, 1.0];
204        let b = [2.0, 3.0];
205        let c = [4.0, 5.0];
206        assert_eq!(orient2d(a, b, c), 0.0);
207    }
208
209    #[cfg(feature = "transpiled")]
210    #[test]
211    fn orient2d_transpiled_regression_test() {
212        unsafe {
213            transpiled::exactinit();
214        }
215        let mut rng: StdRng = SeedableRng::from_seed(SEED);
216
217        let n = 99999;
218        for _ in 0..n {
219            let a = [tol(&mut rng), tol(&mut rng)];
220            let b = [12.0, 12.0];
221            let c = [24.0, 24.0];
222            let o2d = orient2d;
223            let o2d_transpiled = transpiled::orient2d;
224            for p in QuickPerm3::new([a, b, c]) {
225                assert_eq!(
226                    o2d(p[0], p[1], p[2]),
227                    o2d_transpiled(p[0], p[1], p[2]),
228                    "{:?}",
229                    a
230                );
231            }
232        }
233    }
234
235    #[test]
236    fn orient2d_robustness_test() {
237        let mut rng: StdRng = SeedableRng::from_seed(SEED);
238        let n = 99999;
239
240        for o2d in &[orient2d, orient2d_exact, orient2d_slow] {
241            for _ in 0..n {
242                let pa = [tol(&mut rng), tol(&mut rng)];
243                let pb = [12.0, 12.0];
244                let pc = [24.0, 24.0];
245
246                let main = o2d(pa, pb, pc);
247                if main == 0.0 {
248                    for [a, b, c] in QuickPerm3::new([pa, pb, pc]) {
249                        assert_eq!(o2d(a, b, c), 0.0);
250                    }
251                }
252
253                let pred = main > 0.0;
254                for (i, [a, b, c]) in QuickPerm3::new([pa, pb, pc]).enumerate() {
255                    let t = o2d(a, b, c);
256                    assert_eq!(pred, if i % 2 == 0 { t > 0.0 } else { t < 0.0 });
257                }
258            }
259        }
260    }
261
262    // The following test verifies equivalence of all of the robust orient2d variants (including
263    // exact and slow variants).
264    #[test]
265    fn orient2d_regression_test() {
266        let mut rng: StdRng = SeedableRng::from_seed(SEED);
267
268        let n = 99999;
269        for _ in 0..n {
270            let pa = [tol(&mut rng), tol(&mut rng)];
271            let pb = [12.0, 12.0];
272            let pc = [24.0, 24.0];
273
274            let o2d = predicates::orient2d;
275            let o2de = predicates::orient2d_exact;
276            let o2ds = predicates::orient2d_slow;
277
278            // Test all permutations.
279
280            for [a, b, c] in QuickPerm3::new([pa, pb, pc]) {
281                assert_eq!(
282                    o2d(a, b, c).partial_cmp(&0.0),
283                    o2de(a, b, c).partial_cmp(&0.0),
284                    "{:?}",
285                    pa
286                );
287                assert_eq!(
288                    o2d(a, b, c).partial_cmp(&0.0),
289                    o2ds(a, b, c).partial_cmp(&0.0),
290                    "{:?}",
291                    pa
292                );
293            }
294        }
295    }
296
297    #[test]
298    fn orient2d_fast_test() {
299        let a = [0.0, 1.0];
300        let b = [2.0, 3.0];
301        let c = [4.0, 5.0];
302        assert_eq!(orient2d_fast(a, b, c), 0.0);
303
304        // The fast orientation test should also work when the given points are sufficiently
305        // non-colinear.
306        let mut rng: StdRng = SeedableRng::from_seed(SEED);
307        let tol = 5.0e-10; // will not work with 5.0e-14
308
309        for _ in 0..999 {
310            let a = [0.5 + tol * rng.gen::<f64>(), 0.5 + tol * rng.gen::<f64>()];
311            let b = [12.0, 12.0];
312            let c = [24.0, 24.0];
313            assert_eq!(orient2d_fast(a, b, c) > 0.0, orient2d_fast(b, c, a) > 0.0);
314            assert_eq!(orient2d_fast(b, c, a) > 0.0, orient2d_fast(c, a, b) > 0.0);
315            assert_eq!(orient2d_fast(a, b, c) > 0.0, orient2d_fast(b, a, c) < 0.0);
316            assert_eq!(orient2d_fast(a, b, c) > 0.0, orient2d_fast(a, c, b) < 0.0);
317        }
318    }
319
320    #[test]
321    fn orient3d_test() {
322        let a = [0.0, 1.0, 6.0];
323        let b = [2.0, 3.0, 4.0];
324        let c = [4.0, 5.0, 1.0];
325        let d = [6.0, 2.0, 5.3];
326        assert_eq!(orient3d(a, b, c, d), 10.0);
327    }
328
329    #[cfg(feature = "transpiled")]
330    #[test]
331    fn orient3d_transpiled_regression_test() {
332        unsafe {
333            transpiled::exactinit();
334        }
335        let mut rng: StdRng = SeedableRng::from_seed(SEED);
336
337        let n = 9999;
338        for _ in 0..n {
339            let pa = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
340            let pb = [12.0, 12.0, 12.0];
341            let pc = [24.0, 24.0, 24.0];
342            let pd = [48.0, 48.0, 48.0];
343
344            let o3d = predicates::orient3d;
345            let o3d_transpiled = transpiled::orient3d;
346
347            // Test all permutations.
348            for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
349                assert_eq!(o3d(a, c, d, b), o3d_transpiled(a, c, d, b), "{:?}", pa);
350            }
351        }
352    }
353
354    // The following test verifies equivalence of all of the robust orient3d variants.
355    #[test]
356    fn orient3d_regression_test() {
357        let mut rng: StdRng = SeedableRng::from_seed(SEED);
358
359        let n = 5000;
360        for _ in 0..n {
361            let pa = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
362            let pb = [12.0, 12.0, 12.0];
363            let pc = [24.0, 24.0, 24.0];
364            let pd = [48.0, 48.0, 48.0];
365
366            let o3d = predicates::orient3d;
367            let o3de = predicates::orient3d_exact;
368            let o3ds = predicates::orient3d_slow;
369
370            // Test all permutations.
371            // Actually these don't need to be exactly equal, they just need to compare equally to
372            // 0.0. It just so happens that they are also equal.
373            for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
374                assert_eq!(o3d(a, c, d, b), o3de(a, c, d, b), "{:?}", pa);
375                assert_eq!(o3d(a, c, d, b), o3ds(a, c, d, b), "{:?}", pa);
376            }
377        }
378    }
379
380    #[test]
381    fn orient3d_robustness_test() {
382        let mut rng: StdRng = SeedableRng::from_seed(SEED);
383
384        let n = 999;
385
386        for o3d in &[orient3d, orient3d_exact, orient3d_slow] {
387            for _ in 0..n {
388                let pa = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
389                let pb = [12.0, 12.0, 12.0];
390                let pc = [24.0, 24.0, 24.0];
391                let pd = [48.0, 48.0, 48.0];
392
393                // Test all permutations.
394
395                let main = o3d(pa, pb, pc, pd);
396
397                if main == 0.0 {
398                    for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
399                        assert_eq!(o3d(a, b, c, d), 0.0);
400                    }
401                }
402
403                let pred = main > 0.0;
404                for (i, [a, b, c, d]) in QuickPerm4::new([pa, pb, pc, pd]).enumerate() {
405                    let t = o3d(a, b, c, d);
406                    assert_eq!(
407                        pred,
408                        if i % 2 == 0 { t > 0.0 } else { t < 0.0 },
409                        "{} vs. {} at {:?}",
410                        t,
411                        -main,
412                        pa
413                    );
414                }
415            }
416        }
417    }
418
419    #[test]
420    fn orient3d_fast_test() {
421        let a = [0.0, 1.0, 6.0];
422        let b = [2.0, 3.0, 4.0];
423        let c = [4.0, 5.0, 1.0];
424        let d = [6.0, 2.0, 5.3];
425        assert!(f64::abs(orient3d_fast(a, b, c, d) - 10.0) < 1.0e-4);
426
427        // The fast orientation test should also work when the given points are sufficiently
428        // non-colinear.
429        let mut rng: StdRng = SeedableRng::from_seed(SEED);
430        let tol = 5.0;
431
432        let a = [
433            0.5 + tol * rng.gen::<f64>(),
434            0.5 + tol * rng.gen::<f64>(),
435            0.5 + tol * rng.gen::<f64>(),
436        ];
437        let b = [12.0, 12.0, 12.0];
438        let c = [24.0, 24.0, 24.0];
439        let d = [48.0, 48.0, 48.0];
440        assert_eq!(
441            orient3d_fast(a, b, c, d) > 0.0,
442            orient3d_fast(b, c, a, d) > 0.0
443        );
444        assert_eq!(
445            orient3d_fast(b, a, c, d) < 0.0,
446            orient3d_fast(c, b, d, a) < 0.0
447        );
448        // The following orientations are expected to be inconsistent
449        // (this is why we need exact predicates)
450        assert_ne!(
451            orient3d_fast(b, c, d, a) > 0.0,
452            orient3d_fast(c, d, a, b) > 0.0
453        );
454        assert_ne!(
455            orient3d_fast(b, d, c, a) < 0.0,
456            orient3d_fast(c, d, b, a) < 0.0
457        );
458    }
459
460    #[test]
461    fn incircle_test() {
462        let a = [0.0, 1.0];
463        let b = [1.0, 0.0];
464        let c = [1.0, 1.0];
465        let d = [0.0, 0.0];
466        assert_eq!(incircle(a, b, c, d), 0.0);
467        let d = [0.1, 0.1];
468        assert!(incircle(a, b, c, d) > 0.0);
469        let d = [-0.1, -0.1];
470        assert!(incircle(a, b, c, d) < 0.0);
471    }
472
473    #[test]
474    fn incircle_robustness_test() {
475        let mut rng: StdRng = SeedableRng::from_seed(SEED);
476
477        let n = 999;
478        for ic in &[incircle, incircle_exact, incircle_slow] {
479            for _ in 0..n {
480                let pa = [0.0, 1.0];
481                let pb = [1.0, 0.0];
482                let pc = [1.0, 1.0];
483                let pd = [tol(&mut rng), tol(&mut rng)];
484
485                let main = ic(pa, pb, pc, pd);
486
487                if main == 0.0 {
488                    for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
489                        assert_eq!(ic(a, b, c, d), 0.0);
490                    }
491                }
492
493                let pred = main > 0.0;
494                for (i, [a, b, c, d]) in QuickPerm4::new([pa, pb, pc, pd]).enumerate() {
495                    let t = ic(a, b, c, d);
496                    assert_eq!(
497                        pred,
498                        if i % 2 == 0 { t > 0.0 } else { t < 0.0 },
499                        "{} vs. {} at {:?}",
500                        t,
501                        -main,
502                        pd
503                    );
504                }
505            }
506        }
507    }
508
509    #[test]
510    fn insphere_test() {
511        let a = [0.0, 1.0, 0.0];
512        let b = [1.0, 0.0, 0.0];
513        let c = [0.0, 0.0, 1.0];
514        let d = [1.0, 1.0, 1.0];
515        let e = [0.0, 0.0, 0.0];
516        assert_eq!(insphere(a, b, c, d, e), 0.0);
517        let e = [0.1, 0.1, 0.1];
518        assert!(insphere(a, b, c, d, e) > 0.0);
519        let e = [-0.1, -0.1, -0.1];
520        assert!(insphere(a, b, c, d, e) < 0.0);
521    }
522
523    #[test]
524    fn insphere_robustness_test() {
525        let mut rng: StdRng = SeedableRng::from_seed(SEED);
526
527        let n = 99;
528        for is in &[insphere, insphere_exact, insphere_slow] {
529            for _ in 0..n {
530                let pa = [0.0, 1.0, 0.0];
531                let pb = [1.0, 0.0, 0.0];
532                let pc = [0.0, 0.0, 1.0];
533                let pd = [1.0, 1.0, 1.0];
534                let pe = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
535
536                let main = is(pa, pb, pc, pd, pe);
537
538                if main == 0.0 {
539                    for [a, b, c, d, e] in QuickPerm5::new([pa, pb, pc, pd, pe]) {
540                        assert_eq!(is(a, b, c, d, e), 0.0);
541                    }
542                }
543
544                let pred = main > 0.0;
545                for (i, [a, b, c, d, e]) in QuickPerm5::new([pa, pb, pc, pd, pe]).enumerate() {
546                    let t = is(a, b, c, d, e);
547                    assert_eq!(
548                        pred,
549                        if i % 2 == 0 { t > 0.0 } else { t < 0.0 },
550                        "{} vs. {} at {:?}",
551                        t,
552                        -main,
553                        pe
554                    );
555                }
556            }
557        }
558    }
559}