Repository: glennDittmann/rita
Pull request #8: Add `#![no_std]` support
Author: login: TimTheBig
Created: 2025-04-21T21:09:51Z | Merged: 2025-04-25T06:38:26Z

This patch:
- Adds `#![no_std]` support
- Moves to rust 2024 edition
- Makes timing and logging optional, to allow for no standard support and improved efficiency when not needed
- Documents epsilon

std is still enabled be default, no_std does not affect logic.


Diff:
diff --git a/.github/workflows/rita.yml b/.github/workflows/rita.yml
index a5d8b57..04e30a9 100644
--- a/.github/workflows/rita.yml
+++ b/.github/workflows/rita.yml
@@ -18,7 +18,7 @@ jobs:
       - uses: Swatinem/rust-cache@v2
 
       - name: Test
-        run: cargo test -p rita
+        run: cargo test -p rita --features logging
 
   check:
     runs-on: ubuntu-latest
@@ -27,7 +27,9 @@ jobs:
       - uses: Swatinem/rust-cache@v2
 
       - name: fmt
-        run: cargo fmt --check -p rita
+        run: cargo fmt --check -p rita --all-features
         continue-on-error: true
-      - name: clippy
-        run: cargo clippy -p rita
+      - name: clippy std
+        run: cargo clippy -p rita --all-features
+      - name: clippy no-std
+        run: cargo clippy -p rita --no-default-features
diff --git a/Cargo.lock b/Cargo.lock
index 853c8fb..9bd7201 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "ab_glyph"
@@ -127,7 +127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
 dependencies = [
  "cfg-if",
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
  "once_cell",
  "serde",
  "version_check",
@@ -1565,9 +1565,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.15"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
 dependencies = [
  "cfg-if",
  "libc",
@@ -1941,9 +1941,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
 name = "jiff"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ec30f7142be6fe14e1b021f50b85db8df2d4324ea6e91ec3e5dcde092021d0"
+checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6"
 dependencies = [
  "jiff-static",
  "log",
@@ -1954,9 +1954,9 @@ dependencies = [
 
 [[package]]
 name = "jiff-static"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "526b834d727fd59d37b076b0c3236d9adde1b1729a4361e20b2026f738cc1dbe"
+checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2046,9 +2046,9 @@ dependencies = [
 
 [[package]]
 name = "libm"
-version = "0.2.11"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72"
 
 [[package]]
 name = "libredox"
@@ -2931,7 +2931,7 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
 ]
 
 [[package]]
@@ -3064,7 +3064,7 @@ dependencies = [
 
 [[package]]
 name = "rita"
-version = "0.1.7"
+version = "0.2.0"
 dependencies = [
  "anyhow",
  "arbitrary",
@@ -3075,21 +3075,6 @@ dependencies = [
  "rita_test_utils",
 ]
 
-[[package]]
-name = "rita"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd362db48b12a61811c00584f1e757deb19ce4953c0931ae1edc393a77669a64"
-dependencies = [
- "anyhow",
- "geogram_predicates",
- "log",
- "nalgebra",
- "rand 0.9.1",
- "rand_distr",
- "rayon",
-]
-
 [[package]]
 name = "rita_lab"
 version = "0.1.1"
@@ -3102,7 +3087,7 @@ dependencies = [
  "futures",
  "log",
  "rfd",
- "rita 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rita",
  "rita_test_utils",
  "serde",
  "vertex_clustering",
@@ -3272,9 +3257,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "signal-hook-registry"
-version = "1.4.4"
+version = "1.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1ee1aca2bc74ef9589efa7ccaa0f3752751399940356209b3fd80c078149b5e"
+checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
 dependencies = [
  "libc",
 ]
diff --git a/README.md b/README.md
index 2af66c7..ad2de41 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # rita - Randomized Incremental Triangulation Algorithms
 
 [![Crates.io version](https://img.shields.io/crates/v/rita.svg)](https://crates.io/crates/rita)
-[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-brightgreen.svg)](https://github.com/glennDittmann/rita/blob/main/src/lib.rs#L8)
+[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-brightgreen.svg)](https://github.com/glennDittmann/rita/blob/main/src/lib.rs#L12)
 
 An implementation of (randomized) incremental weighted Delaunay triangulations in safe rust.
 
diff --git a/rita/Cargo.toml b/rita/Cargo.toml
index 0003071..4aacfd6 100644
--- a/rita/Cargo.toml
+++ b/rita/Cargo.toml
@@ -1,8 +1,8 @@
 [package]
 name = "rita"
-version = "0.1.7"
-edition = "2021"
-rust-version = "1.75.0"
+version = "0.2.0"
+edition = "2024"
+rust-version = "1.85.0"
 
 description = "2D and 3D Randomized Incremental Triangulation Algorithms"
 keywords = [
@@ -21,12 +21,19 @@ license = "MIT"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-anyhow = "1.0"
+anyhow = { version = "1.0", default-features = false }
 geogram_predicates = "0.2.1"
-log = "0.4"
-nalgebra = "0.33"
+log = { version = "0.4", optional = true }
+nalgebra = { version = "0.33", features = ["libm", "macros", "matrixmultiply"], default-features = false }
 rayon = "1.10"
-arbitrary = { version = "1.4", optional = true , features = ["derive"] }
+arbitrary = { version = "1.4", optional = true, features = ["derive"] }
 
 [dev-dependencies]
 rita_test_utils = { version = "0.1", path = "../rita_test_utils" }
+
+[features]
+default = ["std"]
+std = ["anyhow/std", "nalgebra/std"]
+timing = ["std"]
+logging = ["dep:log"]
+log_timing = ["logging", "timing"]
diff --git a/rita/src/lib.rs b/rita/src/lib.rs
index bf43bc0..2e396ab 100644
--- a/rita/src/lib.rs
+++ b/rita/src/lib.rs
@@ -2,13 +2,19 @@
 //!
 //! An implementation of 2D and 3D weighted delaunay triangulation via incremental algorithms.
 //!
-//! ### Features
-//! - release_no_log (default): disable logging in release builds
-//! - no_log: disable logging in all builds
+//! ## Features
+//! - `std` (default) - enables anyhow and nalgebra's std features
+//! - `timing` - enables timing of function run time, this requires std
+//! - `logging` - uses `log` to record errors and warnings, along with some extra information
+//! - `log_timing` - enables logging and timing, to record timing info
+#![cfg_attr(not(feature = "std"), no_std)]
+
 #![forbid(unsafe_code)]
 #![deny(unused, clippy::incompatible_msrv)]
 #![warn(clippy::all, clippy::missing_const_for_fn)]
 
+extern crate alloc;
+
 pub use node::VertexNode;
 pub use tetrahedralization::Tetrahedralization;
 pub use triangulation::Triangulation;
diff --git a/rita/src/tetds/half_tri_iterator.rs b/rita/src/tetds/half_tri_iterator.rs
index 02fe0e2..31a6eab 100644
--- a/rita/src/tetds/half_tri_iterator.rs
+++ b/rita/src/tetds/half_tri_iterator.rs
@@ -46,6 +46,7 @@ impl<'a> HalfTriIterator<'a> {
             || (n0 == n_opposite2 && n1 == n_opposite1 && n2 == n_opposite0)
             || (n0 == n_opposite1 && n1 == n_opposite0 && n2 == n_opposite2))
         {
+            #[cfg(feature = "logging")]
             log::error!("{}: Wrong opposite triangle: {}", self, self.opposite());
             return false;
         }
@@ -90,8 +91,8 @@ impl<'a> HalfTriIterator<'a> {
     }
 }
 
-impl std::fmt::Display for HalfTriIterator<'_> {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+impl core::fmt::Display for HalfTriIterator<'_> {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
         let [n0, n1, n2] = self.nodes();
         write!(
             f,
diff --git a/rita/src/tetds/hedge_iterator.rs b/rita/src/tetds/hedge_iterator.rs
index 037abe9..6589630 100644
--- a/rita/src/tetds/hedge_iterator.rs
+++ b/rita/src/tetds/hedge_iterator.rs
@@ -36,22 +36,26 @@ impl<'a> HedgeIterator<'a> {
         let mut valid = true;
 
         if !(hedge_next.first_node() == last_node) {
+            #[cfg(feature = "logging")]
             log::error!("{self}: Wrong next hedge");
             valid = false;
         }
         if !(hedge_prev.last_node() == first_node) {
+            #[cfg(feature = "logging")]
             log::error!("{self}: Wrong prev hedge");
             valid = false;
         }
         if !(hedge_opposite.first_node() == last_node)
             || !(hedge_opposite.last_node() == first_node)
         {
+            #[cfg(feature = "logging")]
             log::error!("{self}: Wrong opposite hedge");
             valid = false;
         }
         if !(hedge_neighbor.first_node() == last_node)
             || !(hedge_neighbor.last_node() == first_node)
         {
+            #[cfg(feature = "logging")]
             log::error!("{self}: Wrong neighboring hedge");
             valid = false;
         }
@@ -118,8 +122,8 @@ impl<'a> HedgeIterator<'a> {
     }
 }
 
-impl std::fmt::Display for HedgeIterator<'_> {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+impl core::fmt::Display for HedgeIterator<'_> {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
         write!(f, "Edge: {} -> {}", self.first_node(), self.last_node())
     }
 }
diff --git a/rita/src/tetds/tet_data_structure.rs b/rita/src/tetds/tet_data_structure.rs
index 7aa9bee..a0ea3f9 100644
--- a/rita/src/tetds/tet_data_structure.rs
+++ b/rita/src/tetds/tet_data_structure.rs
@@ -1,10 +1,11 @@
 use crate::VertexNode;
-use anyhow::{Ok as HowOk, Result as HowResult};
-
 use super::{
     half_tri_iterator::HalfTriIterator, hedge_iterator::HedgeIterator, tet_iterator::TetIterator,
 };
 
+use anyhow::{Ok as HowOk, Result as HowResult};
+use alloc::{vec, vec::Vec};
+
 // For each tri idx within a tet, associate list of vertex idx triples, i.e. the face indices
 /// For each triangle index within tetrahedron, associate list of vertices within tetrahedron
 pub(crate) const TRIANGLE_SUBINDICES: [[usize; 3]; 4] =
@@ -644,8 +645,8 @@ impl TetDataStructure {
     }
 }
 
-impl std::fmt::Display for TetDataStructure {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+impl core::fmt::Display for TetDataStructure {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
         for idx in 0..self.num_tets {
             write!(f, "Tet {}: {}", idx, self.tet(idx))?;
         }
diff --git a/rita/src/tetds/tet_iterator.rs b/rita/src/tetds/tet_iterator.rs
index f1fd99e..6c227c5 100644
--- a/rita/src/tetds/tet_iterator.rs
+++ b/rita/src/tetds/tet_iterator.rs
@@ -1,5 +1,4 @@
 use crate::{utils::types::TetIteratorIdx, VertexNode};
-
 use super::{half_tri_iterator::HalfTriIterator, tet_data_structure::TetDataStructure};
 
 pub struct TetIterator<'a> {
@@ -47,6 +46,7 @@ impl<'a> TetIterator<'a> {
 
     pub fn is_sound(&self) -> bool {
         if self.should_del() || self.should_keep() {
+            #[cfg(feature = "logging")]
             log::error!("{self}: tetrahedron remaining after triangulation.");
             return false;
         }
@@ -56,6 +56,7 @@ impl<'a> TetIterator<'a> {
         let mut sound = true;
 
         if n0 == n1 || n0 == n2 || n0 == n3 || n1 == n2 || n1 == n3 || n2 == n3 {
+            #[cfg(feature = "logging")]
             log::error!("{self}: tetrahedron with duplicate nodes.");
             sound = false;
         }
@@ -82,8 +83,8 @@ impl<'a> TetIterator<'a> {
     }
 }
 
-impl std::fmt::Display for TetIterator<'_> {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+impl core::fmt::Display for TetIterator<'_> {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
         let [n0, n1, n2, n3] = self.nodes();
         write!(
             f,
diff --git a/rita/src/tetrahedralization.rs b/rita/src/tetrahedralization.rs
index bb10e56..8c9063f 100644
--- a/rita/src/tetrahedralization.rs
+++ b/rita/src/tetrahedralization.rs
@@ -1,4 +1,5 @@
-use std::cmp;
+use core::cmp;
+use alloc::{vec::Vec, vec};
 
 use crate::{
     tetds::{half_tri_iterator::HalfTriIterator, tet_data_structure::TetDataStructure},
@@ -10,6 +11,7 @@ use crate::{
 };
 use anyhow::Result as HowResult;
 use geogram_predicates as gp;
+#[cfg(feature = "logging")]
 use log::error;
 use rayon::prelude::*;
 
@@ -48,14 +50,22 @@ pub enum ExtendedTetrahedron {
 #[derive(Debug)]
 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 pub struct Tetrahedralization {
+    /// An artificial weight to make points be considered as regular (ie. not lying in a tetrahedrons circumsphere).
+    ///
+    /// Even a small epsilon can make the tetrahedralization faster.
     epsilon: Option<f64>,
     tds: TetDataStructure,
     vertices: Vec<Vertex3>,
     /// The weights of the vertices, `Some` if the vertices are weighted
     weights: Option<Vec<f64>>,
-    pub time_hilbert: u128,
+
+    #[cfg(feature = "timing")]
+    pub(crate) time_hilbert: u128,
+    #[cfg(feature = "timing")]
     time_walking: u128,
+    #[cfg(feature = "timing")]
     time_inserting: u128,
+
     /// Indices of vertices that are inserted, i.e. not skipped due to epsilon
     #[cfg_attr(feature = "arbitrary", arbitrary(default))]
     used_vertices: Vec<VertexIdx>,
@@ -116,8 +126,11 @@ impl Tetrahedralization {
             tds: TetDataStructure::new(),
             vertices: Vec::new(),
             weights: None,
+            #[cfg(feature = "timing")]
             time_hilbert: 0,
+            #[cfg(feature = "timing")]
             time_walking: 0,
+            #[cfg(feature = "timing")]
             time_inserting: 0,
             used_vertices: Vec::new(),
             ignored_vertices: Vec::new(),
@@ -131,8 +144,11 @@ impl Tetrahedralization {
             tds: TetDataStructure::new(),
             vertices: Vec::with_capacity(capacity),
             weights: None,
+            #[cfg(feature = "timing")]
             time_hilbert: 0,
+            #[cfg(feature = "timing")]
             time_walking: 0,
+            #[cfg(feature = "timing")]
             time_inserting: 0,
             used_vertices: Vec::new(),
             ignored_vertices: Vec::new(),
@@ -464,6 +480,7 @@ impl Tetrahedralization {
 
     fn insert_vertex_helper(&mut self, v_idx: usize, near_to_idx: usize) -> HowResult<usize> {
         // Locating vertex via vis walk
+        #[cfg(feature = "timing")]
         let now = std::time::Instant::now();
 
         let containing_tet_idx = if let Ok(idx) = self.locate_vis_walk(v_idx, near_to_idx) {
@@ -473,7 +490,8 @@ impl Tetrahedralization {
             self.walk_check_all(v_idx)?
         };
 
-        self.time_walking += now.elapsed().as_micros();
+        #[cfg(feature = "timing")]
+        { self.time_walking += now.elapsed().as_micros(); }
 
         if self.epsilon.is_some()
             && self.tds().get_tet(containing_tet_idx)?.is_casual()
@@ -495,11 +513,13 @@ impl Tetrahedralization {
         // Inserting vertex
         self.used_vertices.push(v_idx);
 
+        #[cfg(feature = "timing")]
         let now = std::time::Instant::now();
 
         let new_tets = self.insert_bw(v_idx, containing_tet_idx)?;
 
-        self.time_inserting += now.elapsed().as_micros();
+        #[cfg(feature = "timing")]
+        { self.time_inserting += now.elapsed().as_micros(); }
 
         Ok(new_tets[0])
     }
@@ -509,6 +529,7 @@ impl Tetrahedralization {
         idxs_to_insert: &mut Vec<usize>,
         spatial_sorting: bool,
     ) -> HowResult<()> {
+        #[cfg(feature = "log_timing")]
         let now = std::time::Instant::now();
 
         // first tetrahedron insertion
@@ -571,6 +592,7 @@ impl Tetrahedralization {
             idxs_to_insert.append(&mut aligned);
         }
 
+        #[cfg(feature = "log_timing")]
         log::trace!(
             "First tetrahedron computed in {}μs",
             now.elapsed().as_micros()
@@ -600,8 +622,11 @@ impl Tetrahedralization {
 
         self.tds.clean_to_del()?;
 
-        log::trace!("Walks computed in {} μs", self.time_walking);
-        log::trace!("Insertions computed in {} μs", self.time_inserting);
+        #[cfg(feature = "log_timing")]
+        {
+            log::trace!("Walks computed in {} μs", self.time_walking);
+            log::trace!("Insertions computed in {} μs", self.time_inserting);
+        }
 
         Ok(())
     }
@@ -629,9 +654,14 @@ impl Tetrahedralization {
         }
 
         if spatial_sorting {
+            #[cfg(feature = "timing")]
             let now = std::time::Instant::now();
+
             idxs_to_insert = sort_along_hilbert_curve_3d(&self.vertices, idxs_to_insert);
-            self.time_hilbert = now.elapsed().as_micros();
+
+            #[cfg(feature = "timing")]
+            { self.time_hilbert = now.elapsed().as_micros(); }
+            #[cfg(feature = "log_timing")]
             log::trace!("Hilbert curve computed in {} μs", now.elapsed().as_micros());
         }
 
@@ -645,9 +675,11 @@ impl Tetrahedralization {
         }
 
         self.tds.clean_to_del()?;
-
-        log::trace!("Walks computed in {} μs", self.time_walking);
-        log::trace!("Insertions computed in {} μs", self.time_inserting);
+        #[cfg(feature = "log_timing")]
+        {
+            log::trace!("Walks computed in {} μs", self.time_walking);
+            log::trace!("Insertions computed in {} μs", self.time_inserting);
+        }
 
         Ok(())
     }
@@ -659,6 +691,7 @@ impl Tetrahedralization {
 
         for tet_idx in 0..self.tds().num_tets() {
             if self.is_tet_flat(tet_idx)? {
+                #[cfg(feature = "logging")]
                 error!("Flat tetrahedron: {}", self.tds().get_tet(tet_idx)?);
                 regular = false;
                 num_violated_tets += 1;
@@ -678,6 +711,8 @@ impl Tetrahedralization {
                 }
 
                 if self.is_v_in_powersphere(v_idx, tet_idx, false)? {
+                    #[cfg(feature = "logging")]
+                    // FIXME: should this not be an error?
                     log::error!("Non Delaunay tetrahedron: {}", self.tds().get_tet(tet_idx)?);
                     regular = false;
                     num_violated_tets += 1;
@@ -769,6 +804,7 @@ impl Tetrahedralization {
             }
 
             if self.is_tet_flat(tet_idx)? {
+                #[cfg(feature = "logging")]
                 error!("Flat tetrahedron: {}", self.tds().get_tet(tet_idx)?);
                 regular = false;
                 num_violated_tets += 1;
@@ -815,10 +851,13 @@ impl Tetrahedralization {
         match self.tds().is_sound() {
             Ok(true) => Ok(true),
             Ok(false) => {
+                #[cfg(feature = "logging")]
                 error!("Triangulation is not sound!");
                 Ok(false)
             }
+            #[allow(unused)]
             Err(e) => {
+                #[cfg(feature = "logging")]
                 error!("Triangulation is not sound: {e}");
                 Ok(false)
             }
@@ -830,8 +869,8 @@ impl Tetrahedralization {
     }
 }
 
-impl std::fmt::Display for Tetrahedralization {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+impl core::fmt::Display for Tetrahedralization {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
         write!(
             f,
             "Tetrahedralization with {} vertices and {} tets",
@@ -842,6 +881,15 @@ impl std::fmt::Display for Tetrahedralization {
 }
 
 #[cfg(test)]
+mod pre_test {
+    #[cfg(not(feature = "logging"))]
+    #[test]
+    fn logging_enabled() {
+        panic!("\x1b[1;31;7m tests must be run with logging enabled, try `--features logging` \x1b[0m")
+    }
+}
+
+#[cfg(all(test, feature = "logging"))]
 mod tests {
     use super::*;
     use rita_test_utils::{sample_vertices_3d, sample_weights};
diff --git a/rita/src/triangulation.rs b/rita/src/triangulation.rs
index 21eca0e..6f2fe28 100644
--- a/rita/src/triangulation.rs
+++ b/rita/src/triangulation.rs
@@ -1,5 +1,6 @@
 use core::panic;
-use std::cmp;
+use core::cmp;
+use alloc::{vec::Vec, vec};
 
 // TODO: we could allow the epsilon filter on insertion also allow to happen, when the inserted vertex is in a casual triangle, i.e. outside the c-hull
 // TODO: we could also incorporate that in the 3->1 flip, as to remove points in a later stage of the algo (not just at insertion)
@@ -18,6 +19,7 @@ use crate::{
 };
 use anyhow::{Ok as HowOk, Result as HowResult};
 use geogram_predicates as gp;
+#[cfg(feature = "logging")]
 use log::error;
 use rayon::iter::{IntoParallelIterator, ParallelIterator};
 
@@ -64,15 +66,23 @@ pub(crate) enum Flip {
 #[derive(Debug)]
 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 pub struct Triangulation {
+    /// An artificial inverse weight to make points be considered as regular (ie. not lying in a triangles circumcircle).
+    ///
+    /// Even a small epsilon can make the triangulation faster.
+    epsilon: Option<f64>,
     pub tds: TriDataStructure,
     pub vertices: Vec<Vertex2>,
     /// The weights of the vertices, `Some` if the vertices are weighted
     pub weights: Option<Vec<f64>>,
+    last_inserted_triangle: Option<usize>,
+
+    #[cfg(feature = "timing")]
     time_flipping: u128,
+    #[cfg(feature = "timing")]
     time_inserting: u128,
+    #[cfg(feature = "timing")]
     time_walking: u128,
-    last_inserted_triangle: Option<usize>,
-    epsilon: Option<f64>,
+
     /// Vertices that are part of the triangulation
     /// (i.e. the input point set without redundant and ignored vertices).
     #[cfg_attr(feature = "arbitrary", arbitrary(default))]
@@ -135,8 +145,11 @@ impl Triangulation {
             tds: TriDataStructure::new(),
             vertices: Vec::new(),
             weights: None,
+            #[cfg(feature = "timing")]
             time_flipping: 0,
+            #[cfg(feature = "timing")]
             time_inserting: 0,
+            #[cfg(feature = "timing")]
             time_walking: 0,
             last_inserted_triangle: None,
             epsilon,
@@ -152,8 +165,11 @@ impl Triangulation {
             tds: TriDataStructure::new(),
             vertices: Vec::with_capacity(capacity),
             weights: None,
+            #[cfg(feature = "timing")]
             time_flipping: 0,
+            #[cfg(feature = "timing")]
             time_inserting: 0,
+            #[cfg(feature = "timing")]
             time_walking: 0,
             last_inserted_triangle: None,
             epsilon,
@@ -233,13 +249,14 @@ impl Triangulation {
         HowOk(tri_extended)
     }
 
-    /// Gets the height for a vertex
+    /// Gets the height for a vertex, this is affected by weights
     pub fn height(&self, v_idx: VertexIdx) -> f64 {
         self.vertices[v_idx][0].powi(2) + self.vertices[v_idx][1].powi(2)
             - self.weights.as_ref().map_or(0.0, |weights| weights[v_idx])
     }
 
     pub fn insert_init_tri(&mut self, v_idxs: &mut Vec<VertexIdx>) -> HowResult<()> {
+        #[cfg(feature = "log_timing")]
         let now = std::time::Instant::now();
 
         if self.vertices().len() == v_idxs.len() {
@@ -284,6 +301,7 @@ impl Triangulation {
 
         self.last_inserted_triangle = Some(0); // here the first triangle is the last inserted, as it is the initial casual triangle
 
+        #[cfg(feature = "log_timing")]
         log::trace!(
             "Initial triangle inserted in {:.4} µs",
             now.elapsed().as_micros()
@@ -355,10 +373,12 @@ impl Triangulation {
         }
 
         if spatial_sorting {
+            #[cfg(feature = "log_timing")]
             let now = std::time::Instant::now();
 
             idxs_to_insert = sort_along_hilbert_curve_2d(&self.vertices, &idxs_to_insert);
 
+            #[cfg(feature = "log_timing")]
             log::trace!(
                 "Spatial sorting (hilbert curve) computed in {:.4} µs",
                 now.elapsed().as_micros()
@@ -369,6 +389,7 @@ impl Triangulation {
             self.insert_init_tri(&mut idxs_to_insert)?;
         }
 
+        #[cfg(feature = "logging")]
         log::debug!("Inserting {} vertices", idxs_to_insert.len());
 
         while let Some(v_idx) = idxs_to_insert.pop() {
@@ -386,10 +407,12 @@ impl Triangulation {
 
     pub fn insert_v_helper(&mut self, v_idx: usize, near_to: usize) -> HowResult<()> {
         // Perform locate and measure time
+        #[cfg(feature = "timing")]
         let now = std::time::Instant::now();
         let containing_tri_idx = self.locate_vis_walk(v_idx, near_to)?; // the possibly invalid triangle
 
-        self.time_walking += now.elapsed().as_micros();
+        #[cfg(feature = "timing")]
+        { self.time_walking += now.elapsed().as_micros(); }
 
         // Skip vertices that are not in power circle by epsilon (i.e. above the hyperplane)
         // but only if the containing triangle is casual (for now), i.e. the vertex is inside the current convex hull
@@ -410,7 +433,9 @@ impl Triangulation {
         }
         self.used_vertices.push(v_idx);
 
+        #[cfg(feature = "timing")]
         let now = std::time::Instant::now();
+
         let mut hedges_to_verify = Vec::new();
         let [hedge0, hedge1, hedge2] = self.tds().get_tri(containing_tri_idx)?.hedges();
         hedges_to_verify.push(hedge0.twin().idx);
@@ -419,9 +444,12 @@ impl Triangulation {
 
         let [t0, _, _] = self.tds.flip_1_to_3(containing_tri_idx, v_idx)?;
         self.last_inserted_triangle = Some(t0.idx);
-        self.time_inserting += now.elapsed().as_micros();
+
+        #[cfg(feature = "timing")]
+        { self.time_inserting += now.elapsed().as_micros() };
 
         // Perform flips and measure time
+        #[cfg(feature = "timing")]
         let now = std::time::Instant::now();
         while let Some(hedge_idx) = hedges_to_verify.pop() {
             if let Some(flip) = self.should_flip_hedge(hedge_idx)? {
@@ -465,12 +493,14 @@ impl Triangulation {
                         hedges_to_verify.push(hedge2.twin().idx);
                     }
                     _ => {
+                        #[cfg(feature = "logging")]
                         log::error!("Unexpected flip type!");
                     }
                 }
             }
         }
-        self.time_flipping += now.elapsed().as_micros();
+        #[cfg(feature = "timing")]
+        { self.time_flipping += now.elapsed().as_micros(); }
         HowOk(())
     }
 
@@ -564,6 +594,7 @@ impl Triangulation {
             }
 
             if self.is_tri_flat(tri_idx)? {
+                #[cfg(feature = "logging")]
                 error!("Flat triangle: {}", self.tds().get_tri(tri_idx)?);
                 regular = false;
                 num_violated_triangles += 1;
@@ -582,6 +613,7 @@ impl Triangulation {
                 }
 
                 if self.is_v_in_powercircle(v_idx, tri_idx)? {
+                    // #[cfg(feature = "logging")]
                     // log::error!("Vertex in power circle: {}", self.tds().get_tri(tri_idx)?);
                     regular = false;
                     num_violated_triangles += 1; // s. the break below
@@ -602,6 +634,7 @@ impl Triangulation {
                 }
 
                 if self.is_v_in_powercircle(v_idx, tri_idx)? {
+                    // #[cfg(feature = "logging")]
                     // log::error!("Vertex in power circle: {}", self.tds().get_tri(tri_idx)?);
                     regular = false;
                     num_violated_triangles += 1; // s. the break below
@@ -725,6 +758,7 @@ impl Triangulation {
             }
 
             if self.is_tri_flat(tri_idx)? {
+                #[cfg(feature = "logging")]
                 error!("Flat triangle: {}", self.tds().get_tri(tri_idx)?);
                 regular = false;
                 num_violated_triangles += 1;
@@ -772,6 +806,7 @@ impl Triangulation {
         if self.tds().is_sound() {
             HowOk(true)
         } else {
+            #[cfg(feature = "logging")]
             error!("Triangulation is not sound!");
             HowOk(false)
         }
@@ -928,7 +963,7 @@ impl Triangulation {
 
     /// Get the triangulation data structure, as mutable reference.
     #[must_use]
-    pub fn tds_mut(&mut self) -> &mut TriDataStructure {
+    pub const fn tds_mut(&mut self) -> &mut TriDataStructure {
         &mut self.tds
     }
 
@@ -1064,11 +1099,14 @@ impl Triangulation {
     }
 
     fn log_time(&self) {
-        log::debug!("-------------------------------------------");
-        log::debug!("Time elapsed:");
-        log::debug!("Inserts computed in {} μs", self.time_inserting);
-        log::debug!("Walks computed in {} μs", self.time_walking);
-        log::debug!("Flips computed in {} μs", self.time_flipping);
+        #[cfg(feature = "log_timing")]
+        {
+            log::debug!("-------------------------------------------");
+            log::debug!("Time elapsed:");
+            log::debug!("Inserts computed in {} μs", self.time_inserting);
+            log::debug!("Walks computed in {} μs", self.time_walking);
+            log::debug!("Flips computed in {} μs", self.time_flipping);
+        }
     }
 
     fn is_flippable(
@@ -1187,6 +1225,15 @@ impl PartialEq for Triangulation {
 impl Eq for Triangulation {}
 
 #[cfg(test)]
+mod pre_test {
+    #[cfg(not(feature = "logging"))]
+    #[test]
+    fn logging_enabled() {
+        panic!("\x1b[1;31;7m tests must be run with logging enabled, try `--features logging` \x1b[0m")
+    }
+}
+
+#[cfg(all(test, feature = "logging"))]
 mod tests {
     use super::*;
     use rita_test_utils::{sample_vertices_2d, sample_weights};
@@ -1342,6 +1389,7 @@ mod tests {
 
     #[test]
     #[ignore]
+    #[cfg(feature = "timing")]
     // only run this test isolated, as test concurenncy can mess up par_iter
     fn test_parallel_regularity_2d() {
         let n_vertices = 2000;
diff --git a/rita/src/trids/hedge_iterator.rs b/rita/src/trids/hedge_iterator.rs
index adf4c59..e25f114 100644
--- a/rita/src/trids/hedge_iterator.rs
+++ b/rita/src/trids/hedge_iterator.rs
@@ -1,9 +1,9 @@
 use core::fmt;
+#[cfg(feature = "logging")]
 use log::error;
-use std::cmp::Ordering;
+use core::cmp::Ordering;
 
 use crate::{utils::types::HedgeIteratorIdx, VertexNode};
-
 use super::{tri_data_structure::TriDataStructure, tri_iterator::TriIterator};
 
 /// An iterator over the half-edges of a triangulation data structure.
@@ -36,8 +36,10 @@ impl<'a> HedgeIterator<'a> {
         let starting_node = self.starting_node();
         let end_node = self.end_node();
 
+        #[allow(unused)]
         let mut check = |condition: bool, error_msg: &str| {
             if !condition {
+                #[cfg(feature = "logging")]
                 error!("{self}: {error_msg}");
                 sound = false;
             }
diff --git a/rita/src/trids/tri_data_structure.rs b/rita/src/trids/tri_data_structure.rs
index d81aef3..699949e 100644
--- a/rita/src/trids/tri_data_structure.rs
+++ b/rita/src/trids/tri_data_structure.rs
@@ -1,8 +1,9 @@
 use crate::{utils::types::HedgeIteratorIdx, VertexNode};
-
 use super::{hedge_iterator::HedgeIterator, tri_iterator::TriIterator};
+
 use anyhow::{Ok as HowOk, Result as HowResult};
 use geogram_predicates as gp;
+use alloc::vec::Vec;
 
 const INACTIVE: usize = usize::MAX;
 
@@ -299,8 +300,8 @@ impl TriDataStructure {
 
         if orient == -1 {
             // swap second and third edge
-            std::mem::swap(&mut starting_node1, &mut starting_node2);
-            std::mem::swap(&mut twin_idx1, &mut twin_idx2);
+            core::mem::swap(&mut starting_node1, &mut starting_node2);
+            core::mem::swap(&mut twin_idx1, &mut twin_idx2);
         }
 
         // 2.2 First new edge
diff --git a/rita/src/trids/tri_iterator.rs b/rita/src/trids/tri_iterator.rs
index 443e89f..974d6fe 100644
--- a/rita/src/trids/tri_iterator.rs
+++ b/rita/src/trids/tri_iterator.rs
@@ -1,7 +1,6 @@
 use core::fmt;
 
 use crate::{utils::types::TriIteratorIdx, VertexNode};
-
 use super::{hedge_iterator::HedgeIterator, tri_data_structure::TriDataStructure};
 
 pub struct TriIterator<'a> {
diff --git a/rita/src/utils/convexity.rs b/rita/src/utils/convexity.rs
index f3a6293..bcde0db 100644
--- a/rita/src/utils/convexity.rs
+++ b/rita/src/utils/convexity.rs
@@ -1,6 +1,6 @@
 use super::types::Vertex2;
 use geogram_predicates as gp;
-use std::cmp;
+use core::cmp;
 
 /// Checks if ang(v1--v0, v1--v2) is convex, flat, or concave
 pub(crate) fn is_convex(v0: Vertex2, v1: Vertex2, v2: Vertex2) -> bool {
diff --git a/rita/src/utils/point_order.rs b/rita/src/utils/point_order.rs
index 6c63dc9..985f9d0 100644
--- a/rita/src/utils/point_order.rs
+++ b/rita/src/utils/point_order.rs
@@ -1,4 +1,5 @@
 use super::types::{Vertex2, Vertex3};
+use alloc::vec::Vec;
 
 /// Sorts vertices along 2D Hilbert curve
 pub fn sort_along_hilbert_curve_2d(vertices: &[Vertex2], indices_to_add: &[usize]) -> Vec<usize> {
@@ -12,7 +13,7 @@ pub fn sort_along_hilbert_curve_2d(vertices: &[Vertex2], indices_to_add: &[usize
 
     while let Some((rot, pt_min, pt_max, indices_to_add)) = to_subdiv.pop() {
         match indices_to_add.len().cmp(&1) {
-            std::cmp::Ordering::Greater => {
+            core::cmp::Ordering::Greater => {
                 let sep_x = (pt_min[0] + pt_max[0]) / 2.0;
                 let sep_y = (pt_min[1] + pt_max[1]) / 2.0;
 
@@ -90,7 +91,7 @@ pub fn sort_along_hilbert_curve_2d(vertices: &[Vertex2], indices_to_add: &[usize
                     to_subdiv.push((0, pt_d_min, pt_d_max, ind_d));
                 }
             }
-            std::cmp::Ordering::Equal => curve_order.push(indices_to_add[0]),
+            core::cmp::Ordering::Equal => curve_order.push(indices_to_add[0]),
             _ => (),
         }
     }
@@ -154,7 +155,7 @@ pub fn sort_along_hilbert_curve_3d(vertices: &[Vertex3], indices_to_add: Vec<usi
 
     while let Some((start, dir, pt_min, pt_max, indices_to_add)) = to_subdiv.pop() {
         match indices_to_add.len().cmp(&1) {
-            std::cmp::Ordering::Greater => {
+            core::cmp::Ordering::Greater => {
                 let sep_x = (pt_min[0] + pt_max[0]) / 2.0;
                 let sep_y = (pt_min[1] + pt_max[1]) / 2.0;
                 let sep_z = (pt_min[2] + pt_max[2]) / 2.0;
@@ -214,7 +215,7 @@ pub fn sort_along_hilbert_curve_3d(vertices: &[Vertex3], indices_to_add: Vec<usi
                     start_ind[dir[i]] = 1 - start_ind[dir[i]];
                 }
             }
-            std::cmp::Ordering::Equal => curve_order.push(indices_to_add[0]),
+            core::cmp::Ordering::Equal => curve_order.push(indices_to_add[0]),
             _ => (),
         }
     }
diff --git a/rita_lab/Cargo.toml b/rita_lab/Cargo.toml
index b76ba1a..6a9e379 100755
--- a/rita_lab/Cargo.toml
+++ b/rita_lab/Cargo.toml
@@ -1,8 +1,9 @@
 [package]
 name = "rita_lab"
 version = "0.1.1"
-edition = "2021"
-rust-version = "1.75.0"
+edition = "2024"
+rust-version = "1.85.0"
+publish = false
 
 description = "A tool for 2D triangulations and related algorithms."
 keywords = [
@@ -17,7 +18,7 @@ authors = ["Glenn Dittmann <glenn.dittmann@posteo.de>"]
 license = "MIT"
 
 [dependencies]
-rita = "0.1.7"
+rita = { version = "0.2", path = "../rita", features = ["timing"] }
 vertex_clustering = "0.1.1"
 csv = "1.2.2"
 rfd = "0.15.3"


Reviews:
user: login: glennDittmann
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-24T06:03:54Z
user: login: glennDittmann
body: LGTM ! Great work, thanks a lot !

Just a tiny wording change please. :)
state: DISMISSED
author_association: OWNER
submitted_at: 2025-04-24T06:05:08Z
user: login: TimTheBig
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T17:19:36Z
user: login: glennDittmann
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-24T19:44:03Z
user: login: glennDittmann
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-24T21:34:42Z
user: login: glennDittmann
body: this is the correct sentence :)
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-04-24T21:35:04Z
user: login: TimTheBig
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T21:52:39Z
user: login: glennDittmann
state: APPROVED
author_association: OWNER
submitted_at: 2025-04-25T06:38:12Z

Review comments:
diff_hunk: @@ -48,14 +50,22 @@ pub enum ExtendedTetrahedron {
 #[derive(Debug)]
 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 pub struct Tetrahedralization {
+    /// An artificial inverse weight to make points be considered as regular (ie. not lying in a tetrahedron circumcircle).
path: rita/src/tetrahedralization.rs
user: login: glennDittmann
body: for tetrhedra it is the circumsphere :)
created_at: 2025-04-24T06:03:54Z
author_association: OWNER
diff_hunk: @@ -48,14 +50,22 @@ pub enum ExtendedTetrahedron {
 #[derive(Debug)]
 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 pub struct Tetrahedralization {
+    /// An artificial inverse weight to make points be considered as regular (ie. not lying in a tetrahedron circumcircle).
+    ///
+    /// Even a small epsilon can make the tetrahedralization faster.
path: rita/src/tetrahedralization.rs
user: login: TimTheBig
body: ```suggestion
    /// A circumsphere to make points be considered as regular (ie. not lying in a tetrahedron circumsphere).
    ///
    /// Even a small epsilon can make the tetrahedralization faster.
```
Like this?
created_at: 2025-04-24T17:19:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,14 +50,22 @@ pub enum ExtendedTetrahedron {
 #[derive(Debug)]
 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 pub struct Tetrahedralization {
+    /// An artificial inverse weight to make points be considered as regular (ie. not lying in a tetrahedron circumcircle).
+    ///
+    /// Even a small epsilon can make the tetrahedralization faster.
path: rita/src/tetrahedralization.rs
user: login: glennDittmann
body: Nope :D just swap the word _circumcircle_ with _circumsphere_
created_at: 2025-04-24T19:44:03Z
author_association: OWNER
diff_hunk: @@ -50,7 +50,7 @@ pub enum ExtendedTetrahedron {
 #[derive(Debug)]
 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 pub struct Tetrahedralization {
-    /// An artificial inverse weight to make points be considered as regular (ie. not lying in a tetrahedron circumcircle).
+    /// A circumsphere to make points be considered as regular (ie. not lying in a tetrahedron circumsphere).
path: rita/src/tetrahedralization.rs
user: login: glennDittmann
body: ```suggestion
    /// An artificial weight to make points be considered as regular (ie. not lying in a tetrahedrons circumsphere).
```
created_at: 2025-04-24T21:34:42Z
author_association: OWNER
diff_hunk: @@ -50,7 +50,7 @@ pub enum ExtendedTetrahedron {
 #[derive(Debug)]
 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 pub struct Tetrahedralization {
-    /// An artificial inverse weight to make points be considered as regular (ie. not lying in a tetrahedron circumcircle).
+    /// A circumsphere to make points be considered as regular (ie. not lying in a tetrahedron circumsphere).
path: rita/src/tetrahedralization.rs
user: login: TimTheBig
body: I see
created_at: 2025-04-24T21:52:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: winksaville
created_at: 2025-04-21T22:28:32Z
body: @TimTheBig, looking good, thanks for putting in the effort and "no_std does not affect logic" is fantastic!
author_association: NONE
user: login: TimTheBig
created_at: 2025-04-23T16:38:05Z
body: @glennDittmann when can you review this?
author_association: CONTRIBUTOR
user: login: glennDittmann
created_at: 2025-04-23T21:08:12Z
body: I can try tomorrow evening 
author_association: OWNER
user: login: winksaville
created_at: 2025-04-26T21:54:09Z
body: Bravo!!
author_association: NONE
user: login: TimTheBig
created_at: 2025-04-27T21:42:16Z
body: Thank you
author_association: CONTRIBUTOR