From 0c1a2f52bcc35779f0b32533ff96e576df5ac685 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 13 May 2025 13:02:06 -0400 Subject: [PATCH 01/16] Fix no_std --- .github/workflows/rust.yml | 10 ++++++++++ Cargo.toml | 4 +++- src/lib.rs | 11 +++++++++-- src/orient_2dlifted.rs | 2 +- src/orient_3dlifted.rs | 1 - 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 71b8296..9e0ba53 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -23,6 +23,16 @@ jobs: - name: Test run: cargo test + test-no_std: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + + - name: Test + run: cargo test --features no_std + test-legacy: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 130cb1d..4850076 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,10 @@ categories = ["no-std::no-alloc", "mathematics", "graphics"] [dependencies] heapless = "0.8" nalgebra = { version = "0.33", features = ["macros", "matrixmultiply"], default-features = false } -robust = "1.1" +robust = { version = "1.2", features = ["no_std"] } # legacy cxx = { version = "1.0", features = ["alloc", "c++20"], default-features = false, optional = true } +libm = { version = "0.2.15", optional = true } [build-dependencies] cxx-build = { version = "1.0", optional = true } @@ -34,3 +35,4 @@ test_utils = { path = "test_utils" } [features] legacy = ["dep:cxx", "dep:cxx-build"] +no_std = ["dep:libm"] diff --git a/src/lib.rs b/src/lib.rs index 42830fd..efd3e13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,10 @@ //! # Geogram Predicates //! -//! A Rust port of `geogram`s _robust predicates_. +//! A Rust port of `geogram`s _robust predicates_.\ //! An interoperability ffi with `geogram`s _robust predicates_; via `cxx` also available in the `legacy` feature. //! //! All functions are in written in Rust, except for `det_3d` and `det_4d` which are only available in `legacy`. -#![no_std] +#![cfg_attr(feature = "no_std", no_std)] #![warn(clippy::all, unused, clippy::missing_const_for_fn)] @@ -157,9 +157,16 @@ pub fn orient_2d(a: &Point2d, b: &Point2d, c: &Point2d) -> Sign { #[must_use] pub fn dot_3d(a: &Point3d, b: &Point3d, c: &Point3d) -> bool { let ab_diff = Into::>::into(*a) - Into::>::into(*b); + #[cfg(not(feature = "no_std"))] let ab_distance = ab_diff.x.hypot(ab_diff.y).hypot(ab_diff.z); + #[cfg(feature = "no_std")] + let ab_distance = libm::hypot(libm::hypot(ab_diff.x, ab_diff.y), ab_diff.z); + let ac_diff = Into::>::into(*a) - Into::>::into(*c); + #[cfg(not(feature = "no_std"))] let ac_distance = ac_diff.x.hypot(ac_diff.y).hypot(ac_diff.z); + #[cfg(feature = "no_std")] + let ac_distance = libm::hypot(libm::hypot(ac_diff.x, ac_diff.y), ac_diff.z); nalgebra::vector![ab_distance].dot(&nalgebra::vector![ac_distance]) >= 0.0 } diff --git a/src/orient_2dlifted.rs b/src/orient_2dlifted.rs index 8eb983a..a233f97 100644 --- a/src/orient_2dlifted.rs +++ b/src/orient_2dlifted.rs @@ -205,7 +205,7 @@ fn side3h_2d_exact_sos( // Simulation of Simplicity (symbolic perturbation) if sos && r_sign == 0 { let mut p_sort = [p0, p1, p2, p3]; - p_sort.sort_by(lexico_compare_2d); + p_sort.sort_unstable_by(lexico_compare_2d); for i in 0..3 { if p_sort[i] == p0 { diff --git a/src/orient_3dlifted.rs b/src/orient_3dlifted.rs index 5b55ee0..3036ebb 100644 --- a/src/orient_3dlifted.rs +++ b/src/orient_3dlifted.rs @@ -243,7 +243,6 @@ fn side4h_3d_exact_sos( // Simulation of Simplicity (symbolic perturbation) if SOS && r_sign == 0 { let mut p_sort = [p0, p1, p2, p3, p4]; - p_sort.sort_by(lexico_compare_3d); for i in 0..4 { if p_sort[i] == p0 { From fef8db400ea803ebe7301e1b9ec036f70cb75474 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 13 May 2025 13:07:50 -0400 Subject: [PATCH 02/16] Add orient_3d example --- .gitignore | 2 +- build.rs | 5 ++- examples/orient_3d/main.rs | 70 ++++++++++++++++++++++++++++++++++++++ test_utils/src/lib.rs | 25 ++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 examples/orient_3d/main.rs diff --git a/.gitignore b/.gitignore index 2b839a4..0930a41 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ rust-analyzer # tip: on mac put '.DS_Store' in your global gitignore .DS_Store -out_*_orient_2d.png +*.png diff --git a/build.rs b/build.rs index 73cdd8f..0de539c 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,5 @@ +#[cfg(feature = "legacy")] fn main() { - #[cfg(feature = "legacy")] cxx_build::bridge("src/lib.rs") .file("src/geogram_ffi.cpp") .file("include/geogram_predicates_psm/Predicates_psm.cpp") // we need to add the ..._psm.cpp to the compile list, just as when compiling in c++ @@ -10,3 +10,6 @@ fn main() { println!("cargo:rerun-if-changed=src/geogram_ffi.cpp"); println!("cargo:rerun-if-changed=include/geogram_ffi.h"); } + +#[cfg(not(feature = "legacy"))] +fn main() {} diff --git a/examples/orient_3d/main.rs b/examples/orient_3d/main.rs new file mode 100644 index 0000000..40d4bb7 --- /dev/null +++ b/examples/orient_3d/main.rs @@ -0,0 +1,70 @@ +use geogram_predicates::orient_3d; +use std::{fs, path::Path}; +use test_utils::{predicate_3d_test, write_to_png}; + +fn main() { + let args = std::env::args().collect::>(); + + if args.len() != 2 { + usage() + } + + let mode = args[1].as_str(); + + let p1 = [12., 12., 0.]; + let p2 = [24., 24., 12.]; + let p3 = [24., 24., 24.]; + + let predicate: Box f64> = match mode { + "naive" => Box::new(|p| naive_orient_3d(&p1, &p, &p2, &p3)), + "robust" => Box::new(|p| (orient_3d(&p1, &p, &p2, &p3) as i8).into()), + "clean" => { + let _ = fs::remove_file("out_naive_orient_3d.png"); + let _ = fs::remove_file("out_robust_orient_3d.png"); + println!("example images removed"); + std::process::exit(1); + } + "help" | _ => usage(), + }; + + let predicate_results = predicate_3d_test(predicate, [0.5, 0.5], 256, 256); + + let out_path = format!("out_{}_orient_3d.png", mode); + + write_to_png(&predicate_results, Path::new(&out_path), 256, 256); +} + +// Directly evaluate the orient3d determinant (signed volume of a tetrahedron). +// Refer: https://www.cs.cmu.edu/~quake/robust.html +fn naive_orient_3d(a: &[f64; 3], b: &[f64; 3], c: &[f64; 3], d: &[f64; 3]) -> f64 { + let adx = a[0] - d[0]; + let ady = a[1] - d[1]; + let adz = a[2] - d[2]; + + let bdx = b[0] - d[0]; + let bdy = b[1] - d[1]; + let bdz = b[2] - d[2]; + + let cdx = c[0] - d[0]; + let cdy = c[1] - d[1]; + let cdz = c[2] - d[2]; + + adx * (bdy * cdz - bdz * cdy) + - ady * (bdx * cdz - bdz * cdx) + + adz * (bdx * cdy - bdy * cdx) +} + +fn usage() -> ! { + eprintln!(" + Usage: + orient_3d [option] + + MODES: + naive - output an image showing the output of a naive orient_3d implementation + robust - output an image showing the output of the robust orient_3d implementation + OTHER: + help - show this help message + clean - remove the example output images + "); + std::process::exit(1); +} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index ff08063..5a06d64 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -49,3 +49,28 @@ where } data } + +pub fn predicate_3d_test( + predicate: F, + start: [f64; 2], + width: usize, + height: usize, +) -> Vec +where + F: Fn([f64; 3]) -> f64, +{ + let mut yd = start[1]; + let mut data = Vec::with_capacity(width * height); + + for _ in 0..height { + let mut xd = start[0]; + for _ in 0..width { + // let p = [xd, yd, 12.0]; + let p = [xd * 1e305, yd * 1e305, (height + width) as f64 * 0.5 * 1e305]; + data.push(predicate(p).partial_cmp(&0.).unwrap()); + xd = nextafter(xd, std::f64::INFINITY); + } + yd = nextafter(yd, std::f64::INFINITY); + } + data +} From 1656da76ca46b58b82166fa599c58d2421d357ee Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 13 May 2025 13:09:59 -0400 Subject: [PATCH 03/16] Switch `Expansion` to hybrid allocation --- Cargo.lock | 60 ++++++++++++-------------------- Cargo.toml | 4 +-- src/expansion/expansion.rs | 70 ++++++++++++++++++-------------------- src/expansion/macros.rs | 11 +++--- src/lib.rs | 3 +- src/orient_2dlifted.rs | 15 ++++---- src/orient_3dlifted.rs | 1 + src/types.rs | 4 +-- test_utils/src/lib.rs | 3 +- 9 files changed, 77 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9d8e03..122fba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,17 +35,11 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "cc" -version = "1.2.20" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "shlex", ] @@ -58,18 +52,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstyle", "clap_lex", @@ -202,37 +196,25 @@ dependencies = [ "cxx", "cxx-build", "float_extras", - "heapless", + "libm", "nalgebra", "robust", + "smallvec", "test_utils", ] -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "link-cplusplus" version = "1.0.10" @@ -244,9 +226,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ "autocfg", "rawpointer", @@ -371,9 +353,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "robust" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" [[package]] name = "rustversion" @@ -432,10 +414,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "smallvec" +version = "2.0.0-alpha.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "87b96efa4bd6bdd2ff0c6615cc36fc4970cbae63cfd46ddff5cee35a1b4df570" [[package]] name = "strsim" diff --git a/Cargo.toml b/Cargo.toml index 4850076..401b9bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,10 @@ authors = ["Glenn Dittmann", "TimTheBig"] license = "LGPL-3.0 OR MIT" keywords = ["computer-graphics", "math", "geometry", "predicates", "robust"] -categories = ["no-std::no-alloc", "mathematics", "graphics"] +categories = ["no-std", "mathematics", "graphics"] [dependencies] -heapless = "0.8" +smallvec = "2.0.0-alpha.11" nalgebra = { version = "0.33", features = ["macros", "matrixmultiply"], default-features = false } robust = { version = "1.2", features = ["no_std"] } # legacy diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index d596956..66da198 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -1,16 +1,22 @@ use crate::{Sign, geo_sign}; use core::{cmp::Ordering, fmt}; -use heapless::Vec as ArrayVec; +use smallvec::SmallVec; #[derive(Clone, Debug)] pub struct Expansion { - /// Capacity is fixed and inline with no heap allocation - data: ArrayVec, + /// Starts inline then moves to heap allocation once the inline capacity is exceeded + data: SmallVec, } impl fmt::Display for Expansion { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Expansion(len: {}) = [", self.len())?; + write!(f, "Expansion(len: {}) = ", self.len())?; + if self.data.spilled() { + write!(f, "Vec [")?; + } else { + write!(f, "[")?; + } + if self.len() > 1 { for x in self.data.iter().take(self.len() - 1) { write!(f, "{x}, ")?; @@ -47,17 +53,13 @@ impl core::ops::Index for Expansion { impl Default for Expansion { fn default() -> Self { - Self { - data: ArrayVec::new(), - } + Self::with_capacity(N) } } impl Expansion { pub(crate) const fn new() -> Self { - Self { - data: ArrayVec::new(), - } + Self::with_capacity(N) } /// Create a new `Expansion` with the given capacity. @@ -80,11 +82,12 @@ impl Expansion { /// assert_eq!(e.capacity(), 10); /// assert_eq!(e.length(), 0); /// ``` - pub fn with_capacity(capacity: usize) -> Self { - debug_assert_eq!(N, capacity); + pub const fn with_capacity(capacity: usize) -> Self { + debug_assert!(N == capacity); + // it would be nice if this used capacity Self { - data: ArrayVec::::new(), + data: SmallVec::::new(), } } @@ -110,7 +113,7 @@ impl Expansion { } #[inline] - pub(crate) const fn data_mut(&mut self) -> &mut ArrayVec { + pub(crate) const fn data_mut(&mut self) -> &mut SmallVec { &mut self.data } @@ -138,12 +141,12 @@ impl Expansion { /// ``` pub fn assign(&mut self, a: f64) -> &mut Self { self.data.clear(); - self.data.push(a).expect("assert"); + self.data.push(a); self } pub(crate) fn assign_abs(&mut self, rhs: &mut Expansion) -> &mut Self { - self.data.extend_from_slice(rhs.data_mut()).expect("assert"); + self.data.extend_from_slice(rhs.data_mut()); for i in 0..rhs.len() { self.data[i] = rhs.data[i]; } @@ -229,11 +232,7 @@ impl From for Expansion<1> { fn from(a: f64) -> Self { let mut e = Expansion::with_capacity(1); - #[cfg(debug_assertions)] - e.data.push(a).expect("the len is 1 so this will fit"); - #[cfg(not(debug_assertions))] - // SAFETY: the len is 1 so this will fit - unsafe { e.data.push_unchecked(a); } + e.data.push(a); e } } @@ -246,22 +245,20 @@ impl From<[f64; N]> for Expansion { fn from(arr: [f64; N]) -> Self { Expansion { // SAFE: arr len is == N - data: unsafe { ArrayVec::from_slice(&arr).unwrap_unchecked() }, + data: unsafe { SmallVec::from_buf_and_len_unchecked(core::mem::MaybeUninit::new(arr), N) }, } } } -impl TryFrom<&[f64]> for Expansion { - type Error = (); - +impl From<&[f64]> for Expansion { /// Create an expansion from a slice of doubles. /// /// The resulting expansion has length `slice.len()` and /// components equal to the slice elements in order. - fn try_from(slice: &[f64]) -> Result { - Ok(Expansion { - data: ArrayVec::from_slice(slice)?, - }) + fn from(slice: &[f64]) -> Self { + Expansion { + data: SmallVec::from_slice(slice), + } } } @@ -278,8 +275,8 @@ impl Expansion { a: &Expansion, b: &Expansion, ) -> &mut Self { - self.data.extend_from_slice(a.data()).expect("assert"); - self.data.extend_from_slice(b.data()).expect("assert"); + self.data.extend_from_slice(a.data()); + self.data.extend_from_slice(b.data()); self.optimize(); self @@ -309,12 +306,10 @@ impl Expansion { ) -> &mut Self { const { assert!(N > 1, "N must be greater then 1") }; - let cap = Self::product_capacity(a, b); - assert!(cap <= self.capacity(), "assign_product: capacity too small"); + let needed_cap = Self::product_capacity(a, b); debug_assert_ne!(self.data.capacity(), 0); - self.data.resize(N, 0.0).expect("assert"); - debug_assert_eq!(self.len(), N); + self.data.resize(needed_cap, 0.0); let mut idx = 0; // for each pair (i,j), compute two_product and write low,high @@ -487,8 +482,8 @@ impl core::ops::Add for Expansion { type Output = Expansion; fn add(self, rhs: Self) -> Self::Output { - let mut prod: Expansion = Expansion::with_capacity(Expansion::::product_capacity(&self, &rhs)); - prod.assign_product(&self, &rhs); + let mut prod: Expansion = Expansion::with_capacity(N + N); + prod.assign_sum(&self, &rhs); prod } } @@ -501,6 +496,7 @@ impl core::ops::Mul for Expansion { fn mul(self, rhs: Expansion) -> Self::Output { // todo when generic_const_exprs is stabe Expansion<{SN.max(RN)}> let mut prod: Expansion = Expansion::with_capacity(Expansion::::product_capacity(&self, &rhs)); + prod.assign_product(&self, &rhs); prod } diff --git a/src/expansion/macros.rs b/src/expansion/macros.rs index b3eb45d..ddeeaf4 100644 --- a/src/expansion/macros.rs +++ b/src/expansion/macros.rs @@ -7,11 +7,15 @@ /// let e = expansion![1.0, 2.5, 3.75]; /// assert_eq!(e.length(), 3); /// assert_eq!(&e.data(), &[1.0, 2.5, 3.75]); +/// // or +/// let e = expansion!(1.0); +/// # assert_eq!(e.length(), 1); +/// assert_eq!(&e.data(), &[1.0]); /// ``` #[macro_export] macro_rules! expansion { // match zero or more comma-separated expressions, allow trailing comma - ($($x:expr),* $(,)?) => { + [$($x:expr),* $(,)?] => { // create a fixed-length array `[f64; count]`, then call From<[f64;N]> $crate::Expansion::from([$($x),*]) }; @@ -107,8 +111,6 @@ macro_rules! expansion_det2x2 { // Allocate an Expansion with that capacity let mut e = $crate::Expansion::new(); - #[cfg(debug_assertions)] - debug_assert_eq!(cap, e.capacity()); // Perform the determinant assignment e.assign_det2x2(&$a11, &$a12, &$a21, &$a22); e @@ -138,7 +140,8 @@ macro_rules! expansion_sum { let mut expansion = Expansion::new(); expansion.assign_sum(&$a, &$b); - debug_assert_eq!(expansion.data_mut().len(), expansion.data_mut().capacity()); + // limit wasted memory + debug_assert!(expansion.data_mut().len().saturating_sub(expansion.data_mut().capacity()) <= 2); expansion }}; } diff --git a/src/lib.rs b/src/lib.rs index efd3e13..80061fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,8 +77,7 @@ pub const fn geo_sign(value: f64) -> Sign { /// /// # Example /// ``` -/// use geogram_predicates::orient_3d; -/// +/// # use geogram_predicates::orient_3d; /// // Define four points that form a tetrahedron /// let a = [0.0, 0.0, 0.0]; /// let b = [2.0, 0.0, 0.0]; diff --git a/src/orient_2dlifted.rs b/src/orient_2dlifted.rs index a233f97..5fe5e72 100644 --- a/src/orient_2dlifted.rs +++ b/src/orient_2dlifted.rs @@ -180,22 +180,23 @@ fn side3h_2d_exact_sos( let a32: Expansion<2> = expansion_diff!(p3[1], p0[1], 1, 1); let a33: Expansion<2> = expansion_diff!(h0, h3, 1, 1); - let delta1: Expansion<3> = expansion_det2x2!([a21, a22], [a31, a32]); - let delta2: Expansion<3> = expansion_det2x2!([a11, a12], [a31, a32]); - let delta3: Expansion<3> = expansion_det2x2!([a11, a12], [a21, a22]); + let delta1: Expansion<4> = expansion_det2x2!([a21, a22], [a31, a32]); + let delta2: Expansion<4> = expansion_det2x2!([a11, a12], [a31, a32]); + let delta3: Expansion<4> = expansion_det2x2!([a11, a12], [a21, a22]); let delta3_sign = delta3.sign(); + // This fails on NaN input debug_assert!(delta3_sign != 0); let r_1: Expansion<4> = expansion_product!(delta1, a13, 4); let mut r_2: Expansion<4> = expansion_product!(delta2, a23, 4); r_2.negate(); let r_3: Expansion<4> = expansion_product!(delta3, a33, 4); - let r: Expansion<6> = { + let r: Expansion<7> = { // capacity is `r_1.length() + r_2.length()` which is 4 - let mut ab: Expansion<4> = Expansion::new(); + let mut ab: Expansion<5> = Expansion::new(); ab.assign_sum(&r_1, &r_2); - let mut expansion: Expansion<6> = Expansion::new(); + let mut expansion: Expansion<7> = Expansion::new(); expansion.assign_sum(&ab, &r_3); expansion }; @@ -210,7 +211,7 @@ fn side3h_2d_exact_sos( for i in 0..3 { if p_sort[i] == p0 { let z1 = { - let mut expansion: Expansion<2> = Expansion::with_capacity(2); + let mut expansion: Expansion<3> = Expansion::new(); expansion.assign_diff(&delta2, &delta1); expansion }; diff --git a/src/orient_3dlifted.rs b/src/orient_3dlifted.rs index 3036ebb..c7fd34f 100644 --- a/src/orient_3dlifted.rs +++ b/src/orient_3dlifted.rs @@ -243,6 +243,7 @@ fn side4h_3d_exact_sos( // Simulation of Simplicity (symbolic perturbation) if SOS && r_sign == 0 { let mut p_sort = [p0, p1, p2, p3, p4]; + p_sort.sort_unstable_by(lexico_compare_3d); for i in 0..4 { if p_sort[i] == p0 { diff --git a/src/types.rs b/src/types.rs index a413c0a..9db426b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -61,7 +61,7 @@ impl core::ops::Mul for Sign { // as a integer multiply is faster then a 9 branch match. #[inline] fn mul(self, rhs: Self) -> Self::Output { - // SAFETY: Sign is repr(i8) and will be (-1, 0, 1) so the product must be (1, 0, -1) + // SAFETY: Sign is repr(i8) and in the range of (-1, 0, 1) so the product must be (1, 0, -1) unsafe { core::mem::transmute::( (self as i8) * (rhs as i8) ) } @@ -73,7 +73,7 @@ impl core::ops::Neg for Sign { #[inline] fn neg(self) -> Self::Output { - // SAFETY: Sign is repr(i8) and will be (-1, 0, 1) so this is safe + // SAFETY: Sign is repr(i8) and in the range of (-1, 0, 1) so this is safe unsafe { core::mem::transmute::( -(self as i8) ) } diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 5a06d64..837f67c 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -41,7 +41,8 @@ where for _ in 0..height { let mut xd = start[0]; for _ in 0..width { - let p = [xd, yd]; + // let p = [xd, yd]; + let p = [xd * 1e305, yd * 1e305]; data.push(predicate(p).partial_cmp(&0.).unwrap()); xd = nextafter(xd, std::f64::INFINITY); } From 9ce53133e52fcc14f8502efa957f4eb00964e590 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 13 May 2025 13:10:47 -0400 Subject: [PATCH 04/16] Start on adding fuzzing --- fuzz/.gitignore | 5 ++ fuzz/Cargo.toml | 55 +++++++++++++++++++ fuzz/fuzz_targets/fuzz_in_circle_2d_sos.rs | 17 ++++++ fuzz/fuzz_targets/fuzz_in_sphere_3d_sos.rs | 17 ++++++ fuzz/fuzz_targets/fuzz_orient_2d.rs | 12 ++++ fuzz/fuzz_targets/fuzz_orient_2dlifted_sos.rs | 14 +++++ fuzz/fuzz_targets/fuzz_orient_3d.rs | 12 ++++ fuzz/fuzz_targets/fuzz_orient_3dlifted_sos.rs | 12 ++++ 8 files changed, 144 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/fuzz_in_circle_2d_sos.rs create mode 100644 fuzz/fuzz_targets/fuzz_in_sphere_3d_sos.rs create mode 100644 fuzz/fuzz_targets/fuzz_orient_2d.rs create mode 100644 fuzz/fuzz_targets/fuzz_orient_2dlifted_sos.rs create mode 100644 fuzz/fuzz_targets/fuzz_orient_3d.rs create mode 100644 fuzz/fuzz_targets/fuzz_orient_3dlifted_sos.rs diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..abe8855 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +cargo.lock diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..fc5f066 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "geogram_predicates-fuzz" +version = "0.0.0" +publish = false +edition = "2024" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +geogram_predicates = { path = "../", package = "geogram_predicates" } +geogram_predicates_legacy = { git = "https://github.com/glennDittmann/geogram_predicates.git", rev = "30c375e", package = "geogram_predicates" } + +[[bin]] +name = "fuzz_orient_3d" +path = "fuzz_targets/fuzz_orient_3d.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz_orient_2d" +path = "fuzz_targets/fuzz_orient_2d.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz_in_circle_2d_sos" +path = "fuzz_targets/fuzz_in_circle_2d_sos.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz_in_sphere_3d_sos" +path = "fuzz_targets/fuzz_in_sphere_3d_sos.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz_orient_2dlifted_sos" +path = "fuzz_targets/fuzz_orient_2dlifted_sos.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fuzz_orient_3dlifted_sos" +path = "fuzz_targets/fuzz_orient_3dlifted_sos.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/fuzz_in_circle_2d_sos.rs b/fuzz/fuzz_targets/fuzz_in_circle_2d_sos.rs new file mode 100644 index 0000000..c7a65b6 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_in_circle_2d_sos.rs @@ -0,0 +1,17 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use geogram_predicates::{Point2d, Sign}; + +fuzz_target!(|data: (Point2d, Point2d, Point2d, Point2d)| { + let (a, b, c, p) = data; + match geogram_predicates::in_circle_2d_sos::(&a, &b, &c, &p) { + Sign::Zero => panic!("can't be zero"), + _ => () + } + + match geogram_predicates::in_circle_2d_sos::(&a, &b, &c, &p) { + Sign::Zero => panic!("can't be zero"), + _ => () + } +}); diff --git a/fuzz/fuzz_targets/fuzz_in_sphere_3d_sos.rs b/fuzz/fuzz_targets/fuzz_in_sphere_3d_sos.rs new file mode 100644 index 0000000..900c5d3 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_in_sphere_3d_sos.rs @@ -0,0 +1,17 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use geogram_predicates::{Point3d, Sign}; + +fuzz_target!(|data: (Point3d, Point3d, Point3d, Point3d, Point3d)| { + let (a, b, c, d, p) = data; + match geogram_predicates::in_sphere_3d_sos::(&a, &b, &c, &d, &p) { + Sign::Zero => panic!("can't be zero"), + _ => () + } + + match geogram_predicates::in_sphere_3d_sos::(&a, &b, &c, &d, &p) { + Sign::Zero => panic!("can't be zero"), + _ => () + } +}); diff --git a/fuzz/fuzz_targets/fuzz_orient_2d.rs b/fuzz/fuzz_targets/fuzz_orient_2d.rs new file mode 100644 index 0000000..9eb02a7 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_orient_2d.rs @@ -0,0 +1,12 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use geogram_predicates::Point2d; + +fuzz_target!(|data: (Point2d, Point2d, Point2d)| { + let (a, b, c) = data; + assert_eq!(geogram_predicates::orient_2d(&a, &b, &c), geogram_predicates_legacy::orient_2d(&a, &b, &c) as i8); + // match geogram_predicates::orient_2d(&a, &b, &c) { + // _ => () + // } +}); diff --git a/fuzz/fuzz_targets/fuzz_orient_2dlifted_sos.rs b/fuzz/fuzz_targets/fuzz_orient_2dlifted_sos.rs new file mode 100644 index 0000000..b517ed8 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_orient_2dlifted_sos.rs @@ -0,0 +1,14 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use geogram_predicates::Point2d; + +fuzz_target!(|data: (Point2d, Point2d, Point2d, Point2d, [f64; 4])| { + let (a, b, c, p, heights) = data; + + // fails if input contains nan, geogram is failing not us + assert_eq!( + geogram_predicates::orient_2dlifted_sos(&a, &b, &c, &p, heights), + geogram_predicates_legacy::orient_2dlifted_SOS(&a, &b, &c, &p, heights[0], heights[1], heights[2], heights[3]) as i8 + ) +}); diff --git a/fuzz/fuzz_targets/fuzz_orient_3d.rs b/fuzz/fuzz_targets/fuzz_orient_3d.rs new file mode 100644 index 0000000..3cbca70 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_orient_3d.rs @@ -0,0 +1,12 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use geogram_predicates::Point3d; + +fuzz_target!(|data: (Point3d, Point3d, Point3d, Point3d)| { + let (a, b, c, d) = data; + assert_eq!(geogram_predicates::orient_3d(&a, &b, &c, &d), geogram_predicates_legacy::orient_3d(&a, &b, &c, &d) as i8); + // match geogram_predicates::orient_3d(&a, &b, &c, &d) { + // _ => () + // } +}); diff --git a/fuzz/fuzz_targets/fuzz_orient_3dlifted_sos.rs b/fuzz/fuzz_targets/fuzz_orient_3dlifted_sos.rs new file mode 100644 index 0000000..cb6af38 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_orient_3dlifted_sos.rs @@ -0,0 +1,12 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use geogram_predicates::Point3d; + +fuzz_target!(|data: (Point3d, Point3d, Point3d, Point3d, Point3d, [f64; 5])| { + let (a, b, c, d, p, heights) = data; + + match geogram_predicates::orient_3dlifted_sos(&a, &b, &c, &d, &p, heights) { + _ => () + } +}); From ca5fbcd41fce05900060dec07db377c6c6a4ab54 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Sun, 18 May 2025 17:51:41 -0400 Subject: [PATCH 05/16] Improve `with_capacity` --- src/expansion/expansion.rs | 10 ++++++---- src/expansion/macros.rs | 3 +-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index 66da198..3203704 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -58,7 +58,7 @@ impl Default for Expansion { } impl Expansion { - pub(crate) const fn new() -> Self { + pub(crate) fn new() -> Self { Self::with_capacity(N) } @@ -82,12 +82,12 @@ impl Expansion { /// assert_eq!(e.capacity(), 10); /// assert_eq!(e.length(), 0); /// ``` - pub const fn with_capacity(capacity: usize) -> Self { - debug_assert!(N == capacity); + pub fn with_capacity(capacity: usize) -> Self { + debug_assert!(N >= capacity); // it would be nice if this used capacity Self { - data: SmallVec::::new(), + data: SmallVec::::with_capacity(capacity), } } @@ -279,6 +279,7 @@ impl Expansion { self.data.extend_from_slice(b.data()); self.optimize(); + self.data.shrink_to_fit(); self } @@ -323,6 +324,7 @@ impl Expansion { } self.optimize(); + self.data.shrink_to_fit(); self } diff --git a/src/expansion/macros.rs b/src/expansion/macros.rs index ddeeaf4..6725313 100644 --- a/src/expansion/macros.rs +++ b/src/expansion/macros.rs @@ -105,11 +105,10 @@ macro_rules! expansion_det2x2 { [$a21:expr, $a22:expr]$(,)? ) => {{ // Compute exactly the capacity needed - #[cfg(debug_assertions)] let cap = $crate::Expansion::det2x2_capacity(&$a11, &$a12, &$a21, &$a22); // Allocate an Expansion with that capacity - let mut e = $crate::Expansion::new(); + let mut e = $crate::Expansion::with_capacity(cap); // Perform the determinant assignment e.assign_det2x2(&$a11, &$a12, &$a21, &$a22); From 8a3c948853f02446737edc07b8bcde3895f8822b Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Wed, 21 May 2025 14:11:45 -0400 Subject: [PATCH 06/16] Fix and lint --- src/expansion/expansion.rs | 34 +++++++++--------- src/expansion/macros.rs | 21 ------------ src/lib.rs | 2 ++ src/orient_2dlifted.rs | 70 ++++++++++++++++++-------------------- src/orient_3dlifted.rs | 49 +++++++++++++------------- 5 files changed, 77 insertions(+), 99 deletions(-) diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index 3203704..67335d5 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -92,13 +92,13 @@ impl Expansion { } #[inline] - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.data.len() } /// Returns `true` if the vector is empty #[inline] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.data.len() == 0 } @@ -142,6 +142,7 @@ impl Expansion { pub fn assign(&mut self, a: f64) -> &mut Self { self.data.clear(); self.data.push(a); + self } @@ -174,7 +175,7 @@ impl Expansion { } pub(crate) fn scale_fast(&mut self, s: f64) -> &mut Self { - for v in self.data.iter_mut() { + for v in &mut self.data { *v *= s; } self @@ -195,10 +196,10 @@ impl Expansion { } pub fn sign(&self) -> Sign { - if self.is_empty() { + if let Some(data_last) = self.data.last() { + geo_sign(*data_last) + } else /* empty */ { Sign::Zero - } else { - geo_sign(*self.data.last().unwrap()) } } @@ -265,7 +266,7 @@ impl From<&[f64]> for Expansion { impl Expansion { /// Compute capacity needed to multiply two expansions a and b. pub(crate) fn product_capacity(a: &Expansion, b: &Expansion) -> usize { - a.length().saturating_mul(b.length()).saturating_mul(2).max(1) + a.len().saturating_mul(b.len()).saturating_mul(2).max(1) } /// Assign `self` = a + b (expansion sum). @@ -275,6 +276,12 @@ impl Expansion { a: &Expansion, b: &Expansion, ) -> &mut Self { + // todo maybe it's faster if `a` and `b` are optimized first if `self.capacity` is less then `a.len + b.len` + // if self.capacity() < a.len() + b.len() { + // a.optimize(); + // b.optimize(); + // } + self.data.extend_from_slice(a.data()); self.data.extend_from_slice(b.data()); @@ -314,8 +321,8 @@ impl Expansion { let mut idx = 0; // for each pair (i,j), compute two_product and write low,high - for &ai in a.data.iter() { - for &bi in b.data.iter() { + for &ai in &a.data { + for &bi in &b.data { let (low, high) = two_product(ai, bi); self.data[idx] = low; self.data[idx + 1] = high; @@ -328,18 +335,13 @@ impl Expansion { self } - /// Return the number of components in the expansion. - pub fn length(&self) -> usize { - self.data.len() - } - /// Remove trailing zero components to maintain a canonical form. /// /// After this call, `length()` is the smallest index such that /// the last component is non-zero, or zero if all components are zero. pub(crate) fn optimize(&mut self) -> &mut Self { while let Some(&last) = self.data.last() { - if last < f64::EPSILON { + if last == 0.0 { self.data.pop(); } else { break; @@ -367,7 +369,7 @@ impl Expansion { let c11 = Self::det2x2_capacity(a22, a23, a32, a33); let c12 = Self::det2x2_capacity(a21, a23, a31, a33); let c13 = Self::det2x2_capacity(a21, a22, a31, a32); - 2 * ((a11.length() * c11) + (a12.length() * c12) + (a13.length() * c13)) + 2 * ((a11.len() * c11) + (a12.len() * c12) + (a13.len() * c13)) } /// Assign to `self` the 3×3 determinant of the nine expansions. diff --git a/src/expansion/macros.rs b/src/expansion/macros.rs index 6725313..b3e4dd4 100644 --- a/src/expansion/macros.rs +++ b/src/expansion/macros.rs @@ -62,27 +62,6 @@ macro_rules! expansion_det3x3 { ); e }}; - ( - [$a11:expr, $a12:expr, $a13:expr], - [$a21:expr, $a22:expr, $a23:expr], - [$a31:expr, $a32:expr, $a33:expr]$(,)? - ) => {{ - // Compute exactly the capacity needed - let cap = $crate::Expansion::det3x3_capacity( - [&$a11, &$a12, &$a13], - [&$a21, &$a22, &$a23], - [&$a31, &$a32, &$a33], - ); - // Allocate an Expansion with that capacity - let mut e = $crate::Expansion::with_capacity(cap); - // Perform the determinant assignment - e.assign_det3x3( - [&$a11, &$a12, &$a13], - [&$a21, &$a22, &$a23], - [&$a31, &$a32, &$a33], - ); - e - }}; } /// Compute the 2×2 determinant of four `Expansion`s, returning a new `Expansion`. diff --git a/src/lib.rs b/src/lib.rs index 80061fc..5915ae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -513,6 +513,7 @@ pub fn points_are_colinear_3d(p1: &[f64; 3], p2: &[f64; 3], p3: &[f64; 3]) -> bo /// /// assert!(points_are_identical_2d(&p1, &p2)); /// ``` +#[must_use] pub const fn points_are_identical_2d(p1: &Point2d, p2: &Point2d) -> bool { p1[0] == p2[0] && p1[1] == p2[1] } @@ -536,6 +537,7 @@ pub const fn points_are_identical_2d(p1: &Point2d, p2: &Point2d) -> bool { /// /// assert!(points_are_identical_3d(&p1, &p2)); /// ``` +#[must_use] pub const fn points_are_identical_3d(p1: &Point3d, p2: &Point3d) -> bool { p1[0] == p2[0] && p1[1] == p2[1] && p1[2] == p2[2] } diff --git a/src/orient_2dlifted.rs b/src/orient_2dlifted.rs index 5fe5e72..17458eb 100644 --- a/src/orient_2dlifted.rs +++ b/src/orient_2dlifted.rs @@ -19,7 +19,7 @@ use core::cmp::Ordering; /// ### Return values /// - `+1` - if p3' lies below the plane /// - `-1` - if p3' lies above the plane -/// - `0` - if `p'` lies exactly on the hyperplane +/// - `0` - if `p'` lies exactly on the hyperplane /// /// # Example /// For a graphical representation see this [geogebra example](https://www.geogebra.org/m/etyzj96t) of the code below. @@ -34,16 +34,16 @@ use core::cmp::Ordering; /// // Additionally in this scenario, each point is associated with a weight w_i /// // And the height of a point is defined as h_i = x_i**2 + y_i**2 - w_i /// // One can interpret the height as the z-coordinate of a point lifted to R^3 -/// let h_a = a[0].powf(2.0) + a[1].powf(2.0) + 2.0; // i.e. w_a = -2.0 -/// let h_b = b[0].powf(2.0) + b[1].powf(2.0) - 1.0; // i.e. w_b = 1.0 -/// let h_c = c[0].powf(2.0) + c[1].powf(2.0) - 0.5; // i.e. w_c = 0.5 +/// let h_a = a[0].powi(2) + a[1].powi(2) + 2.0; // i.e. w_a = -2.0 +/// let h_b = b[0].powi(2) + b[1].powi(2) - 1.0; // i.e. w_b = 1.0 +/// let h_c = c[0].powi(2) + c[1].powi(2) - 0.5; // i.e. w_c = 0.5 /// /// // Define weighted points, to test against the plane, that contains the lifted triangle /// let p_below: [f64; 2] = [0.6, 0.6]; -/// let h_p_below = p_below[0].powf(2.0) + p_below[1].powf(2.0) + 1.28; // i.e. w_p_below = -1.28 +/// let h_p_below = p_below[0].powi(2) + p_below[1].powi(2) + 1.28; // i.e. w_p_below = -1.28 /// /// let p_above: [f64; 2] = [0.6, 0.6]; -/// let h_p_above = p_above[0].powf(2.0) + p_above[1].powf(2.0) + 2.78; // i.e. w_p_above = -2.78 +/// let h_p_above = p_above[0].powi(2) + p_above[1].powi(2) + 2.78; // i.e. w_p_above = -2.78 /// /// let orientation_below = orient_2dlifted_sos(&a, &b, &c, &p_below, [h_a, h_b, h_c, h_p_below]); /// assert_eq!(1, orientation_below); @@ -51,6 +51,9 @@ use core::cmp::Ordering; /// let orientation_above = orient_2dlifted_sos(&a, &b, &c, &p_above, [h_a, h_b, h_c, h_p_above]); /// assert_eq!(-1, orientation_above); /// ``` +/// ## Panics +/// This will panic if any input contains a NaN value +#[must_use] pub fn orient_2dlifted_sos( a: &Point2d, b: &Point2d, @@ -101,20 +104,17 @@ fn side3_2dlifted_2d_filter( } else if max2 > upper_bound_1 { upper_bound_1 = max2; } - if lower_bound_1 < 5.00368081960964635413e-147 { + + if lower_bound_1 < 5.00368081960964635413e-147 || upper_bound_1 > 5.59936185544450928309e+101 { return FPG_UNCERTAIN_VALUE; + } + eps = 8.88720573725927976811e-16 * (max1 * max2); + if delta3 > eps { + int_tmp_result = Sign::Positive; + } else if delta3 < -eps { + int_tmp_result = Sign::Negative; } else { - if upper_bound_1 > 5.59936185544450928309e+101 { - return FPG_UNCERTAIN_VALUE; - } - eps = 8.88720573725927976811e-16 * (max1 * max2); - if delta3 > eps { - int_tmp_result = Sign::Positive; - } else if delta3 < -eps { - int_tmp_result = Sign::Negative; - } else { - return FPG_UNCERTAIN_VALUE; - } + return FPG_UNCERTAIN_VALUE; } let delta3_sign = int_tmp_result; @@ -137,20 +137,16 @@ fn side3_2dlifted_2d_filter( } // I think that is a bit out of the f64 range - if lower_bound_1 < 1.63288018496748314939e-98 { + if lower_bound_1 < 1.63288018496748314939e-98 || upper_bound_1 > 5.59936185544450928309e+101 { return FPG_UNCERTAIN_VALUE; + } + eps = 5.11071278299732992696e-15 * ((max3 * max5) * max4); + if r > eps { + int_tmp_result_ffwkcaa = Sign::Positive; + } else if r < -eps { + int_tmp_result_ffwkcaa = Sign::Negative; } else { - if upper_bound_1 > 5.59936185544450928309e+101 { - return FPG_UNCERTAIN_VALUE; - } - eps = 5.11071278299732992696e-15 * ((max3 * max5) * max4); - if r > eps { - int_tmp_result_ffwkcaa = Sign::Positive; - } else if r < -eps { - int_tmp_result_ffwkcaa = Sign::Negative; - } else { - return FPG_UNCERTAIN_VALUE; - } + return FPG_UNCERTAIN_VALUE; } delta3_sign * int_tmp_result_ffwkcaa @@ -186,7 +182,7 @@ fn side3h_2d_exact_sos( let delta3_sign = delta3.sign(); // This fails on NaN input - debug_assert!(delta3_sign != 0); + assert!(delta3_sign != 0); let r_1: Expansion<4> = expansion_product!(delta1, a13, 4); let mut r_2: Expansion<4> = expansion_product!(delta2, a23, 4); @@ -208,10 +204,10 @@ fn side3h_2d_exact_sos( let mut p_sort = [p0, p1, p2, p3]; p_sort.sort_unstable_by(lexico_compare_2d); - for i in 0..3 { - if p_sort[i] == p0 { + for &sorting in p_sort.iter().take(3) { + if sorting == p0 { let z1 = { - let mut expansion: Expansion<3> = Expansion::new(); + let mut expansion: Expansion<8> = Expansion::with_capacity(delta2.len() + delta1.len()); expansion.assign_diff(&delta2, &delta1); expansion }; @@ -220,17 +216,17 @@ fn side3h_2d_exact_sos( if z_sign != 0 { return delta3_sign * z_sign; } - } else if p_sort[i] == p1 { + } else if sorting == p1 { let delta1_sign = delta1.sign(); if delta1_sign != 0 { return delta3_sign * delta1_sign; } - } else if p_sort[i] == p2 { + } else if sorting == p2 { let delta2_sign = delta2.sign(); if delta2_sign != 0 { return (-delta3_sign) * delta2_sign; } - } else if p_sort[i] == p3 { + } else if sorting == p3 { return Sign::Negative; } } diff --git a/src/orient_3dlifted.rs b/src/orient_3dlifted.rs index c7fd34f..8481687 100644 --- a/src/orient_3dlifted.rs +++ b/src/orient_3dlifted.rs @@ -34,17 +34,17 @@ use core::cmp::Ordering; /// // Additionally in this scenario, each point is associated with a weight w_i /// // And the height of a point is defined as h_i = x_i**2 + y_i**2 + z_i**2 - w_i /// // One can interpret the height as the 4th-coordinate of a point lifted to R^4 -/// let h_a = a[0].powf(2.0) + a[1].powf(2.0) + a[2].powf(2.0) + 2.0; // i.e. w_a = -2.0 -/// let h_b = b[0].powf(2.0) + b[1].powf(2.0) + b[2].powf(2.0) - 1.0; // i.e. w_b = 1.0 -/// let h_c = c[0].powf(2.0) + c[1].powf(2.0) + c[2].powf(2.0) - 0.5; // i.e. w_c = 0.5 -/// let h_d = d[0].powf(2.0) + d[1].powf(2.0) + d[2].powf(2.0) - 0.5; // i.e. w_c = 0.5 +/// let h_a = a[0].powi(2) + a[1].powi(2) + a[2].powi(2) + 2.0; // i.e. w_a = -2.0 +/// let h_b = b[0].powi(2) + b[1].powi(2) + b[2].powi(2) - 1.0; // i.e. w_b = 1.0 +/// let h_c = c[0].powi(2) + c[1].powi(2) + c[2].powi(2) - 0.5; // i.e. w_c = 0.5 +/// let h_d = d[0].powi(2) + d[1].powi(2) + d[2].powi(2) - 0.5; // i.e. w_c = 0.5 /// /// // Define weighted points, to test against the hyperplane, that contains the lifted tetrahedron /// let p_below: [f64; 3] = [0.6, 0.6, 0.6]; -/// let h_p_below = p_below[0].powf(2.0) + p_below[1].powf(2.0) + 0.28; // i.e. w_p_below = -0.28 +/// let h_p_below = p_below[0].powi(2) + p_below[1].powi(2) + 0.28; // i.e. w_p_below = -0.28 /// /// let p_above: [f64; 3] = [0.6, 0.6, 0.6]; -/// let h_p_above = p_above[0].powf(2.0) + p_above[1].powf(2.0) + 2.78; // i.e. w_p_above = -2.78 +/// let h_p_above = p_above[0].powi(2) + p_above[1].powi(2) + 2.78; // i.e. w_p_above = -2.78 /// /// let orientation_below = orient_3dlifted_sos(&a, &b, &c, &d, &p_below, [h_a, h_b, h_c, h_d, h_p_below]); /// assert_eq!(1, orientation_below); @@ -53,6 +53,9 @@ use core::cmp::Ordering; /// assert_eq!(-1, orientation_above); /// /// ``` +/// ## Panics +/// This will panic if any input contains a NaN value +#[must_use] pub fn orient_3dlifted_sos( a: &[f64; 3], b: &[f64; 3], @@ -128,20 +131,16 @@ fn side4h_3d_filter( } else if max3 > upper_bound_1 { upper_bound_1 = max3; } - if lower_bound_1 < 1.63288018496748314939e-98 { + if lower_bound_1 < 1.63288018496748314939e-98 || upper_bound_1 > 7.23700557733225980357e+75 { return FPG_UNCERTAIN_VALUE; + } + eps = 5.11071278299732992696e-15 * ((max2 * max3) * max1); + if delta4 > eps { + int_tmp_result = Sign::Positive; + } else if delta4 < -eps { + int_tmp_result = Sign::Negative; } else { - if upper_bound_1 > 7.23700557733225980357e+75 { - return FPG_UNCERTAIN_VALUE; - } - eps = 5.11071278299732992696e-15 * ((max2 * max3) * max1); - if delta4 > eps { - int_tmp_result = Sign::Positive; - } else if delta4 < -eps { - int_tmp_result = Sign::Negative; - } else { - return FPG_UNCERTAIN_VALUE; - } + return FPG_UNCERTAIN_VALUE; } let delta4_sign = int_tmp_result; @@ -228,7 +227,7 @@ fn side4h_3d_exact_sos( let delta4: Expansion<6> = expansion_det3x3!(a11, a12, a13, a21, a22, a23, a31, a32, a33); let delta4_sign = delta4.sign(); - debug_assert!(delta4_sign != 0); + assert!(delta4_sign != 0); let r_1: Expansion<2> = expansion_product!(delta1, a14); let mut r_2: Expansion<2> = expansion_product!(delta2, a24); @@ -245,8 +244,8 @@ fn side4h_3d_exact_sos( let mut p_sort = [p0, p1, p2, p3, p4]; p_sort.sort_unstable_by(lexico_compare_3d); - for i in 0..4 { - if p_sort[i] == p0 { + for &sorting in p_sort.iter().take(4) { + if sorting == p0 { let z1: Expansion<2> = expansion_diff!(Expansions: delta2, delta1); let z2: Expansion<2> = expansion_diff!(Expansions: delta4, delta3); let z: Expansion<1> = expansion_sum!(z1, z2); @@ -254,22 +253,22 @@ fn side4h_3d_exact_sos( if z_sign != 0 { return delta4_sign * z_sign; } - } else if p_sort[i] == p1 { + } else if sorting == p1 { let delta1_sign = delta1.sign(); if delta1_sign != 0 { return delta4_sign * delta1_sign; } - } else if p_sort[i] == p2 { + } else if sorting == p2 { let delta2_sign = delta2.sign(); if delta2_sign != 0 { return (-delta4_sign) * delta2_sign; } - } else if p_sort[i] == p3 { + } else if sorting == p3 { let delta3_sign = delta3.sign(); if delta3_sign != 0 { return delta4_sign * delta3_sign; } - } else if p_sort[i] == p4 { + } else if sorting == p4 { return Sign::Negative; } } From 6f878582ef9b60dffd53c428ca2f69ef8bb9b617 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Wed, 21 May 2025 19:05:58 -0400 Subject: [PATCH 07/16] Refactor and add `compress_expansion` to `optimize` --- src/expansion/expansion.rs | 75 ++++++++++++++++++++++++++++++++++---- src/expansion/macros.rs | 4 +- src/tests.rs | 5 ++- 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index 67335d5..2543876 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -64,28 +64,27 @@ impl Expansion { /// Create a new `Expansion` with the given capacity. /// - /// Internally uses a [`heapless::Vec`](`heapless::Vec`). + /// Internally uses a [`smallvec::Vec`](`smallvec::SmallVec`). /// /// ## Parameters - /// - `capacity`: maximum number of components, if this is less then the inline capacity it will still be 9. + /// - `capacity`: maximum number of components, if this is less then the inline capacity it will still be `N`. /// /// ## Examples /// ``` /// # use geogram_predicates::Expansion; /// let e: Expansion = Expansion::with_capacity(6); /// assert_eq!(e.capacity(), 6); // 6 is the default Expansion capacity - /// assert_eq!(e.length(), 0); + /// assert_eq!(e.len(), 0); /// ``` /// ```should_panic /// # use geogram_predicates::Expansion; /// let e = Expansion::<9>::with_capacity(10); /// assert_eq!(e.capacity(), 10); - /// assert_eq!(e.length(), 0); + /// assert_eq!(e.len(), 0); /// ``` pub fn with_capacity(capacity: usize) -> Self { debug_assert!(N >= capacity); - // it would be nice if this used capacity Self { data: SmallVec::::with_capacity(capacity), } @@ -129,14 +128,14 @@ impl Expansion { /// Assign a single double to this expansion. /// - /// After this call, `self.length() == 1` and `self[0] == a`. + /// After this call, `self.len() == 1` and `self[0] == a`. /// /// ## Examples /// ``` /// # use geogram_predicates::Expansion; /// let mut e = Expansion::<2>::with_capacity(2); /// e.assign(3.14); - /// assert_eq!(e.length(), 1); + /// assert_eq!(e.len(), 1); /// assert_eq!(e[0], 3.14); /// ``` pub fn assign(&mut self, a: f64) -> &mut Self { @@ -336,8 +335,9 @@ impl Expansion { } /// Remove trailing zero components to maintain a canonical form. + /// As well as compress into the least terms. /// - /// After this call, `length()` is the smallest index such that + /// After this call, `len()` is the smallest index such that /// the last component is non-zero, or zero if all components are zero. pub(crate) fn optimize(&mut self) -> &mut Self { while let Some(&last) = self.data.last() { @@ -348,9 +348,56 @@ impl Expansion { } } + self.compress_expansion(); + self } + /// Compression works by traversing the expansion from largest to smallest component, then back + /// from smallest to largest, replacing each adjacent pair with its two-component sum. + /// [Shewchuk 97](https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf) + fn compress_expansion(&mut self) { + let e = self; + + let m = e.len(); + // empty or one item is a no-op + if m <= 1 { + return; + } /* else if m == 2 { + return ; // sum of two + } */ + + let mut q; + + let mut bottom = m.saturating_sub(1); + #[allow(non_snake_case)] + let mut Q = e[bottom]; + + for i in (0..=(m as i32).saturating_sub(2)).rev() { + (Q, q) = fast_two_sum(Q, e[i as usize]); + + if q != 0.0 { + e.data[bottom] = Q; + bottom -= 1; + Q = q; + } + } + e.data[bottom] = Q; + + let mut top = 0; + for i in (bottom + 1)..m { + (Q, q) = fast_two_sum(e[i], Q); + + if q != 0.0 { + e.data[top] = q; + top += 1; + } + } + e.data[top] = Q; + + e.data.truncate(top + 1); + } + /// Compute the capacity needed to form the 3×3 determinant /// of the nine expansions a11…a33. /// @@ -505,3 +552,15 @@ impl core::ops::Mul for Expansion { prod } } + +#[inline(always)] +fn fast_two_sum_tail(a: f64, b: f64, x: f64) -> f64 { + let bvirt = x - a; + b - bvirt +} + +#[inline] +fn fast_two_sum(a: f64, b: f64) -> (f64, f64) { + let x = a + b; + (x, fast_two_sum_tail(a, b, x)) +} diff --git a/src/expansion/macros.rs b/src/expansion/macros.rs index b3e4dd4..39f5b22 100644 --- a/src/expansion/macros.rs +++ b/src/expansion/macros.rs @@ -5,11 +5,11 @@ /// ``` /// # use geogram_predicates::expansion; /// let e = expansion![1.0, 2.5, 3.75]; -/// assert_eq!(e.length(), 3); +/// assert_eq!(e.len(), 3); /// assert_eq!(&e.data(), &[1.0, 2.5, 3.75]); /// // or /// let e = expansion!(1.0); -/// # assert_eq!(e.length(), 1); +/// # assert_eq!(e.len(), 1); /// assert_eq!(&e.data(), &[1.0]); /// ``` #[macro_export] diff --git a/src/tests.rs b/src/tests.rs index 1350b03..cbaa054 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -204,7 +204,7 @@ fn test_det_3d() { } #[test] -fn test_orient_2dlifted_and_3dlifted() { +fn test_orient_2dlifted() { // 2D lifted: trivial case with zero weights equals incircle let a2 = [0.0, 0.0]; let b2 = [1.0, 0.0]; @@ -213,7 +213,10 @@ fn test_orient_2dlifted_and_3dlifted() { let h = 0.0; let res = gp::orient_2dlifted_sos(&a2, &b2, &c2, &p2, [h, h, h, h]); assert_eq!(res, gp::in_circle_2d_sos::(&a2, &b2, &c2, &p2)); +} +#[test] +fn test_orient_3dlifted() { // 3D lifted: trivial with zero weights equals insphere let a3 = [0.0, 0.0, 0.0]; let b3 = [1.0, 0.0, 0.0]; From 9e000ad29b2e1a08c9484b64cd3af0c77569fb10 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Thu, 22 May 2025 11:30:22 -0400 Subject: [PATCH 08/16] Add docs to compress_expansion and improve optimize perf --- src/expansion/expansion.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index 2543876..cd2500f 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -335,7 +335,6 @@ impl Expansion { } /// Remove trailing zero components to maintain a canonical form. - /// As well as compress into the least terms. /// /// After this call, `len()` is the smallest index such that /// the last component is non-zero, or zero if all components are zero. @@ -348,15 +347,24 @@ impl Expansion { } } - self.compress_expansion(); - self } + /// ### Compress into the least terms. /// Compression works by traversing the expansion from largest to smallest component, then back /// from smallest to largest, replacing each adjacent pair with its two-component sum. /// [Shewchuk 97](https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf) - fn compress_expansion(&mut self) { + /// ## Usage + /// ``` + /// # use geogram_predicates::Expansion; + /// let mut expansion = Expansion::from([0.1, 0.1]); + /// expansion.compress_expansion(); + /// # assert_eq!(expansion, Expansion::<2>::from([0.2].as_slice())); + /// ``` + /// ```ignore + /// assert_eq!(expansion, Expansion::from(0.2)); + /// ``` + pub fn compress_expansion(&mut self) { let e = self; let m = e.len(); From 669cbcf66c02de23f9e354b185fa7bc55781da12 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Thu, 22 May 2025 12:41:46 -0400 Subject: [PATCH 09/16] Improve `Expansion` comparison --- src/expansion/expansion.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index cd2500f..b36ffe6 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -29,14 +29,14 @@ impl fmt::Display for Expansion { } } -impl PartialEq for Expansion { - fn eq(&self, other: &Self) -> bool { +impl PartialEq> for Expansion { + fn eq(&self, other: &Expansion) -> bool { self.equals(other) } } -impl PartialOrd for Expansion { - fn partial_cmp(&self, other: &Expansion) -> Option { +impl PartialOrd> for Expansion { + fn partial_cmp(&self, other: &Expansion) -> Option { // Always returns Some(Ordering) because compare() gives a total order. let est_self = self.estimate(); let est_rhs = other.estimate(); @@ -46,11 +46,18 @@ impl PartialOrd for Expansion { impl core::ops::Index for Expansion { type Output = f64; + fn index(&self, idx: usize) -> &f64 { &self.data[idx] } } +impl core::ops::IndexMut for Expansion { + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + &mut self.data[idx] + } +} + impl Default for Expansion { fn default() -> Self { Self::with_capacity(N) @@ -202,7 +209,7 @@ impl Expansion { } } - pub(crate) fn equals(&self, rhs: &Expansion) -> bool { + pub(crate) fn equals(&self, rhs: &Expansion) -> bool { self.compare(rhs) == Sign::Zero } @@ -220,7 +227,7 @@ impl Expansion { /// let b = Expansion::from(2.0); /// assert!(a < b); /// ``` - pub(crate) fn compare(&self, rhs: &Expansion) -> Sign { + pub(crate) fn compare(&self, rhs: &Expansion) -> Sign { let est_self = self.estimate(); let est_rhs = rhs.estimate(); geo_sign(est_self - est_rhs) From b6082ef6d3a48239bfaf6a506380673b78835451 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Thu, 22 May 2025 13:51:47 -0400 Subject: [PATCH 10/16] Improve `compress_expansion` --- src/expansion/expansion.rs | 42 +++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index b36ffe6..72d6c79 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -4,7 +4,7 @@ use smallvec::SmallVec; #[derive(Clone, Debug)] pub struct Expansion { - /// Starts inline then moves to heap allocation once the inline capacity is exceeded + /// Starts inline then moves to heap allocation once the inline capacity (`N`) is exceeded data: SmallVec, } @@ -366,29 +366,32 @@ impl Expansion { /// # use geogram_predicates::Expansion; /// let mut expansion = Expansion::from([0.1, 0.1]); /// expansion.compress_expansion(); - /// # assert_eq!(expansion, Expansion::<2>::from([0.2].as_slice())); - /// ``` - /// ```ignore + /// /// assert_eq!(expansion, Expansion::from(0.2)); /// ``` pub fn compress_expansion(&mut self) { let e = self; - let m = e.len(); + let e_len = e.len(); // empty or one item is a no-op - if m <= 1 { + if e_len <= 1 { + return; + } else if e_len == 2 { + // sum of two + let sum = e[0] + e[1]; + + e.data.clear(); + e.data.push(sum); return; - } /* else if m == 2 { - return ; // sum of two - } */ + } let mut q; - let mut bottom = m.saturating_sub(1); + let mut bottom = e_len.saturating_sub(1); #[allow(non_snake_case)] let mut Q = e[bottom]; - for i in (0..=(m as i32).saturating_sub(2)).rev() { + for i in (0..=(e_len as i32).saturating_sub(2)).rev() { (Q, q) = fast_two_sum(Q, e[i as usize]); if q != 0.0 { @@ -400,7 +403,7 @@ impl Expansion { e.data[bottom] = Q; let mut top = 0; - for i in (bottom + 1)..m { + for i in (bottom + 1)..e_len { (Q, q) = fast_two_sum(e[i], Q); if q != 0.0 { @@ -415,14 +418,6 @@ impl Expansion { /// Compute the capacity needed to form the 3×3 determinant /// of the nine expansions a11…a33. - /// - /// Mirrors C++: - /// ```cpp - /// index_t c11 = det2x2_capacity(a22,a23,a32,a33); - /// index_t c12 = det2x2_capacity(a21,a23,a31,a33); - /// index_t c13 = det2x2_capacity(a21,a22,a31,a32); - /// return 2 * (a11.len()*c11 + a12.len()*c12 + a13.len()*c13); - /// ``` pub(crate) fn det3x3_capacity( [a11, a12, a13]: [&Expansion; 3], [a21, a22, a23]: [&Expansion; 3], @@ -431,6 +426,7 @@ impl Expansion { let c11 = Self::det2x2_capacity(a22, a23, a32, a33); let c12 = Self::det2x2_capacity(a21, a23, a31, a33); let c13 = Self::det2x2_capacity(a21, a22, a31, a32); + 2 * ((a11.len() * c11) + (a12.len() * c12) + (a13.len() * c13)) } @@ -543,7 +539,7 @@ const fn split(a: f64) -> (f64, f64) { } impl core::ops::Add for Expansion { - // todo use when generic_const_exprs is stabe + // todo use when generic_const_exprs is stable // type Output = Expansion<{N + N}>; type Output = Expansion; @@ -555,12 +551,12 @@ impl core::ops::Add for Expansion { } impl core::ops::Mul for Expansion { - // todo use when generic_const_exprs is stabe + // todo use when generic_const_exprs is stable // type Output = Expansion<{N.saturating_mul(b.length()).saturating_mul(2)}>; type Output = Expansion; fn mul(self, rhs: Expansion) -> Self::Output { - // todo when generic_const_exprs is stabe Expansion<{SN.max(RN)}> + // todo when generic_const_exprs is stable Expansion<{SN.max(RN)}> let mut prod: Expansion = Expansion::with_capacity(Expansion::::product_capacity(&self, &rhs)); prod.assign_product(&self, &rhs); From c4a538e72be516ebd2764afeba1306337377fe94 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Thu, 22 May 2025 14:20:33 -0400 Subject: [PATCH 11/16] update workflows --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9e0ba53..80577b1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: ["main"] + branches: ["main", "dev-rust-port"] pull_request: - branches: ["*"] + branches: ["main", "dev-rust-port", "*"] env: CARGO_TERM_COLOR: always From 43a877e399c60168e1e8e1e31055ea482dc4df79 Mon Sep 17 00:00:00 2001 From: Ryan <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:35:21 -0500 Subject: [PATCH 12/16] Update licence to be more permisive --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 401b9bc..fb949b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,8 @@ rust-version = "1.85.1" repository = "https://github.com/glennDittmann/geogram_predicates" authors = ["Glenn Dittmann", "TimTheBig"] -license = "LGPL-3.0 OR MIT" +# when the legacy feature is enabled the c++ code it use is apache +license = "MIT OR (MIT AND Apache-2.0)" keywords = ["computer-graphics", "math", "geometry", "predicates", "robust"] categories = ["no-std", "mathematics", "graphics"] From cc8752bc31fd606c10171cab92218c49a8ef0b61 Mon Sep 17 00:00:00 2001 From: Ryan <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:36:22 -0500 Subject: [PATCH 13/16] Update README.md licence info --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a0a0d6..71d548b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Be sure to check it out [here](https://github.com/BrunoLevy/geogram). It yields easy access to dependency-free parts of its code base, as so called _Pluggable Software Modules_ (PSM), which in turn make it easy to write `cxx_bridges` for these. -This library is licenced under the `LGPL-3.0 OR MIT`, when the legacy feature is enabled the c++ code it uses is under `Apache-2.0`. +This library is licenced under the `MIT`, when the legacy feature is enabled the c++ code it uses is under `Apache-2.0`. ## Example From 2f93a6c3187cd7bdc37d21c05a26024fe915d9e4 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:38:36 -0500 Subject: [PATCH 14/16] Add more tests --- src/tests.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index cbaa054..4d9deed 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -225,7 +225,65 @@ fn test_orient_3dlifted() { let p3 = [0.1, 0.1, 0.1]; let h3 = 0.0; let res3 = gp::orient_3dlifted_sos(&a3, &b3, &c3, &d3, &p3, [h3, h3, h3, h3, h3]); + assert_eq!(res3, 1 /* res from c++ */); + assert_eq!(res3, gp::in_sphere_3d_sos::(&a3, &b3, &c3, &d3, &p3)); + + let a4 = [0.0, 3.0, 0.0]; + let res4 = gp::orient_3dlifted_sos(&a4, &b3, &c3, &d3, &p3, [h3, h3, h3, h3, h3]); + assert_eq!(res4, 1 /* res from c++ */); + + let a = [0.0, 3.0, 0.0]; + let b = [-1.0, 0.0, 0.0]; + let c = [0.0, -1.0, 0.0]; + let d = [-9.0, 0.0, -1.0]; + let p = [-0.1, -0.1, -0.1]; + let res5 = gp::orient_3dlifted_sos( + &a, &b, &c, &d, &p, + [2.0, 0.1, -0.1, -3.0, 0.0] + ); + assert_eq!(res5, 1 /* res from c++ */); + + // Case: zero weights, point inside the sphere + let a = [1.0, 0.0, 0.0]; + let b = [0.0, 1.0, 0.0]; + let c = [0.0, 0.0, 1.0]; + let d = [1.0, 1.0, 1.0]; + let p = [0.5, 0.5, 0.5]; + let weights = [0.0; 5]; + let res = gp::orient_3dlifted_sos(&a, &b, &c, &d, &p, weights); + assert_eq!(res, 1); + assert_eq!(res, gp::in_sphere_3d_sos::(&a, &b, &c, &d, &p)); + + // Case: non-zero weights, point outside sphere + let weights = [1.0, 2.0, 3.0, 4.0, 10.0]; + let res = gp::orient_3dlifted_sos(&a, &b, &c, &d, &p, weights); + assert_eq!(res, -1); + + // Case: point lies on the lifted sphere (weights chosen for coplanarity) + let weights = [1.0, 1.0, 1.0, 1.0, 1.0]; + let res = gp::orient_3dlifted_sos(&a, &b, &c, &d, &p, weights); + assert_eq!(res, 0); + + // Degenerate case: repeated point + let res = gp::orient_3dlifted_sos(&a, &a, &c, &d, &p, weights); + assert_eq!(res, 0); // Expect zero or handled degeneracy + + // Degenerate case: flat tetrahedron (coplanar points) + let a = [0.0, 0.0, 0.0]; + let b = [1.0, 0.0, 0.0]; + let c = [0.0, 1.0, 0.0]; + let d = [1.0, 1.0, 0.0]; + let p = [0.5, 0.5, 0.0]; + let weights = [0.0, 0.0, 0.0, 0.0, 0.0]; + let res = gp::orient_3dlifted_sos(&a, &b, &c, &d, &p, weights); + assert_eq!(res, 0); // Planar configuration should be degenerate + + // Case: large weights, test numerical stability + let weights = [1e9, 1e9, 1e9, 1e9, 1e9]; + let res = gp::orient_3dlifted_sos(&a, &b, &c, &d, &p, weights); + // Result depends on actual implementation; this checks for crash/stability + assert!((res as i8).abs() <= 1); } #[test] From 7cc0928c0f8ff3b10895078087049b26a74bb995 Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:48:26 -0500 Subject: [PATCH 15/16] Update deps --- Cargo.lock | 264 +++++++++++++++++++----------------------- Cargo.toml | 6 +- test_utils/Cargo.toml | 6 +- 3 files changed, 128 insertions(+), 148 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 122fba7..951aad4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,15 +4,15 @@ version = 4 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "approx" @@ -25,45 +25,46 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "cc" -version = "1.2.22" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.38" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" dependencies = [ "anstyle", "clap_lex", @@ -72,15 +73,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codespan-reporting" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ "serde", "termcolor", @@ -89,20 +90,21 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "cxx" -version = "1.0.158" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a71ea7f29c73f7ffa64c50b83c9fe4d3a6d4be89a86b009eb80d5a6d3429d741" +checksum = "47ac4eaf7ebe29e92f1b091ceefec7710a53a6f6154b2460afda626c113b65b9" dependencies = [ "cc", + "cxx-build", "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", @@ -112,12 +114,13 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.158" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a8232661d66dcf713394726157d3cfe0a89bfc85f52d6e9f9bbc2306797fe7" +checksum = "2abd4c3021eefbac5149f994c117b426852bca3a0aad227698527bca6d4ea657" dependencies = [ "cc", "codespan-reporting", + "indexmap", "proc-macro2", "quote", "scratch", @@ -126,12 +129,13 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.158" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f44296c8693e9ea226a48f6a122727f77aa9e9e338380cb021accaeeb7ee279" +checksum = "6f12fbc5888b2311f23e52a601e11ad7790d8f0dbb903ec26e2513bf5373ed70" dependencies = [ "clap", "codespan-reporting", + "indexmap", "proc-macro2", "quote", "syn", @@ -139,22 +143,28 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.158" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f69c181c176981ae44ba9876e2ea41ce8e574c296b38d06925ce9214fb8e4" +checksum = "83d3dd7870af06e283f3f8ce0418019c96171c9ce122cfb9c8879de3d84388fd" [[package]] name = "cxxbridge-macro" -version = "1.0.158" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8faff5d4467e0709448187df29ccbf3b0982cc426ee444a193f87b11afb565a8" +checksum = "a26f0d82da663316786791c3d0e9f9edc7d1ee1f04bdad3d2643086a69d6256c" dependencies = [ + "indexmap", "proc-macro2", "quote", - "rustversion", "syn", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "fdeflate" version = "0.3.7" @@ -164,11 +174,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -185,9 +201,9 @@ dependencies = [ [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "geogram_predicates" @@ -203,11 +219,27 @@ dependencies = [ "test_utils", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "libc" -version = "0.2.172" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" @@ -217,9 +249,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "link-cplusplus" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" dependencies = [ "cc", ] @@ -236,9 +268,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -246,9 +278,9 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.33.2" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" dependencies = [ "approx", "matrixmultiply", @@ -262,9 +294,9 @@ dependencies = [ [[package]] name = "nalgebra-macros" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" dependencies = [ "proc-macro2", "quote", @@ -316,9 +348,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "png" -version = "0.17.16" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ "bitflags", "crc32fast", @@ -329,18 +361,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -358,31 +390,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" [[package]] -name = "rustversion" -version = "1.0.20" +name = "scratch" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" [[package]] -name = "scratch" -version = "1.0.8" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] [[package]] -name = "serde" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -397,9 +433,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simba" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" dependencies = [ "approx", "num-complex", @@ -415,9 +451,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "smallvec" -version = "2.0.0-alpha.11" +version = "2.0.0-alpha.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b96efa4bd6bdd2ff0c6615cc36fc4970cbae63cfd46ddff5cee35a1b4df570" +checksum = "ef784004ca8777809dcdad6ac37629f0a97caee4c685fcea805278d81dd8b857" [[package]] name = "strsim" @@ -427,9 +463,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -455,100 +491,42 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", ] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index fb949b2..8aad443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,12 @@ keywords = ["computer-graphics", "math", "geometry", "predicates", "robust"] categories = ["no-std", "mathematics", "graphics"] [dependencies] -smallvec = "2.0.0-alpha.11" -nalgebra = { version = "0.33", features = ["macros", "matrixmultiply"], default-features = false } +smallvec = "2.0.0-alpha.12" +nalgebra = { version = "0.34", features = ["macros", "matrixmultiply"], default-features = false } robust = { version = "1.2", features = ["no_std"] } # legacy cxx = { version = "1.0", features = ["alloc", "c++20"], default-features = false, optional = true } -libm = { version = "0.2.15", optional = true } +libm = { version = "0.2", optional = true } [build-dependencies] cxx-build = { version = "1.0", optional = true } diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 7a2b0d9..d556428 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -3,6 +3,8 @@ name = "test_utils" version = "0.1.0" edition = "2021" +publish = false + [dependencies] -float_extras = "0.1.6" -png = "0.17.16" \ No newline at end of file +float_extras = "0.1" +png = "0.18" \ No newline at end of file From eaf2e7ac33cebc53df49c8912d41c8f2abe22a6b Mon Sep 17 00:00:00 2001 From: TimTheBig <132001783+TimTheBig@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:48:47 -0500 Subject: [PATCH 16/16] Improve impls --- src/expansion/expansion.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/expansion/expansion.rs b/src/expansion/expansion.rs index 72d6c79..83987b6 100644 --- a/src/expansion/expansion.rs +++ b/src/expansion/expansion.rs @@ -264,7 +264,7 @@ impl From<&[f64]> for Expansion { /// components equal to the slice elements in order. fn from(slice: &[f64]) -> Self { Expansion { - data: SmallVec::from_slice(slice), + data: SmallVec::from_slice_copy(slice), } } } @@ -508,7 +508,7 @@ impl Expansion { /// Two-product: return (low, high) parts of ai*bi #[inline] -fn two_product(a: f64, b: f64) -> (f64, f64) { +const fn two_product(a: f64, b: f64) -> (f64, f64) { let x = a * b; (x, two_product_tail(a, b, x)) @@ -538,24 +538,24 @@ const fn split(a: f64) -> (f64, f64) { (ahi, alo) } -impl core::ops::Add for Expansion { +impl core::ops::Add> for Expansion { // todo use when generic_const_exprs is stable // type Output = Expansion<{N + N}>; type Output = Expansion; - fn add(self, rhs: Self) -> Self::Output { + fn add(self, rhs: Expansion) -> Self::Output { let mut prod: Expansion = Expansion::with_capacity(N + N); prod.assign_sum(&self, &rhs); prod } } -impl core::ops::Mul for Expansion { +impl core::ops::Mul> for Expansion { // todo use when generic_const_exprs is stable // type Output = Expansion<{N.saturating_mul(b.length()).saturating_mul(2)}>; type Output = Expansion; - fn mul(self, rhs: Expansion) -> Self::Output { + fn mul(self, rhs: Expansion) -> Self::Output { // todo when generic_const_exprs is stable Expansion<{SN.max(RN)}> let mut prod: Expansion = Expansion::with_capacity(Expansion::::product_capacity(&self, &rhs));