diff --git a/README.md b/README.md index 6421635b..6e3a0d15 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ efficient spatial indexing. Provides an explicit [4-level validation hierarchy][Validation Guide] on individual elements, triangulation data structure validity, manifold topology, and Delaunay property adherence. Allows for the complete set of [Pachner moves] up to D=5 using -bistellar flips, vertex insertion and deletion, and the conversion of +bistellar flips insertion and deletion, and the conversion of non-Delaunay triangulations into Delaunay triangulations via bounded flip/rebuilds. Auxiliary data may be stored directly in vertices and simplices with external [secondary maps][Secondary maps] provided for vertex- and @@ -74,9 +74,9 @@ complete technical background. for well-conditioned exploratory work. - [x] D-dimensional [Convex hulls] and [Delaunay triangulations]. - [x] Euclidean and toroidal construction through - [`DelaunayTriangulationBuilder`]: `.toroidal(...)` builds the periodic + [`DelaunayTriangulationBuilder`]: `.try_toroidal(...)` builds the periodic image-point quotient in validated dimensions, while - `.canonicalized_toroidal(...)` wraps coordinates without quotient rewiring. + `.try_canonicalized_toroidal(...)` wraps coordinates without quotient rewiring. - [x] Exact predicates, stack-allocated linear algebra through [la-stack], and deterministic SoS degeneracy handling. - [x] Focused public preludes for common construction, query, geometry, repair, @@ -119,17 +119,23 @@ prelude map and namespace policy, see the ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]) + .expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]) + .expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]) + .expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]) + .expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]) + .expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]) + .expect("finite vertex coordinates"), ]; let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -148,23 +154,29 @@ fn main() -> Result<(), DelaunayTriangulationConstructionError> { ### Toroidal Triangulations For coordinate wrapping on a toroidal domain, use -`DelaunayTriangulationBuilder::canonicalized_toroidal`: +`DelaunayTriangulationBuilder::try_canonicalized_toroidal`: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyKind, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyKind, }; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.1, 0.2]), - vertex!([0.8, 0.3]), - vertex!([0.5, 0.7]), - vertex!([1.2, 0.4]), // Wraps to [0.2, 0.4] + delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.2]) + .expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.3]) + .expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.7]) + .expect("finite vertex coordinates"), + // Wraps to [0.2, 0.4]. + delaunay::prelude::Vertex::<(), _>::try_new([1.2, 0.4]) + .expect("finite vertex coordinates"), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .expect("unit toroidal domain is valid") .build::<()>()?; assert_eq!(dt.topology_kind(), TopologyKind::Toroidal); @@ -173,7 +185,7 @@ fn main() -> Result<(), DelaunayTriangulationConstructionError> { ``` For boundary-facet identification and periodic neighbor pointers, use -`.toroidal([..])` in 2D or compact 3D; see the +`.try_toroidal([..])` in 2D or compact 3D; see the [toroidal construction workflow] for the full recipe and current 4D/5D guardrails. @@ -257,8 +269,8 @@ For reproducible checks in CI/local runs, use `just check`, `just test`, skipped none, ran final repair, and passed `validation_report` in about 52 seconds. The 3,000-point 4D harness remains a manual characterization probe for issue #340 rather than routine CI. -- **Periodic domains:** `.toroidal()` uses the periodic image-point method and - is release-validated in 2D and compact 3D. `.canonicalized_toroidal()` +- **Periodic domains:** `.try_toroidal()` uses the periodic image-point method and + is release-validated in 2D and compact 3D. `.try_canonicalized_toroidal()` canonicalizes coordinates into the fundamental domain without quotient rewiring. 4D/5D periodic quotients fail fast pending scalable construction work in issue #416. diff --git a/benches/allocation_hot_paths.rs b/benches/allocation_hot_paths.rs index 4fc117d1..8048154b 100644 --- a/benches/allocation_hot_paths.rs +++ b/benches/allocation_hot_paths.rs @@ -22,11 +22,11 @@ mod allocation_contracts { use criterion::{BenchmarkGroup, BenchmarkId, Criterion, measurement::WallTime}; use delaunay::prelude::algorithms::{LocateResult, locate_with_stats}; use delaunay::prelude::construction::{ - ConstructionOptions, DelaunayTriangulation, RetryPolicy, Vertex, vertex, + ConstructionOptions, DelaunayTriangulation, RetryPolicy, Vertex, }; use delaunay::prelude::generators::generate_random_points_in_range_seeded; use delaunay::prelude::geometry::{ - AdaptiveKernel, Coordinate, CoordinateRange, FastKernel, Point, simplex_volume, + AdaptiveKernel, CoordinateRange, FastKernel, Point, simplex_volume, }; use delaunay::prelude::query::measure_with_result; use delaunay::prelude::tds::{SimplexKey, TdsError, VertexKey, facet_key_from_vertices}; @@ -48,6 +48,10 @@ mod allocation_contracts { type BenchTriangulation = DelaunayTriangulation, (), (), D>; + fn finite_point(coords: [f64; D]) -> Point { + Point::try_new(coords).unwrap_or_else(|_| std::process::abort()) + } + #[derive(Debug, Error)] enum AllocationBenchError { #[error("{dimension}D fixture did not contain a simplex")] @@ -77,7 +81,7 @@ mod allocation_contracts { dt: BenchTriangulation, simplex_key: SimplexKey, facet_vertices: [VertexKey; D], - query: Point, + query: Point, simplex_count: usize, vertex_count: usize, } @@ -96,10 +100,17 @@ mod allocation_contracts { ) } - fn canary_vertices(count: usize, seed: u64) -> Vec> { - let points = - generate_random_points_in_range_seeded::(count, benchmark_bounds(), seed); - points.into_iter().map(|point| vertex!(point)).collect() + fn canary_vertices(count: usize, seed: u64) -> Vec> { + let points = generate_random_points_in_range_seeded::(count, benchmark_bounds(), seed); + points + .into_iter() + .map(|point| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new(point.into()), + "finite benchmark vertex coordinates", + ) + }) + .collect() } fn first_simplex_key( @@ -114,7 +125,7 @@ mod allocation_contracts { fn simplex_points( dt: &BenchTriangulation, simplex_key: SimplexKey, - ) -> Result>, AllocationBenchError> { + ) -> Result>, AllocationBenchError> { let tds = dt.tds(); tds.simplex_vertices(simplex_key)? @@ -176,7 +187,7 @@ mod allocation_contracts { fn simplex_barycenter( dt: &BenchTriangulation, simplex_key: SimplexKey, - ) -> Result, AllocationBenchError> { + ) -> Result, AllocationBenchError> { let points = simplex_points(dt, simplex_key)?; let mut coords = [0.0_f64; D]; for point in &points { @@ -194,7 +205,7 @@ mod allocation_contracts { *coord *= inv_vertex_count; } - Ok(Point::new(coords)) + Ok(finite_point(coords)) } fn prepare_fixture(count: usize, seed: u64) -> DimensionFixture { diff --git a/benches/boundary_uuid_iter.rs b/benches/boundary_uuid_iter.rs index c676c19a..3c6926ad 100644 --- a/benches/boundary_uuid_iter.rs +++ b/benches/boundary_uuid_iter.rs @@ -7,7 +7,7 @@ //! preserving the quick performance probes. use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; -use delaunay::prelude::construction::{DelaunayTriangulation, Vertex, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulation, Vertex}; use delaunay::prelude::generators::generate_random_points_in_range_seeded; use delaunay::prelude::geometry::CoordinateRange; use delaunay::prelude::query::BoundaryAnalysis; @@ -33,7 +33,7 @@ fn benchmark_bounds() -> CoordinateRange { fn boundary_triangulation_3d( requested_vertices: usize, ) -> DelaunayTriangulation, (), (), 3> { - let points = generate_random_points_in_range_seeded::( + let points = generate_random_points_in_range_seeded::<3>( requested_vertices, benchmark_bounds(), 0xB0DA_FACE_0000_0000 ^ requested_vertices as u64, @@ -99,10 +99,22 @@ fn bench_boundary_facets_micro(c: &mut Criterion) { fn uuid_iter_source() -> DelaunayTriangulation, (), (), 3> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]), + "finite benchmark vertex coordinates", + ), + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]), + "finite benchmark vertex coordinates", + ), + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]), + "finite benchmark vertex coordinates", + ), + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]), + "finite benchmark vertex coordinates", + ), ]; bench_result( DelaunayTriangulation::new(&vertices), diff --git a/benches/ci_performance_suite.rs b/benches/ci_performance_suite.rs index be9e74a4..0d2b32eb 100644 --- a/benches/ci_performance_suite.rs +++ b/benches/ci_performance_suite.rs @@ -48,9 +48,8 @@ use delaunay::prelude::construction::{ }; use delaunay::prelude::flips::{FacetHandle, RidgeHandle, SimplexKey}; use delaunay::prelude::generators::generate_random_points_in_range_seeded; -use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate, CoordinateRange, Point}; +use delaunay::prelude::geometry::{AdaptiveKernel, CoordinateRange, Point}; use delaunay::prelude::query::ConvexHull; -use delaunay::vertex; use std::{env, hint::black_box, num::NonZeroUsize, sync::Once}; #[cfg(feature = "bench-logging")] use tracing::warn; @@ -89,9 +88,13 @@ const CANARY_COUNT_5D: usize = 25; const INSERT_BATCH_COUNT_2D_3D: usize = 10; const INSERT_BATCH_COUNT_4D: usize = 6; const INSERT_BATCH_COUNT_5D: usize = 4; -type SeedSearchResult = Option<(u64, Vec>, Vec>)>; +type SeedSearchResult = Option<(u64, Vec>, Vec>)>; type BenchTriangulation = DelaunayTriangulation, (), (), D>; +fn finite_point(coords: [f64; D]) -> Point { + Point::try_new(coords).unwrap_or_else(|_| std::process::abort()) +} + fn retry_attempts(value: usize) -> NonZeroUsize { let Some(attempts) = NonZeroUsize::new(value) else { unreachable!("hard-coded retry attempt count must be non-zero"); @@ -323,7 +326,7 @@ fn prepare_data( count: usize, bounds: CoordinateRange, attempts: NonZeroUsize, -) -> (u64, Vec>, Vec>) { +) -> (u64, Vec>, Vec>) { // Fast path: use the pre-computed seed (single verification construction) if let Some(seed) = known_seed(D, count) { if let Some(result) = find_seed_vertices::(seed, count, bounds, 1, attempts) { @@ -401,13 +404,13 @@ fn prepare_inserts( dim_seed: u64, count: usize, dataset: Dataset, -) -> Vec> { +) -> Vec> { let mut seed = dim_seed.wrapping_add(0x5151_5151); if matches!(dataset, Dataset::Adversarial) { seed ^= 0xA5A5_A5A5; } let points = match dataset { - Dataset::WellConditioned => generate_random_points_in_range_seeded::( + Dataset::WellConditioned => generate_random_points_in_range_seeded::( count, bench_result( CoordinateRange::try_new(-50.0_f64, 50.0), @@ -417,7 +420,15 @@ fn prepare_inserts( ), Dataset::Adversarial => generate_adv_points::(count, seed), }; - points.iter().map(|point| vertex!(*point)).collect() + points + .iter() + .map(|point| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*point).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect() } fn find_seed_vertices( @@ -429,9 +440,16 @@ fn find_seed_vertices( ) -> SeedSearchResult { for offset in 0..limit { let candidate_seed = start_seed.wrapping_add(offset as u64); - let points = - generate_random_points_in_range_seeded::(count, bounds, candidate_seed); - let vertices = points.iter().map(|p| vertex!(*p)).collect::>(); + let points = generate_random_points_in_range_seeded::(count, bounds, candidate_seed); + let vertices = points + .iter() + .map(|p| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*p).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect::>(); let options = ConstructionOptions::default().with_retry_policy(RetryPolicy::Shuffled { attempts, @@ -452,7 +470,15 @@ fn stable_adv_points( attempts: NonZeroUsize, ) -> SeedSearchResult { let points = generate_adv_points::(count, seed); - let vertices = points.iter().map(|p| vertex!(*p)).collect::>(); + let vertices = points + .iter() + .map(|p| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*p).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect::>(); let options = ConstructionOptions::default().with_retry_policy(RetryPolicy::Shuffled { attempts, base_seed: Some(seed), @@ -467,7 +493,7 @@ fn prepare_adv_data( dim_seed: u64, count: usize, attempts: NonZeroUsize, -) -> (u64, Vec>, Vec>) { +) -> (u64, Vec>, Vec>) { if !discover_seeds_enabled() && let Some(seed) = known_adv_seed(D, count) { @@ -500,8 +526,8 @@ fn prepare_adv_data( )); } -fn generate_adv_points(count: usize, seed: u64) -> Vec> { - let base_points = generate_random_points_in_range_seeded::( +fn generate_adv_points(count: usize, seed: u64) -> Vec> { + let base_points = generate_random_points_in_range_seeded::( count, bench_result( CoordinateRange::try_new(-1.0_f64, 1.0), @@ -527,7 +553,7 @@ fn generate_adv_points(count: usize, seed: u64) -> Vec usize { /// make the generated release data misleading. fn emit_construction_metric( benchmark_id: &str, - vertices: &[Vertex], + vertices: &[Vertex<(), D>], options: ConstructionOptions, ) { let dt = bench_result( @@ -1145,7 +1171,7 @@ fn bench_hull_case( ); } -fn exterior_hull_query_point(dt: &BenchTriangulation) -> Point { +fn exterior_hull_query_point(dt: &BenchTriangulation) -> Point { let mut mins = [f64::INFINITY; D]; let mut maxs = [f64::NEG_INFINITY; D]; let mut has_vertices = false; @@ -1167,7 +1193,7 @@ fn exterior_hull_query_point(dt: &BenchTriangulation) -> Poin let span = maxs[axis] - mins[axis]; *coord = maxs[axis] + span.max(1.0); } - Point::new(coords) + finite_point(coords) } fn bench_hull_query_case( @@ -1285,7 +1311,7 @@ fn bench_insert_case( dataset: Dataset, count: usize, base_dt: &BenchTriangulation, - insert_vertices: &[Vertex], + insert_vertices: &[Vertex<(), D>], ) { group.throughput(Throughput::Elements(count as u64)); group.bench_function( diff --git a/benches/circumsphere_containment.rs b/benches/circumsphere_containment.rs index 27aa51de..e0ded049 100644 --- a/benches/circumsphere_containment.rs +++ b/benches/circumsphere_containment.rs @@ -24,6 +24,10 @@ use std::hint::black_box; pub mod bench_utils; use bench_utils::{abort_benchmark, bench_option, bench_result}; +fn finite_point(coords: [f64; D]) -> Point { + Point::try_new(coords).unwrap_or_else(|_| std::process::abort()) +} + fn coordinate_range(min: f64, max: f64, context: &'static str) -> CoordinateRange { bench_result(CoordinateRange::try_new(min, max), context) } @@ -33,24 +37,24 @@ fn coordinate_range(min: f64, max: f64, context: &'static str) -> CoordinateRang /// Creates a simplex with vertices at: /// - Origin: [0, 0, ..., 0] /// - Unit basis vectors: [1, 0, ..., 0], [0, 1, 0, ..., 0], ..., [0, 0, ..., 1] -fn standard_simplex() -> Vec> { +fn standard_simplex() -> Vec> { let mut pts = Vec::with_capacity(D + 1); // Add origin - pts.push(Point::new([0.0; D])); + pts.push(finite_point([0.0; D])); // Add unit basis vectors for i in 0..D { let mut coords = [0.0; D]; coords[i] = 1.0; - pts.push(Point::new(coords)); + pts.push(finite_point(coords)); } pts } /// Generate a random 3D simplex (tetrahedron) for benchmarking using seeded generation -fn generate_random_simplex_3d(seed: u64) -> Vec> { +fn generate_random_simplex_3d(seed: u64) -> Vec> { generate_random_points_in_range_seeded( 4, coordinate_range(-10.0, 10.0, "random simplex bounds must be valid"), @@ -59,7 +63,7 @@ fn generate_random_simplex_3d(seed: u64) -> Vec> { } /// Generate a random 3D test point using seeded generation -fn generate_random_test_point_3d(seed: u64) -> Point { +fn generate_random_test_point_3d(seed: u64) -> Point<3> { let points = generate_random_points_in_range_seeded( 1, coordinate_range(-5.0, 5.0, "random test point bounds must be valid"), @@ -223,22 +227,22 @@ macro_rules! bench_edge_case { fn benchmark_different_dimensions(c: &mut Criterion) { // 2D case - triangle in 2D space let simplex_2d = standard_simplex::<2>(); - let test_point_2d = Point::new([0.3, 0.3]); + let test_point_2d = finite_point([0.3, 0.3]); bench_simplex!(c, 2, simplex_2d, test_point_2d); // 3D case - tetrahedron in 3D space let simplex_3d = standard_simplex::<3>(); - let test_point_3d = Point::new([0.25, 0.25, 0.25]); + let test_point_3d = finite_point([0.25, 0.25, 0.25]); bench_simplex!(c, 3, simplex_3d, test_point_3d); // 4D case - 4-simplex in 4D space let simplex_4d = standard_simplex::<4>(); - let test_point_4d = Point::new([0.2, 0.2, 0.2, 0.2]); + let test_point_4d = finite_point([0.2, 0.2, 0.2, 0.2]); bench_simplex!(c, 4, simplex_4d, test_point_4d); // 5D case - 5-simplex in 5D space let simplex_5d = standard_simplex::<5>(); - let test_point_5d = Point::new([0.16, 0.16, 0.16, 0.16, 0.16]); + let test_point_5d = finite_point([0.16, 0.16, 0.16, 0.16, 0.16]); bench_simplex!(c, 5, simplex_5d, test_point_5d); } @@ -247,10 +251,10 @@ fn benchmark_edge_cases(c: &mut Criterion) { // 2D edge cases - triangle let simplex_2d = standard_simplex::<2>(); let boundary_point_2d = simplex_2d[0]; // Point on boundary - let far_point_2d = Point::new([1000.0, 1000.0]); + let far_point_2d = finite_point([1000.0, 1000.0]); // Near-boundary point (epsilon away from circumsphere) let eps = 1e-9; - let near_boundary_2d = Point::new([eps, 0.0]); + let near_boundary_2d = finite_point([eps, 0.0]); bench_edge_case!(c, 2, "boundary_point", simplex_2d, boundary_point_2d); bench_edge_case!(c, 2, "far_point", simplex_2d, far_point_2d); bench_edge_case!(c, 2, "near_boundary", simplex_2d, near_boundary_2d); @@ -258,9 +262,9 @@ fn benchmark_edge_cases(c: &mut Criterion) { // 3D edge cases - tetrahedron let simplex_3d = standard_simplex::<3>(); let boundary_point_3d = simplex_3d[0]; // Point on boundary - let far_point_3d = Point::new([1000.0, 1000.0, 1000.0]); + let far_point_3d = finite_point([1000.0, 1000.0, 1000.0]); // Near-boundary point (epsilon away from circumsphere) - let near_boundary_3d = Point::new([eps, 0.0, 0.0]); + let near_boundary_3d = finite_point([eps, 0.0, 0.0]); bench_edge_case!(c, 3, "boundary_point", simplex_3d, boundary_point_3d); bench_edge_case!(c, 3, "far_point", simplex_3d, far_point_3d); bench_edge_case!(c, 3, "near_boundary", simplex_3d, near_boundary_3d); @@ -268,9 +272,9 @@ fn benchmark_edge_cases(c: &mut Criterion) { // 4D edge cases - 4-simplex let simplex_4d = standard_simplex::<4>(); let boundary_point_4d = simplex_4d[0]; // Point on boundary - let far_point_4d = Point::new([1000.0, 1000.0, 1000.0, 1000.0]); + let far_point_4d = finite_point([1000.0, 1000.0, 1000.0, 1000.0]); // Near-boundary point (epsilon away from circumsphere) - let near_boundary_4d = Point::new([eps, 0.0, 0.0, 0.0]); + let near_boundary_4d = finite_point([eps, 0.0, 0.0, 0.0]); bench_edge_case!(c, 4, "boundary_point", simplex_4d, boundary_point_4d); bench_edge_case!(c, 4, "far_point", simplex_4d, far_point_4d); bench_edge_case!(c, 4, "near_boundary", simplex_4d, near_boundary_4d); @@ -278,9 +282,9 @@ fn benchmark_edge_cases(c: &mut Criterion) { // 5D edge cases - 5-simplex let simplex_5d = standard_simplex::<5>(); let boundary_point_5d = simplex_5d[0]; // Point on boundary - let far_point_5d = Point::new([1000.0, 1000.0, 1000.0, 1000.0, 1000.0]); + let far_point_5d = finite_point([1000.0, 1000.0, 1000.0, 1000.0, 1000.0]); // Near-boundary point (epsilon away from circumsphere) - let near_boundary_5d = Point::new([eps, 0.0, 0.0, 0.0, 0.0]); + let near_boundary_5d = finite_point([eps, 0.0, 0.0, 0.0, 0.0]); bench_edge_case!(c, 5, "boundary_point", simplex_5d, boundary_point_5d); bench_edge_case!(c, 5, "far_point", simplex_5d, far_point_5d); bench_edge_case!(c, 5, "near_boundary", simplex_5d, near_boundary_5d); diff --git a/benches/cold_path_predicates.rs b/benches/cold_path_predicates.rs index 9fe152d9..a0974e1b 100644 --- a/benches/cold_path_predicates.rs +++ b/benches/cold_path_predicates.rs @@ -45,6 +45,10 @@ use std::hint::black_box; pub mod bench_utils; use bench_utils::{abort_benchmark, bench_result}; +fn finite_point(coords: [f64; D]) -> Point { + Point::try_new(coords).unwrap_or_else(|_| std::process::abort()) +} + fn coordinate_range(min: f64, max: f64, context: &'static str) -> CoordinateRange { bench_result(CoordinateRange::try_new(min, max), context) } @@ -61,13 +65,13 @@ const HOT_QUERIES: usize = 10_000; const NEAR_BOUNDARY_QUERIES: usize = 1_000; /// Standard D-dimensional simplex: origin + unit basis vectors. -fn standard_simplex() -> Vec> { +fn standard_simplex() -> Vec> { let mut pts = Vec::with_capacity(D + 1); - pts.push(Point::new([0.0; D])); + pts.push(finite_point([0.0; D])); for i in 0..D { let mut coords = [0.0; D]; coords[i] = 1.0; - pts.push(Point::new(coords)); + pts.push(finite_point(coords)); } pts } @@ -76,7 +80,7 @@ fn standard_simplex() -> Vec> { /// /// Uses the range `[-10, 10]` against a unit simplex so that the Shewchuk /// errbound comfortably resolves the sign in Stage 1. -fn hot_queries() -> Vec> { +fn hot_queries() -> Vec> { generate_random_points_in_range_seeded( HOT_QUERIES, coordinate_range(-10.0, 10.0, "hot-path query bounds must be valid"), @@ -88,7 +92,7 @@ fn hot_queries() -> Vec> { /// /// Uses a narrow range centered on the standard simplex so many queries land /// within the Stage-1 errbound window and spill into Stage 2. -fn near_boundary_queries() -> Vec> { +fn near_boundary_queries() -> Vec> { // Centered near the circumsphere radius of the standard simplex (~0.5 for // the D = 3 unit case); the exact value is unimportant — we just want a // high rate of errbound-ambiguous inputs. @@ -100,7 +104,7 @@ fn near_boundary_queries() -> Vec> { } /// Run `insphere` across `queries` against `simplex`, black-boxing each result. -fn run_insphere(simplex: &[Point], queries: &[Point]) { +fn run_insphere(simplex: &[Point], queries: &[Point]) { for q in queries { let result = match insphere(black_box(simplex), black_box(*q)) { Ok(value) => value, @@ -111,7 +115,7 @@ fn run_insphere(simplex: &[Point], queries: &[Point(simplex: &[Point], queries: &[Point]) { +fn run_insphere_lifted(simplex: &[Point], queries: &[Point]) { for q in queries { let result = match insphere_lifted(black_box(simplex), black_box(*q)) { Ok(value) => value, diff --git a/benches/common/flip_workflows.rs b/benches/common/flip_workflows.rs index d9506458..99de8f85 100644 --- a/benches/common/flip_workflows.rs +++ b/benches/common/flip_workflows.rs @@ -12,16 +12,16 @@ use delaunay::prelude::construction::{ InsertionOrderStrategy, TopologyGuarantee, Vertex, }; use delaunay::prelude::flips::{ - BistellarFlips, EdgeKey, FacetHandle, FlipError, RidgeHandle, SimplexKey, TriangleHandle, + BistellarFlips, EdgeKey, EdgeKeyError, FacetHandle, FlipError, RidgeHandle, SimplexKey, + TriangleHandle, TriangleHandleError, }; -use delaunay::prelude::geometry::{Point, RobustKernel, simplex_volume}; +use delaunay::prelude::geometry::{CoordinateConversionError, Point, RobustKernel, simplex_volume}; use delaunay::prelude::query::{JaccardComputationError, format_jaccard_report}; -use delaunay::prelude::tds::{InvariantError, TdsError, VertexKey}; +use delaunay::prelude::tds::{FacetError, InvariantError, TdsError, VertexKey}; use delaunay::prelude::topology::validation::{ ManifoldError, RidgeVertices, RidgeVerticesError, ridge_star_simplices, }; use delaunay::prelude::validation::DelaunayTriangulationValidationError; -use delaunay::vertex; use thiserror::Error; use uuid::Uuid; @@ -49,6 +49,10 @@ pub enum FlipWorkflowError { source: Box, }, + /// Vertex coordinate conversion failed during fixture preparation. + #[error(transparent)] + CoordinateConversion(#[from] CoordinateConversionError), + /// A simplex key referenced by the workflow was not live. #[error("simplex key {simplex_key:?} does not exist")] MissingSimplex { @@ -63,6 +67,32 @@ pub enum FlipWorkflowError { vertex_key: VertexKey, }, + /// Facet handle construction failed before candidate inspection. + #[error("failed to construct facet handle {simplex_key:?}:{facet_index}: {source}")] + FacetHandleConstruction { + /// Candidate simplex key. + simplex_key: SimplexKey, + /// Candidate facet index. + facet_index: u8, + /// Underlying facet-handle construction failure. + #[source] + source: FacetError, + }, + + /// Ridge handle construction failed before candidate inspection. + #[error("failed to construct ridge handle {simplex_key:?}:({omit_a}, {omit_b}): {source}")] + RidgeHandleConstruction { + /// Candidate simplex key. + simplex_key: SimplexKey, + /// First omitted index. + omit_a: u8, + /// Second omitted index. + omit_b: u8, + /// Underlying ridge-handle construction failure. + #[source] + source: Box, + }, + /// Ridge vertices could not be parsed into a valid ridge vertex set. #[error("invalid ridge vertices for {ridge:?}: {source}")] InvalidRidgeVertices { @@ -262,6 +292,26 @@ pub enum FlipWorkflowError { expected: usize, }, + /// A flip reported two inserted edge endpoints that do not form a real edge. + #[error("{move_kind} flip reported an invalid inserted edge: {source}")] + InvalidInsertedEdge { + /// Flip move kind. + move_kind: FlipMoveKind, + /// Underlying edge-key parsing failure. + #[source] + source: EdgeKeyError, + }, + + /// A flip reported three inserted triangle vertices that do not form a real triangle. + #[error("{move_kind} flip reported an invalid inserted triangle: {source}")] + InvalidInsertedTriangle { + /// Flip move kind. + move_kind: FlipMoveKind, + /// Underlying triangle-handle parsing failure. + #[source] + source: TriangleHandleError, + }, + /// A roundtrip produced a triangulation that failed validation. #[error("{context} produced invalid triangulation: {source}")] InvalidAfterRoundtrip { @@ -350,6 +400,24 @@ pub enum FlipCandidateError { /// Expected inserted vertex count. expected: usize, }, + /// A candidate flip reported two inserted edge endpoints that do not form a real edge. + #[error("{move_kind} candidate reported an invalid inserted edge: {source}")] + InvalidInsertedEdge { + /// Candidate move kind. + move_kind: FlipMoveKind, + /// Underlying edge-key parsing failure. + #[source] + source: EdgeKeyError, + }, + /// A candidate flip reported three inserted triangle vertices that do not form a real triangle. + #[error("{move_kind} candidate reported an invalid inserted triangle: {source}")] + InvalidInsertedTriangle { + /// Candidate move kind. + move_kind: FlipMoveKind, + /// Underlying triangle-handle parsing failure. + #[source] + source: TriangleHandleError, + }, /// Candidate validation failed after the forward flip. #[error("{move_kind} candidate produced invalid triangulation: {source}")] InvalidAfterForwardFlip { @@ -375,7 +443,7 @@ pub enum CandidateFilter { impl CandidateFilter { /// Returns whether the candidate support points satisfy this selection policy. - fn accepts(self, points: &[Point]) -> bool { + fn accepts(self, points: &[Point]) -> bool { match self { Self::Any => true, Self::TouchesAdversarialFeature => points @@ -410,8 +478,8 @@ pub fn build_flip_dt( ) -> FlipWorkflowResult> { let vertices = points .iter() - .map(|coords| vertex!(*coords)) - .collect::>>(); + .map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(*coords)) + .collect::>, _>>()?; let options = ConstructionOptions::default().with_insertion_order(InsertionOrderStrategy::Input); @@ -606,7 +674,14 @@ pub fn flippable_k2_facet( let Ok(facet_index) = u8::try_from(facet_index) else { continue; }; - let facet = FacetHandle::new(simplex_key, facet_index); + let facet = + FacetHandle::try_new(dt.tds(), simplex_key, facet_index).map_err(|source| { + FlipWorkflowError::FacetHandleConstruction { + simplex_key, + facet_index, + source, + } + })?; let support = facet_support_points(dt, facet)?; if !filter.accepts(&support) { continue; @@ -625,10 +700,20 @@ pub fn flippable_k2_facet( )); continue; } - let edge = EdgeKey::new( + let edge = match EdgeKey::try_new( + trial.tds(), info.inserted_face_vertices[0], info.inserted_face_vertices[1], - ); + ) { + Ok(edge) => edge, + Err(source) => { + last_error = Some(Box::new(FlipCandidateError::InvalidInsertedEdge { + move_kind: FlipMoveKind::K2, + source, + })); + continue; + } + }; if let Err(source) = trial.as_triangulation().validate() { last_error = Some(Box::new(FlipCandidateError::InvalidAfterForwardFlip { move_kind: FlipMoveKind::K2, @@ -689,7 +774,14 @@ pub fn flippable_k3_ridge( let Ok(omit_b) = u8::try_from(j) else { continue; }; - let ridge = RidgeHandle::new(simplex_key, omit_a, omit_b); + let ridge = RidgeHandle::try_new(dt.tds(), simplex_key, omit_a, omit_b).map_err( + |source| FlipWorkflowError::RidgeHandleConstruction { + simplex_key, + omit_a, + omit_b, + source: Box::new(source), + }, + )?; let support = ridge_support_points(dt, ridge)?; if !filter.accepts(&support) { continue; @@ -708,11 +800,21 @@ pub fn flippable_k3_ridge( )); continue; } - let triangle = TriangleHandle::new( + let triangle = match TriangleHandle::try_new( info.inserted_face_vertices[0], info.inserted_face_vertices[1], info.inserted_face_vertices[2], - ); + ) { + Ok(triangle) => triangle, + Err(source) => { + last_error = + Some(Box::new(FlipCandidateError::InvalidInsertedTriangle { + move_kind: FlipMoveKind::K3, + source, + })); + continue; + } + }; if let Err(source) = trial.as_triangulation().validate() { last_error = Some(Box::new(FlipCandidateError::InvalidAfterForwardFlip { @@ -792,7 +894,8 @@ pub fn roundtrip_k1( dt: &mut FlipTriangulation, simplex_key: SimplexKey, ) -> FlipWorkflowResult<()> { - let new_vertex = vertex!(simplex_centroid(dt, simplex_key)?); + let new_vertex = + delaunay::prelude::Vertex::<(), _>::try_new(simplex_centroid(dt, simplex_key)?)?; let new_uuid = new_vertex.uuid(); dt.flip_k1_insert(simplex_key, new_vertex) .map_err(|source| FlipWorkflowError::FlipFailed { @@ -840,10 +943,15 @@ pub fn roundtrip_k2( expected: 2, }); } - let edge = EdgeKey::new( + let edge = EdgeKey::try_new( + dt.tds(), info.inserted_face_vertices[0], info.inserted_face_vertices[1], - ); + ) + .map_err(|source| FlipWorkflowError::InvalidInsertedEdge { + move_kind: FlipMoveKind::K2, + source, + })?; dt.flip_k2_inverse_from_edge(edge) .map_err(|source| FlipWorkflowError::InverseFlipFailed { dimension: D, @@ -908,11 +1016,15 @@ pub fn roundtrip_k3( expected: 3, }); } - let triangle = TriangleHandle::new( + let triangle = TriangleHandle::try_new( info.inserted_face_vertices[0], info.inserted_face_vertices[1], info.inserted_face_vertices[2], - ); + ) + .map_err(|source| FlipWorkflowError::InvalidInsertedTriangle { + move_kind: FlipMoveKind::K3, + source, + })?; dt.flip_k3_inverse_from_triangle(triangle) .map_err(|source| FlipWorkflowError::InverseFlipFailed { dimension: D, @@ -1086,7 +1198,7 @@ fn simplex_centroid( fn simplex_points( dt: &FlipTriangulation, simplex_key: SimplexKey, -) -> FlipWorkflowResult>> { +) -> FlipWorkflowResult>> { let simplex = dt .tds() .simplex(simplex_key) @@ -1104,7 +1216,7 @@ fn simplex_points( fn facet_support_points( dt: &FlipTriangulation, facet: FacetHandle, -) -> FlipWorkflowResult>> { +) -> FlipWorkflowResult>> { let simplex = dt.tds() .simplex(facet.simplex_key()) @@ -1149,7 +1261,7 @@ fn facet_support_points( fn ridge_support_points( dt: &FlipTriangulation, ridge: RidgeHandle, -) -> FlipWorkflowResult>> { +) -> FlipWorkflowResult>> { let simplex = dt.tds() .simplex(ridge.simplex_key()) @@ -1228,7 +1340,7 @@ fn ridge_star_error(ridge: RidgeHandle, source: ManifoldError) -> FlipWorkflowEr fn vertex_points( dt: &FlipTriangulation, keys: &[VertexKey], -) -> FlipWorkflowResult>> { +) -> FlipWorkflowResult>> { keys.iter() .map(|vertex_key| { dt.tds() diff --git a/benches/profiling_suite.rs b/benches/profiling_suite.rs index 7059fd69..802c6caf 100644 --- a/benches/profiling_suite.rs +++ b/benches/profiling_suite.rs @@ -80,9 +80,8 @@ use delaunay::prelude::construction::{ ConstructionOptions, DelaunayTriangulation, DelaunayTriangulationBuilder, RetryPolicy, Vertex, }; use delaunay::prelude::generators::generate_random_points_in_range_seeded; -use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate, CoordinateRange, Point}; +use delaunay::prelude::geometry::{AdaptiveKernel, CoordinateRange, Point}; use delaunay::prelude::query::*; -use delaunay::vertex; use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System, get_current_pid}; /// Shared benchmark setup error helpers. @@ -90,6 +89,10 @@ use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System, get_cu pub mod bench_utils; use bench_utils::{abort_benchmark, bench_result}; +fn finite_point(coords: [f64; D]) -> Point { + Point::try_new(coords).unwrap_or_else(|_| std::process::abort()) +} + fn retry_attempts(value: usize) -> NonZeroUsize { let Some(attempts) = NonZeroUsize::new(value) else { unreachable!("hard-coded retry attempt count must be non-zero"); @@ -284,7 +287,7 @@ fn construction_options(seed: u64) -> ConstructionOptions { } fn construct_triangulation( - vertices: &[Vertex], + vertices: &[Vertex<(), D>], seed: u64, ) -> DelaunayTriangulation, (), (), D> { bench_result( @@ -297,14 +300,22 @@ fn construct_triangulation( } /// Converts generated points into benchmark vertices without attaching data. -fn vertices_from_points(points: Vec>) -> Vec> { - points.into_iter().map(|point| vertex!(point)).collect() +fn vertices_from_points(points: Vec>) -> Vec> { + points + .into_iter() + .map(|point| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new(point.into()), + "finite benchmark vertex coordinates", + ) + }) + .collect() } /// Measure memory delta during triangulation construction. fn measure_construction_with_memory(n_points: usize, seed: u64) -> MemoryInfo { let mem_before = memory_usage_kib(); - let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); + let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); let vertices = vertices_from_points(points); let mem_before_tds = memory_usage_kib(); @@ -356,12 +367,12 @@ fn gen_points( count: usize, distribution: PointDistribution, seed: u64, -) -> Vec> { +) -> Vec> { match distribution { PointDistribution::Random => { generate_random_points_in_range_seeded(count, wide_bounds(), seed) } - PointDistribution::Adversarial => generate_random_points_in_range_seeded::( + PointDistribution::Adversarial => generate_random_points_in_range_seeded::( count, adversarial_bounds(), seed ^ 0xA5A5_A5A5_A5A5_A5A5, @@ -382,7 +393,7 @@ fn gen_points( let perturbation = f64::from((index + axis_number) % 11) * 1.0e-6; *coord = base.mul_add(1.0e3, 1.0e9 + axis_offset + cluster_offset + perturbation); } - Point::new(coords) + finite_point(coords) }) .collect(), } @@ -410,7 +421,7 @@ fn bench_construction(c: &mut Criterion, dimension_name: &str, n b.iter_batched( || { let points = - generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); + generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); vertices_from_points(points) }, |vertices| { @@ -473,7 +484,7 @@ fn bench_validation(c: &mut Criterion, dimension_name: &str, n_p } let seed = seed_for_case::(n_points); - let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); + let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); let vertices = vertices_from_points(points); let dt = construct_triangulation::(&vertices, seed); let tri = dt.as_triangulation(); @@ -509,7 +520,7 @@ fn bench_neighbor_queries( } let seed = seed_for_case::(n_points); - let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); + let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); let vertices = vertices_from_points(points); let dt = construct_triangulation::(&vertices, seed); let tds = dt.tds(); @@ -546,7 +557,7 @@ fn bench_vertex_iteration( } let seed = seed_for_case::(n_points); - let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); + let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); let vertices = vertices_from_points(points); let dt = construct_triangulation::(&vertices, seed); let tds = dt.tds(); @@ -581,7 +592,7 @@ fn bench_simplex_iteration( } let seed = seed_for_case::(n_points); - let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); + let points = generate_random_points_in_range_seeded::(n_points, wide_bounds(), seed); let vertices = vertices_from_points(points); let dt = construct_triangulation::(&vertices, seed); let tds = dt.tds(); @@ -822,7 +833,15 @@ fn bench_memory_usage( let points = gen_points::(count, PointDistribution::Random, DEFAULT_SEED); #[cfg(all(feature = "count-allocations", feature = "bench-logging"))] let pts_len = points.len(); - let vertices: Vec<_> = points.iter().map(|p| vertex!(*p)).collect(); + let vertices: Vec<_> = points + .iter() + .map(|p| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*p).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect(); let start_time = Instant::now(); let alloc_info = measure(|| { @@ -914,7 +933,15 @@ fn benchmark_query_latency(c: &mut Criterion) { |b, &count| { // Setup: Create triangulation and query points let points = gen_points::<3>(count, PointDistribution::Random, DEFAULT_SEED); - let vertices: Vec<_> = points.iter().map(|p| vertex!(*p)).collect(); + let vertices: Vec<_> = points + .iter() + .map(|p| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*p).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect(); let Ok(dt) = DelaunayTriangulationBuilder::new(&vertices).build::<()>() else { // Construction hit a geometric degeneracy; skip this benchmark entry b.iter(|| {}); @@ -927,7 +954,7 @@ fn benchmark_query_latency(c: &mut Criterion) { // Precompute all valid simplex vertices outside the benchmark loop let mut precomputed_simplices: Vec< - SmallBuffer, SIMPLEX_VERTICES_BUFFER_SIZE>, + SmallBuffer, SIMPLEX_VERTICES_BUFFER_SIZE>, > = Vec::with_capacity(MAX_PRECOMPUTED_SIMPLICES); let mut sampled_count = 0; for simplex in tds.simplices() { @@ -939,10 +966,8 @@ fn benchmark_query_latency(c: &mut Criterion) { let vertex_keys = simplex.1.vertices(); if vertex_keys.len() == 4 { // Valid 3D simplex - collect points - let mut vertex_points: SmallBuffer< - Point, - SIMPLEX_VERTICES_BUFFER_SIZE, - > = SmallBuffer::new(); + let mut vertex_points: SmallBuffer, SIMPLEX_VERTICES_BUFFER_SIZE> = + SmallBuffer::new(); for vkey in vertex_keys { if let Some(vertex) = tds.vertex(*vkey) { vertex_points.push(*vertex.point()); @@ -1007,7 +1032,15 @@ macro_rules! benchmark_validation_components_dimension { .find_map(|offset| { let seed = DEFAULT_SEED.wrapping_add(offset); let points = gen_points::<$dim>($count, distribution, seed); - let vertices: Vec<_> = points.iter().map(|point| vertex!(*point)).collect(); + let vertices: Vec<_> = points + .iter() + .map(|point| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*point).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect(); let builder = DelaunayTriangulationBuilder::new(&vertices); let builder = if is_adversarial { let attempts = retry_attempts(8); @@ -1124,7 +1157,15 @@ fn bench_bottlenecks(c: &mut Criterion) { || { let points = gen_points::<3>(count, PointDistribution::Random, DEFAULT_SEED); - let vertices: Vec<_> = points.iter().map(|p| vertex!(*p)).collect(); + let vertices: Vec<_> = points + .iter() + .map(|p| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*p).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect(); DelaunayTriangulationBuilder::new(&vertices) .build::<()>() .ok() @@ -1156,7 +1197,15 @@ fn bench_bottlenecks(c: &mut Criterion) { || { let points = gen_points::<3>(count, PointDistribution::Random, DEFAULT_SEED); - let vertices: Vec<_> = points.iter().map(|p| vertex!(*p)).collect(); + let vertices: Vec<_> = points + .iter() + .map(|p| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new((*p).into()), + "finite benchmark vertex coordinates", + ) + }) + .collect(); DelaunayTriangulationBuilder::new(&vertices) .build::<()>() .ok() diff --git a/benches/remove_vertex.rs b/benches/remove_vertex.rs index e3527d1b..3fbd7d6c 100644 --- a/benches/remove_vertex.rs +++ b/benches/remove_vertex.rs @@ -18,7 +18,7 @@ use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use delaunay::prelude::construction::{DelaunayTriangulation, Vertex}; use delaunay::prelude::generators::generate_random_points_in_range_seeded; -use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate, CoordinateRange, Point}; +use delaunay::prelude::geometry::{AdaptiveKernel, CoordinateRange, Point}; use delaunay::prelude::tds::VertexKey; use std::hint::black_box; use std::time::Duration; @@ -36,6 +36,10 @@ const COSPHERICAL_CENTER: f64 = 0.5; const COSPHERICAL_RADIUS: f64 = 0.25; const LARGE_COORDINATE_SCALE: f64 = 1.0e6; +fn finite_point(coords: [f64; D]) -> Point { + Point::try_new(coords).unwrap_or_else(|_| std::process::abort()) +} + fn interior_bounds() -> CoordinateRange { bench_result( CoordinateRange::try_new(0.0_f64, 1.0), @@ -128,7 +132,7 @@ fn generate_vertices( requested_vertices: usize, seed: u64, fixture_kind: FixtureKind, -) -> Vec> { +) -> Vec> { let generated_count = requested_vertices.saturating_sub(D + 1); let mut points = simplex_points::(); let generated_points = match fixture_kind { @@ -144,9 +148,8 @@ fn generate_vertices( } /// Generate well-conditioned interior points inside the canonical simplex. -fn generate_interior_points(count: usize, seed: u64) -> Vec> { - let raw_points = - generate_random_points_in_range_seeded::(count, interior_bounds(), seed); +fn generate_interior_points(count: usize, seed: u64) -> Vec> { + let raw_points = generate_random_points_in_range_seeded::(count, interior_bounds(), seed); let mut points = Vec::with_capacity(count); for (index, raw_point) in raw_points.iter().enumerate() { @@ -156,16 +159,15 @@ fn generate_interior_points(count: usize, seed: u64) -> Vec(count: usize, seed: u64) -> Vec> { - let raw_points = - generate_random_points_in_range_seeded::(count, interior_bounds(), seed); +fn generate_near_boundary_points(count: usize, seed: u64) -> Vec> { + let raw_points = generate_random_points_in_range_seeded::(count, interior_bounds(), seed); let mut points = Vec::with_capacity(count); for (index, raw_point) in raw_points.iter().enumerate() { @@ -177,16 +179,15 @@ fn generate_near_boundary_points(count: usize, seed: u64) -> Vec } coords[near_boundary_axis] = NEAR_BOUNDARY_EPSILON * usize_to_f64(index + 1, "near-boundary index too large"); - points.push(Point::new(coords)); + points.push(finite_point(coords)); } points } /// Generate points on a shared sphere to stress cospherical predicates. -fn generate_cospherical_points(count: usize, seed: u64) -> Vec> { - let raw_points = - generate_random_points_in_range_seeded::(count, interior_bounds(), seed); +fn generate_cospherical_points(count: usize, seed: u64) -> Vec> { + let raw_points = generate_random_points_in_range_seeded::(count, interior_bounds(), seed); let mut points = Vec::with_capacity(count); for raw_point in &raw_points { @@ -195,14 +196,14 @@ fn generate_cospherical_points(count: usize, seed: u64) -> Vec

(count: usize, seed: u64) -> Vec> { +fn generate_near_degenerate_simplex(count: usize, seed: u64) -> Vec> { let seed_offset = f64::from(bench_result( u32::try_from(seed % 997), "near-degenerate seed phase does not fit in u32", @@ -219,16 +220,15 @@ fn generate_near_degenerate_simplex(count: usize, seed: u64) -> *coord = (NEAR_DEGENERATE_EPSILON * axis_factor) .mul_add(index_factor, diagonal + seed_offset); } - points.push(Point::new(coords)); + points.push(finite_point(coords)); } points } /// Generate finite points with large coordinates to stress scale-sensitive paths. -fn generate_large_coordinate_points(count: usize, seed: u64) -> Vec> { - let raw_points = - generate_random_points_in_range_seeded::(count, interior_bounds(), seed); +fn generate_large_coordinate_points(count: usize, seed: u64) -> Vec> { + let raw_points = generate_random_points_in_range_seeded::(count, interior_bounds(), seed); let mut points = Vec::with_capacity(count); for (index, raw_point) in raw_points.iter().enumerate() { @@ -241,28 +241,28 @@ fn generate_large_coordinate_points(count: usize, seed: u64) -> LARGE_COORDINATE_JITTER.mul_add(*raw_coord, index_offset), ); } - points.push(Point::new(coords)); + points.push(finite_point(coords)); } points } /// Generate the minimal full-dimensional simplex points. -fn simplex_points() -> Vec> { +fn simplex_points() -> Vec> { let mut points = Vec::with_capacity(D + 1); - points.push(Point::new([0.0; D])); + points.push(finite_point([0.0; D])); for axis in 0..D { let mut coords = [0.0; D]; coords[axis] = 1.0; - points.push(Point::new(coords)); + points.push(finite_point(coords)); } points } /// Generate the minimal full-dimensional simplex for the rollback benchmark. -fn simplex_vertices() -> Vec> { +fn simplex_vertices() -> Vec> { Vertex::from_points(&simplex_points::()) } @@ -276,7 +276,7 @@ fn interior_radius(index: usize) -> f64 { } /// Convert a random point in `[0, 1]^D` into a positive simplex direction. -fn normalized_positive_direction(point: &Point) -> [f64; D] { +fn normalized_positive_direction(point: &Point) -> [f64; D] { let mut weights = [0.0; D]; let mut weight_sum = 0.0; @@ -293,7 +293,7 @@ fn normalized_positive_direction(point: &Point) -> [f64; } /// Convert a random point in `[0, 1]^D` into a unit direction around the origin. -fn centered_unit_direction(point: &Point) -> [f64; D] { +fn centered_unit_direction(point: &Point) -> [f64; D] { let mut direction = [0.0; D]; let mut norm_squared = 0.0; diff --git a/benches/tds_clone.rs b/benches/tds_clone.rs index ab890add..6cf3dd03 100644 --- a/benches/tds_clone.rs +++ b/benches/tds_clone.rs @@ -44,7 +44,7 @@ fn benchmark_bounds() -> CoordinateRange { struct CloneSource { vertex_count: usize, simplex_count: usize, - tds: Tds, + tds: Tds<(), (), D>, } /// Derive a deterministic, dimension-specific seed for one benchmark case. @@ -58,15 +58,9 @@ fn seed_for_case(requested_vertices: usize, seed_base: u64) -> u } /// Generate a reproducible vertex set for one clone-cost benchmark fixture. -fn generate_vertices( - requested_vertices: usize, - seed: u64, -) -> Vec> { - let points = generate_random_points_in_range_seeded::( - requested_vertices, - benchmark_bounds(), - seed, - ); +fn generate_vertices(requested_vertices: usize, seed: u64) -> Vec> { + let points = + generate_random_points_in_range_seeded::(requested_vertices, benchmark_bounds(), seed); Vertex::from_points(&points) } diff --git a/benches/topology_guarantee_construction.rs b/benches/topology_guarantee_construction.rs index 8da08113..8762f196 100644 --- a/benches/topology_guarantee_construction.rs +++ b/benches/topology_guarantee_construction.rs @@ -3,7 +3,7 @@ //! Benchmark: construction cost vs topology guarantee (2D–5D) //! //! This benchmark compares `TopologyGuarantee::Pseudomanifold`, `TopologyGuarantee::PLManifold` -//! (incremental: ridge-link during insertion, vertex-link at completion), and +//! (incremental: ridge-link during insertion-link at completion), and //! `TopologyGuarantee::PLManifoldStrict` (vertex-link after every insertion) for Delaunay //! triangulation construction. //! @@ -20,7 +20,6 @@ use delaunay::prelude::generators::generate_random_points_in_range_seeded; use delaunay::prelude::geometry::CoordinateRange; use delaunay::prelude::repair::DelaunayRepairPolicy; use delaunay::prelude::validation::ValidationPolicy; -use delaunay::vertex; use std::hint::black_box; use std::time::Duration; @@ -56,8 +55,16 @@ fn bench_dimension( // Deterministic input per (dimension, count). let seed = seed_base ^ (n_points as u64).wrapping_mul(SEED_SALT); let points = - generate_random_points_in_range_seeded::(n_points, benchmark_bounds(), seed); - let vertices = points.into_iter().map(|p| vertex!(p)).collect::>(); + generate_random_points_in_range_seeded::(n_points, benchmark_bounds(), seed); + let vertices = points + .into_iter() + .map(|p| { + bench_result( + delaunay::prelude::Vertex::<(), _>::try_new(p.into()), + "finite benchmark vertex coordinates", + ) + }) + .collect::>(); group.bench_with_input( BenchmarkId::new("pseudomanifold", n_points), diff --git a/docs/ORIENTATION_SPEC.md b/docs/ORIENTATION_SPEC.md index d3a37abf..c298fea6 100644 --- a/docs/ORIENTATION_SPEC.md +++ b/docs/ORIENTATION_SPEC.md @@ -200,7 +200,7 @@ drive repair, but replacement-simplex orientation itself uses `robust_orientatio the TDS, calls `normalize_and_promote_positive_orientation()`, validates TDS structure/topology, rejects geometrically degenerate simplices, and then enforces the Delaunay property. -- `.toroidal([..])` builds an image-point triangulation and then runs +- `.try_toroidal([..])` builds an image-point triangulation and then runs orientation normalization, lifted geometric orientation validation, final Levels 1-3 topology validation, and final Level 4 Delaunay validation before returning the quotient triangulation. diff --git a/docs/api_design.md b/docs/api_design.md index f466fefd..2cf28995 100644 --- a/docs/api_design.md +++ b/docs/api_design.md @@ -62,7 +62,7 @@ For most use cases, the builder with default options is sufficient: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::insertion::InsertionError; use delaunay::prelude::tds::InvariantError; @@ -80,15 +80,15 @@ enum ExampleError { fn main() -> Result<(), ExampleError> { // Simple construction from vertices (Euclidean space, default options) let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; // Incremental insertion (maintains Delaunay property) - let new_vertex = vertex!([0.5, 0.5, 0.5]); + let new_vertex = delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?; dt.insert(new_vertex)?; // Vertex removal (topology-preserving, with automatic repair when enabled) @@ -107,7 +107,6 @@ use `DelaunayTriangulationBuilder`: ```rust use delaunay::prelude::construction::{ DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee, - vertex, }; use delaunay::prelude::insertion::InsertionError; use delaunay::prelude::validation::ValidationPolicy; @@ -123,28 +122,29 @@ enum ExampleError { fn main() -> Result<(), ExampleError> { // Canonicalized toroidal triangulation in 2D let vertices = vec![ - vertex!([0.1, 0.1]), - vertex!([0.9, 0.9]), - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.1])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.9, 0.9])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) // Canonicalized toroidal construction + .try_canonicalized_toroidal([1.0, 1.0]) + .expect("unit toroidal domain is valid") // Canonicalized toroidal construction .topology_guarantee(TopologyGuarantee::PLManifoldStrict) .build::<()>()?; dt.set_validation_policy(ValidationPolicy::Always); // Works like any other DelaunayTriangulation - dt.insert(vertex!([0.25, 0.75]))?; + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.75])?)?; Ok(()) } ``` **When to use the Builder:** -- **Toroidal construction**: Use `.toroidal()` for periodic image-point construction or - `.canonicalized_toroidal()` for canonicalized construction with explicit domain periods. +- **Toroidal construction**: Use `.try_toroidal()` for periodic image-point construction or + `.try_canonicalized_toroidal()` for canonicalized construction with explicit domain periods. The periodic image-point path is release-validated in 2D and compact 3D; 4D/5D fail fast pending scalable quotient construction in issue #416. - **Custom topology guarantees**: Set stricter or more relaxed manifold checks @@ -185,7 +185,7 @@ The Edit API is exposed through the `BistellarFlips` trait in `prelude::flips`: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::flips::*; @@ -200,10 +200,10 @@ enum ExampleError { fn main() -> Result<(), ExampleError> { // Start with a valid triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -211,7 +211,7 @@ fn main() -> Result<(), ExampleError> { let Some((simplex_key, _)) = dt.simplices().next() else { return Ok(()); }; - let info = dt.flip_k1_insert(simplex_key, vertex!([0.25, 0.25, 0.25]))?; + let info = dt.flip_k1_insert(simplex_key, delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25])?)?; // k=1 inverse: Remove a vertex (collapses its star) let vertex_key = info.inserted_face_vertices[0]; @@ -222,7 +222,7 @@ fn main() -> Result<(), ExampleError> { let info = dt.flip_k2(facet)?; // k=2 inverse: Flip from an edge star (D simplices ↔ 2 simplices) - let edge = EdgeKey::new(info.inserted_face_vertices[0], info.inserted_face_vertices[1]); + let edge = EdgeKey::try_new(info.inserted_face_vertices[0], info.inserted_face_vertices[1])?; dt.flip_k2_inverse_from_edge(edge)?; // k=3 move: Flip a ridge (3 simplices ↔ D-1 simplices, requires D ≥ 3) @@ -230,11 +230,11 @@ fn main() -> Result<(), ExampleError> { let info = dt.flip_k3(ridge)?; // k=3 inverse: Flip from a triangle star (D-1 simplices ↔ 3 simplices) - let triangle = TriangleHandle::new( + let triangle = TriangleHandle::try_new( info.inserted_face_vertices[0], info.inserted_face_vertices[1], info.inserted_face_vertices[2], - ); + )?; dt.flip_k3_inverse_from_triangle(triangle)?; Ok(()) } @@ -308,7 +308,7 @@ You can mix both APIs in the same workflow: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::flips::*; use delaunay::prelude::insertion::InsertionError; @@ -326,15 +326,15 @@ enum ExampleError { fn main() -> Result<(), ExampleError> { // 1. Build initial triangulation (Builder API) let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; // 2. Add vertices using Builder API (maintains Delaunay) - dt.insert(vertex!([0.5, 0.5, 0.5]))?; + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?)?; // 3. Make custom topology edits (Edit API) let facet = /* ... */; @@ -418,7 +418,7 @@ common "repair topology then restore Delaunay" workflow: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::delaunayize::{ DelaunayizeConfig, DelaunayizeError, delaunayize_by_flips, @@ -434,10 +434,10 @@ enum ExampleError { fn main() -> Result<(), ExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; diff --git a/docs/archive/fix-delaunay.md b/docs/archive/fix-delaunay.md index ed1c1029..01d4789a 100644 --- a/docs/archive/fix-delaunay.md +++ b/docs/archive/fix-delaunay.md @@ -38,7 +38,7 @@ Goal: build an initial D-simplex from the input vertex set, handling duplicates ## Stage 2: Unified Per-Vertex Insertion (Fast → Robust → Skip) -Goal: for each vertex, attempt fast insertion, then robust insertion, and finally skip with diagnostics if both fail. +Goal: for each attempt fast insertion, then robust insertion, and finally skip with diagnostics if both fail. ### Vertex classification diff --git a/docs/archive/invariant_validation_plan.md b/docs/archive/invariant_validation_plan.md index e8f0e500..7d05f608 100644 --- a/docs/archive/invariant_validation_plan.md +++ b/docs/archive/invariant_validation_plan.md @@ -206,7 +206,7 @@ fn prop_empty_circumsphere_4d(vertices: Vec, 4>>) { let mut algorithm = RobustBowyerWatson::new(); for vertex in vertices { - match algorithm.insert_vertex(&mut tds, vertex) { + match algorithm.insert_vertex(&mut tds, vertex.uuid(), vertex.point().coords()) { Ok(_) => { // Success - verify invariants hold assert!(tds.is_valid().is_ok(), "Invariants must hold after successful insertion"); diff --git a/docs/archive/optimization_recommendations_historical.md b/docs/archive/optimization_recommendations_historical.md index e6705b12..977e886e 100644 --- a/docs/archive/optimization_recommendations_historical.md +++ b/docs/archive/optimization_recommendations_historical.md @@ -168,7 +168,7 @@ impl Tds { **Optimizations IMPLEMENTED:** -- **✅ InsertionBuffers structure**: Reusable buffers for bad cells, boundary facets, vertex points, and visible facets +- **✅ InsertionBuffers structure**: Reusable buffers for bad cells, boundary facets points, and visible facets - **✅ Pre-allocated capacity**: Buffers created with appropriate initial capacity - **✅ Buffer preparation methods**: `prepare_bad_cells_buffer()`, `prepare_vertex_points_buffer()`, etc. - **✅ Reduced allocations**: Algorithm reuses buffers across multiple insertions @@ -434,7 +434,7 @@ where SpatialIndex { tree } } - fn find_bad_cells_spatial(&self, vertex: &Vertex, + fn find_bad_cells_spatial(&self, vertex: &Vertex, index: &SpatialIndex) -> Vec { let vertex_point = vertex.point().to_array(); @@ -533,7 +533,7 @@ fn is_valid_parallel(&self) -> Result<(), TriangulationValidationError> { } // Parallel bad cell detection for Bowyer-Watson -fn find_bad_cells_parallel(&self, vertex: &Vertex) -> Vec { + fn find_bad_cells_parallel(&self, vertex: &Vertex) -> Vec { self.cells .par_iter() .filter_map(|(cell_key, cell)| { diff --git a/docs/archive/phase2_bowyer_watson_optimization.md b/docs/archive/phase2_bowyer_watson_optimization.md index 8f7942c1..d74b1286 100644 --- a/docs/archive/phase2_bowyer_watson_optimization.md +++ b/docs/archive/phase2_bowyer_watson_optimization.md @@ -305,7 +305,7 @@ fn test_cache_invalidation_on_tds_operations() { let initial_gen = algorithm.cached_generation().load(Ordering::Acquire); // Test vertex insertion - let vertex = vertex![1.0, 2.0, 3.0]; + let vertex = Vertex::<(), _>::try_new([1.0, 2.0, 3.0])?; tds.add_vertex(vertex); assert_ne!(algorithm.cached_generation().load(Ordering::Acquire), initial_gen); diff --git a/docs/archive/phase_3a_implementation_guide.md b/docs/archive/phase_3a_implementation_guide.md index da7d6153..4291edd2 100644 --- a/docs/archive/phase_3a_implementation_guide.md +++ b/docs/archive/phase_3a_implementation_guide.md @@ -1291,10 +1291,10 @@ mod tests { // Helper to create test TDS fn create_test_tds_3d() -> Tds, Option<()>, 3> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), ]; Tds::new(&vertices).unwrap() } diff --git a/docs/archive/topology_integration_design_historical.md b/docs/archive/topology_integration_design_historical.md index 6259120a..17aa4b28 100644 --- a/docs/archive/topology_integration_design_historical.md +++ b/docs/archive/topology_integration_design_historical.md @@ -662,9 +662,9 @@ mod euler_tests { fn test_2d_triangle_euler_characteristic() { // Single triangle: V=3, E=3, F=1 → χ = 3-3+1 = 1 let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0])?, ]; let tds: Tds, Option<()>, 2> = Tds::new(&vertices).unwrap(); @@ -677,10 +677,10 @@ mod euler_tests { // Note: χ here is for the full simplicial complex (a 3-ball), not just the boundary. // Single tetrahedron: V=4, E=6, F=4, C=1 → χ = 4-6+4-1 = 1 let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let tds: Tds, Option<()>, 3> = Tds::new(&vertices).unwrap(); @@ -691,11 +691,11 @@ mod euler_tests { #[test] fn test_4d_simplex_euler_characteristic() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0])?, ]; let tds: Tds, Option<()>, 4> = Tds::new(&vertices).unwrap(); @@ -720,10 +720,10 @@ mod topology_validation_tests { #[test] fn test_planar_topology_validation_success() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let tds: Tds, Option<()>, 3> = Tds::new(&vertices).unwrap(); @@ -736,10 +736,10 @@ mod topology_validation_tests { #[test] fn test_topology_summary() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let tds: Tds, Option<()>, 3> = Tds::new(&vertices).unwrap(); @@ -776,7 +776,7 @@ mod random_topology_tests { ) ) { let vertex_vec: Vec<_> = vertices.into_iter() - .map(|coords| vertex!(coords)) + .map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords)?) .collect(); if let Ok(tds) = Tds::, Option<()>, 2>::new(&vertex_vec) { @@ -796,7 +796,7 @@ mod random_topology_tests { ) ) { let vertex_vec: Vec<_> = vertices.into_iter() - .map(|coords| vertex!(coords)) + .map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords)?) .collect(); if let Ok(tds) = Tds::, Option<()>, 3>::new(&vertex_vec) { @@ -837,10 +837,10 @@ fn test_topology_debug_analysis() -> Result<(), TdsConstructionError> { fn test_2d_topology_debug() -> Result<(), TdsConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), - vertex!([0.2, 0.3]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.3])?, ]; let tds: Tds, Option<()>, 2> = Tds::new(&vertices)?; @@ -1605,9 +1605,9 @@ fn gather_boundary_facet_info( fn test_2d_single_triangle_euler() { // V=3, E=3, F=1 → χ = 3-3+1 = 1 let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0])?, ]; let tds: Tds, Option<()>, 2> = Tds::new(&vertices).unwrap(); @@ -1621,10 +1621,10 @@ fn test_2d_single_triangle_euler() { fn test_2d_two_triangles_euler() { // Two triangles sharing an edge let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), - vertex!([0.5, -1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, -1.0])?, ]; let tds: Tds, Option<()>, 2> = Tds::new(&vertices).unwrap(); @@ -1641,10 +1641,10 @@ fn test_2d_two_triangles_euler() { fn test_3d_tetrahedron_euler() { // Single tetrahedron: V=4, E=6, F=4, C=1 → χ = 4-6+4-1 = 1 let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let tds: Tds, Option<()>, 3> = Tds::new(&vertices).unwrap(); @@ -1656,11 +1656,11 @@ fn test_3d_tetrahedron_euler() { #[test] fn test_3d_with_interior_vertex() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), // Interior + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25])?, // Interior ]; let tds: Tds, Option<()>, 3> = Tds::new(&vertices).unwrap(); @@ -1677,11 +1677,11 @@ fn test_4d_simplex_euler() { // V=5, E=10, F=10, Tet=5, Cell=1 // χ = 5 - 10 + 10 - 5 + 1 = 1 let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0])?, ]; let tds: Tds, Option<()>, 4> = Tds::new(&vertices).unwrap(); @@ -1704,7 +1704,7 @@ fn test_empty_triangulation_euler() { #[test] fn test_single_vertex_euler() { - let vertices = vec![vertex!([0.0, 0.0, 0.0])]; + let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?]; // This may fail to create a valid triangulation // Document expected behavior } @@ -1724,7 +1724,7 @@ proptest! { ) ) { let vertices: Vec<_> = points.into_iter() - .map(|p| vertex!(p)) + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p)?) .collect(); if let Ok(tds) = Tds::, Option<()>, 2>::new(&vertices) { @@ -1741,7 +1741,7 @@ proptest! { ) ) { let vertices: Vec<_> = points.into_iter() - .map(|p| vertex!(p)) + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p)?) .collect(); if let Ok(tds) = Tds::, Option<()>, 3>::new(&vertices) { @@ -1758,10 +1758,10 @@ proptest! { #[test] fn test_boundary_report_includes_euler() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let tds: Tds, Option<()>, 3> = Tds::new(&vertices).unwrap(); diff --git a/docs/code_organization.md b/docs/code_organization.md index 58f86552..f03ebb89 100644 --- a/docs/code_organization.md +++ b/docs/code_organization.md @@ -468,7 +468,7 @@ paths instead. - `hashing.rs` - Stable, deterministic hash primitives - `deduplication.rs` - Vertex deduplication utilities - `measurement.rs` - Allocation measurement helper (feature-gated) - - `facet_utils.rs` - Facet helpers (adjacency, vertex extraction, combination generation) + - `facet_utils.rs` - Facet helpers (adjacency extraction, combination generation) - `facet_keys.rs` - Facet key derivation and consistency helpers - `jaccard.rs` - Set similarity utilities and diagnostics macro - `delaunay_validation.rs` - Delaunay property validation helpers (expensive; debug-oriented) @@ -1006,7 +1006,7 @@ where // Advanced implementation block for methods requiring ComplexField impl StructName where - T: CoordinateScalar + Clone + ComplexField + PartialEq + PartialOrd + Sum, + T: Clone + ComplexField + PartialEq + PartialOrd + Sum, // additional specialized bounds { /// Advanced method requiring specialized traits @@ -1148,7 +1148,7 @@ mod tests { - `hashing.rs`: stable, deterministic hash primitives - `deduplication.rs`: vertex deduplication utilities - `measurement.rs`: allocation measurement helper (feature-gated) - - `facet_utils.rs`: facet helpers (adjacency, vertex extraction, combination generation) + - `facet_utils.rs`: facet helpers (adjacency extraction, combination generation) - `facet_keys.rs`: facet key derivation + facet index consistency helpers - `jaccard.rs`: set similarity utilities + diagnostics macro `assert_jaccard_gte!` - `delaunay_validation.rs`: Delaunay property validation helpers (expensive; debug-oriented) @@ -1191,7 +1191,7 @@ surface-level numeric generality. Exact arithmetic is already used internally by robust predicate fallbacks. If exact coordinates become caller-visible input in a future release, they should be introduced as an explicit documented coordinate model/API rather than by -loosening `CoordinateScalar` to arbitrary numeric types. +loosening ordinary `f64` APIs to arbitrary numeric types. #### Testing Patterns diff --git a/docs/dev/rust.md b/docs/dev/rust.md index a48889be..02c0a985 100644 --- a/docs/dev/rust.md +++ b/docs/dev/rust.md @@ -604,7 +604,7 @@ Example: /// # Examples /// /// ```rust -/// # use delaunay::prelude::construction::{DelaunayTriangulation, vertex}; +/// # use delaunay::prelude::construction::{DelaunayTriangulation}; /// # use delaunay::prelude::insertion::InsertionError; /// # fn main() -> Result<(), InsertionError> { /// let mut triangulation = DelaunayTriangulation::<_, _, _, 2>::default(); diff --git a/docs/dev/testing.md b/docs/dev/testing.md index 0eac42a5..19efa72a 100644 --- a/docs/dev/testing.md +++ b/docs/dev/testing.md @@ -215,8 +215,8 @@ asserting results. Dimension-specific point construction, translation, and other setup belongs in `const`-generic helper functions: ```rust -fn build_degenerate_points() -> Vec> { … } -fn translate_point(p: &Point) -> Point { … } +fn build_degenerate_points() -> Vec> { … } +fn translate_point(p: &Point) -> Point { … } ``` This keeps the macro readable and the helpers independently testable. diff --git a/docs/diagnostics.md b/docs/diagnostics.md index 93c24cb0..13ca9546 100644 --- a/docs/diagnostics.md +++ b/docs/diagnostics.md @@ -66,15 +66,15 @@ For most validation work, start with the always-available APIs: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), ]; let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -114,7 +114,7 @@ empty-circumsphere violations: ```rust use delaunay::prelude::diagnostics::delaunay_violation_report; use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::DelaunayValidationError; @@ -128,10 +128,10 @@ enum DiagnosticsExampleError { fn main() -> Result<(), DiagnosticsExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), ]; let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; diff --git a/docs/invariants.md b/docs/invariants.md index 4a3b3998..21317fd9 100644 --- a/docs/invariants.md +++ b/docs/invariants.md @@ -229,7 +229,7 @@ via `TopologyGuarantee`, re-exported at the crate root and in - `TopologyGuarantee::Pseudomanifold` checks the codimension-1 incidence conditions (plus boundary consistency, connectedness, - isolated-vertex, and Euler characteristic checks). + isolated-vertex checks, and Euler characteristic checks). - `TopologyGuarantee::PLManifold` and `TopologyGuarantee::PLManifoldStrict` add **link-based** conditions (ridge links and/or vertex links) that are characteristic of @@ -249,7 +249,7 @@ and intuition. A **vertex link** is the simplicial complex formed by taking all simplices incident to a given vertex and removing that vertex from each simplex. Intuitively, the -vertex link represents the local neighborhood “around” the vertex, abstracted +vertex link represents the local neighborhood around the vertex, abstracted away from the embedding space. For a PL-manifold, the link of every interior vertex must be homeomorphic to a @@ -301,9 +301,9 @@ facets are expected unless the simplex complex represents a closed manifold by c Toroidal workflows are integrated as first-class topology options: -- `.canonicalized_toroidal()` canonicalizes coordinates into the fundamental domain and uses +- `.try_canonicalized_toroidal()` canonicalizes coordinates into the fundamental domain and uses toroidal topology metadata for validation. -- `.toroidal(...)` constructs a periodic image-point triangulation over neighboring +- `.try_toroidal(...)` constructs a periodic image-point triangulation over neighboring fundamental domains. The 2D and compact 3D paths are validated periodic quotients; 4D/5D periodic construction fails fast until quotient selection scales to routine release validation under issue #416. diff --git a/docs/limitations.md b/docs/limitations.md index 959ae16f..d59a4973 100644 --- a/docs/limitations.md +++ b/docs/limitations.md @@ -66,11 +66,11 @@ Euclidean construction is the default and best-covered path. Toroidal support has two modes: -- `.canonicalized_toroidal([..])` canonicalizes vertices into the fundamental domain, then +- `.try_canonicalized_toroidal([..])` canonicalizes vertices into the fundamental domain, then builds the Euclidean Delaunay triangulation of the wrapped point set. It does not identify opposite boundary facets. The 2D and 3D canonical toroidal construction paths are part of routine release coverage. -- `.toroidal([..])` uses the 3^D image-point method to construct a +- `.try_toroidal([..])` uses the 3^D image-point method to construct a true periodic quotient with rewired neighbor pointers. This path is release covered in 2D and compact 3D, where periodic triangulations validate as closed tori through Levels 1-4. 4D/5D periodic construction fails fast until diff --git a/docs/numerical_robustness_guide.md b/docs/numerical_robustness_guide.md index f3c889a1..eca30f97 100644 --- a/docs/numerical_robustness_guide.md +++ b/docs/numerical_robustness_guide.md @@ -123,15 +123,15 @@ kernel, use the explicit-kernel constructors: ```rust use delaunay::prelude::geometry::RobustKernel; -use delaunay::prelude::construction::{DelaunayTriangulation, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulation}; let kernel = RobustKernel::::new(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let dt: DelaunayTriangulation, (), (), 3> = @@ -202,13 +202,13 @@ cases involve cavity/topology failures rather than predicate degeneracies. Use `insert_best_effort_with_statistics()` to observe this behavior: ```rust -use delaunay::prelude::construction::{DelaunayTriangulation, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulation}; use delaunay::prelude::insertion::InsertionOutcome; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::empty(); let (outcome, stats) = dt - .insert_best_effort_with_statistics(vertex!([0.5, 0.5, 0.5])) + .insert_best_effort_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?) .unwrap(); if stats.used_perturbation() { @@ -362,8 +362,8 @@ active regardless of this setting. - `DedupPolicy::Exact`: additionally apply `dedup_vertices_exact` before construction. This is a performance optimisation for inputs with many exact duplicates — it avoids paying per-vertex insertion overhead for each one. -- `DedupPolicy::Epsilon(value)`: additionally apply `dedup_vertices_epsilon` - with the given tolerance before construction. +- `DedupPolicy::try_epsilon(value)`: additionally apply + `dedup_vertices_epsilon` with the parsed tolerance before construction. The default (`Off`) is recommended because Hilbert dedup is free (zero extra cost) and per-insertion checks handle any remaining cases. @@ -391,8 +391,8 @@ and per-insertion checks handle any remaining cases. - If you see retryable insertion errors, frequent perturbation retries, or skipped vertices, preprocess your input (dedup / rescale if appropriate). - Treat `InsertionOutcome::Skipped { .. }` from the best-effort API as an expected outcome on - pathological data; decide at the application level whether to drop the vertex, - perturb/rescale your point set, or re-run with a different kernel. + pathological data; decide at the application level whether to drop the skipped point, + perturb or rescale your point set, or re-run with a different kernel. ## Current limitations diff --git a/docs/topology.md b/docs/topology.md index e1d2c75d..126c9784 100644 --- a/docs/topology.md +++ b/docs/topology.md @@ -44,10 +44,10 @@ src/ Notes: - This repository does not use `mod.rs`; module declarations live in `src/lib.rs`. -- Topology is combinatorial: core types (`Tds`, `Simplex`, `Vertex`) do not require - `T: CoordinateScalar` at the type level; geometric operations/validation are - gated behind `T: CoordinateScalar`. The currently supported caller-visible - coordinate scalar is `f64`; exact-coordinate input, if added in the future, +- Topology is combinatorial: core types (`Tds`, `Simplex`, `Vertex`) keep + topology and payload data separate from coordinate parsing. The currently + supported caller-visible coordinate scalar is finite `f64`, stored through + validated `Point` coordinates; exact-coordinate input, if added in the future, should be an explicit documented API rather than incidental generic support. ## Level 3 topology validation (`Triangulation::is_valid()`) @@ -184,26 +184,27 @@ Toroidal (periodic) triangulations are **fully implemented and functional**. You construct toroidal triangulations using `DelaunayTriangulationBuilder`: ```rust -use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; // 2D canonicalized toroidal triangulation let vertices = vec![ - vertex!([0.1, 0.1]), - vertex!([0.9, 0.9]), + delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.1])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.9, 0.9])?, // ... ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) // Canonicalized toroidal construction + .try_canonicalized_toroidal([1.0, 1.0]) + .expect("unit toroidal domain is valid") // Canonicalized toroidal construction .build::<()>() .unwrap(); ``` Canonicalized toroidal construction wraps coordinates into the fundamental domain before building the Euclidean triangulation. Topology-aware operations can -use the toroidal domain for periodic distances, but `.canonicalized_toroidal([..])` does not +use the toroidal domain for periodic distances, but `.try_canonicalized_toroidal([..])` does not rewire opposite boundary facets. For a true periodic quotient, use -`.toroidal([..])`; the validated image-point path currently covers 2D +`.try_toroidal([..])`; the validated image-point path currently covers 2D and compact 3D fixtures. 4D/5D periodic quotients fail fast pending scalable construction work in issue #416. diff --git a/docs/validation.md b/docs/validation.md index ffa1b42e..13dea2c4 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -91,7 +91,7 @@ deviates from the happy-path and trips internal **suspicion flags**, e.g.: ```rust use delaunay::prelude::construction::{ DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee, - Vertex, vertex, + Vertex, }; use delaunay::prelude::validation::{ValidationConfigurationError, ValidationPolicy}; @@ -105,10 +105,10 @@ enum ValidationExampleError { fn main() -> Result<(), ValidationExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -156,16 +156,16 @@ PL-manifoldness. You can trigger that final certification via ```rust use delaunay::prelude::construction::{ DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee, - Vertex, vertex, + Vertex, }; use delaunay::prelude::validation::ValidationPolicy; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -251,11 +251,11 @@ Validates basic data integrity of individual vertices and simplices. ```rust use delaunay::prelude::construction::{ - DelaunayTriangulation, TopologyGuarantee, Vertex, vertex, + DelaunayTriangulation, TopologyGuarantee, Vertex, }; use delaunay::prelude::validation::ValidationPolicy; -let v = vertex!([0.0, 0.0, 0.0]); +let v = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?; assert!(v.is_valid().is_ok()); ``` @@ -308,16 +308,16 @@ Validates the combinatorial structure of the Triangulation Data Structure. ```rust use delaunay::prelude::construction::{ DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee, - Vertex, vertex, + Vertex, }; use delaunay::prelude::validation::ValidationPolicy; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -393,17 +393,17 @@ Validates that the triangulation forms a valid topological manifold. ```rust use delaunay::prelude::construction::{ DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee, - Vertex, vertex, + Vertex, }; use delaunay::prelude::validation::ValidationPolicy; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25])?, // Interior point ]; let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -465,16 +465,16 @@ Validates the geometric optimality of the triangulation. ```rust use delaunay::prelude::construction::{ DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee, - Vertex, vertex, + Vertex, }; use delaunay::prelude::validation::ValidationPolicy; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; diff --git a/docs/workflows.md b/docs/workflows.md index 43d172ad..981f5f65 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -15,15 +15,15 @@ For most use cases, construction is a single call: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; fn main() -> Result<(), DelaunayTriangulationConstructionError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -106,7 +106,7 @@ You can also run a global repair pass manually: ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::repair::DelaunayRepairError; @@ -120,10 +120,10 @@ enum RepairExampleError { fn main() -> Result<(), RepairExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -167,7 +167,7 @@ detections, etc.). ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::repair::DelaunayRepairError; @@ -179,10 +179,10 @@ enum RepairExampleError { fn main() -> Result<(), RepairExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -219,7 +219,7 @@ from the current vertex set. ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::repair::{DelaunayRepairError, DelaunayRepairHeuristicConfig}; @@ -233,10 +233,10 @@ enum RepairExampleError { fn main() -> Result<(), RepairExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -253,14 +253,14 @@ fn main() -> Result<(), RepairExampleError> { ## Builder API: toroidal construction modes -Toroidal construction has two modes. `.canonicalized_toroidal([..])` wraps vertices into the -fundamental domain before Euclidean construction. `.toroidal([..])` +Toroidal construction has two modes. `.try_canonicalized_toroidal([..])` wraps vertices into the +fundamental domain before Euclidean construction. `.try_toroidal([..])` uses the image-point method to build a true periodic quotient in the validated 2D and compact 3D cases. ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::insertion::InsertionError; @@ -275,18 +275,19 @@ enum ToroidalExampleError { fn main() -> Result<(), ToroidalExampleError> { // 2D canonicalized toroidal triangulation with unit square domain let vertices = vec![ - vertex!([0.1, 0.1]), - vertex!([0.9, 0.9]), - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.1])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.9, 0.9])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) // canonicalized toroidal construction + .try_canonicalized_toroidal([1.0, 1.0]) + .expect("unit toroidal domain is valid") // canonicalized toroidal construction .build::<()>()?; // Insert more points - they'll be wrapped to [0,1)×[0,1) - dt.insert(vertex!([1.2, 0.3]))?; // wraps to [0.2, 0.3] - dt.insert(vertex!([-0.1, 0.7]))?; // wraps to [0.9, 0.7] + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([1.2, 0.3])?)?; // wraps to [0.2, 0.3] + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([-0.1, 0.7])?)?; // wraps to [0.9, 0.7] Ok(()) } ``` @@ -298,8 +299,8 @@ fn main() -> Result<(), ToroidalExampleError> { - **Distance computation**: Topology-aware operations can use the toroidal metric when the triangulation carries toroidal domain metadata - **Construction modes**: - - `.canonicalized_toroidal([..])`: canonicalized construction (wrap into fundamental domain) - - `.toroidal([..])`: periodic image-point construction; currently validated as a true + - `.try_canonicalized_toroidal([..])`: canonicalized construction (wrap into fundamental domain) + - `.try_toroidal([..])`: periodic image-point construction; currently validated as a true toroidal quotient in 2D and compact 3D; 4D/5D fail fast pending issue #416 For more details, see `docs/topology.md` and the toroidal section in the main `README.md`. @@ -307,20 +308,20 @@ For more details, see `docs/topology.md` and the toroidal section in the main `R ## Builder API: auxiliary vertex and simplex data Vertices and simplices can carry user-defined auxiliary data (`U` for vertices, `V` for simplices). -Data is attached at construction time via `VertexBuilder::data()`, read via the `data()` accessor, +Data is attached at construction time via `Vertex::try_new_with_data()`, read via the `data()` accessor, and modified post-construction via `set_vertex_data` / `set_simplex_data`. ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, }; fn main() -> Result<(), DelaunayTriangulationConstructionError> { // Attach integer labels at construction time - let vertices: [Vertex; 3] = [ - vertex!([0.0, 0.0], 10i32), - vertex!([1.0, 0.0], 20), - vertex!([0.0, 1.0], 30), + let vertices: [Vertex; 3] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 10i32)?, + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 20)?, + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 30)?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -374,13 +375,13 @@ want to keep going after skipped vertices, use the explicitly best-effort `insert_best_effort_with_statistics()`. ```rust -use delaunay::prelude::construction::{DelaunayTriangulation, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulation}; use delaunay::prelude::insertion::{InsertionError, InsertionOutcome}; fn main() -> Result<(), InsertionError> { let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::empty(); - let (outcome, stats) = dt.insert_best_effort_with_statistics(vertex!([0.5, 0.5, 0.5]))?; + let (outcome, stats) = dt.insert_best_effort_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?)?; if stats.used_perturbation() { println!("used perturbation (attempts={})", stats.attempts); @@ -410,7 +411,7 @@ the operation rolls back to the pre-removal triangulation. ```rust use delaunay::prelude::construction::{ - DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, }; use delaunay::prelude::tds::InvariantError; @@ -424,11 +425,11 @@ enum RemovalExampleError { fn main() -> Result<(), RemovalExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -468,7 +469,7 @@ After using flips, you typically: See [`api_design.md`](api_design.md) for the full Builder vs Edit API design. ```rust -use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; use delaunay::prelude::flips::*; #[derive(Debug, thiserror::Error)] @@ -481,10 +482,10 @@ enum FlipExampleError { fn main() -> Result<(), FlipExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -492,7 +493,7 @@ fn main() -> Result<(), FlipExampleError> { let Some((simplex_key, _)) = dt.simplices().next() else { return Ok(()); }; - let info = dt.flip_k1_insert(simplex_key, vertex!([0.1, 0.1, 0.1]))?; + let info = dt.flip_k1_insert(simplex_key, delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.1, 0.1])?)?; let inserted_vertex = info.inserted_face_vertices[0]; // k=1 inverse: remove the inserted vertex (collapse its star). diff --git a/examples/delaunayize_repair.rs b/examples/delaunayize_repair.rs index 85b7e7be..40d605b9 100644 --- a/examples/delaunayize_repair.rs +++ b/examples/delaunayize_repair.rs @@ -23,11 +23,12 @@ use delaunay::prelude::construction::DelaunayTriangulationConstructionError; use delaunay::prelude::delaunayize::*; use delaunay::prelude::flips::*; +use delaunay::prelude::geometry::CoordinateConversionError; +use delaunay::prelude::tds::FacetError; use delaunay::prelude::validation::DelaunayTriangulationValidationError; // For the generic print_outcome helper. use delaunay::prelude::DataType; -use delaunay::prelude::geometry::CoordinateScalar; #[derive(Debug, thiserror::Error)] enum DelaunayizeRepairExampleError { @@ -39,6 +40,10 @@ enum DelaunayizeRepairExampleError { Validation(#[from] DelaunayTriangulationValidationError), #[error(transparent)] Flip(#[from] FlipError), + #[error(transparent)] + Facet(#[from] FacetError), + #[error(transparent)] + CoordinateConversion(#[from] CoordinateConversionError), } #[expect( @@ -74,11 +79,11 @@ fn already_delaunay_3d() -> Result<(), DelaunayizeRepairExampleError> { println!("--------------------------------------------\n"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?, ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices)?; @@ -106,12 +111,12 @@ fn already_delaunay_4d() -> Result<(), DelaunayizeRepairExampleError> { println!("--------------------------------------------\n"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25, 0.25]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25, 0.25])?, ]; let mut dt: DelaunayTriangulation<_, (), (), 4> = DelaunayTriangulation::new(&vertices)?; @@ -141,13 +146,13 @@ fn flip_then_repair_2d() -> Result<(), DelaunayizeRepairExampleError> { println!("-------------------------------------------------------\n"); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([4.0, 4.0]), - vertex!([0.0, 4.0]), - vertex!([2.0, 2.0]), - vertex!([1.0, 1.0]), - vertex!([3.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 4.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 4.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 2.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 1.0])?, ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices)?; @@ -165,7 +170,7 @@ fn flip_then_repair_2d() -> Result<(), DelaunayizeRepairExampleError> { if let Some(neighbors) = simplex.neighbors() { for (i, n) in neighbors.enumerate() { if let (Some(_), Ok(idx)) = (n, u8::try_from(i)) { - facets.push(FacetHandle::new(ck, idx)); + facets.push(FacetHandle::try_new(dt.tds(), ck, idx)?); } } } @@ -217,11 +222,11 @@ fn custom_config_2d() -> Result<(), DelaunayizeRepairExampleError> { println!("----------------------------------------------\n"); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5])?, ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices)?; @@ -251,9 +256,7 @@ fn custom_config_2d() -> Result<(), DelaunayizeRepairExampleError> { Ok(()) } -fn print_outcome( - outcome: &DelaunayizeOutcome, -) { +fn print_outcome(outcome: &DelaunayizeOutcome) { println!( " Topology repair: succeeded={}, iterations={}, simplices_removed={}", outcome.topology_repair.succeeded, diff --git a/examples/diagnostics.rs b/examples/diagnostics.rs index c8a112ab..08f087bf 100644 --- a/examples/diagnostics.rs +++ b/examples/diagnostics.rs @@ -21,12 +21,11 @@ use delaunay::prelude::diagnostics::{ #[cfg(feature = "diagnostics")] use delaunay::prelude::flips::*; #[cfg(feature = "diagnostics")] -use delaunay::prelude::geometry::AdaptiveKernel; +use delaunay::prelude::geometry::{AdaptiveKernel, CoordinateConversionError}; #[cfg(feature = "diagnostics")] -use delaunay::prelude::validation::DelaunayTriangulationValidationError; +use delaunay::prelude::tds::FacetError; #[cfg(feature = "diagnostics")] -use delaunay::vertex; - +use delaunay::prelude::validation::DelaunayTriangulationValidationError; #[cfg(feature = "diagnostics")] #[derive(Debug, thiserror::Error)] enum DiagnosticsExampleError { @@ -34,6 +33,10 @@ enum DiagnosticsExampleError { Construction(#[from] DelaunayTriangulationConstructionError), #[error(transparent)] DelaunayValidation(#[from] DelaunayValidationError), + #[error(transparent)] + CoordinateConversion(#[from] CoordinateConversionError), + #[error(transparent)] + Facet(#[from] FacetError), #[error("expected at least one public k=2 flip to produce a Delaunay violation")] NoDelaunayViolatingFlip, } @@ -71,10 +74,10 @@ fn init_tracing() { #[cfg(feature = "diagnostics")] fn report_valid_triangulation() -> Result<(), DiagnosticsExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices)?; @@ -119,13 +122,13 @@ fn report_non_delaunay_triangulation() -> Result<(), DiagnosticsExampleError> { fn build_non_delaunay_triangulation_2d() -> Result, (), (), 2>, DiagnosticsExampleError> { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([4.0, 4.0]), - vertex!([0.0, 4.0]), - vertex!([2.0, 2.0]), - vertex!([1.0, 1.0]), - vertex!([3.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 4.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 4.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 2.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 1.0])?, ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices)?; @@ -139,7 +142,7 @@ fn build_non_delaunay_triangulation_2d() let Ok(facet_index) = u8::try_from(facet_index) else { continue; }; - let facet = FacetHandle::new(simplex_key, facet_index); + let facet = FacetHandle::try_new(dt.tds(), simplex_key, facet_index)?; let mut trial = dt.clone(); if trial.flip_k2(facet).is_ok() && trial.as_triangulation().validate().is_ok() diff --git a/examples/into_from_conversions.rs b/examples/into_from_conversions.rs index 722f6947..1a199e52 100644 --- a/examples/into_from_conversions.rs +++ b/examples/into_from_conversions.rs @@ -19,6 +19,7 @@ //! //! Run this example with: `cargo run --example into_from_conversions` +use delaunay::prelude::geometry::CoordinateConversionError; use delaunay::prelude::query::*; /// Demonstrates Into/From trait conversions from vertices and points to coordinate arrays. @@ -26,9 +27,9 @@ use delaunay::prelude::query::*; /// This function shows various ways to convert vertices and points to coordinate /// arrays using the `Into`/`From` traits, which provide more ergonomic alternatives /// to explicitly calling `.to_array()`. -fn main() { +fn main() -> Result<(), CoordinateConversionError> { // Create a vertex with 3D coordinates - let vertex: Vertex = vertex!([1.0, 2.0, 3.0]); + let vertex: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 3.0])?; // Before: You had to call .to_array() explicitly let coords_explicit: [f64; 3] = vertex.point().to_array(); @@ -39,7 +40,8 @@ fn main() { println!("Into/From conversion from vertex: {coords_from_vertex:?}"); // Create another vertex for reference conversion - let another_vertex: Vertex = vertex!([4.0, 5.0, 6.0]); + let another_vertex: Vertex<(), 3> = + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 5.0, 6.0])?; // You can also convert from a reference to preserve the original vertex let coords_from_ref: [f64; 3] = (&another_vertex).into(); @@ -50,16 +52,17 @@ fn main() { ); // Point Into/From conversion also works - let point = Point::new([7.0, 8.0, 9.0]); + let point = Point::try_from([7.0, 8.0, 9.0])?; let coords_from_point: [f64; 3] = point.into(); println!("Into/From conversion from point: {coords_from_point:?}"); // And from point reference - let another_point = Point::new([10.0, 11.0, 12.0]); + let another_point = Point::try_from([10.0, 11.0, 12.0])?; let coords_from_point_ref: [f64; 3] = (&another_point).into(); println!("Into/From conversion from point reference: {coords_from_point_ref:?}"); println!( "Original point still available: {:?}", another_point.to_array() ); + Ok(()) } diff --git a/examples/numerical_robustness.rs b/examples/numerical_robustness.rs index b0dfd740..5bfe1afe 100644 --- a/examples/numerical_robustness.rs +++ b/examples/numerical_robustness.rs @@ -11,11 +11,10 @@ use delaunay::prelude::construction::{ DelaunayTriangulation, DelaunayTriangulationConstructionError, }; use delaunay::prelude::geometry::{ - AdaptiveKernel, CircumcenterError, Coordinate, CoordinateConversionError, FastKernel, Kernel, - Point, RobustKernel, robust_insphere, robust_orientation, + AdaptiveKernel, CircumcenterError, CoordinateConversionError, CoordinateValidationError, + FastKernel, Kernel, Point, RobustKernel, robust_insphere, robust_orientation, }; use delaunay::prelude::validation::DelaunayTriangulationValidationError; -use delaunay::vertex; #[derive(Debug, thiserror::Error)] enum NumericalRobustnessExampleError { @@ -24,6 +23,8 @@ enum NumericalRobustnessExampleError { #[error(transparent)] CoordinateConversion(#[from] CoordinateConversionError), #[error(transparent)] + CoordinateValidation(#[from] CoordinateValidationError), + #[error(transparent)] Construction(#[from] DelaunayTriangulationConstructionError), #[error(transparent)] Validation(#[from] DelaunayTriangulationValidationError), @@ -44,9 +45,9 @@ fn main() -> Result<(), NumericalRobustnessExampleError> { /// Compares orientation predicate behavior on a degenerate collinear simplex. fn compare_orientation_kernels() -> Result<(), NumericalRobustnessExampleError> { let collinear = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 1.0]), - Point::new([2.0, 2.0]), + Point::try_new([0.0, 0.0])?, + Point::try_new([1.0, 1.0])?, + Point::try_new([2.0, 2.0])?, ]; let fast = FastKernel::::new(); @@ -72,11 +73,11 @@ fn compare_orientation_kernels() -> Result<(), NumericalRobustnessExampleError> /// Compares explicit boundary reporting with adaptive `SoS` tie-breaking. fn compare_insphere_boundary_handling() -> Result<(), NumericalRobustnessExampleError> { let simplex = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::try_new([0.0, 0.0])?, + Point::try_new([1.0, 0.0])?, + Point::try_new([0.0, 1.0])?, ]; - let boundary_point = Point::new([1.0, 1.0]); + let boundary_point = Point::try_new([1.0, 1.0])?; let robust = RobustKernel::::new(); let adaptive = AdaptiveKernel::::new(); @@ -98,11 +99,11 @@ fn compare_insphere_boundary_handling() -> Result<(), NumericalRobustnessExample /// Builds a small triangulation with the default exact adaptive kernel and validates it. fn build_with_adaptive_kernel() -> Result<(), NumericalRobustnessExampleError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25])?, ]; let dt: DelaunayTriangulation, (), (), 3> = diff --git a/examples/point_comparison_and_hashing.rs b/examples/point_comparison_and_hashing.rs index 99cd0c2e..2691698f 100644 --- a/examples/point_comparison_and_hashing.rs +++ b/examples/point_comparison_and_hashing.rs @@ -1,348 +1,110 @@ //! # Point Comparison and Hashing Example //! -//! This example demonstrates the robust comparison and hashing behavior of the Point struct, -//! with special emphasis on handling of NaN (Not a Number) and infinity values. +//! Demonstrates finite [`Point`] comparison, hashing, and validation failures for +//! non-finite input coordinates. //! -//! ## Key Features Demonstrated: -//! -//! - **NaN-aware equality**: Unlike IEEE 754 standard where NaN ≠ NaN, our Point implementation -//! treats NaN values as equal to themselves for consistent behavior in data structures. -//! - **Consistent hashing**: Points with identical coordinates (including NaN) produce the same -//! hash value, enabling reliable use in `HashMap` and `HashSet`. -//! - **Mathematical properties**: Equality satisfies reflexivity, symmetry, and transitivity. -//! - **Special value handling**: Proper comparison of infinity, negative infinity, and zero values. -//! -//! Run this example with: `cargo run --example point_comparison_and_hashing` +//! Run with: `cargo run --example point_comparison_and_hashing` -#![allow(clippy::similar_names)] - -use delaunay::prelude::query::*; +use delaunay::prelude::geometry::{CoordinateValidationError, InvalidCoordinateValue, Point}; use std::collections::{HashMap, HashSet}; -fn main() { +fn main() -> Result<(), CoordinateValidationError> { println!("=== Point Comparison and Hashing Example ===\n"); - // Basic comparison and equality - basic_comparison_demo(); - - // NaN handling demonstration - nan_comparison_demo(); - - // Infinity handling - infinity_comparison_demo(); - - // HashMap usage with special values - hashmap_demo(); - - // HashSet behavior - hashset_demo(); + basic_comparison_demo()?; + hashmap_demo()?; + hashset_demo()?; + validation_demo(); - // Mathematical properties validation - mathematical_properties_demo(); - - // Different numeric types - numeric_types_demo(); + Ok(()) } -/// Demonstrates basic point comparison and equality -fn basic_comparison_demo() { - println!("🔍 Basic Point Comparison"); - println!("-------------------------"); +fn basic_comparison_demo() -> Result<(), CoordinateValidationError> { + println!("Basic point comparison"); + println!("----------------------"); - let point1 = Point::new([1.0, 2.0, 3.0]); - let point2 = Point::new([1.0, 2.0, 3.0]); - let point3 = Point::new([1.0, 2.0, 4.0]); + let point1 = Point::try_new([1.0, 2.0, 3.0])?; + let point2 = Point::try_new([1.0, 2.0, 3.0])?; + let point3 = Point::try_new([1.0, 2.0, 4.0])?; println!("point1 = {:?}", point1.coords()); println!("point2 = {:?}", point2.coords()); println!("point3 = {:?}", point3.coords()); - println!("point1 == point2: {}", point1 == point2); println!("point1 == point3: {}", point1 == point3); - println!("point1 != point3: {}", point1 != point3); - - // Demonstrate ordering println!("point1 < point3: {}", point1 < point3); - println!("point3 > point1: {}", point3 > point1); - println!(); -} - -/// Demonstrates NaN comparison behavior - the key innovation -fn nan_comparison_demo() { - println!("🔥 NaN Comparison Behavior"); - println!("--------------------------"); - - // Standard IEEE 754 behavior for comparison - let standard_nan1 = f64::NAN; - let standard_nan2 = f64::from_bits(standard_nan1.to_bits()); - let standard_nan_eq = standard_nan1 - .partial_cmp(&standard_nan2) - .is_some_and(std::cmp::Ordering::is_eq); - let standard_nan_ne = !standard_nan_eq; - println!("Standard IEEE 754: NaN == NaN is {standard_nan_eq}"); - println!("Standard IEEE 754: NaN != NaN is {standard_nan_ne}"); - - println!("\nOur Point implementation:"); - - // Create points with NaN values - let point_nan1 = Point::new([f64::NAN, 2.0, 3.0]); - let point_nan2 = Point::new([f64::NAN, 2.0, 3.0]); - let point_normal = Point::new([1.0, 2.0, 3.0]); - - println!( - "point_nan1 = [{}, 2.0, 3.0]", - if point_nan1.coords()[0].is_nan() { - "NaN" - } else { - "not NaN" - } - ); - println!( - "point_nan2 = [{}, 2.0, 3.0]", - if point_nan2.coords()[0].is_nan() { - "NaN" - } else { - "not NaN" - } - ); - println!("point_normal = {:?}", point_normal.coords()); - - // Our implementation: NaN points are equal to themselves - println!("point_nan1 == point_nan2: {}", point_nan1 == point_nan2); - println!("point_nan1 == point_normal: {}", point_nan1 == point_normal); - // Test reflexivity with NaN (this demonstrates our custom implementation) - #[expect(clippy::eq_op)] - let reflexivity_result = point_nan1 == point_nan1; - println!("point_nan1 == point_nan1: {reflexivity_result}"); - - // Different NaN patterns - let point_nan_diff1 = Point::new([f64::NAN, 2.0, 3.0]); - let point_nan_diff2 = Point::new([1.0, f64::NAN, 3.0]); - println!( - "Different NaN positions equal: {}", - point_nan_diff1 == point_nan_diff2 - ); - - // Multiple ways to create NaN should be equal - let nan1 = f64::NAN; - let nan2 = f64::NAN; // Use f64::NAN instead of division - let nan3 = f64::NAN; // Use f64::NAN instead of subtraction - - let point_nan_variant1 = Point::new([nan1, 1.0]); - let point_nan_variant2 = Point::new([nan2, 1.0]); - let point_nan_variant3 = Point::new([nan3, 1.0]); - - println!( - "Different NaN bit patterns equal: {}", - point_nan_variant1 == point_nan_variant2 && point_nan_variant2 == point_nan_variant3 - ); - - println!(); + Ok(()) } -/// Demonstrates infinity value comparison -fn infinity_comparison_demo() { - println!("♾️ Infinity Comparison"); - println!("----------------------"); - - let point_pos_inf1 = Point::new([f64::INFINITY, 2.0]); - let point_pos_inf2 = Point::new([f64::INFINITY, 2.0]); - let point_neg_inf = Point::new([f64::NEG_INFINITY, 2.0]); - let point_normal = Point::new([1.0, 2.0]); - - println!("point_pos_inf1 = [∞, 2.0]"); - println!("point_pos_inf2 = [∞, 2.0]"); - println!("point_neg_inf = [-∞, 2.0]"); - println!("point_normal = {:?}", point_normal.coords()); - - println!("∞ == ∞: {}", point_pos_inf1 == point_pos_inf2); - println!("∞ == -∞: {}", point_pos_inf1 == point_neg_inf); - println!("∞ == normal: {}", point_pos_inf1 == point_normal); - - // Mixed special values - let point_mixed = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY]); - let point_mixed2 = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY]); - println!( - "Mixed special values equal: {}", - point_mixed == point_mixed2 - ); - - println!(); -} - -/// Demonstrates `HashMap` usage with points containing special values -fn hashmap_demo() { - println!("🗺️ HashMap with Special Values"); - println!("-------------------------------"); - - let mut point_map: HashMap, &str> = HashMap::new(); +fn hashmap_demo() -> Result<(), CoordinateValidationError> { + println!("HashMap with finite points"); + println!("--------------------------"); - // Insert points with various special values - let point_normal = Point::new([1.0, 2.0, 3.0]); - let point_nan = Point::new([f64::NAN, 2.0, 3.0]); - let point_inf = Point::new([f64::INFINITY, 2.0, 3.0]); - let point_neg_inf = Point::new([f64::NEG_INFINITY, 2.0, 3.0]); + let mut point_map: HashMap, &str> = HashMap::new(); + let point = Point::try_new([1.0, 2.0, 3.0])?; + let same_point = Point::try_new([1.0, 2.0, 3.0])?; + let other_point = Point::try_new([2.0, 3.0, 4.0])?; - point_map.insert(point_normal, "normal point"); - point_map.insert(point_nan, "point with NaN"); - point_map.insert(point_inf, "point with +∞"); - point_map.insert(point_neg_inf, "point with -∞"); + point_map.insert(point, "first point"); + point_map.insert(other_point, "second point"); println!("HashMap size: {}", point_map.len()); - - // Test retrieval with equivalent points - let point_normal_copy = Point::new([1.0, 2.0, 3.0]); - let point_nan_copy = Point::new([f64::NAN, 2.0, 3.0]); - println!( - "Can retrieve normal point: {}", - point_map.contains_key(&point_normal_copy) + "Can retrieve equivalent key: {}", + point_map.contains_key(&same_point) ); - println!( - "Can retrieve NaN point: {}", - point_map.contains_key(&point_nan_copy) - ); - - if let Some(value) = point_map.get(&point_nan_copy) { - println!("Retrieved value for NaN point: {value}"); - } - - // Demonstrate that NaN points can be used as keys reliably - let mut nan_counter = HashMap::new(); - for _ in 0..5 { - let nan_point = Point::new([f64::NAN, 1.0]); - *nan_counter.entry(nan_point).or_insert(0) += 1; - } - println!( - "NaN point appears {} times (should be 5)", - nan_counter.values().next().unwrap_or(&0) - ); - println!(); -} -/// Demonstrates `HashSet` behavior with special values -fn hashset_demo() { - println!("📦 HashSet with Special Values"); - println!("------------------------------"); - - let mut point_set: HashSet> = HashSet::new(); + Ok(()) +} - // Add various points including duplicates with special values - let points = vec![ - Point::new([1.0, 2.0]), - Point::new([1.0, 2.0]), // Duplicate normal point - Point::new([f64::NAN, 2.0]), - Point::new([f64::NAN, 2.0]), // Duplicate NaN point - Point::new([f64::INFINITY, 2.0]), - Point::new([f64::INFINITY, 2.0]), // Duplicate infinity point - Point::new([0.0, -0.0]), // Zero and negative zero - Point::new([-0.0, 0.0]), // Should be treated as equal - ]; +fn hashset_demo() -> Result<(), CoordinateValidationError> { + println!("HashSet duplicate handling"); + println!("--------------------------"); - for point in points { + let mut point_set: HashSet> = HashSet::new(); + for point in [ + Point::try_new([1.0, 2.0])?, + Point::try_new([1.0, 2.0])?, + Point::try_new([0.0, -0.0])?, + Point::try_new([-0.0, 0.0])?, + Point::try_new([3.0, 4.0])?, + ] { point_set.insert(point); } - println!( - "HashSet size after inserting duplicates: {}", - point_set.len() - ); - println!("Expected size: 4 (normal, NaN, ∞, and ±0.0 treated as the same key)"); - - // Test membership - let test_nan = Point::new([f64::NAN, 2.0]); - let test_inf = Point::new([f64::INFINITY, 2.0]); - - println!( - "HashSet contains NaN point: {}", - point_set.contains(&test_nan) - ); - println!( - "HashSet contains ∞ point: {}", - point_set.contains(&test_inf) - ); - + println!("HashSet size after duplicates: {}", point_set.len()); + println!("Expected size: 3"); println!(); -} - -/// Demonstrates mathematical properties of equality -fn mathematical_properties_demo() { - println!("🧮 Mathematical Properties"); - println!("--------------------------"); - - let point_a = Point::new([f64::NAN, 2.0, f64::INFINITY]); - let point_b = Point::new([f64::NAN, 2.0, f64::INFINITY]); - let point_c = Point::new([f64::NAN, 2.0, f64::INFINITY]); - println!("Testing with points containing NaN and ∞:"); - - // Reflexivity: a == a (this demonstrates our custom implementation) - #[expect(clippy::eq_op)] - let reflexivity_result = point_a == point_a; - println!("Reflexivity (a == a): {reflexivity_result}"); - - // Symmetry: if a == b, then b == a - let symmetry_ab = point_a == point_b; - let symmetry_ba = point_b == point_a; - println!( - "Symmetry (a == b and b == a): {} and {} = {}", - symmetry_ab, - symmetry_ba, - symmetry_ab && symmetry_ba - ); - - // Transitivity: if a == b and b == c, then a == c - let trans_ab = point_a == point_b; - let trans_bc = point_b == point_c; - let trans_ac = point_a == point_c; - println!( - "Transitivity (a == b, b == c, a == c): {}, {}, {} = {}", - trans_ab, - trans_bc, - trans_ac, - trans_ab && trans_bc && trans_ac - ); - - println!(); + Ok(()) } -/// Demonstrates numeric-coordinate behavior -fn numeric_types_demo() { - println!("🔢 Numeric Coordinates"); - println!("----------------------"); - - // Integer-like values using f64 - let point_int_1 = Point::new([10.0, 20.0, 30.0]); - let point_int_2 = Point::new([10.0, 20.0, 30.0]); - let point_int_3 = Point::new([10.0, 20.0, 31.0]); - - println!("Integer-like points equal: {}", point_int_1 == point_int_2); - println!( - "Integer-like points different: {}", - point_int_1 != point_int_3 - ); - - // Demonstrate HashMap with integer-like points - let mut int_map: HashMap, String> = HashMap::new(); - int_map.insert(Point::new([1.0, 2.0]), "integer-like point".to_string()); - - let lookup_key = Point::new([1.0, 2.0]); - println!( - "Can retrieve integer-like point: {}", - int_map.contains_key(&lookup_key) - ); - - // Mixed precision floating point - println!("\nFloating Point Precision:"); - let point_precise = Point::new([1.000_000_000_000_000_1_f64, 2.0]); - let point_rounded = Point::new([1.0f64, 2.0]); - println!( - "High precision vs rounded equal: {}", - point_precise == point_rounded - ); - - println!(); +fn validation_demo() { + println!("Non-finite input validation"); + println!("---------------------------"); + + for (label, result) in [ + ("NaN", Point::<2>::try_new([f64::NAN, 1.0])), + ("+infinity", Point::<2>::try_new([f64::INFINITY, 1.0])), + ("-infinity", Point::<2>::try_new([f64::NEG_INFINITY, 1.0])), + ] { + match result { + Ok(point) => println!("{label}: unexpectedly accepted {:?}", point.coords()), + Err(CoordinateValidationError::InvalidCoordinate { + coordinate_value, .. + }) => { + let kind = match coordinate_value { + InvalidCoordinateValue::Nan => "NaN", + InvalidCoordinateValue::PositiveInfinity => "+infinity", + InvalidCoordinateValue::NegativeInfinity => "-infinity", + _ => "non-finite value", + }; + println!("{label}: rejected {kind}"); + } + Err(_) => println!("{label}: rejected invalid coordinate"), + } + } } diff --git a/examples/topology_editing.rs b/examples/topology_editing.rs index 5ec99c3d..63ea581a 100644 --- a/examples/topology_editing.rs +++ b/examples/topology_editing.rs @@ -25,15 +25,14 @@ use delaunay::prelude::construction::{ DelaunayTriangulation, DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, - vertex, }; use delaunay::prelude::flips::*; use delaunay::prelude::geometry::{ - CircumcenterError, Coordinate, Kernel, Point, circumcenter, hypot, + CircumcenterError, Coordinate, CoordinateConversionError, Kernel, Point, circumcenter, hypot, }; use delaunay::prelude::insertion::InsertionError; use delaunay::prelude::validation::DelaunayTriangulationValidationError; -use delaunay::prelude::{TdsError, VertexKey}; +use delaunay::prelude::{EdgeKeyError, TdsError, VertexKey}; type ExampleResult = Result; @@ -48,9 +47,13 @@ enum TopologyEditingExampleError { #[error(transparent)] Flip(#[from] FlipError), #[error(transparent)] + Edge(#[from] EdgeKeyError), + #[error(transparent)] Tds(#[from] TdsError), #[error(transparent)] Circumcenter(#[from] CircumcenterError), + #[error(transparent)] + CoordinateConversion(#[from] CoordinateConversionError), #[error("{demo} triangulation has no simplices")] EmptyTriangulation { demo: &'static str }, #[error("{demo} simplex key was not found")] @@ -106,9 +109,9 @@ fn builder_api_2d() -> ExampleResult { // Build initial triangulation let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([2.0, 3.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 3.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -121,9 +124,9 @@ fn builder_api_2d() -> ExampleResult { // Insert vertices using Builder API println!("Inserting 3 vertices using Builder API:"); let new_vertices = vec![ - vertex!([2.0, 1.0]), - vertex!([1.0, 1.5]), - vertex!([3.0, 1.5]), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 1.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.5])?, + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 1.5])?, ]; for (i, v) in new_vertices.into_iter().enumerate() { @@ -148,9 +151,9 @@ fn edit_api_2d_k1() -> ExampleResult { println!("------------------------------------------\n"); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([3.0, 0.0]), - vertex!([1.5, 2.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.5, 2.5])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -172,7 +175,7 @@ fn edit_api_2d_k1() -> ExampleResult { .ok_or(TopologyEditingExampleError::MissingSimplex { demo: "2D k=1 demo", })?; - let vertex_points: Vec> = simplex + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vkey| { @@ -204,7 +207,10 @@ fn edit_api_2d_k1() -> ExampleResult { circumcenter_coords[0], circumcenter_coords[1] ); - let flip_info = dt.flip_k1_insert(simplex_key, vertex!(circumcenter_coords))?; + let flip_info = dt.flip_k1_insert( + simplex_key, + delaunay::prelude::Vertex::<(), _>::try_new(circumcenter_coords)?, + )?; println!("After k=1 forward:"); print_stats_2d(&dt); @@ -237,10 +243,10 @@ fn edit_api_2d_k2() -> ExampleResult { // Create a square with diagonal (2 triangles) let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([2.0, 2.0]), - vertex!([0.0, 2.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 2.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 2.0])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -311,10 +317,10 @@ fn builder_api_3d() -> ExampleResult { println!("------------------------------------------------\n"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), - vertex!([1.0, 0.5, 1.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5, 1.5])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -326,7 +332,10 @@ fn builder_api_3d() -> ExampleResult { // Insert vertices using Builder API println!("Inserting 2 vertices using Builder API:"); - let new_vertices = vec![vertex!([1.0, 0.5, 0.5]), vertex!([0.8, 0.8, 0.8])]; + let new_vertices = vec![ + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5, 0.5])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.8, 0.8])?, + ]; for (i, v) in new_vertices.into_iter().enumerate() { dt.insert(v)?; @@ -349,10 +358,10 @@ fn edit_api_3d_k1() -> ExampleResult { println!("------------------------------------------\n"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), - vertex!([1.0, 0.5, 1.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5, 1.5])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -374,7 +383,7 @@ fn edit_api_3d_k1() -> ExampleResult { .ok_or(TopologyEditingExampleError::MissingSimplex { demo: "3D k=1 demo", })?; - let vertex_points: Vec> = simplex + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vkey| { @@ -406,7 +415,10 @@ fn edit_api_3d_k1() -> ExampleResult { "\nApplying k=1 flip (split tetrahedron at circumcenter [{:.2}, {:.2}, {:.2}]):", circumcenter_coords[0], circumcenter_coords[1], circumcenter_coords[2] ); - let flip_info = dt.flip_k1_insert(simplex_key, vertex!(circumcenter_coords))?; + let flip_info = dt.flip_k1_insert( + simplex_key, + delaunay::prelude::Vertex::<(), _>::try_new(circumcenter_coords)?, + )?; println!("After k=1 forward:"); print_stats_3d(&dt); @@ -437,11 +449,11 @@ fn edit_api_3d_k2() -> ExampleResult { // Build a simple regular tetrahedron and add one interior point let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 1.8, 0.0]), - vertex!([1.0, 0.6, 1.6]), - vertex!([1.0, 0.6, 0.4]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.8, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.6, 1.6])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.6, 0.4])?, // Interior point ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -460,10 +472,11 @@ fn edit_api_3d_k2() -> ExampleResult { // Try inverse println!("\nApplying k=2 inverse:"); - let edge = EdgeKey::new( + let edge = EdgeKey::try_new( + dt.tds(), flip_info.inserted_face_vertices[0], flip_info.inserted_face_vertices[1], - ); + )?; match dt.flip_k2_inverse_from_edge(edge) { Ok(_) => { @@ -511,10 +524,10 @@ fn edit_api_3d_k3() -> ExampleResult { println!("Building a simple 3D triangulation..."); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 1.7, 0.0]), - vertex!([1.0, 0.6, 1.4]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.7, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.6, 1.4])?, ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -546,7 +559,7 @@ fn edit_api_3d_k3() -> ExampleResult { // UTILITY FUNCTIONS // ============================================================================ -fn print_stats_2d>(dt: &DelaunayTriangulation) { +fn print_stats_2d>(dt: &DelaunayTriangulation) { println!( " Vertices: {}, Triangles: {}", dt.number_of_vertices(), @@ -554,7 +567,7 @@ fn print_stats_2d>(dt: &DelaunayTriangulation) { ); } -fn print_stats_3d>(dt: &DelaunayTriangulation) { +fn print_stats_3d>(dt: &DelaunayTriangulation) { println!( " Vertices: {}, Tetrahedra: {}", dt.number_of_vertices(), @@ -562,7 +575,7 @@ fn print_stats_3d>(dt: &DelaunayTriangulation) { ); } -fn find_interior_facet_2d>( +fn find_interior_facet_2d>( dt: &DelaunayTriangulation, ) -> Option { for (simplex_key, simplex) in dt.simplices() { @@ -572,7 +585,7 @@ fn find_interior_facet_2d>( let Ok(facet_idx) = u8::try_from(facet_idx) else { continue; }; - return Some(FacetHandle::new(simplex_key, facet_idx)); + return FacetHandle::try_new(dt.tds(), simplex_key, facet_idx).ok(); } } } @@ -580,7 +593,7 @@ fn find_interior_facet_2d>( None } -fn find_interior_facet_3d>( +fn find_interior_facet_3d>( dt: &DelaunayTriangulation, ) -> Option { for (simplex_key, simplex) in dt.simplices() { @@ -590,7 +603,7 @@ fn find_interior_facet_3d>( let Ok(facet_idx) = u8::try_from(facet_idx) else { continue; }; - return Some(FacetHandle::new(simplex_key, facet_idx)); + return FacetHandle::try_new(dt.tds(), simplex_key, facet_idx).ok(); } } } @@ -598,7 +611,7 @@ fn find_interior_facet_3d>( None } -fn find_flippable_ridge_3d>( +fn find_flippable_ridge_3d>( dt: &DelaunayTriangulation, ) -> Option { // Try to find any ridge (edge in 3D shared by multiple tetrahedra) @@ -615,7 +628,9 @@ fn find_flippable_ridge_3d>( let Ok(omit_b) = u8::try_from(i + 1) else { continue; }; - let ridge = RidgeHandle::new(simplex_key, omit_a, omit_b); + let Ok(ridge) = RidgeHandle::try_new(dt.tds(), simplex_key, omit_a, omit_b) else { + continue; + }; // Just return the first one we find // (In practice, you'd want to check if it's actually flippable) diff --git a/examples/triangulation_and_hull.rs b/examples/triangulation_and_hull.rs index 2cae74bb..c2529da3 100644 --- a/examples/triangulation_and_hull.rs +++ b/examples/triangulation_and_hull.rs @@ -15,14 +15,15 @@ use std::num::NonZeroUsize; use std::time::Instant; use delaunay::prelude::construction::{ - ConstructionOptions, DelaunayTriangulation, DelaunayTriangulationConstructionError, - RetryPolicy, vertex, + ConstructionOptions, DelaunayTriangulation, DelaunayTriangulationConstructionError, RetryPolicy, }; use delaunay::prelude::generators::generate_random_points_in_range_seeded; -use delaunay::prelude::geometry::{AdaptiveKernel, CoordinateRange, CoordinateRangeError}; +use delaunay::prelude::geometry::{ + AdaptiveKernel, CoordinateConversionError, CoordinateRange, CoordinateRangeError, + CoordinateValidationError, +}; use delaunay::prelude::query::{ - AdjacencyIndexBuildError, ConvexHull, ConvexHullConstructionError, Coordinate, Point, - QueryError, + AdjacencyIndexBuildError, ConvexHull, ConvexHullConstructionError, Point, QueryError, }; type WorkflowTriangulation = DelaunayTriangulation, (), (), D>; @@ -44,6 +45,10 @@ enum WorkflowExampleError { }, #[error("retry attempt count must be non-zero")] ZeroRetryAttempts, + #[error(transparent)] + CoordinateValidation(#[from] CoordinateValidationError), + #[error(transparent)] + CoordinateConversion(#[from] CoordinateConversionError), #[error("point count {point_count} is too large for centroid normalization")] PointCountTooLarge { point_count: usize }, } @@ -70,11 +75,11 @@ fn run_case( seed: u64, bounds: CoordinateRange, ) -> Result<(), WorkflowExampleError> { - let points = generate_random_points_in_range_seeded::(point_count, bounds, seed); + let points = generate_random_points_in_range_seeded::(point_count, bounds, seed); let vertices = points .iter() - .map(|point| vertex!(*point)) - .collect::>(); + .map(|point| delaunay::prelude::Vertex::<(), _>::try_new((*point).into())) + .collect::, _>>()?; let options = ConstructionOptions::default().with_retry_policy(RetryPolicy::Shuffled { attempts: retry_attempts()?, base_seed: Some(seed), @@ -96,7 +101,7 @@ fn run_case( println!(" hull facets: {}", hull.number_of_facets()); let inside = centroid_point(&points)?; - let outside = Point::new([bounds.max() * 2.5; D]); + let outside = Point::try_new([bounds.max() * 2.5; D])?; println!( " hull query: centroid outside? {}", @@ -124,9 +129,7 @@ fn retry_attempts() -> Result { NonZeroUsize::new(6).ok_or(WorkflowExampleError::ZeroRetryAttempts) } -fn centroid_point( - points: &[Point], -) -> Result, WorkflowExampleError> { +fn centroid_point(points: &[Point]) -> Result, WorkflowExampleError> { let mut coords = [0.0; D]; for point in points { for (coord, value) in coords.iter_mut().zip(point.coords()) { @@ -143,5 +146,5 @@ fn centroid_point( *coord /= point_count; } - Ok(Point::new(coords)) + Ok(Point::try_new(coords)?) } diff --git a/semgrep.yaml b/semgrep.yaml index 18c10bb4..3cac6d34 100644 --- a/semgrep.yaml +++ b/semgrep.yaml @@ -166,6 +166,7 @@ rules: paths: include: - "/src/core/**/*.rs" + - "/tests/semgrep/src/core/**/*.rs" - "/src/geometry/**/*.rs" - "/src/topology/**/*.rs" - "/src/delaunay/**/*.rs" @@ -381,6 +382,287 @@ rules: - pattern: $VALUE.expect(...) - pattern: panic!(...) + - id: delaunay.rust.no-legacy-coordinate-generic-api + languages: + - generic + severity: WARNING + message: "Use the current f64-coordinate API: Point, Vertex, Tds, ConvexHull, and Point::try_new/try_from." + metadata: + category: maintainability + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + Coordinate storage is now finite f64 by construction. The coordinate + scalar is no longer a public type parameter on Point, Vertex, Tds, or + related builders, and raw point construction must parse through the + fallible boundary. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + - "/docs/**/*.md" + - "/README.md" + exclude: + - "/docs/archive/**" + pattern-regex: >- + \b(?:Point\s*<\s*(?:T|f64|K::Scalar|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,|Vertex\s*<\s*(?:T|f64|K::Scalar|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,[^>\n,]+,\s*[^>\n]+>|VertexBuilder\s*::\s*<\s*(?:T|f64|K::Scalar|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,|Tds\s*<\s*(?:T|f64|K::Scalar|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,|Simplex\s*<\s*(?:T|f64|K::Scalar|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,|DelaunayTriangulationBuilder\s*<\s*(?:'[^,]+,\s*)?(?:T|f64|K::Scalar|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,|ConvexHull\s*<\s*(?:_|T|f64|K|K::Scalar|FastKernel\s*<[^>\n]+>|AdaptiveKernel\s*<[^>\n]+>|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,[^>\n,]+,\s*[^>\n,]+,\s*[^>\n]+>|ConvexHull(?:2D|3D|4D)\s*<\s*(?:_|T|f64|K|K::Scalar|FastKernel\s*<[^>\n]+>|AdaptiveKernel\s*<[^>\n]+>|[A-Za-z_][A-Za-z0-9_]*::Scalar)\s*,|Point::new\s*\() + + - id: delaunay.rust.no-raw-point-coordinate-storage + languages: + - generic + severity: WARNING + message: "Point must store validated coordinate domain state, not a raw [f64; D] array." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + Raw f64 storage discards the finite-coordinate proof and risks regressing + the Eq/Hash/Ord semantics for signed zero and other floating-point edge + cases. Keep raw f64 at parse/serialization boundaries and store a + validated coordinate wrapper inside Point. + paths: + include: + - "/src/geometry/point.rs" + - "/tests/semgrep/src/project_rules/**/*.rs" + pattern-regex: >- + (?s)\bstruct\s+Point\s*<[^>{}]*>\s*\{[^}]*\bcoords\s*:\s*\[\s*f64\s*;\s*D\s*\] + + - id: delaunay.rust.no-coordinate-scalar-trait + languages: + - generic + severity: WARNING + message: "Do not reintroduce CoordinateScalar; keep coordinate invariants on validated coordinate/domain types." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + CoordinateScalar made arbitrary numeric support look available while + the crate's storage, predicates, and topology model support validated + f64 coordinates. Future exact or alternate coordinate models should be + explicit invariant-bearing types, not a loose scalar trait. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + - "/docs/code_organization.md" + - "/README.md" + pattern-regex: '\bCoordinateScalar\b' + + - id: delaunay.rust.no-vertex-macro + languages: + - generic + severity: WARNING + message: "Use Vertex::try_new or Vertex::try_new_with_data so coordinate validation remains explicit." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + The vertex! macro hid a fallible coordinate-construction boundary behind + macro syntax. Vertex construction should now flow through the named + smart constructors that return typed validation errors. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + - "/docs/**/*.md" + - "/README.md" + exclude: + - "/docs/archive/**" + - "/tests/semgrep/**" + pattern-regex: '\bvertex!\s*(?:\(|\[)' + + - id: delaunay.rust.no-vertex-empty-constructor + languages: + - generic + severity: WARNING + message: "Use Vertex::try_new or Vertex::try_new_with_data; do not construct placeholder vertices with invalid UUIDs." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + Vertex::empty produced a storable invalid vertex with a nil UUID. Invalid + UUIDs should be rejected at construction boundaries rather than stored + and discovered by later validation. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + exclude: + - "/tests/semgrep/**" + pattern-regex: '\bVertex\s*(?:::<[^>\n]+>)?\s*::\s*empty\s*\(' + + - id: delaunay.rust.no-slotmap-key-topology-deserialization + languages: + - generic + severity: WARNING + message: "Do not deserialize topology relationships from slotmap keys; deserialize UUIDs and resolve them inside Tds reconstruction." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + VertexKey and SimplexKey values are storage-local slotmap keys. Serialized + topology must use stable vertex and simplex UUIDs, then resolve those UUIDs + to fresh live keys while rebuilding the TDS. + paths: + include: + - "/src/**/*.rs" + - "/tests/semgrep/src/project_rules/**/*.rs" + pattern-either: + - pattern-regex: '\bnext_value\s*::\s*<\s*(?:Vec\s*<\s*)?(?:VertexKey|SimplexKey|SimplexVertexKeyBuffer|VertexKeyBuffer|SimplexKeyBuffer)\b' + - pattern-regex: '\bnext_value\s*::\s*<\s*NeighborBuffer\s*<\s*(?:Option\s*<\s*)?SimplexKey\b' + + - id: delaunay.rust.no-simplex-slotmap-key-serialization + languages: + - generic + severity: WARNING + message: "Do not serialize Simplex slotmap-key fields; serialize simplex UUIDs plus Tds-level vertex UUID relationships instead." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + Simplex vertex and neighbor slots contain VertexKey and SimplexKey values, + which are storage-local handles. The wire format must carry UUIDs so it + can be deserialized into fresh slotmap keys. + paths: + include: + - "/src/core/simplex.rs" + - "/tests/semgrep/src/project_rules/**/*.rs" + pattern-regex: '\bserialize_field\s*\(\s*"(?:vertices|neighbors|neighbor|incident_simplex)"\s*,\s*&self\.' + + - id: delaunay.rust.no-simplex-new-constructor + languages: + - generic + severity: WARNING + message: "Use Simplex::try_new or Simplex::try_new_with_data so fallible simplex validation is explicit." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + Simplex construction validates D+1 vertex-key arity and duplicate keys. + The constructor name must expose that boundary instead of looking like an + infallible raw-value constructor. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + pattern-regex: '\bSimplex\s*::\s*new\s*\(' + + - id: delaunay.rust.no-facet-new-constructors + languages: + - generic + severity: WARNING + message: "Use try_new for fallible facet handles, views, and iterators so validation and dimension-capacity errors are explicit." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + FacetHandle and FacetView validate simplex presence and facet index + bounds, while facet iterators can fail when the dimension exceeds the + u8 facet-index representation. Constructor names must expose those + fallible boundaries. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + pattern-either: + - pattern-regex: '\bFacetView\s*::\s*new\s*\(' + - pattern-regex: '\bFacetHandle\s*::\s*new\s*\(' + - pattern-regex: '\bAllFacetsIter\s*::\s*new\s*\(' + - pattern-regex: '\bBoundaryFacetsIter\s*::\s*new\s*\(' + + - id: delaunay.rust.no-ridgehandle-new-constructor + languages: + - generic + severity: WARNING + message: "Use RidgeHandle::try_new for raw omitted indices, or the crate-private validated constructor after bounds are already proven." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + A RidgeHandle must identify a valid codimension-2 face in a live simplex. + Public raw index construction must reject duplicate or out-of-range + omitted indices instead of storing invalid ridge selectors for later + validation. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + pattern-regex: '\bRidgeHandle\s*::\s*new\s*\(' + + - id: delaunay.rust.no-edgekey-new-constructor + languages: + - generic + severity: WARNING + message: "Use EdgeKey::try_new with a live TDS for raw endpoints, or the crate-private validated constructor after edge incidence is already proven." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + An EdgeKey must identify a real edge in a live TDS, so public raw + endpoint construction needs to reject duplicate, missing, or non-incident + vertex keys instead of storing an invalid edge selector. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + pattern-regex: '\bEdgeKey\s*::\s*new\s*\(' + + - id: delaunay.rust.no-edgekey-try-new-without-tds + languages: + - generic + severity: WARNING + message: "Pass a live TDS to EdgeKey::try_new so the edge can be proven to exist." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + EdgeKey::try_new must parse raw endpoint keys against live topology. + A two-argument call can only check distinctness and cannot prove that the + endpoints form a real edge. + paths: + include: + - "/src/**/*.rs" + - "/tests/**/*.rs" + - "/benches/**/*.rs" + - "/examples/**/*.rs" + pattern-regex: '\bEdgeKey\s*::\s*try_new\s*\(\s*[^,\n()]+,\s*[^,\n()]+\s*\)' + + - id: delaunay.rust.no-tds-storage-map-serde + languages: + - generic + severity: WARNING + message: "Do not serialize or deserialize TDS StorageMap/DenseSlotMap storage; use UUID-keyed DTO records and rebuild fresh slotmap keys." + metadata: + category: correctness + tracking_issue: "https://github.com/acgetchell/delaunay/issues/442" + rationale: >- + DenseSlotMap/StorageMap layouts and generated keys are process-local storage + details. TDS persistence must serialize stable vertex/simplex UUID records + and UUID relationships, then reconstruct fresh keys during deserialization. + paths: + include: + - "/src/core/**/*.rs" + - "/tests/semgrep/src/project_rules/**/*.rs" + pattern-either: + - pattern-regex: '\bserialize_field\s*\(\s*"(?:vertices|simplices)"\s*,\s*&(?:self|tds)\.(?:vertices|simplices)\b' + - pattern-regex: '\bOption\s*<\s*StorageMap\s*<\s*(?:VertexKey|SimplexKey)\b' + - pattern-regex: '\bserialized_simplices\s*:\s*StorageMap\s*<\s*SimplexKey\b' + - id: delaunay.rust.public-error-enums-non-exhaustive languages: - generic @@ -543,6 +825,24 @@ rules: - "/tests/semgrep/**" pattern-regex: '^\s*pub(?:\([^)]*\))?\s+(?:const\s+|async\s+|unsafe\s+)?fn\s+[A-Za-z_][A-Za-z0-9_]*_unchecked\s*\(' # yamllint disable-line rule:line-length + - id: delaunay.rust.no-public-vertex-new-with-uuid + languages: + - generic + severity: WARNING + message: "Use public Vertex::try_new_with_uuid for raw UUIDs; keep infallible UUID-preserving constructors crate-private." + metadata: + category: correctness + rationale: >- + Caller-provided UUIDs are invariant-bearing input. Public APIs must + validate them before storing them in Vertex, while crate-internal + UUID-preserving paths may remain infallible after validation has already + happened at the boundary. + paths: + include: + - "/src/core/vertex.rs" + - "/tests/semgrep/src/project_rules/**/*.rs" + pattern-regex: '^\s*pub\s+(?:const\s+)?fn\s+new_with_uuid\s*\(' + - id: delaunay.rust.no-unwrap-expect-in-doctests languages: - generic @@ -553,11 +853,18 @@ rules: rationale: >- Public Rust documentation examples should model typed error handling with DelaunayResult and ? rather than teaching panic-based control flow. + Literal vertex fixtures may use `expect("finite vertex coordinates")` + with `try_new` or `try_new_with_data` because the panic documents an + impossible finite-literal invariant, while keeping examples focused on + the API under discussion. paths: include: - "/src/**/*.rs" - "/tests/semgrep/doctests/**/*.txt" - pattern-regex: '^\s*//[!/]\s*(?:#\s*)?.*(?:\b[\w:]+|[\]\)])\.(unwrap|expect)\s*\(' + patterns: + - pattern-regex: '^\s*//[!/]\s*(?:#\s*)?.*(?:\b[\w:]+|[\]\)])\.(unwrap|expect)\s*\(' + - pattern-not-regex: '^\s*//[!/].*\bVertex(?:\s*::<[^>]+>)?::try_new\s*\(\s*\[[^\]]*\]\s*\)\.expect\("finite vertex coordinates"\)' + - pattern-not-regex: '^\s*//[!/].*\bVertex(?:\s*::<[^>]+>)?::try_new_with_data\s*\(\s*\[[^\]]*\]\s*,[^()]*\)\.expect\("finite vertex coordinates"\)' - id: delaunay.rust.no-box-dyn-error-in-doctests languages: diff --git a/src/core/adjacency.rs b/src/core/adjacency.rs index 8b7e55a1..92468b74 100644 --- a/src/core/adjacency.rs +++ b/src/core/adjacency.rs @@ -57,7 +57,7 @@ pub enum AdjacencyIndexBuildError { /// Immutable adjacency maps for topology traversal. /// /// This is an *opt-in* structure returned by -/// [`Triangulation::build_adjacency_index`](crate::core::triangulation::Triangulation::build_adjacency_index). +/// [`Triangulation::build_adjacency_index`](crate::prelude::triangulation::Triangulation::build_adjacency_index). /// /// ## Notes /// - No sorted-order guarantees are provided for the values. @@ -79,12 +79,12 @@ pub enum AdjacencyIndexBuildError { /// // Two tetrahedra sharing a triangular facet. /// let vertices: Vec<_> = vec![ /// // Shared triangle -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([2.0, 0.0, 0.0]), -/// vertex!([1.0, 2.0, 0.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).expect("finite vertex coordinates"), /// // Two apices -/// vertex!([1.0, 0.7, 1.5]), -/// vertex!([1.0, 0.7, -1.5]), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).expect("finite vertex coordinates"), /// ]; /// /// let dt: DelaunayTriangulation<_, (), (), 3> = @@ -137,12 +137,12 @@ impl AdjacencyIndex { /// // Two tetrahedra sharing a triangular facet. /// let vertices: Vec<_> = vec![ /// // Shared triangle - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([2.0, 0.0, 0.0]), - /// vertex!([1.0, 2.0, 0.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).expect("finite vertex coordinates"), /// // Two apices - /// vertex!([1.0, 0.7, 1.5]), - /// vertex!([1.0, 0.7, -1.5]), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -187,12 +187,12 @@ impl AdjacencyIndex { /// # fn main() -> Result<(), ExampleError> { /// # let vertices: Vec<_> = vec![ /// # // Shared triangle - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([2.0, 0.0, 0.0]), - /// # vertex!([1.0, 2.0, 0.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).expect("finite vertex coordinates"), /// # // Two apices - /// # vertex!([1.0, 0.7, 1.5]), - /// # vertex!([1.0, 0.7, -1.5]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -231,10 +231,10 @@ impl AdjacencyIndex { /// # } /// # fn main() -> Result<(), ExampleError> { /// # let vertices = vec![ - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([1.0, 0.0, 0.0]), - /// # vertex!([0.0, 1.0, 0.0]), - /// # vertex!([0.0, 0.0, 1.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -271,10 +271,10 @@ impl AdjacencyIndex { /// # } /// # fn main() -> Result<(), ExampleError> { /// # let vertices = vec![ - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([1.0, 0.0, 0.0]), - /// # vertex!([0.0, 1.0, 0.0]), - /// # vertex!([0.0, 0.0, 1.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -309,12 +309,12 @@ impl AdjacencyIndex { /// # fn main() -> Result<(), ExampleError> { /// # let vertices: Vec<_> = vec![ /// # // Shared triangle - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([2.0, 0.0, 0.0]), - /// # vertex!([1.0, 2.0, 0.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).expect("finite vertex coordinates"), /// # // Two apices - /// # vertex!([1.0, 0.7, 1.5]), - /// # vertex!([1.0, 0.7, -1.5]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -352,12 +352,12 @@ impl AdjacencyIndex { /// # fn main() -> Result<(), ExampleError> { /// # let vertices: Vec<_> = vec![ /// # // Shared triangle - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([2.0, 0.0, 0.0]), - /// # vertex!([1.0, 2.0, 0.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).expect("finite vertex coordinates"), /// # // Two apices - /// # vertex!([1.0, 0.7, 1.5]), - /// # vertex!([1.0, 0.7, -1.5]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -395,10 +395,10 @@ impl AdjacencyIndex { /// # } /// # fn main() -> Result<(), ExampleError> { /// # let vertices = vec![ - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([1.0, 0.0, 0.0]), - /// # vertex!([0.0, 1.0, 0.0]), - /// # vertex!([0.0, 0.0, 1.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -434,10 +434,10 @@ impl AdjacencyIndex { /// # } /// # fn main() -> Result<(), ExampleError> { /// # let vertices = vec![ - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([1.0, 0.0, 0.0]), - /// # vertex!([0.0, 1.0, 0.0]), - /// # vertex!([0.0, 0.0, 1.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -457,7 +457,6 @@ impl AdjacencyIndex { mod tests { use super::*; use crate::DelaunayTriangulation; - use crate::vertex; use slotmap::SlotMap; use std::collections::HashSet; @@ -495,12 +494,12 @@ mod tests { // Two tetrahedra sharing a triangular facet. let vertices: Vec<_> = vec![ // Shared triangle - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).unwrap(), // Two apices - vertex!([1.0, 0.7, 1.5]), - vertex!([1.0, 0.7, -1.5]), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = diff --git a/src/core/algorithms/flips.rs b/src/core/algorithms/flips.rs index c7059c72..165a2f29 100644 --- a/src/core/algorithms/flips.rs +++ b/src/core/algorithms/flips.rs @@ -54,7 +54,7 @@ use crate::geometry::predicates::simplex_orientation; use crate::geometry::predicates::{Orientation, simplex_orientation_fast_filter_sign}; use crate::geometry::robust_predicates::robust_orientation; use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateScalar, CoordinateValues, + CoordinateConversionError, CoordinateValidationError, CoordinateValues, }; use crate::topology::traits::global_topology_model::{ GlobalTopologyModel, GlobalTopologyModelAdapter, @@ -98,13 +98,13 @@ pub struct BistellarFlipKind { } /// Run a single flip-repair attempt using k=2 (and k=3 in 3D+). fn repair_delaunay_with_flips_k2_k3_attempt( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, config: &RepairAttemptConfig, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -117,14 +117,14 @@ where reason = "Repair loop contains inline tracing and queue handling for diagnostics" )] fn repair_delaunay_with_flips_k2_k3_attempt_timed( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, config: &RepairAttemptConfig, mut timing: Option<&mut LocalRepairPhaseTiming>, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -322,8 +322,8 @@ where /// /// The snapshot lets later diagnostics describe removed simplices even after /// their `SimplexKey`s no longer resolve in the TDS. -fn snapshot_removed_simplex_vertices( - tds: &Tds, +fn snapshot_removed_simplex_vertices( + tds: &Tds, removed_simplices: &SimplexKeyBuffer, ) -> Result where @@ -351,8 +351,8 @@ where clippy::too_many_lines, reason = "Keep flip construction, validation, and wiring together for clarity" )] -fn apply_bistellar_flip_with_k( - tds: &mut Tds, +fn apply_bistellar_flip_with_k( + tds: &mut Tds, k_move: usize, removed_face_vertices: &[VertexKey], inserted_face_vertices: &[VertexKey], @@ -362,7 +362,6 @@ fn apply_bistellar_flip_with_k( validation_scope: FlipValidationScope, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -544,7 +543,7 @@ where let mut trial = tds.clone_for_rollback(); for (vertices, periodic_offsets) in new_simplex_vertices.into_iter().zip(new_simplex_offsets) { - let mut simplex = Simplex::new(vertices, None)?; + let mut simplex = Simplex::try_new(vertices)?; if let Some(offsets) = periodic_offsets { simplex.set_periodic_vertex_offsets(offsets)?; } @@ -619,8 +618,8 @@ enum FlipValidationScope { } /// Checks the flip cavity after mutation without rescanning the full TDS. -fn validate_flip_trial_cavity( - tds: &Tds, +fn validate_flip_trial_cavity( + tds: &Tds, new_simplices: &[SimplexKey], external_facets: &[FacetHandle], removed_simplices: &[SimplexKey], @@ -677,8 +676,8 @@ fn push_unique_simplex_key( } /// Ensures affected replacement simplices agree on shared facets and multiplicity. -fn validate_flip_trial_local_facet_sharing( - tds: &Tds, +fn validate_flip_trial_local_facet_sharing( + tds: &Tds, affected_simplices: &[SimplexKey], ) -> Result<(), TdsValidationFailure> where @@ -746,8 +745,8 @@ where } /// Checks one affected simplex's local references after a flip mutation. -fn validate_flip_trial_simplex( - tds: &Tds, +fn validate_flip_trial_simplex( + tds: &Tds, simplex_key: SimplexKey, removed_simplices: &[SimplexKey], ) -> Result<(), TdsValidationFailure> @@ -783,10 +782,10 @@ where } /// Verifies that affected simplices reference existing vertices with valid incidence. -fn validate_flip_trial_simplex_vertices( - tds: &Tds, +fn validate_flip_trial_simplex_vertices( + tds: &Tds, simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, ) -> Result<(), TdsValidationFailure> where U: DataType, @@ -840,10 +839,10 @@ where } /// Verifies affected-simplex neighbor links, mirror facets, and orientation parity. -fn validate_flip_trial_simplex_neighbors( - tds: &Tds, +fn validate_flip_trial_simplex_neighbors( + tds: &Tds, simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, removed_simplices: &[SimplexKey], ) -> Result<(), TdsValidationFailure> where @@ -944,13 +943,7 @@ where } /// Mirrors TDS validation's periodic self-neighbor allowance locally. -fn simplex_allows_periodic_self_neighbor( - simplex: &Simplex, -) -> bool -where - U: DataType, - V: DataType, -{ +fn simplex_allows_periodic_self_neighbor(simplex: &Simplex) -> bool { let Some(offsets) = simplex.periodic_vertex_offsets() else { return false; }; @@ -958,8 +951,8 @@ where } /// Requires two simplices sharing an affected facet to point back to each other. -fn validate_flip_trial_mutual_facet_neighbors( - tds: &Tds, +fn validate_flip_trial_mutual_facet_neighbors( + tds: &Tds, facet_key: u64, source_simplex_key: SimplexKey, source_facet: usize, @@ -1006,18 +999,14 @@ where } /// Checks coherent orientation across one locally affected neighbor pair. -fn validate_flip_trial_neighbor_orientation( +fn validate_flip_trial_neighbor_orientation( simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, neighbor_key: SimplexKey, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, mirror_idx: usize, -) -> Result<(), TdsValidationFailure> -where - U: DataType, - V: DataType, -{ +) -> Result<(), TdsValidationFailure> { let (observed_odd_permutation, expected_odd_permutation, facet_vertex_count, target_count) = match flip_trial_neighbor_orientation_parity( simplex_key, @@ -1073,18 +1062,14 @@ where } /// Computes local neighbor-orientation parity, including periodic facet offsets. -fn flip_trial_neighbor_orientation_parity( +fn flip_trial_neighbor_orientation_parity( simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, neighbor_key: SimplexKey, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, mirror_idx: usize, -) -> Result<(bool, bool, usize, usize), FlipError> -where - U: DataType, - V: DataType, -{ +) -> Result<(bool, bool, usize, usize), FlipError> { let expected_odd_permutation = (facet_idx + mirror_idx).is_multiple_of(2); if simplex.periodic_vertex_offsets().is_some() || neighbor_simplex.periodic_vertex_offsets().is_some() @@ -1127,8 +1112,8 @@ where /// Detects replacement simplices that already exist outside the flip cavity so /// a flip cannot silently duplicate a simplex. -fn find_simplex_containing_simplex( - tds: &Tds, +fn find_simplex_containing_simplex( + tds: &Tds, simplex_vertices: &[VertexKey], removed_simplices: &[SimplexKey], ) -> Option @@ -1161,8 +1146,8 @@ where } /// Chooses replacement-simplex parity from the oriented cavity boundary. -fn orient_replacement_simplices( - tds: &Tds, +fn orient_replacement_simplices( + tds: &Tds, simplices: &mut [SmallBuffer], periodic_offsets: &mut [Option>], external_facets: &[FacetHandle], @@ -1254,8 +1239,8 @@ fn orient_replacement_simplices( } /// Applies external boundary-facet parity constraints to replacement simplices. -fn assign_external_replacement_orientation( - tds: &Tds, +fn assign_external_replacement_orientation( + tds: &Tds, simplices: &[SmallBuffer], periodic_offsets: &[Option>], external_facets: &[FacetHandle], @@ -1340,8 +1325,8 @@ fn set_flip_assignment( } /// Builds periodic offsets for replacement simplices in one shared cavity frame. -fn replacement_simplex_periodic_offsets( - tds: &Tds, +fn replacement_simplex_periodic_offsets( + tds: &Tds, simplices: &[SmallBuffer], removed_simplices: &[SimplexKey], external_facets: &[FacetHandle], @@ -1398,8 +1383,8 @@ fn replacement_periodic_source_simplices( } /// Returns whether any source simplex carries explicit periodic offsets. -fn replacement_sources_use_periodic_offsets( - tds: &Tds, +fn replacement_sources_use_periodic_offsets( + tds: &Tds, source_simplices: &[SimplexKey], ) -> Result { let mut uses_periodic_offsets = false; @@ -1416,8 +1401,8 @@ fn replacement_sources_use_periodic_offsets( } /// Checks whether a vertex already has a periodic representative in any source simplex. -fn source_simplices_contain_vertex( - tds: &Tds, +fn source_simplices_contain_vertex( + tds: &Tds, source_simplices: &[SimplexKey], vertex_key: VertexKey, ) -> Result { @@ -1433,8 +1418,8 @@ fn source_simplices_contain_vertex( } /// Places a newly inserted k=1 vertex in the target simplex's local lattice sheet. -fn new_vertex_periodic_offset_in_frame( - tds: &Tds, +fn new_vertex_periodic_offset_in_frame( + tds: &Tds, target_simplex_key: SimplexKey, ) -> Result<[i8; D], FlipError> { let target_simplex = tds @@ -1578,7 +1563,7 @@ fn facet_orders_coherent_with_periodic_offsets( Ok(observed_odd == expected_odd) } -/// Returns facet `(vertex, offset)` identities in simplex-local order. +/// Returns facet `(offset)` identities in simplex-local order. fn facet_order_with_offsets( vertices: &[VertexKey], offsets: &[[i8; D]], @@ -1761,12 +1746,11 @@ fn permutation_odd(source_order: &[Id], target_order: &[Id]) -> O } /// Ensures Delaunay-repair replacement simplices have positive geometric orientation. -fn validate_replacement_orientation( - tds: &Tds, +fn validate_replacement_orientation( + tds: &Tds, simplices: &[SmallBuffer], ) -> Result<(), FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -1794,12 +1778,11 @@ where /// Scans the whole TDS for ridge diagnostics when local neighbor links are the /// thing being investigated. -fn simplices_containing_vertices( - tds: &Tds, +fn simplices_containing_vertices( + tds: &Tds, vertices: &[VertexKey], ) -> SimplexKeyBuffer where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -1820,14 +1803,13 @@ where /// /// The local neighbor walk and the global simplex scan are logged side by side /// because #204 currently fails in cases where those two views disagree. -fn debug_ridge_context( - tds: &Tds, +fn debug_ridge_context( + tds: &Tds, ridge: RidgeHandle, reported_multiplicity: Option, diagnostics: &mut RepairDiagnostics, last_applied_flip: Option<&LastAppliedFlip>, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -1892,13 +1874,12 @@ fn debug_ridge_context( /// Formats one incident simplex around a ridge so debug output can distinguish /// oversharing from bad local neighbor traversal. -fn ridge_incident_simplex_summary( - tds: &Tds, +fn ridge_incident_simplex_summary( + tds: &Tds, simplex_key: SimplexKey, ridge_vertices: &SmallBuffer, ) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -1916,13 +1897,11 @@ where /// Extracts the neighbors reached by omitting the two vertices opposite the /// ridge, which is exactly the adjacency walk used by k=3 context recovery. -fn ridge_neighbor_simplices_for_simplex( - simplex: &Simplex, +fn ridge_neighbor_simplices_for_simplex( + simplex: &Simplex, ridge_vertices: &SmallBuffer, ) -> SmallBuffer where - T: CoordinateScalar, - U: DataType, V: DataType, { let mut ridge_neighbors: SmallBuffer = SmallBuffer::new(); @@ -1940,14 +1919,13 @@ where /// Relates the current bad ridge to the immediately preceding flip so #204 /// traces can confirm whether repair just created the inconsistent local star. -fn predecessor_flip_summary( - tds: &Tds, +fn predecessor_flip_summary( + tds: &Tds, ridge: RidgeHandle, global_simplices: &[SimplexKey], last_applied_flip: &LastAppliedFlip, ) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -1980,12 +1958,11 @@ where /// Formats one simplex's current vertex set so predecessor-flip traces can show /// the exact simplices that were introduced before a bad ridge appeared. -fn simplex_vertex_summary( - tds: &Tds, +fn simplex_vertex_summary( + tds: &Tds, simplex_key: SimplexKey, ) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -1997,14 +1974,13 @@ where /// Captures the first unresolved k=2 postcondition site so #204 debugging can /// compare the violating facet directly against the last applied repair flip. -fn debug_postcondition_facet_context( - tds: &Tds, +fn debug_postcondition_facet_context( + tds: &Tds, facet: FacetHandle, context: &FlipContext, diagnostics: &mut RepairDiagnostics, last_applied_flip: Option<&LastAppliedFlip>, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2046,13 +2022,12 @@ fn debug_postcondition_facet_context( /// Formats the two simplices incident to a violating facet so postcondition traces /// can see both their full simplex vertices and their opposite vertices. -fn facet_incident_simplex_summary( - tds: &Tds, +fn facet_incident_simplex_summary( + tds: &Tds, simplex_key: SimplexKey, facet_vertices: &[VertexKey], ) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2078,13 +2053,12 @@ where /// Relates the first unresolved postcondition facet to the immediately /// preceding repair flip so we can tell whether that last move touched the bad /// local neighborhood or whether the violation was already present. -fn postcondition_facet_predecessor_summary( - tds: &Tds, +fn postcondition_facet_predecessor_summary( + tds: &Tds, incident_simplices: &[SimplexKey], last_applied_flip: &LastAppliedFlip, ) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2127,7 +2101,7 @@ where /// Returns a [`FlipError`] if any referenced simplex/vertex is missing or a predicate /// evaluation fails. fn is_delaunay_violation_k3( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, context: &FlipContext, @@ -2135,7 +2109,7 @@ fn is_delaunay_violation_k3( diagnostics: &mut RepairDiagnostics, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -2159,12 +2133,11 @@ where /// Returns a [`FlipError`] if the flip would be degenerate, duplicate an existing simplex, /// create non-manifold topology, if predicate evaluation fails, or if underlying TDS /// mutations fail. -pub(crate) fn apply_bistellar_flip( - tds: &mut Tds, +pub(crate) fn apply_bistellar_flip( + tds: &mut Tds, context: &FlipContext, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2188,13 +2161,12 @@ where /// Returns a [`FlipError`] if the flip would be degenerate, duplicate an existing simplex, /// create non-manifold topology, if predicate evaluation fails, or if underlying TDS /// mutations fail. -pub(crate) fn apply_bistellar_flip_dynamic( - tds: &mut Tds, +pub(crate) fn apply_bistellar_flip_dynamic( + tds: &mut Tds, k_move: usize, context: &FlipContextDyn, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2212,12 +2184,11 @@ where } /// Apply a k=2 Delaunay-repair move with positive replacement geometry. -fn apply_delaunay_flip_k2( - tds: &mut Tds, +fn apply_delaunay_flip_k2( + tds: &mut Tds, context: &FlipContext, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2234,12 +2205,11 @@ where } /// Apply a k=3 Delaunay-repair move with positive replacement geometry. -fn apply_delaunay_flip_k3( - tds: &mut Tds, +fn apply_delaunay_flip_k3( + tds: &mut Tds, context: &FlipContext, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2256,13 +2226,12 @@ where } /// Apply a runtime-k Delaunay-repair move with positive replacement geometry. -fn apply_delaunay_flip_dynamic( - tds: &mut Tds, +fn apply_delaunay_flip_dynamic( + tds: &mut Tds, k_move: usize, context: &FlipContextDyn, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2337,8 +2306,8 @@ impl<'a> FlipCycleContext<'a> { /// Converts repeated flip signatures into typed non-convergence before the /// repair loop burns the full budget on a short oscillation. -fn check_flip_cycle( - tds: &Tds, +fn check_flip_cycle( + tds: &Tds, context: FlipCycleContext<'_>, diagnostics: &mut RepairDiagnostics, stats: &DelaunayRepairStats, @@ -2346,7 +2315,6 @@ fn check_flip_cycle( config: &RepairAttemptConfig, ) -> Result<(), DelaunayRepairError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2393,13 +2361,12 @@ where /// Slot swaps can invalidate the original facet index while preserving the facet /// vertex set (and therefore its hash key). This helper checks the original /// index first, then scans the owning simplex to recover the correct index for `key`. -fn resolve_facet_handle_for_key( - tds: &Tds, +fn resolve_facet_handle_for_key( + tds: &Tds, handle: FacetHandle, key: u64, ) -> Option where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2418,7 +2385,7 @@ where let facet_vertices = facet_vertices_from_simplex(simplex, candidate_idx); if facet_key_from_vertices(&facet_vertices) == key { let facet_index = u8::try_from(candidate_idx).ok()?; - return Some(FacetHandle::new(simplex_key, facet_index)); + return Some(FacetHandle::from_validated(simplex_key, facet_index)); } } @@ -2430,13 +2397,12 @@ where /// Slot swaps can invalidate the original omit-index pair while preserving the /// ridge vertex set (and therefore its hash key). This helper checks the original /// pair first, then scans the owning simplex for the pair matching `key`. -fn resolve_ridge_handle_for_key( - tds: &Tds, +fn resolve_ridge_handle_for_key( + tds: &Tds, handle: RidgeHandle, key: u64, ) -> Option where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2466,7 +2432,7 @@ where if facet_key_from_vertices(&ridge_vertices) == key { let omit_a = u8::try_from(i).ok()?; let omit_b = u8::try_from(j).ok()?; - return Some(RidgeHandle::new(simplex_key, omit_a, omit_b)); + return Some(RidgeHandle::from_validated(simplex_key, omit_a, omit_b)); } } } @@ -2894,6 +2860,9 @@ pub enum FlipFailureKind { /// Inserted simplex already exists. #[error("inserted simplex already exists")] InsertedSimplexAlreadyExists, + /// Facet iteration failed. + #[error("facet iteration")] + FacetIteration, /// Simplex creation failed. #[error("simplex creation")] SimplexCreation, @@ -3304,6 +3273,15 @@ pub enum FlipNeighborWiringError { #[source] reason: SpatialIndexConstructionFailure, }, + /// Perturbation retry produced invalid coordinates. + #[error( + "perturbation retry produced invalid coordinates before flip neighbor wiring: {source}" + )] + PerturbedCoordinateInvalid { + /// Structured coordinate validation failure for the perturbed point. + #[source] + source: CoordinateValidationError, + }, } impl From for FlipNeighborWiringError { @@ -3351,6 +3329,9 @@ impl From for FlipNeighborWiringError { InsertionError::SpatialIndexConstruction { reason } => { Self::SpatialIndexConstruction { reason } } + InsertionError::PerturbedCoordinateInvalid { source } => { + Self::PerturbedCoordinateInvalid { source } + } } } } @@ -3452,6 +3433,18 @@ pub enum FlipTriangleAdjacencyError { }, } +/// Error returned when constructing a [`TriangleHandle`] from invalid vertices. +#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)] +#[non_exhaustive] +pub enum TriangleHandleError { + /// At least two triangle vertices refer to the same vertex. + #[error("triangle vertices must be distinct, got {vertices:?}")] + DuplicateVertices { + /// The supplied triangle vertices. + vertices: [VertexKey; 3], + }, +} + /// Structured reason inverse k=1 vertex-star adjacency is inconsistent. #[derive(Clone, Copy, Debug, Error, PartialEq, Eq)] #[non_exhaustive] @@ -3688,6 +3681,13 @@ pub enum FlipError { /// A witness simplex key that already contains the inserted simplex. existing_simplex: SimplexKey, }, + /// Facet iteration failed while seeding flip or repair work. + #[error("Facet iteration failed: {source}")] + FacetIteration { + /// Structured facet iteration failure. + #[source] + source: Box, + }, /// Simplex creation failed. #[error(transparent)] SimplexCreation(#[from] Box), @@ -3753,6 +3753,14 @@ impl From for FlipError { } } +impl From for FlipError { + fn from(source: FacetError) -> Self { + Self::FacetIteration { + source: Box::new(source), + } + } +} + impl From for FlipError { fn from(reason: FlipNeighborWiringError) -> Self { Self::NeighborWiring { @@ -3796,6 +3804,7 @@ impl From<&FlipError> for FlipFailureKind { FlipError::DuplicateSimplex => Self::DuplicateSimplex, FlipError::NonManifoldFacet => Self::NonManifoldFacet, FlipError::InsertedSimplexAlreadyExists { .. } => Self::InsertedSimplexAlreadyExists, + FlipError::FacetIteration { .. } => Self::FacetIteration, FlipError::SimplexCreation(_) => Self::SimplexCreation, FlipError::NeighborWiring { reason } => match reason.as_ref() { FlipNeighborWiringError::TopologyValidation { .. } @@ -3915,8 +3924,9 @@ pub(crate) struct FlipContextDyn { /// let b = VertexKey::from(KeyData::from_ffi(2)); /// let c = VertexKey::from(KeyData::from_ffi(3)); /// -/// let handle = TriangleHandle::new(b, a, c); +/// let handle = TriangleHandle::try_new(b, a, c)?; /// assert_eq!(handle.vertices().len(), 3); +/// # Ok::<(), delaunay::prelude::flips::TriangleHandleError>(()) /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TriangleHandle { @@ -3927,7 +3937,6 @@ pub struct TriangleHandle { impl TriangleHandle { /// Create a canonical triangle handle with ordered vertex keys. - #[must_use] /// /// # Examples /// @@ -3940,10 +3949,32 @@ impl TriangleHandle { /// let b = VertexKey::from(KeyData::from_ffi(20)); /// let c = VertexKey::from(KeyData::from_ffi(30)); /// - /// let handle = TriangleHandle::new(a, b, c); + /// let handle = TriangleHandle::try_new(a, b, c)?; /// assert_eq!(handle.vertices(), [a, b, c]); + /// # Ok::<(), delaunay::prelude::flips::TriangleHandleError>(()) /// ``` - pub fn new(a: VertexKey, b: VertexKey, c: VertexKey) -> Self { + /// + /// # Errors + /// + /// Returns [`TriangleHandleError::DuplicateVertices`] if any two supplied + /// vertices are equal. + pub fn try_new(a: VertexKey, b: VertexKey, c: VertexKey) -> Result { + if a == b || a == c || b == c { + return Err(TriangleHandleError::DuplicateVertices { + vertices: [a, b, c], + }); + } + + Ok(Self::from_validated_vertices(a, b, c)) + } + + /// Creates a canonical triangle handle from vertices already known to be distinct. + #[must_use] + pub(crate) fn from_validated_vertices(a: VertexKey, b: VertexKey, c: VertexKey) -> Self { + debug_assert!( + a != b && a != c && b != c, + "triangle vertices must be distinct" + ); let mut verts = [a, b, c]; verts.sort_unstable_by_key(|v| v.data().as_ffi()); Self { @@ -3960,19 +3991,45 @@ impl TriangleHandle { } } +impl TryFrom<[VertexKey; 3]> for TriangleHandle { + type Error = TriangleHandleError; + + fn try_from([a, b, c]: [VertexKey; 3]) -> Result { + Self::try_new(a, b, c) + } +} + /// Lightweight handle to a ridge (codimension-2 face) within a simplex. /// /// # Examples /// /// ```rust +/// use delaunay::prelude::*; /// use delaunay::prelude::flips::RidgeHandle; -/// use delaunay::prelude::tds::SimplexKey; -/// use slotmap::KeyData; /// -/// let simplex_key = SimplexKey::from(KeyData::from_ffi(7)); -/// let handle = RidgeHandle::new(simplex_key, 2, 0); +/// # #[derive(Debug, thiserror::Error)] +/// # enum ExampleError { +/// # #[error(transparent)] +/// # Construction(#[from] DelaunayTriangulationConstructionError), +/// # #[error(transparent)] +/// # Flip(#[from] delaunay::prelude::flips::FlipError), +/// # } +/// # fn main() -> Result<(), ExampleError> { +/// let vertices = [ +/// Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), +/// ]; +/// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; +/// let Some((simplex_key, _)) = dt.simplices().next() else { +/// return Ok(()); +/// }; +/// let handle = RidgeHandle::try_new(dt.tds(), simplex_key, 2, 0)?; /// assert_eq!(handle.omit_a(), 0); /// assert_eq!(handle.omit_b(), 2); +/// # Ok(()) +/// # } /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct RidgeHandle { @@ -3982,9 +4039,48 @@ pub struct RidgeHandle { } impl RidgeHandle { - /// Creates a new ridge handle by specifying the two omitted vertex indices. - #[must_use] - pub const fn new(simplex_key: SimplexKey, omit_a: u8, omit_b: u8) -> Self { + /// Creates a new ridge handle by validating the omitted vertex indices + /// against a live TDS simplex. + /// + /// # Errors + /// + /// Returns [`FlipError::UnsupportedDimension`] for dimensions below 3, + /// [`FlipError::MissingSimplex`] if `simplex_key` is not present in `tds`, + /// or [`FlipError::InvalidRidgeIndex`] if either omitted index is out of + /// bounds or both indices are the same. + pub fn try_new( + tds: &Tds, + simplex_key: SimplexKey, + omit_a: u8, + omit_b: u8, + ) -> Result { + if D < 3 { + return Err(FlipError::UnsupportedDimension { dimension: D }); + } + + let simplex = tds + .simplex(simplex_key) + .ok_or(FlipError::MissingSimplex { simplex_key })?; + let vertex_count = simplex.number_of_vertices(); + let first_omit_index = usize::from(omit_a); + let second_omit_index = usize::from(omit_b); + if first_omit_index >= vertex_count || second_omit_index >= vertex_count || omit_a == omit_b + { + return Err(FlipError::InvalidRidgeIndex { + simplex_key, + omit_a, + omit_b, + vertex_count, + }); + } + + Ok(Self::from_validated(simplex_key, omit_a, omit_b)) + } + + /// Creates a ridge handle from omitted vertex indices already proven valid + /// by the caller. + #[inline] + pub(crate) const fn from_validated(simplex_key: SimplexKey, omit_a: u8, omit_b: u8) -> Self { if omit_a <= omit_b { Self { simplex_key, @@ -4475,6 +4571,12 @@ impl From for DelaunayRepairError { } } +impl From for DelaunayRepairError { + fn from(source: FacetError) -> Self { + Self::from(FlipError::from(source)) + } +} + impl From for FlipNeighborRepairFailure { fn from(source: DelaunayRepairError) -> Self { match source { @@ -4522,12 +4624,11 @@ impl From for FlipNeighborRepairFailure { /// /// Returns a [`FlipError`] if the facet is invalid, lies on the boundary, references /// missing simplices/vertices, or the adjacency data is inconsistent. -pub(crate) fn build_k2_flip_context( - tds: &Tds, +pub(crate) fn build_k2_flip_context( + tds: &Tds, facet: FacetHandle, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -4621,9 +4722,9 @@ where /// Finds the neighbor slot that points back to a source simplex when reciprocal /// neighbor pointers are already available. -fn back_reference_facet_index( +fn back_reference_facet_index( source_simplex: SimplexKey, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, ) -> Option { neighbor_simplex .neighbor_keys()? @@ -4652,12 +4753,11 @@ fn increment_vertex_count( /// /// Returns a [`FlipError`] if the edge is invalid, references missing vertices/simplices, /// or the adjacency data is inconsistent. -pub(crate) fn build_k2_flip_context_from_edge( - tds: &Tds, +pub(crate) fn build_k2_flip_context_from_edge( + tds: &Tds, edge: EdgeKey, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -4741,13 +4841,12 @@ where }) } /// Build a forward k=1 flip context from a simplex and inserted vertex. -fn build_k1_forward_context_from_simplex( - tds: &Tds, +fn build_k1_forward_context_from_simplex( + tds: &Tds, simplex_key: SimplexKey, inserted_vertex: VertexKey, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -4786,12 +4885,11 @@ where /// /// Returns a [`FlipError`] if the vertex is missing, its incident simplex count is /// not D+1, or the adjacency data is inconsistent. -pub(crate) fn build_k1_inverse_context( - tds: &Tds, +pub(crate) fn build_k1_inverse_context( + tds: &Tds, vertex_key: VertexKey, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -4866,13 +4964,10 @@ where /// and for geometries whose orientation cannot be certified by the f64 fast /// filter, causing callers to use the full orientation-aware predicate instead. #[inline] -fn source_simplex_is_certified_positive( +fn source_simplex_is_certified_positive( source_simplex: Option, - points: &[Point], -) -> bool -where - T: CoordinateScalar, -{ + points: &[Point], +) -> bool { if source_simplex.is_none() { return false; } @@ -4899,7 +4994,7 @@ where )] /// Evaluate the k=2 facet flip predicate for a local Delaunay violation. fn delaunay_violation_k2_for_facet( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, facet_vertices: &[VertexKey], @@ -4911,7 +5006,7 @@ fn delaunay_violation_k2_for_facet( diagnostics: &mut RepairDiagnostics, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -5090,13 +5185,12 @@ where /// Builds the replacement simplices from the given removed/inserted face vertices /// and checks each with [`robust_orientation`]. Returns `Ok(true)` if any /// replacement simplex is degenerate. -fn flip_would_create_degenerate_simplex( - tds: &Tds, +fn flip_would_create_degenerate_simplex( + tds: &Tds, removed_face_vertices: &[VertexKey], inserted_face_vertices: &[VertexKey], ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -5131,12 +5225,11 @@ where } /// Check whether a k=2 flip would create a degenerate simplex. -fn k2_flip_would_create_degenerate_simplex( - tds: &Tds, +fn k2_flip_would_create_degenerate_simplex( + tds: &Tds, context: &FlipContext, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -5162,7 +5255,7 @@ where /// Returns a [`FlipError`] if any referenced simplex/vertex is missing or a predicate /// evaluation fails. fn is_delaunay_violation_k2( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, context: &FlipContext, @@ -5170,7 +5263,7 @@ fn is_delaunay_violation_k2( diagnostics: &mut RepairDiagnostics, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -5205,16 +5298,15 @@ where /// Returns a [`FlipError`] if the flip would be degenerate, duplicate an existing simplex, /// create non-manifold topology, if predicate evaluation fails, or if underlying TDS /// mutations fail. -pub(crate) fn apply_bistellar_flip_k2( - tds: &mut Tds, +pub(crate) fn apply_bistellar_flip_k2( + tds: &mut Tds, context: &FlipContext, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { - apply_bistellar_flip::(tds, context) + apply_bistellar_flip::(tds, context) } /// Build flip context for a k=3 (ridge) flip. @@ -5223,8 +5315,8 @@ where /// /// Returns a [`FlipError`] if the ridge is invalid, references missing simplices/vertices, /// or the adjacency data is inconsistent. -pub(crate) fn build_k3_flip_context( - tds: &Tds, +pub(crate) fn build_k3_flip_context( + tds: &Tds, ridge: RidgeHandle, ) -> Result, FlipError> where @@ -5235,8 +5327,8 @@ where } /// Builds k=3 repair context only for true three-simplex ridge stars. -fn build_k3_flip_context_for_repair( - tds: &Tds, +fn build_k3_flip_context_for_repair( + tds: &Tds, ridge: RidgeHandle, ) -> Result, FlipError> where @@ -5247,8 +5339,8 @@ where } /// Builds k=3 flip context while optionally rejecting ridge stars above a caller limit. -fn build_k3_flip_context_with_star_limit( - tds: &Tds, +fn build_k3_flip_context_with_star_limit( + tds: &Tds, ridge: RidgeHandle, max_simplices: Option, ) -> Result, FlipError> @@ -5355,12 +5447,11 @@ where /// /// Returns a [`FlipError`] if the triangle is invalid, references missing vertices/simplices, /// or the adjacency data is inconsistent. -pub(crate) fn build_k3_flip_context_from_triangle( - tds: &Tds, +pub(crate) fn build_k3_flip_context_from_triangle( + tds: &Tds, triangle: TriangleHandle, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -5452,7 +5543,7 @@ where )] /// Evaluate the k=3 ridge flip predicate for a local Delaunay violation. fn delaunay_violation_k3_for_ridge( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, ridge_vertices: &[VertexKey], @@ -5463,7 +5554,7 @@ fn delaunay_violation_k3_for_ridge( diagnostics: &mut RepairDiagnostics, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -5577,16 +5668,15 @@ where /// Returns a [`FlipError`] if the flip would be degenerate, duplicate an existing simplex, /// create non-manifold topology, if predicate evaluation fails, or if underlying TDS /// mutations fail. -pub(crate) fn apply_bistellar_flip_k3( - tds: &mut Tds, +pub(crate) fn apply_bistellar_flip_k3( + tds: &mut Tds, context: &FlipContext, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { - apply_bistellar_flip::(tds, context) + apply_bistellar_flip::(tds, context) } /// Apply a forward k=1 move (simplex split) by inserting a new vertex. @@ -5595,13 +5685,12 @@ where /// /// Returns a [`FlipError`] if the simplex is missing, the vertex cannot be inserted, /// or the flip would be degenerate. -pub(crate) fn apply_bistellar_flip_k1( - tds: &mut Tds, +pub(crate) fn apply_bistellar_flip_k1( + tds: &mut Tds, simplex_key: SimplexKey, - vertex: Vertex, + vertex: Vertex, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -5624,7 +5713,7 @@ where } }; - let result = apply_bistellar_flip::(tds, &context); + let result = apply_bistellar_flip::(tds, &context); if result.is_err() { let _ = tds.remove_vertex(vertex_key); @@ -5639,12 +5728,11 @@ where /// # Errors /// /// Returns a [`FlipError`] if the vertex star is invalid or the flip would be degenerate. -pub(crate) fn apply_bistellar_flip_k1_inverse( - tds: &mut Tds, +pub(crate) fn apply_bistellar_flip_k1_inverse( + tds: &mut Tds, vertex_key: VertexKey, ) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -5671,13 +5759,13 @@ where reason = "Repair loop contains inline tracing and queue handling for diagnostics" )] fn repair_delaunay_with_flips_k2_attempt( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, config: &RepairAttemptConfig, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -5712,8 +5800,8 @@ where )?; } } else { - for facet in AllFacetsIter::new(tds) { - let handle = FacetHandle::new(facet.simplex_key(), facet.facet_index()); + for facet in AllFacetsIter::try_new(tds)? { + let handle = FacetHandle::from_validated(facet.simplex_key(), facet.facet_index()); enqueue_facet( tds, handle, @@ -5890,14 +5978,14 @@ where /// Returns a [`DelaunayRepairError`] if the repair fails to converge or an underlying /// flip operation encounters an unrecoverable error. pub(crate) fn repair_delaunay_with_flips_k2_k3( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, topology: TopologyGuarantee, max_flips_override: Option, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -5906,13 +5994,13 @@ where } fn run_full_reseed_retry( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, config: &RepairAttemptConfig, - snapshot: Tds, + snapshot: Tds, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -5951,14 +6039,14 @@ where /// Returns a [`DelaunayRepairError`] if the repair fails to converge or an underlying /// flip operation encounters an unrecoverable error. pub(crate) fn repair_delaunay_with_flips_k2_k3_run( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, topology: TopologyGuarantee, max_flips_override: Option, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6064,13 +6152,13 @@ where /// Returns [`DelaunayRepairError::NonConvergent`] if both attempts fail to converge. /// Other errors (topology violations, predicate failures) are forwarded as-is. pub(crate) fn repair_delaunay_local_single_pass( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, seed_simplices: &[SimplexKey], max_flips: usize, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6083,14 +6171,14 @@ where reason = "bounded two-attempt repair keeps rollback, retry, and postcondition timing together" )] pub(crate) fn repair_delaunay_local_single_pass_timed( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, seed_simplices: &[SimplexKey], max_flips: usize, mut timing: Option<&mut LocalRepairPhaseTiming>, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6264,10 +6352,10 @@ where /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt: DelaunayTriangulation<_, (), (), 3> = @@ -6280,11 +6368,11 @@ where /// # } /// ``` pub fn verify_delaunay_via_flip_predicates( - tds: &Tds, + tds: &Tds, kernel: &K, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6312,10 +6400,10 @@ where /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt: DelaunayTriangulation<_, (), (), 3> = @@ -6330,7 +6418,7 @@ pub fn verify_delaunay_for_triangulation( triangulation: &Triangulation, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6346,12 +6434,12 @@ where /// For periodic topologies this evaluates predicates in lifted coordinates using the /// per-simplex periodic vertex offsets stored on quotient simplices. fn verify_delaunay_with_topology( - tds: &Tds, + tds: &Tds, kernel: &K, global_topology: GlobalTopology, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6369,13 +6457,13 @@ where /// Keeps legacy Euclidean repair checks on the same validation path as the /// topology-aware verifier. fn verify_repair_postcondition( - tds: &Tds, + tds: &Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, last_applied_flip: Option<&LastAppliedFlip>, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6392,13 +6480,13 @@ where /// Replays local repair postconditions without forcing the full connectivity check. fn verify_local_repair_postcondition( - tds: &Tds, + tds: &Tds, kernel: &K, seed_simplices: &[SimplexKey], last_applied_flip: Option<&LastAppliedFlip>, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6439,7 +6527,7 @@ fn verification_failed( /// Adapts the public topology enum into the model used for lifted predicate /// evaluation. fn verify_repair_postcondition_with_topology( - tds: &Tds, + tds: &Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, global_topology: GlobalTopology, @@ -6448,7 +6536,7 @@ fn verify_repair_postcondition_with_topology( connectivity: ConnectivityPostcondition, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6467,7 +6555,7 @@ where /// Replays the repair queues without mutating the TDS so postconditions cover /// the same local predicates that drive repair. fn verify_repair_postcondition_locally( - tds: &Tds, + tds: &Tds, kernel: &K, seed_simplices: Option<&[SimplexKey]>, topology_model: &GlobalTopologyModelAdapter, @@ -6476,7 +6564,7 @@ fn verify_repair_postcondition_locally( connectivity: ConnectivityPostcondition, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6582,7 +6670,7 @@ fn resolve_postcondition_predicate_failure( reason = "Postcondition replay threads topology, diagnostics, and predecessor context explicitly" )] fn verify_postcondition_k2_facets( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, queue: &mut VecDeque<(FacetHandle, u64)>, @@ -6592,7 +6680,7 @@ fn verify_postcondition_k2_facets( last_applied_flip: Option<&LastAppliedFlip>, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6699,7 +6787,7 @@ where reason = "Postcondition replay threads topology, diagnostics, and predecessor context explicitly (matches k=2 signature)" )] fn verify_postcondition_k3_ridges( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, queue: &mut VecDeque<(RidgeHandle, u64)>, @@ -6709,7 +6797,7 @@ fn verify_postcondition_k3_ridges( last_applied_flip: Option<&LastAppliedFlip>, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6798,7 +6886,7 @@ where /// Exercises inverse k=2 predicates after repair because an apparently valid /// facet pass can still leave an edge-collapse move applicable. fn verify_postcondition_inverse_k2_edges( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, queue: &mut VecDeque<(EdgeKey, u64)>, @@ -6807,7 +6895,7 @@ fn verify_postcondition_inverse_k2_edges( mode: PostconditionMode, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -6881,7 +6969,7 @@ where /// Exercises inverse k=3 predicates after repair so triangle-collapse moves do /// not hide behind forward-only verification. fn verify_postcondition_inverse_k3_triangles( - tds: &Tds, + tds: &Tds, kernel: &K, topology_model: &GlobalTopologyModelAdapter, queue: &mut VecDeque<(TriangleHandle, u64)>, @@ -6890,7 +6978,7 @@ fn verify_postcondition_inverse_k3_triangles( mode: PostconditionMode, ) -> Result<(), DelaunayRepairError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -7577,14 +7665,13 @@ impl RepairQueues { clippy::too_many_lines, reason = "seeding logic mirrors runtime queues and stays as one diagnostic flow" )] -fn seed_repair_queues( - tds: &Tds, +fn seed_repair_queues( + tds: &Tds, seed_simplices: Option<&[SimplexKey]>, queues: &mut RepairQueues, stats: &mut DelaunayRepairStats, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -7657,8 +7744,8 @@ where return Ok(true); } } else { - for facet in AllFacetsIter::new(tds) { - let handle = FacetHandle::new(facet.simplex_key(), facet.facet_index()); + for facet in AllFacetsIter::try_new(tds)? { + let handle = FacetHandle::from_validated(facet.simplex_key(), facet.facet_index()); enqueue_facet( tds, handle, @@ -7700,14 +7787,13 @@ where /// Requeues the local neighborhood created by a flip so the repair loop follows /// newly exposed violations instead of rescanning the whole triangulation. -fn enqueue_new_simplices_for_repair( - tds: &Tds, +fn enqueue_new_simplices_for_repair( + tds: &Tds, new_simplices: &[SimplexKey], queues: &mut RepairQueues, stats: &mut DelaunayRepairStats, ) -> Result<(), FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -7758,7 +7844,7 @@ where reason = "Repair step contains inline tracing and queue handling for diagnostics" )] fn run_next_ridge_repair_step( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, queues: &mut RepairQueues, stats: &mut DelaunayRepairStats, @@ -7770,7 +7856,7 @@ fn run_next_ridge_repair_step( touched_simplex_set: &mut FastHashSet, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -7956,7 +8042,7 @@ where reason = "Repair step contains inline tracing and queue handling for diagnostics" )] fn run_next_edge_repair_step( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, queues: &mut RepairQueues, stats: &mut DelaunayRepairStats, @@ -7968,7 +8054,7 @@ fn run_next_edge_repair_step( touched_simplex_set: &mut FastHashSet, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -8148,7 +8234,7 @@ where reason = "Repair step contains inline tracing and queue handling for diagnostics" )] fn run_next_triangle_repair_step( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, queues: &mut RepairQueues, stats: &mut DelaunayRepairStats, @@ -8160,7 +8246,7 @@ fn run_next_triangle_repair_step( touched_simplex_set: &mut FastHashSet, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -8331,7 +8417,7 @@ where reason = "Repair step contains inline tracing and queue handling for diagnostics" )] fn run_next_facet_repair_step( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, queues: &mut RepairQueues, stats: &mut DelaunayRepairStats, @@ -8343,7 +8429,7 @@ fn run_next_facet_repair_step( touched_simplex_set: &mut FastHashSet, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -8510,14 +8596,10 @@ where /// Extracts facet vertices by omitted slot so facet hashing matches the simplex's /// current vertex ordering. -fn facet_vertices_from_simplex( - simplex: &Simplex, +fn facet_vertices_from_simplex( + simplex: &Simplex, facet_index: usize, -) -> SmallBuffer -where - U: DataType, - V: DataType, -{ +) -> SmallBuffer { let mut vertices: SmallBuffer = SmallBuffer::with_capacity(D + 1); for (i, &vkey) in simplex.vertices().iter().enumerate() { @@ -8530,15 +8612,11 @@ where /// Extracts ridge vertices by omitted slots so ridge handles remain compact but /// can still be converted into stable vertex sets. -fn ridge_vertices_from_simplex( - simplex: &Simplex, +fn ridge_vertices_from_simplex( + simplex: &Simplex, omit_a: usize, omit_b: usize, -) -> SmallBuffer -where - U: DataType, - V: DataType, -{ +) -> SmallBuffer { let mut vertices: SmallBuffer = SmallBuffer::with_capacity(D + 1); for (i, &vkey) in simplex.vertices().iter().enumerate() { @@ -8551,15 +8629,11 @@ where /// Finds the two vertices opposite a ridge in one simplex while validating that the /// requested ridge is actually incident to that simplex. -fn simplex_extras_for_ridge( +fn simplex_extras_for_ridge( simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, ridge: &SmallBuffer, -) -> Result, FlipError> -where - U: DataType, - V: DataType, -{ +) -> Result, FlipError> { if !ridge.iter().all(|v| simplex.contains_vertex(*v)) { return Err(FlipError::InvalidRidgeAdjacency { simplex_key }); } @@ -8592,8 +8666,8 @@ fn missing_opposite_for_simplex( /// many incident simplices. Repair uses this to reject non-k=3 edge stars as soon /// as they are known to be too large, while public flip construction leaves the /// value unset to preserve exact multiplicity diagnostics. -fn collect_simplices_around_ridge( - tds: &Tds, +fn collect_simplices_around_ridge( + tds: &Tds, start_simplex: SimplexKey, ridge: &SmallBuffer, max_simplices: Option, @@ -8660,12 +8734,11 @@ where } /// Returns a vertex's Euclidean point without applying topology-frame lifting. -fn vertex_point( - tds: &Tds, +fn vertex_point( + tds: &Tds, vertex_key: VertexKey, -) -> Result, FlipError> +) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -8676,11 +8749,11 @@ where } /// Small per-predicate cache for Euclidean vertex coordinates. -struct EuclideanPointCache { - points: SmallBuffer<(VertexKey, Point), MAX_PRACTICAL_DIMENSION_SIZE>, +struct EuclideanPointCache { + points: SmallBuffer<(VertexKey, Point), MAX_PRACTICAL_DIMENSION_SIZE>, } -impl EuclideanPointCache { +impl EuclideanPointCache { /// Starts an empty cache for one local predicate evaluation. fn new() -> Self { Self { @@ -8689,16 +8762,13 @@ impl EuclideanPointCache { } } -impl EuclideanPointCache -where - T: CoordinateScalar, -{ +impl EuclideanPointCache { /// Returns a cached Euclidean point, loading it from the TDS on first use. fn point( &mut self, - tds: &Tds, + tds: &Tds, vertex_key: VertexKey, - ) -> Result, FlipError> + ) -> Result, FlipError> where U: DataType, V: DataType, @@ -8715,14 +8785,14 @@ where /// Converts a small vertex-key slice into Euclidean points while sharing cache hits. fn points_for_vertices( &mut self, - tds: &Tds, + tds: &Tds, vertices: &[VertexKey], - ) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> + ) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> where U: DataType, V: DataType, { - let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(vertices.len()); for &vertex_key in vertices { points.push(self.point(tds, vertex_key)?); @@ -8733,16 +8803,15 @@ where /// Converts vertex keys to Euclidean points for predicates that do not need a /// periodic frame. -fn vertices_to_points( - tds: &Tds, +fn vertices_to_points( + tds: &Tds, vertices: &[VertexKey], -) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> +) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { - let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(vertices.len()); for &vkey in vertices { points.push(vertex_point(tds, vkey)?); @@ -8752,19 +8821,18 @@ where /// Builds predicate points in one periodic frame so quotient-simplex coordinates /// compare as lifted representatives. -fn vertices_to_points_with_optional_lift( - tds: &Tds, +fn vertices_to_points_with_optional_lift( + tds: &Tds, topology_model: &GlobalTopologyModelAdapter, vertices: &[VertexKey], source_simplex: Option, source_simplices: &[SimplexKey], -) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> +) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { - let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(vertices.len()); for &vkey in vertices { points.push(vertex_point_lifted_into_simplex( @@ -8780,14 +8848,13 @@ where /// Applies a simplex-local periodic offset when the vertex is already present in /// the selected source simplex. -fn vertex_point_with_optional_lift( - tds: &Tds, +fn vertex_point_with_optional_lift( + tds: &Tds, topology_model: &GlobalTopologyModelAdapter, vertex_key: VertexKey, source_simplex: Option, -) -> Result, FlipError> +) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -8804,15 +8871,14 @@ where /// Lifts a vertex into a target simplex's frame, aligning from neighboring source /// simplices instead of falling back to bare periodic coordinates. -fn vertex_point_lifted_into_simplex( - tds: &Tds, +fn vertex_point_lifted_into_simplex( + tds: &Tds, topology_model: &GlobalTopologyModelAdapter, vertex_key: VertexKey, target_simplex: Option, source_simplices: &[SimplexKey], -) -> Result, FlipError> +) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -8830,8 +8896,8 @@ where } /// Aligns a vertex's periodic offset into a target simplex frame. -fn periodic_offset_lifted_into_simplex( - tds: &Tds, +fn periodic_offset_lifted_into_simplex( + tds: &Tds, vertex_key: VertexKey, target_simplex_key: SimplexKey, source_simplices: &[SimplexKey], @@ -8903,14 +8969,13 @@ fn periodic_offset_lifted_into_simplex( /// Centralizes topology-model lifting so missing vertices and non-liftable /// offsets become typed flip errors. -fn lift_vertex_point( - tds: &Tds, +fn lift_vertex_point( + tds: &Tds, topology_model: &GlobalTopologyModelAdapter, vertex_key: VertexKey, periodic_offset: Option<[i8; D]>, -) -> Result, FlipError> +) -> Result, FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -8923,13 +8988,19 @@ where vertex_key, details: source.to_string(), })?; - Ok(Point::new(lifted_coords)) + Point::try_new(lifted_coords).map_err(|source| { + FlipPredicateError::PeriodicVertexLift { + vertex_key, + details: source.to_string(), + } + .into() + }) } /// Looks up the offset paired with a vertex slot, preserving the invariant that /// periodic offsets are indexed exactly like simplex vertices. -fn periodic_offset_for_simplex_vertex( - tds: &Tds, +fn periodic_offset_for_simplex_vertex( + tds: &Tds, simplex_key: SimplexKey, vertex_key: VertexKey, ) -> Result, FlipError> { @@ -8946,9 +9017,9 @@ fn periodic_offset_for_simplex_vertex( /// Borrows stored periodic offsets, or treats a periodic simplex without explicit /// offsets as a zero-offset frame. -fn periodic_offsets_or_zero_frame( +fn periodic_offsets_or_zero_frame( simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, ) -> Result, FlipError> { let offsets = simplex.periodic_vertex_offsets().map_or_else( // The fallback frame is synthesized locally, so `Cow::Owned` keeps the @@ -8962,9 +9033,9 @@ fn periodic_offsets_or_zero_frame( /// Rejects malformed quotient simplices before offset indexing can desynchronize /// vertices from their lifted representatives. -fn validate_periodic_offset_len( +fn validate_periodic_offset_len( simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, offsets: &[[i8; D]], ) -> Result<(), FlipError> { if offsets.len() == simplex.number_of_vertices() { @@ -8980,9 +9051,9 @@ fn validate_periodic_offset_len( /// Finds every common vertex to act as a consistency check when aligning two /// periodic simplex frames. -fn shared_vertex_indices( - target_simplex: &Simplex, - source_simplex: &Simplex, +fn shared_vertex_indices( + target_simplex: &Simplex, + source_simplex: &Simplex, ) -> SmallBuffer<(usize, usize), MAX_PRACTICAL_DIMENSION_SIZE> { let mut shared = SmallBuffer::new(); for (target_index, &target_vertex) in target_simplex.vertices().iter().enumerate() { @@ -9019,8 +9090,8 @@ fn align_periodic_offset( /// Reuses an existing removed simplex as the predicate frame when the candidate /// simplex exactly matches that simplex. -fn matching_source_simplex( - tds: &Tds, +fn matching_source_simplex( + tds: &Tds, vertices: &[VertexKey], source_simplices: &[SimplexKey], ) -> Option @@ -9091,14 +9162,13 @@ fn simplex_signature(vertices: &[VertexKey]) -> u64 { /// Builds the small topology index needed to reject duplicate simplices and /// non-manifold internal facets without repeated global scans. -fn build_flip_topology_index( - tds: &Tds, +fn build_flip_topology_index( + tds: &Tds, new_simplex_vertices: &[SmallBuffer], removed_simplices: &[SimplexKey], inserted_face_vertices: &[VertexKey], ) -> FlipTopologyIndex where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9235,13 +9305,12 @@ where /// Checks candidate simplices against the topology index before mutation so a flip /// cannot introduce two simplices with the same vertex set. -fn flip_would_duplicate_simplex_any( - tds: &Tds, +fn flip_would_duplicate_simplex_any( + tds: &Tds, vertices: &[VertexKey], topology: &FlipTopologyIndex, ) -> bool where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9336,8 +9405,8 @@ fn flip_would_create_nonmanifold_facets_any( /// Queues all interior facets of a simplex because k=2 repair is driven by shared /// facet predicates. -fn enqueue_simplex_facets( - tds: &Tds, +fn enqueue_simplex_facets( + tds: &Tds, simplex_key: SimplexKey, queue: &mut VecDeque<(FacetHandle, u64)>, queued: &mut FastHashSet, @@ -9345,7 +9414,6 @@ fn enqueue_simplex_facets( stats: &mut DelaunayRepairStats, ) -> Result<(), FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9353,7 +9421,7 @@ where return Ok(()); }; for facet_index in 0..simplex.number_of_vertices() { - let handle = FacetHandle::new( + let handle = FacetHandle::from_validated( simplex_key, u8::try_from(facet_index).map_err(|_| FlipError::InvalidFacetIndex { simplex_key, @@ -9368,15 +9436,14 @@ where /// Enqueues a facet by stable vertex hash so stale handles can be resolved after /// slot swaps. -fn enqueue_facet( - tds: &Tds, +fn enqueue_facet( + tds: &Tds, handle: FacetHandle, queue: &mut VecDeque<(FacetHandle, u64)>, queued: &mut FastHashSet, handles: &mut FastHashMap, stats: &mut DelaunayRepairStats, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9408,14 +9475,13 @@ fn enqueue_facet( } /// Queues simplex edges only in dimensions where inverse k=2 repair is admissible. -fn enqueue_simplex_edges( - tds: &Tds, +fn enqueue_simplex_edges( + tds: &Tds, simplex_key: SimplexKey, queue: &mut VecDeque<(EdgeKey, u64)>, queued: &mut FastHashSet, stats: &mut DelaunayRepairStats, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9431,7 +9497,7 @@ fn enqueue_simplex_edges( let vertex_count = vertices.len(); for i in 0..vertex_count { for j in (i + 1)..vertex_count { - let edge = EdgeKey::new(vertices[i], vertices[j]); + let edge = EdgeKey::from_validated_endpoints(vertices[i], vertices[j]); enqueue_edge(edge, queue, queued, stats); } } @@ -9453,14 +9519,13 @@ fn enqueue_edge( /// Queues simplex triangles only in dimensions where inverse k=3 repair is /// admissible. -fn enqueue_simplex_triangles( - tds: &Tds, +fn enqueue_simplex_triangles( + tds: &Tds, simplex_key: SimplexKey, queue: &mut VecDeque<(TriangleHandle, u64)>, queued: &mut FastHashSet, stats: &mut DelaunayRepairStats, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9477,7 +9542,8 @@ fn enqueue_simplex_triangles( for i in 0..vertex_count { for j in (i + 1)..vertex_count { for k in (j + 1)..vertex_count { - let triangle = TriangleHandle::new(vertices[i], vertices[j], vertices[k]); + let triangle = + TriangleHandle::from_validated_vertices(vertices[i], vertices[j], vertices[k]); enqueue_triangle(triangle, queue, queued, stats); } } @@ -9502,8 +9568,8 @@ fn enqueue_triangle( /// Queues all ridges of a simplex because k=3 repair needs codimension-two local /// stars. -fn enqueue_simplex_ridges( - tds: &Tds, +fn enqueue_simplex_ridges( + tds: &Tds, simplex_key: SimplexKey, queue: &mut VecDeque<(RidgeHandle, u64)>, queued: &mut FastHashSet, @@ -9511,7 +9577,6 @@ fn enqueue_simplex_ridges( stats: &mut DelaunayRepairStats, ) -> Result<(), FlipError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9526,7 +9591,7 @@ where let vertex_count = simplex.number_of_vertices(); for i in 0..vertex_count { for j in (i + 1)..vertex_count { - let handle = RidgeHandle::new( + let handle = RidgeHandle::from_validated( simplex_key, u8::try_from(i).map_err(|_| FlipError::InvalidRidgeIndex { simplex_key, @@ -9550,15 +9615,14 @@ where /// Enqueues a ridge by stable vertex hash so post-flip slot swaps do not strand /// stale ridge handles. -fn enqueue_ridge( - tds: &Tds, +fn enqueue_ridge( + tds: &Tds, handle: RidgeHandle, queue: &mut VecDeque<(RidgeHandle, u64)>, queued: &mut FastHashSet, handles: &mut FastHashMap, stats: &mut DelaunayRepairStats, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -9604,7 +9668,6 @@ mod tests { use crate::geometry::traits::coordinate::CoordinateConversionValue; use crate::repair::DelaunayRepairOperation; use crate::topology::traits::topological_space::ToroidalConstructionMode; - use crate::vertex; use approx::assert_relative_eq; use proptest::prelude::*; use rand::{RngExt, SeedableRng, rngs::StdRng}; @@ -9642,16 +9705,35 @@ mod tests { coords } + #[test] + fn triangle_handle_rejects_duplicate_vertices() { + let a = VertexKey::from(KeyData::from_ffi(1)); + let b = VertexKey::from(KeyData::from_ffi(2)); + + assert_matches!( + TriangleHandle::try_new(a, b, a), + Err(TriangleHandleError::DuplicateVertices { vertices }) + if vertices == [a, b, a] + ); + } + /// Inserts the canonical D-simplex fixture shared by replacement-orientation tests. fn insert_standard_simplex_vertices( - tds: &mut Tds, + tds: &mut Tds<(), (), D>, ) -> Vec { let mut vertices = Vec::with_capacity(D + 1); - vertices.push(tds.insert_vertex_with_mapping(vertex!([0.0; D])).unwrap()); + vertices.push( + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; D]).unwrap(), + ) + .unwrap(), + ); for axis in 0..D { vertices.push( - tds.insert_vertex_with_mapping(vertex!(unit_vector::(axis))) - .unwrap(), + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::(axis)).unwrap(), + ) + .unwrap(), ); } vertices @@ -9670,7 +9752,7 @@ mod tests { /// Asserts exact vertex-to-periodic-offset slot pairing independent of simplex orientation. fn assert_simplex_offsets_by_vertex( - tds: &Tds, + tds: &Tds<(), (), D>, simplex_key: SimplexKey, expected_offsets: &[(VertexKey, [i8; D])], ) { @@ -9698,9 +9780,9 @@ mod tests { fn test_source_simplex_is_certified_positive_requires_source_and_positive_order() { let source_simplex = SimplexKey::from(KeyData::from_ffi(42)); let positive = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; let negative = [positive[1], positive[0], positive[2]]; @@ -9782,10 +9864,10 @@ mod tests { pastey::paste! { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let vertices = insert_standard_simplex_vertices::<$dim>(&mut tds); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vertices.clone(), None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vertices.clone(), None).unwrap()) .unwrap(); let removed_simplices: SimplexKeyBuffer = std::iter::once(simplex_key).collect(); @@ -9873,7 +9955,7 @@ mod tests { gen_removed_simplex_snapshot_tests!(5); struct RidgeDiagnosticFixture3d { - tds: Tds, + tds: Tds<(), (), 3>, origin_vertex: VertexKey, x_axis_vertex: VertexKey, y_axis_vertex: VertexKey, @@ -9885,26 +9967,36 @@ mod tests { impl RidgeDiagnosticFixture3d { fn new() -> Self { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let origin_vertex = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let x_axis_vertex = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let y_axis_vertex = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let upper_apex_vertex = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let lower_apex_vertex = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, -1.0]).unwrap(), + ) .unwrap(); let upper_tetrahedron = tds .insert_simplex_with_mapping( - Simplex::new( + Simplex::try_new_with_data( vec![ origin_vertex, x_axis_vertex, @@ -9918,7 +10010,7 @@ mod tests { .unwrap(); let lower_neighbor = tds .insert_simplex_with_mapping( - Simplex::new( + Simplex::try_new_with_data( vec![ origin_vertex, x_axis_vertex, @@ -9951,7 +10043,7 @@ mod tests { } fn ridge_handle_abcd(&self) -> RidgeHandle { - RidgeHandle::new(self.upper_tetrahedron, 2, 3) + RidgeHandle::from_validated(self.upper_tetrahedron, 2, 3) } fn last_applied_flip(&self) -> LastAppliedFlip { @@ -10061,7 +10153,7 @@ mod tests { let ridge_summary = predecessor_flip_summary( &fixture.tds, - RidgeHandle::new(fixture.lower_neighbor, 2, 3), + RidgeHandle::from_validated(fixture.lower_neighbor, 2, 3), &[fixture.lower_neighbor], &last, ); @@ -10099,7 +10191,7 @@ mod tests { let missing_simplex = SimplexKey::from(KeyData::from_ffi(999_903)); debug_ridge_context( &fixture.tds, - RidgeHandle::new(missing_simplex, 0, 1), + RidgeHandle::from_validated(missing_simplex, 0, 1), None, &mut diagnostics, None, @@ -10108,7 +10200,7 @@ mod tests { debug_ridge_context( &fixture.tds, - RidgeHandle::new(fixture.upper_tetrahedron, 0, 0), + RidgeHandle::from_validated(fixture.upper_tetrahedron, 0, 0), None, &mut diagnostics, None, @@ -10155,7 +10247,7 @@ mod tests { debug_postcondition_facet_context( &fixture.tds, - FacetHandle::new(fixture.upper_tetrahedron, 3), + FacetHandle::from_validated(fixture.upper_tetrahedron, 3), &context, &mut diagnostics, Some(&last), @@ -10165,7 +10257,7 @@ mod tests { } fn facet_index_for_edge_2d( - tds: &Tds, + tds: &Tds<(), (), 2>, simplex_key: SimplexKey, edge_start: VertexKey, edge_end: VertexKey, @@ -10184,7 +10276,7 @@ mod tests { } fn facet_index_for_face_3d( - tds: &Tds, + tds: &Tds<(), (), 3>, simplex_key: SimplexKey, face_v0: VertexKey, face_v1: VertexKey, @@ -10210,7 +10302,7 @@ mod tests { /// Assert that `robust_orientation` returns a non-degenerate sign for /// every new-simplex point set that a k=2 flip context would produce. fn assert_context_has_nonzero_robust_orientation( - tds: &Tds, + tds: &Tds<(), (), 2>, context: &FlipContext<2, 2>, ) { for &omit in &context.removed_face_vertices { @@ -10232,15 +10324,29 @@ mod tests { #[test] fn test_resolve_facet_handle_for_key_remaps_after_slot_swap() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); - let stale_handle = FacetHandle::new(simplex_key, 0); + let stale_handle = FacetHandle::from_validated(simplex_key, 0); let stable_key = { let simplex = tds.simplex(simplex_key).unwrap(); let facet_vertices = @@ -10269,24 +10375,34 @@ mod tests { #[test] fn test_resolve_ridge_handle_for_key_remaps_after_slot_swap() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); - let stale_handle = RidgeHandle::new(simplex_key, 0, 1); + let stale_handle = RidgeHandle::from_validated(simplex_key, 0, 1); let stable_key = { let simplex = tds.simplex(simplex_key).unwrap(); let ridge_vertices = ridge_vertices_from_simplex( @@ -10322,32 +10438,53 @@ mod tests { #[test] fn test_k2_flip_rewires_external_neighbors_across_cavity_boundary() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_left_bottom = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_right_bottom = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap(); - let v_left_top = tds.insert_vertex_with_mapping(vertex!([0.0, 2.0])).unwrap(); - let v_right_top = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let v_left_bottom = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_right_bottom = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_left_top = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), + ) + .unwrap(); + let v_right_top = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let v_external = tds - .insert_vertex_with_mapping(vertex!([-1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, 1.0]).unwrap(), + ) .unwrap(); // Flip cavity: two triangles sharing the bottom edge. let simplex_cavity_left = tds .insert_simplex_with_mapping( - Simplex::new(vec![v_left_bottom, v_right_bottom, v_left_top], None).unwrap(), + Simplex::try_new_with_data(vec![v_left_bottom, v_right_bottom, v_left_top], None) + .unwrap(), ) .unwrap(); let simplex_cavity_right = tds .insert_simplex_with_mapping( - Simplex::new(vec![v_right_bottom, v_left_bottom, v_right_top], None).unwrap(), + Simplex::try_new_with_data(vec![v_right_bottom, v_left_bottom, v_right_top], None) + .unwrap(), ) .unwrap(); // External simplex glued along the left edge of the cavity. let simplex_external_left = tds .insert_simplex_with_mapping( - Simplex::new(vec![v_left_bottom, v_left_top, v_external], None).unwrap(), + Simplex::try_new_with_data(vec![v_left_bottom, v_left_top, v_external], None) + .unwrap(), ) .unwrap(); @@ -10358,7 +10495,7 @@ mod tests { facet_index_for_edge_2d(&tds, simplex_cavity_left, v_left_bottom, v_right_bottom); let ctx = build_k2_flip_context( &tds, - FacetHandle::new(simplex_cavity_left, facet_idx_flip_edge), + FacetHandle::from_validated(simplex_cavity_left, facet_idx_flip_edge), ) .unwrap(); @@ -10411,16 +10548,38 @@ mod tests { } #[test] + #[expect( + clippy::too_many_lines, + reason = "regression test keeps the periodic flip fixture explicit" + )] fn test_k2_flip_preserves_periodic_external_offsets() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_left_bottom = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_right_bottom = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap(); - let v_left_top = tds.insert_vertex_with_mapping(vertex!([0.0, 2.0])).unwrap(); - let v_right_top = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let v_left_bottom = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_right_bottom = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_left_top = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), + ) + .unwrap(); + let v_right_top = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let v_external = tds - .insert_vertex_with_mapping(vertex!([-1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, 1.0]).unwrap(), + ) .unwrap(); let offset_left_bottom = [0_i8, 0_i8]; @@ -10451,7 +10610,7 @@ mod tests { facet_index_for_edge_2d(&tds, simplex_cavity_left, v_left_bottom, v_right_bottom); let ctx = build_k2_flip_context( &tds, - FacetHandle::new(simplex_cavity_left, facet_idx_flip_edge), + FacetHandle::from_validated(simplex_cavity_left, facet_idx_flip_edge), ) .unwrap(); @@ -10523,11 +10682,11 @@ mod tests { pastey::paste! { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); let offsets = periodic_test_offsets::<$dim>($dim + 1); - let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap(); + let mut external_simplex = Simplex::try_new_with_data(simplex_vertices.clone(), None).unwrap(); external_simplex.set_periodic_vertex_offsets(offsets.clone()).unwrap(); let external_simplex_key = tds.insert_simplex_with_mapping(external_simplex).unwrap(); @@ -10538,7 +10697,7 @@ mod tests { &tds, &mut replacement_simplices, &mut replacement_offsets, - &[FacetHandle::new(external_simplex_key, 0)], + &[FacetHandle::from_validated(external_simplex_key, 0)], ) .unwrap(); @@ -10560,11 +10719,11 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); let external_offsets = vec![[0_i8; $dim]; $dim + 1]; - let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap(); + let mut external_simplex = Simplex::try_new_with_data(simplex_vertices.clone(), None).unwrap(); external_simplex .set_periodic_vertex_offsets(external_offsets) .unwrap(); @@ -10579,7 +10738,7 @@ mod tests { &tds, &mut replacement_simplices, &mut replacement_offsets, - &[FacetHandle::new(external_simplex_key, 0)], + &[FacetHandle::from_validated(external_simplex_key, 0)], ); assert!( @@ -10601,7 +10760,7 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); let mut replacement_simplices = vec![vertex_key_buffer(&simplex_vertices)]; let mut replacement_offsets: Vec>> = Vec::new(); @@ -10631,10 +10790,10 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); - let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap(); + let mut external_simplex = Simplex::try_new_with_data(simplex_vertices.clone(), None).unwrap(); external_simplex .set_periodic_vertex_offsets(vec![[0_i8; $dim]; $dim + 1]) .unwrap(); @@ -10646,7 +10805,7 @@ mod tests { &tds, &mut replacement_simplices, &mut replacement_offsets, - &[FacetHandle::new(external_simplex_key, 0)], + &[FacetHandle::from_validated(external_simplex_key, 0)], ); assert!( @@ -10666,10 +10825,10 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); - let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap(); + let mut external_simplex = Simplex::try_new_with_data(simplex_vertices.clone(), None).unwrap(); external_simplex .set_periodic_vertex_offsets(vec![[0_i8; $dim]; $dim + 1]) .unwrap(); @@ -10683,7 +10842,7 @@ mod tests { &tds, &mut replacement_simplices, &mut replacement_offsets, - &[FacetHandle::new(external_simplex_key, 0)], + &[FacetHandle::from_validated(external_simplex_key, 0)], ); assert!( @@ -10705,10 +10864,10 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); let external_simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(simplex_vertices.clone(), None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(simplex_vertices.clone(), None).unwrap()) .unwrap(); assert_eq!(tds.remove_simplices_by_keys(&[external_simplex_key]), 1); @@ -10719,7 +10878,7 @@ mod tests { &tds, &mut replacement_simplices, &mut replacement_offsets, - &[FacetHandle::new(external_simplex_key, 0)], + &[FacetHandle::from_validated(external_simplex_key, 0)], ); assert!( @@ -10733,11 +10892,11 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); - let v_square = tds.insert_vertex_with_mapping(vertex!([1.0; $dim])).unwrap(); + let v_square = tds.insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([1.0; $dim]).unwrap()).unwrap(); let v_collinear = tds - .insert_vertex_with_mapping(vertex!(scaled_unit_vector::<$dim>(0, 2.0))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(scaled_unit_vector::<$dim>(0, 2.0)).unwrap()) .unwrap(); let source = vertex_key_buffer(&simplex_vertices); @@ -10839,11 +10998,11 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); - let v_square = tds.insert_vertex_with_mapping(vertex!([1.0; $dim])).unwrap(); + let v_square = tds.insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([1.0; $dim]).unwrap()).unwrap(); let external_simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(simplex_vertices.clone(), None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(simplex_vertices.clone(), None).unwrap()) .unwrap(); let mut external_aligned = vec![vertex_key_buffer(&simplex_vertices)]; @@ -10853,7 +11012,7 @@ mod tests { &tds, &mut external_aligned, &mut external_offsets, - &[FacetHandle::new(external_simplex_key, 0)], + &[FacetHandle::from_validated(external_simplex_key, 0)], ) .unwrap(); let mut expected_external = simplex_vertices.clone(); @@ -10895,10 +11054,10 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let simplex_vertices = insert_standard_simplex_vertices(&mut tds); let v_collinear = tds - .insert_vertex_with_mapping(vertex!(scaled_unit_vector::<$dim>(0, 2.0))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(scaled_unit_vector::<$dim>(0, 2.0)).unwrap()) .unwrap(); let mut positive_vertices = simplex_vertices.clone(); @@ -10947,46 +11106,70 @@ mod tests { )] fn test_k3_flip_rewires_external_neighbors_across_cavity_boundary() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); // NOTE: keep `v_edge_start` off the plane of (v_cycle_0, v_cycle_1, v_cycle_2) // so the post-flip inserted tetrahedra are non-degenerate. let v_edge_start = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_edge_end = tds - .insert_vertex_with_mapping(vertex!([2.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_cycle_0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 2.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0, 0.0]).unwrap(), + ) .unwrap(); let v_cycle_1 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), + ) .unwrap(); let v_cycle_2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 2.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0, 2.0]).unwrap(), + ) .unwrap(); let v_external = tds - .insert_vertex_with_mapping(vertex!([-1.0, 1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, 1.0, 1.0]).unwrap(), + ) .unwrap(); // Three tetrahedra around the ridge (edge) (v_edge_start, v_edge_end). // This is the configuration removed by a k=3 flip (3→2). let simplex_around_edge_0 = tds .insert_simplex_with_mapping( - Simplex::new(vec![v_edge_start, v_edge_end, v_cycle_0, v_cycle_1], None).unwrap(), + Simplex::try_new_with_data( + vec![v_edge_start, v_edge_end, v_cycle_0, v_cycle_1], + None, + ) + .unwrap(), ) .unwrap(); let simplex_around_edge_1 = tds .insert_simplex_with_mapping( - Simplex::new(vec![v_edge_start, v_edge_end, v_cycle_1, v_cycle_2], None).unwrap(), + Simplex::try_new_with_data( + vec![v_edge_start, v_edge_end, v_cycle_1, v_cycle_2], + None, + ) + .unwrap(), ) .unwrap(); let simplex_around_edge_2 = tds .insert_simplex_with_mapping( - Simplex::new(vec![v_edge_start, v_edge_end, v_cycle_2, v_cycle_0], None).unwrap(), + Simplex::try_new_with_data( + vec![v_edge_start, v_edge_end, v_cycle_2, v_cycle_0], + None, + ) + .unwrap(), ) .unwrap(); @@ -10994,7 +11177,11 @@ mod tests { // This face must be rewired to a newly inserted tetrahedron after the flip. let simplex_external = tds .insert_simplex_with_mapping( - Simplex::new(vec![v_edge_start, v_cycle_0, v_cycle_1, v_external], None).unwrap(), + Simplex::try_new_with_data( + vec![v_edge_start, v_cycle_0, v_cycle_1, v_external], + None, + ) + .unwrap(), ) .unwrap(); @@ -11003,7 +11190,7 @@ mod tests { // In `simplex_around_edge_0`, the ridge is the edge (v_edge_start, v_edge_end). // We omitted the two non-ridge vertices by construction (indices 2 and 3). - let ridge = RidgeHandle::new(simplex_around_edge_0, 2, 3); + let ridge = RidgeHandle::from_validated(simplex_around_edge_0, 2, 3); let ctx = build_k3_flip_context(&tds, ridge).unwrap(); assert_eq!(ctx.removed_simplices.len(), 3); assert!( @@ -11104,10 +11291,10 @@ mod tests { let v0 = VertexKey::from(KeyData::from_ffi(101)); let v1 = VertexKey::from(KeyData::from_ffi(102)); let v2 = VertexKey::from(KeyData::from_ffi(103)); - let edge = EdgeKey::new(v0, v1); - let facet = FacetHandle::new(simplex, 0); - let ridge = RidgeHandle::new(simplex, 0, 1); - let triangle = TriangleHandle::new(v0, v1, v2); + let edge = EdgeKey::from_validated_endpoints(v0, v1); + let facet = FacetHandle::from_validated(simplex, 0); + let ridge = RidgeHandle::from_validated(simplex, 0, 1); + let triangle = TriangleHandle::try_new(v0, v1, v2).unwrap(); let first_inserted_sample = InsertedSimplexSkipSample { location: RepairSkipLocation::Facet(facet), @@ -11139,7 +11326,7 @@ mod tests { }; diagnostics.record_invalid_ridge_multiplicity_skip(first_ridge_sample); diagnostics.record_invalid_ridge_multiplicity_skip(RidgeMultiplicitySkipSample { - ridge: RidgeHandle::new(simplex, 1, 2), + ridge: RidgeHandle::from_validated(simplex, 1, 2), multiplicity: 4, }); assert_eq!(diagnostics.invalid_ridge_multiplicity_skips, 2); @@ -11171,9 +11358,9 @@ mod tests { let v0 = VertexKey::from(KeyData::from_ffi(101)); let v1 = VertexKey::from(KeyData::from_ffi(102)); let v2 = VertexKey::from(KeyData::from_ffi(103)); - let facet = FacetHandle::new(simplex, 0); - let ridge = RidgeHandle::new(simplex, 0, 1); - let triangle = TriangleHandle::new(v0, v1, v2); + let facet = FacetHandle::from_validated(simplex, 0); + let ridge = RidgeHandle::from_validated(simplex, 0, 1); + let triangle = TriangleHandle::try_new(v0, v1, v2).unwrap(); let removed_face: VertexKeyList = [v0, v1].into_iter().collect(); let inserted_face: VertexKeyList = std::iter::once(v2).collect(); @@ -11223,7 +11410,7 @@ mod tests { simplex_neighbors: Vec>>, } - fn snapshot_topology(tds: &Tds) -> TopologySnapshot { + fn snapshot_topology(tds: &Tds<(), (), D>) -> TopologySnapshot { let mut vertices: Vec = tds.vertices().map(|(_, vertex)| vertex.uuid()).collect(); vertices.sort(); @@ -11250,7 +11437,7 @@ mod tests { } } - fn snapshot_neighbors(tds: &Tds) -> Vec>> { + fn snapshot_neighbors(tds: &Tds<(), (), D>) -> Vec>> { let mut simplex_neighbors: Vec>> = tds .simplices() .map(|(_, simplex)| { @@ -11274,7 +11461,7 @@ mod tests { simplex_neighbors } - fn snapshot_incidence(tds: &Tds) -> Vec<(Uuid, Option)> { + fn snapshot_incidence(tds: &Tds<(), (), D>) -> Vec<(Uuid, Option)> { let mut incident_simplices: Vec<(Uuid, Option)> = tds .vertices() .map(|(_, vertex)| { @@ -11296,35 +11483,48 @@ mod tests { } fn insert_translated_simplex( - tds: &mut Tds, + tds: &mut Tds<(), (), D>, offset: f64, ) -> (Vec, SimplexKey) { let mut vertices = Vec::with_capacity(D + 1); vertices.push( - tds.insert_vertex_with_mapping(vertex!([offset; D])) - .unwrap(), + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([offset; D]).unwrap(), + ) + .unwrap(), ); for axis in 0..D { let mut coords = [offset; D]; coords[axis] += 1.0; - vertices.push(tds.insert_vertex_with_mapping(vertex!(coords)).unwrap()); + vertices.push( + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + ) + .unwrap(), + ); } let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vertices.clone(), None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices.clone(), None).unwrap(), + ) .unwrap(); (vertices, simplex_key) } fn test_flip_trial_validation_rollback_for_dim() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), D> = Tds::empty(); let (_first_vertices, first_simplex) = insert_translated_simplex(&mut tds, 0.0); let (_second_vertices, second_simplex) = insert_translated_simplex(&mut tds, 10.0); repair_neighbor_pointers(&mut tds).unwrap(); tds.assign_incident_simplices().unwrap(); - let isolated_vertex = tds.insert_vertex_with_mapping(vertex!([20.0; D])).unwrap(); + let isolated_vertex = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([20.0; D]).unwrap(), + ) + .unwrap(); tds.vertex_mut(isolated_vertex) .unwrap() .set_incident_simplex(Some(second_simplex)); @@ -11332,7 +11532,8 @@ mod tests { let before = snapshot_topology(&tds); let before_incidence = snapshot_incidence(&tds); let denominator = f64::from(u32::try_from(D + 2).unwrap()); - let new_vertex = vertex!([1.0 / denominator; D]); + let new_vertex = + crate::core::vertex::Vertex::<(), _>::try_new([1.0 / denominator; D]).unwrap(); let result = apply_bistellar_flip_k1(&mut tds, first_simplex, new_vertex); match result { @@ -11370,12 +11571,26 @@ mod tests { #[test] fn test_flip_trial_validation_rejects_unassigned_neighbor_slot() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); tds.assign_neighbors().unwrap(); @@ -11406,13 +11621,16 @@ mod tests { scale: f64, ) -> Result<(), TestCaseError> { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), D> = Tds::empty(); let mut shared_vertices = Vec::with_capacity(D); for i in 0..D { let vertex = tds - .insert_vertex_with_mapping(vertex!(translated_scaled_unit_vector::( - i, offset, scale - ))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new( + translated_scaled_unit_vector::(i, offset, scale), + ) + .unwrap(), + ) .map_err(|err| { TestCaseError::fail(format!("shared vertex insertion failed: {err:?}")) })?; @@ -11420,24 +11638,30 @@ mod tests { } let opposite_a = tds - .insert_vertex_with_mapping(vertex!([offset; D])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([offset; D]).unwrap(), + ) .map_err(|err| TestCaseError::fail(format!("opposite A insertion failed: {err:?}")))?; let opposite_b = tds - .insert_vertex_with_mapping(vertex!([offset + scale; D])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([offset + scale; D]).unwrap(), + ) .map_err(|err| TestCaseError::fail(format!("opposite B insertion failed: {err:?}")))?; let mut vertices_with_first_opposite = shared_vertices.clone(); vertices_with_first_opposite.push(opposite_a); let simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).map_err( - |err| TestCaseError::fail(format!("simplex A creation failed: {err:?}")), - )?) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_first_opposite, None).map_err(|err| { + TestCaseError::fail(format!("simplex A creation failed: {err:?}")) + })?, + ) .map_err(|err| TestCaseError::fail(format!("simplex A insertion failed: {err:?}")))?; let mut vertices_with_second_opposite = shared_vertices.clone(); vertices_with_second_opposite.push(opposite_b); tds.insert_simplex_with_mapping( - Simplex::new(vertices_with_second_opposite, None).map_err(|err| { + Simplex::try_new_with_data(vertices_with_second_opposite, None).map_err(|err| { TestCaseError::fail(format!("simplex B creation failed: {err:?}")) })?, ) @@ -11447,7 +11671,7 @@ mod tests { .map_err(|err| TestCaseError::fail(format!("neighbor repair failed: {err:?}")))?; let before = snapshot_topology(&tds); - let facet = FacetHandle::new( + let facet = FacetHandle::from_validated( simplex_a, u8::try_from(D).map_err(|err| { TestCaseError::fail(format!("facet index conversion failed: {err:?}")) @@ -11472,7 +11696,7 @@ mod tests { .iter() .position(|&vertex| vertex != opposite_a && vertex != opposite_b) .ok_or_else(|| TestCaseError::fail("missing inverse k=2 facet vertex"))?; - inverse_facet = Some(FacetHandle::new( + inverse_facet = Some(FacetHandle::from_validated( simplex_key, u8::try_from(facet_index).map_err(|err| { TestCaseError::fail(format!( @@ -11494,7 +11718,7 @@ mod tests { } else { let inserted = inserted_face_vertices(&info, 2)?; let edge = match inserted.as_slice() { - [a, b] => EdgeKey::new(*a, *b), + [a, b] => EdgeKey::from_validated_endpoints(*a, *b), _ => { return Err(TestCaseError::fail( "validated k=2 inserted-face arity changed", @@ -11529,13 +11753,16 @@ mod tests { let ridge_vertex_count = D .checked_sub(1) .ok_or_else(|| TestCaseError::fail("k=3 fixture requires D >= 1"))?; - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), D> = Tds::empty(); let mut ridge_vertices = Vec::with_capacity(ridge_vertex_count); for i in 0..ridge_vertex_count { let vertex = tds - .insert_vertex_with_mapping(vertex!(translated_scaled_unit_vector::( - i, offset, scale - ))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new( + translated_scaled_unit_vector::(i, offset, scale), + ) + .unwrap(), + ) .map_err(|err| { TestCaseError::fail(format!("ridge vertex insertion failed: {err:?}")) })?; @@ -11543,33 +11770,43 @@ mod tests { } let a = tds - .insert_vertex_with_mapping(vertex!([offset; D])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([offset; D]).unwrap(), + ) .map_err(|err| TestCaseError::fail(format!("opposite A insertion failed: {err:?}")))?; let b = tds - .insert_vertex_with_mapping(vertex!(translated_scaled_unit_vector::( - ridge_vertex_count, - offset, - scale - ))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(translated_scaled_unit_vector::( + ridge_vertex_count, + offset, + scale, + )) + .unwrap(), + ) .map_err(|err| TestCaseError::fail(format!("opposite B insertion failed: {err:?}")))?; let c = tds - .insert_vertex_with_mapping(vertex!(translated_scaled_skewed_point::(offset, scale))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(translated_scaled_skewed_point::( + offset, scale, + )) + .unwrap(), + ) .map_err(|err| TestCaseError::fail(format!("opposite C insertion failed: {err:?}")))?; let mut first_vertices = ridge_vertices.clone(); first_vertices.push(a); first_vertices.push(b); let first_simplex = tds - .insert_simplex_with_mapping(Simplex::new(first_vertices, None).map_err(|err| { - TestCaseError::fail(format!("simplex A creation failed: {err:?}")) - })?) + .insert_simplex_with_mapping(Simplex::try_new_with_data(first_vertices, None).map_err( + |err| TestCaseError::fail(format!("simplex A creation failed: {err:?}")), + )?) .map_err(|err| TestCaseError::fail(format!("simplex A insertion failed: {err:?}")))?; let mut second_vertices = ridge_vertices.clone(); second_vertices.push(b); second_vertices.push(c); tds.insert_simplex_with_mapping( - Simplex::new(second_vertices, None).map_err(|err| { + Simplex::try_new_with_data(second_vertices, None).map_err(|err| { TestCaseError::fail(format!("simplex B creation failed: {err:?}")) })?, ) @@ -11579,7 +11816,7 @@ mod tests { third_vertices.push(c); third_vertices.push(a); tds.insert_simplex_with_mapping( - Simplex::new(third_vertices, None).map_err(|err| { + Simplex::try_new_with_data(third_vertices, None).map_err(|err| { TestCaseError::fail(format!("simplex C creation failed: {err:?}")) })?, ) @@ -11589,7 +11826,7 @@ mod tests { .map_err(|err| TestCaseError::fail(format!("neighbor repair failed: {err:?}")))?; let before = snapshot_topology(&tds); - let ridge = RidgeHandle::new( + let ridge = RidgeHandle::from_validated( first_simplex, u8::try_from(ridge_vertex_count).map_err(|err| { TestCaseError::fail(format!("ridge index conversion failed: {err:?}")) @@ -11620,7 +11857,7 @@ mod tests { .iter() .position(|&vertex| vertex != a && vertex != b && vertex != c) .ok_or_else(|| TestCaseError::fail("missing inverse k=3 facet vertex"))?; - inverse_facet = Some(FacetHandle::new( + inverse_facet = Some(FacetHandle::from_validated( simplex_key, u8::try_from(facet_index).map_err(|err| { TestCaseError::fail(format!( @@ -11642,7 +11879,9 @@ mod tests { } else { let inserted = inserted_face_vertices(&info, 3)?; let triangle = match inserted.as_slice() { - [a, b, c] => TriangleHandle::new(*a, *b, *c), + [a, b, c] => TriangleHandle::try_new(*a, *b, *c).map_err(|err| { + TestCaseError::fail(format!("invalid inserted triangle: {err}")) + })?, _ => { return Err(TestCaseError::fail( "validated k=3 inserted-face arity changed", @@ -11738,25 +11977,25 @@ mod tests { #[test] fn []() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); - let origin = tds.insert_vertex_with_mapping(vertex!([0.0; $dim])).unwrap(); + let origin = tds.insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([0.0; $dim]).unwrap()).unwrap(); let mut vertices = Vec::with_capacity($dim + 1); vertices.push(origin); for i in 0..$dim { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(i))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<$dim>(i)).unwrap()) .unwrap(); vertices.push(v); } let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vertices, None).unwrap()) .unwrap(); let before = snapshot_topology(&tds); - let new_vertex = vertex!([0.1; $dim]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.1; $dim]).unwrap(); let new_uuid = new_vertex.uuid(); let _info = apply_bistellar_flip_k1(&mut tds, simplex_key, new_vertex) .unwrap(); @@ -11773,27 +12012,27 @@ mod tests { #[test] fn []() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let mut shared_vertices = Vec::with_capacity($dim); for i in 0..$dim { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(i))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<$dim>(i)).unwrap()) .unwrap(); shared_vertices.push(v); } let opposite_a = tds - .insert_vertex_with_mapping(vertex!([0.0; $dim])) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([0.0; $dim]).unwrap()) .unwrap(); let opposite_b = tds - .insert_vertex_with_mapping(vertex!([1.0; $dim])) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([1.0; $dim]).unwrap()) .unwrap(); let mut vertices_with_first_opposite = shared_vertices.clone(); vertices_with_first_opposite.push(opposite_a); let simplex_a = tds .insert_simplex_with_mapping( - Simplex::new(vertices_with_first_opposite, None).unwrap(), + Simplex::try_new_with_data(vertices_with_first_opposite, None).unwrap(), ) .unwrap(); @@ -11801,7 +12040,7 @@ mod tests { vertices_with_second_opposite.push(opposite_b); let _simplex_b = tds .insert_simplex_with_mapping( - Simplex::new(vertices_with_second_opposite, None).unwrap(), + Simplex::try_new_with_data(vertices_with_second_opposite, None).unwrap(), ) .unwrap(); @@ -11809,7 +12048,7 @@ mod tests { let before = snapshot_topology(&tds); - let facet = FacetHandle::new(simplex_a, u8::try_from($dim).unwrap()); + let facet = FacetHandle::from_validated(simplex_a, u8::try_from($dim).unwrap()); let context = build_k2_flip_context(&tds, facet).unwrap(); let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); assert!(tds.is_valid().is_ok()); @@ -11824,7 +12063,7 @@ mod tests { .iter() .position(|&v| v != opposite_a && v != opposite_b) .expect("missing shared vertex for inverse k=2"); - inverse_facet = Some(FacetHandle::new( + inverse_facet = Some(FacetHandle::from_validated( simplex_key, u8::try_from(facet_index).unwrap(), )); @@ -11837,7 +12076,7 @@ mod tests { let _info_back = apply_bistellar_flip_k2(&mut tds, &context_back).unwrap(); } else { - let edge = EdgeKey::new(opposite_a, opposite_b); + let edge = EdgeKey::from_validated_endpoints(opposite_a, opposite_b); let context_back = build_k2_flip_context_from_edge(&tds, edge).unwrap(); let _info_back = apply_bistellar_flip_dynamic(&mut tds, $dim, &context_back) @@ -11856,51 +12095,51 @@ mod tests { #[test] fn []() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let mut ridge_vertices = Vec::with_capacity($dim - 1); for i in 0..($dim - 1) { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(i))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<$dim>(i)).unwrap()) .unwrap(); ridge_vertices.push(v); } let a = tds - .insert_vertex_with_mapping(vertex!([0.0; $dim])) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([0.0; $dim]).unwrap()) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<$dim>($dim - 1))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<$dim>($dim - 1)).unwrap()) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!(skewed_point::<$dim>())) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(skewed_point::<$dim>()).unwrap()) .unwrap(); let mut c1_vertices = ridge_vertices.clone(); c1_vertices.push(a); c1_vertices.push(b); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(c1_vertices, None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(c1_vertices, None).unwrap()) .unwrap(); let mut c2_vertices = ridge_vertices.clone(); c2_vertices.push(b); c2_vertices.push(c); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(c2_vertices, None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(c2_vertices, None).unwrap()) .unwrap(); let mut c3_vertices = ridge_vertices.clone(); c3_vertices.push(c); c3_vertices.push(a); let _c3 = tds - .insert_simplex_with_mapping(Simplex::new(c3_vertices, None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(c3_vertices, None).unwrap()) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); let before = snapshot_topology(&tds); - let ridge = RidgeHandle::new( + let ridge = RidgeHandle::from_validated( c1, u8::try_from($dim - 1).unwrap(), u8::try_from($dim).unwrap(), @@ -11922,7 +12161,7 @@ mod tests { .iter() .position(|&v| v != a && v != b && v != c) .expect("missing ridge vertex for inverse k=3"); - inverse_facet = Some(FacetHandle::new( + inverse_facet = Some(FacetHandle::from_validated( simplex_key, u8::try_from(facet_index).unwrap(), )); @@ -11935,7 +12174,7 @@ mod tests { let _info_back = apply_bistellar_flip_k2(&mut tds, &context_back).unwrap(); } else { - let triangle = TriangleHandle::new(a, b, c); + let triangle = TriangleHandle::try_new(a, b, c).unwrap(); let context_back = build_k3_flip_context_from_triangle(&tds, triangle).unwrap(); let _info_back = apply_bistellar_flip_dynamic( @@ -12000,7 +12239,7 @@ mod tests { fn dynamic_flip_rejects_bad_context_for_dimension() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), D> = Tds::empty(); let vertices = (1..=D + 2) .map(|index| { synthetic_vertex_key( @@ -12122,22 +12361,38 @@ mod tests { #[test] fn test_flip_k2_2d_edge_flip() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([1.0, 0.2])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.2]).unwrap(), + ) + .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, d], None).unwrap()) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(c1, 2); // facet opposite vertex index 2 (edge AB) + let facet = FacetHandle::from_validated(c1, 2); // facet opposite vertex index 2 (edge AB) let context = build_k2_flip_context(&tds, facet).unwrap(); let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); @@ -12160,27 +12415,43 @@ mod tests { #[test] fn test_flip_k2_rejects_duplicate_simplex() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([1.0, 0.2])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.2]).unwrap(), + ) + .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, d], None).unwrap()) .unwrap(); // Pre-existing simplex that the flip would recreate (B,C,D) let _existing = tds - .insert_simplex_with_mapping(Simplex::new(vec![b, c, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![b, c, d], None).unwrap()) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(c1, 2); // facet opposite vertex index 2 (edge AB) + let facet = FacetHandle::from_validated(c1, 2); // facet opposite vertex index 2 (edge AB) let context = build_k2_flip_context(&tds, facet).unwrap(); let result = apply_bistellar_flip_k2(&mut tds, &context); @@ -12191,54 +12462,74 @@ mod tests { #[test] fn test_flip_k2_rejects_inserting_existing_edge_in_3d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); // Opposite vertices across the shared face. let v_a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_b = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); // Shared face vertices. let v_x = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v_y = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v_z = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 1.0]).unwrap(), + ) .unwrap(); // Extra vertices for an existing tetrahedron containing the edge (v_a, v_b). let v_p = tds - .insert_vertex_with_mapping(vertex!([2.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_q = tds - .insert_vertex_with_mapping(vertex!([2.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); // Two tetrahedra sharing face (v_x, v_y, v_z): a k=2 flip across that face would insert edge (v_a, v_b). let simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_x, v_y, v_z], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_x, v_y, v_z], None).unwrap(), + ) .unwrap(); let _simplex_b = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_b, v_x, v_y, v_z], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_b, v_x, v_y, v_z], None).unwrap(), + ) .unwrap(); // Existing tetrahedron that already contains edge (v_a, v_b) but does not contain any of // the shared-face vertices (v_x, v_y, v_z). let _edge_witness = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_p, v_q], None).unwrap()) - .unwrap(); + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_p, v_q], None).unwrap(), + ) + .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); assert!(tds.is_valid().is_ok()); // Face (v_x, v_y, v_z) is opposite v_a in `simplex_a` (index 0 by construction). - let facet = FacetHandle::new(simplex_a, 0); + let facet = FacetHandle::from_validated(simplex_a, 0); let ctx = build_k2_flip_context(&tds, facet).unwrap(); let result = apply_bistellar_flip_k2(&mut tds, &ctx); @@ -12250,28 +12541,54 @@ mod tests { #[test] fn test_flip_k2_rejects_nonmanifold_internal_facet() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 0.2])).unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.2]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); // Existing simplex containing the would-be inserted diagonal (C,D). let _cd_external = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_c, v_d, v_e], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_c, v_d, v_e], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(c1, 2); // facet opposite vertex index 2 (edge AB) + let facet = FacetHandle::from_validated(c1, 2); // facet opposite vertex index 2 (edge AB) let context = build_k2_flip_context(&tds, facet).unwrap(); let result = apply_bistellar_flip_k2(&mut tds, &context); @@ -12282,33 +12599,47 @@ mod tests { #[test] fn test_flip_k2_3d_two_to_three() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v_a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_b = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_c = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v_d = tds - .insert_vertex_with_mapping(vertex!([0.2, 0.2, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 1.0]).unwrap(), + ) .unwrap(); let v_e = tds - .insert_vertex_with_mapping(vertex!([0.3, -0.1, -0.8])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.3, -0.1, -0.8]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c, v_d], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_e], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c, v_e], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(c1, 3); // facet opposite vertex d (ABC) + let facet = FacetHandle::from_validated(c1, 3); // facet opposite vertex d (ABC) let context = build_k2_flip_context(&tds, facet).unwrap(); let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); @@ -12319,36 +12650,52 @@ mod tests { #[test] fn test_flip_k3_3d_three_to_two() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let r0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.2, 0.2, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, -1.0]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, a, b], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, a, b], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, b, c], None).unwrap(), + ) .unwrap(); let _c3 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, c, a], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, c, a], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let ridge = RidgeHandle::new(c1, 2, 3); + let ridge = RidgeHandle::from_validated(c1, 2, 3); let context = build_k3_flip_context(&tds, ridge).unwrap(); let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap(); @@ -12361,39 +12708,57 @@ mod tests { #[test] fn test_flip_k3_4d_three_to_three() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 4> = Tds::empty(); let r0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, a, b], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, a, b], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, b, c], None).unwrap(), + ) .unwrap(); let _c3 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, c, a], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, c, a], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let ridge = RidgeHandle::new(c1, 3, 4); + let ridge = RidgeHandle::from_validated(c1, 3, 4); let context = build_k3_flip_context(&tds, ridge).unwrap(); let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap(); @@ -12406,42 +12771,62 @@ mod tests { #[test] fn test_flip_k3_5d_three_to_four() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 5> = Tds::empty(); let r0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, 0.5]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, a, b], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, b, c], None).unwrap(), + ) .unwrap(); let _c3 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, c, a], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let ridge = RidgeHandle::new(c1, 4, 5); + let ridge = RidgeHandle::from_validated(c1, 4, 5); let context = build_k3_flip_context(&tds, ridge).unwrap(); let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap(); @@ -12454,16 +12839,28 @@ mod tests { #[test] fn test_flip_k2_boundary_facet_error_2d() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let before = snapshot_topology(&tds); - let facet = FacetHandle::new(simplex, 0); + let facet = FacetHandle::from_validated(simplex, 0); let err = build_k2_flip_context(&tds, facet).unwrap_err(); assert_matches!(err, FlipError::BoundaryFacet { .. }); assert_eq!(snapshot_topology(&tds), before); @@ -12472,24 +12869,34 @@ mod tests { #[test] fn test_flip_k3_invalid_ridge_multiplicity_3d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let d = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c, d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, c, d], None).unwrap(), + ) .unwrap(); - let ridge = RidgeHandle::new(simplex, 0, 1); + let ridge = RidgeHandle::from_validated(simplex, 0, 1); let err = build_k3_flip_context(&tds, ridge).unwrap_err(); assert_matches!(err, FlipError::InvalidRidgeMultiplicity { found: 1 }); } @@ -12497,25 +12904,35 @@ mod tests { #[test] fn test_flip_k3_reports_dangling_ridge_neighbor_3d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let ridge_start = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let ridge_end = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let first_opposite = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let second_opposite = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let dangling_opposite = tds - .insert_vertex_with_mapping(vertex!([1.0, 1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), + ) .unwrap(); let simplex = tds .insert_simplex_with_mapping( - Simplex::new( + Simplex::try_new_with_data( vec![ridge_start, ridge_end, first_opposite, second_opposite], None, ) @@ -12524,7 +12941,7 @@ mod tests { .unwrap(); let dangling_neighbor = tds .insert_simplex_with_mapping( - Simplex::new( + Simplex::try_new_with_data( vec![ridge_start, ridge_end, first_opposite, dangling_opposite], None, ) @@ -12538,7 +12955,7 @@ mod tests { .set_neighbors_from_keys([Some(dangling_neighbor), None, None, None]) .unwrap(); - let ridge = RidgeHandle::new(simplex, 0, 1); + let ridge = RidgeHandle::from_validated(simplex, 0, 1); let err = build_k3_flip_context(&tds, ridge).unwrap_err(); assert_eq!( err, @@ -12552,31 +12969,45 @@ mod tests { #[test] fn test_flip_k2_inverse_invalid_edge_multiplicity_4d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 4> = Tds::empty(); let mut shared_vertices = Vec::with_capacity(4); for i in 0..4 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<4>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<4>(i)).unwrap(), + ) .unwrap(); shared_vertices.push(v); } - let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap(); - let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap(); + let opposite_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 4]).unwrap(), + ) + .unwrap(); + let opposite_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0; 4]).unwrap(), + ) + .unwrap(); let mut vertices_with_first_opposite = shared_vertices.clone(); vertices_with_first_opposite.push(opposite_a); let _simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_first_opposite, None).unwrap(), + ) .unwrap(); let mut vertices_with_second_opposite = shared_vertices.clone(); vertices_with_second_opposite.push(opposite_b); let _simplex_b = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_second_opposite, None).unwrap(), + ) .unwrap(); - let edge = EdgeKey::new(opposite_a, opposite_b); + let edge = EdgeKey::from_validated_endpoints(opposite_a, opposite_b); let err = build_k2_flip_context_from_edge(&tds, edge).unwrap_err(); assert_matches!(err, FlipError::InvalidEdgeMultiplicity { .. }); } @@ -12584,21 +13015,29 @@ mod tests { #[test] fn test_flip_k3_inverse_invalid_triangle_multiplicity_5d() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let origin = tds.insert_vertex_with_mapping(vertex!([0.0; 5])).unwrap(); + let mut tds: Tds<(), (), 5> = Tds::empty(); + let origin = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 5]).unwrap(), + ) + .unwrap(); let mut vertices = Vec::with_capacity(6); vertices.push(origin); for i in 0..5 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<5>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<5>(i)).unwrap(), + ) .unwrap(); vertices.push(v); } let _simplex = tds - .insert_simplex_with_mapping(Simplex::new(vertices.clone(), None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices.clone(), None).unwrap(), + ) .unwrap(); - let triangle = TriangleHandle::new(vertices[0], vertices[1], vertices[2]); + let triangle = TriangleHandle::try_new(vertices[0], vertices[1], vertices[2]).unwrap(); let err = build_k3_flip_context_from_triangle(&tds, triangle).unwrap_err(); assert_matches!( err, @@ -12612,16 +13051,33 @@ mod tests { #[test] fn test_flip_k1_degenerate_insert_rejected() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let before = snapshot_topology(&tds); - let err = apply_bistellar_flip_k1(&mut tds, simplex_key, vertex!([0.5, 0.0])).unwrap_err(); + let err = apply_bistellar_flip_k1( + &mut tds, + simplex_key, + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.0]).unwrap(), + ) + .unwrap_err(); assert_matches!(err, FlipError::DegenerateSimplex); assert_eq!(snapshot_topology(&tds), before); @@ -12631,33 +13087,47 @@ mod tests { #[test] fn test_dynamic_k2_forward_4d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 4> = Tds::empty(); let mut shared_vertices = Vec::with_capacity(4); for i in 0..4 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<4>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<4>(i)).unwrap(), + ) .unwrap(); shared_vertices.push(v); } - let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap(); - let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap(); + let opposite_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 4]).unwrap(), + ) + .unwrap(); + let opposite_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0; 4]).unwrap(), + ) + .unwrap(); let mut vertices_with_first_opposite = shared_vertices.clone(); vertices_with_first_opposite.push(opposite_a); let simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_first_opposite, None).unwrap(), + ) .unwrap(); let mut vertices_with_second_opposite = shared_vertices.clone(); vertices_with_second_opposite.push(opposite_b); let _simplex_b = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_second_opposite, None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(simplex_a, 4); + let facet = FacetHandle::from_validated(simplex_a, 4); let context = build_k2_flip_context(&tds, facet).unwrap(); let context_dyn = to_dynamic(context); let info = apply_bistellar_flip_dynamic(&mut tds, 2, &context_dyn).unwrap(); @@ -12671,42 +13141,62 @@ mod tests { #[test] fn test_dynamic_k3_forward_5d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 5> = Tds::empty(); let r0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, 0.5]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, a, b], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, b, c], None).unwrap(), + ) .unwrap(); let _c3 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, c, a], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let ridge = RidgeHandle::new(c1, 4, 5); + let ridge = RidgeHandle::from_validated(c1, 4, 5); let context = build_k3_flip_context(&tds, ridge).unwrap(); let context_dyn = to_dynamic(context); let info = apply_bistellar_flip_dynamic(&mut tds, 3, &context_dyn).unwrap(); @@ -12731,39 +13221,54 @@ mod tests { out }; - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v_a = tds - .insert_vertex_with_mapping(vertex!(jitter([0.0, 0.0, 0.0]))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(jitter([0.0, 0.0, 0.0])).unwrap(), + ) .unwrap(); let v_b = tds - .insert_vertex_with_mapping(vertex!(jitter([1.0, 0.0, 0.0]))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(jitter([1.0, 0.0, 0.0])).unwrap(), + ) .unwrap(); let v_c = tds - .insert_vertex_with_mapping(vertex!(jitter([0.0, 1.0, 0.0]))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(jitter([0.0, 1.0, 0.0])).unwrap(), + ) .unwrap(); let v_d = tds - .insert_vertex_with_mapping(vertex!(jitter([0.2, 0.2, 1.0]))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(jitter([0.2, 0.2, 1.0])).unwrap(), + ) .unwrap(); let v_e = tds - .insert_vertex_with_mapping(vertex!(jitter([0.3, -0.1, -0.8]))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(jitter([0.3, -0.1, -0.8])) + .unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c, v_d], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_e], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c, v_e], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); let before = snapshot_topology(&tds); - let facet = FacetHandle::new(c1, 3); + let facet = FacetHandle::from_validated(c1, 3); let context = build_k2_flip_context(&tds, facet).unwrap(); let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); assert!(tds.is_valid().is_ok()); - let edge = EdgeKey::new( + let edge = EdgeKey::from_validated_endpoints( info.inserted_face_vertices[0], info.inserted_face_vertices[1], ); @@ -12787,25 +13292,37 @@ mod tests { let mut tds = None; for d_coords in d_candidates { - let mut candidate: Tds = Tds::empty(); + let mut candidate: Tds<(), (), 2> = Tds::empty(); let a = candidate - .insert_vertex_with_mapping(vertex!(a_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(a_coords).unwrap(), + ) .unwrap(); let b = candidate - .insert_vertex_with_mapping(vertex!(b_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(b_coords).unwrap(), + ) .unwrap(); let c = candidate - .insert_vertex_with_mapping(vertex!(c_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(c_coords).unwrap(), + ) .unwrap(); let d = candidate - .insert_vertex_with_mapping(vertex!(d_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(d_coords).unwrap(), + ) .unwrap(); let _c1 = candidate - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, c], None).unwrap(), + ) .unwrap(); let _c2 = candidate - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, d], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut candidate).unwrap(); @@ -12842,25 +13359,37 @@ mod tests { let mut tds = None; for d_coords in d_candidates { - let mut candidate: Tds = Tds::empty(); + let mut candidate: Tds<(), (), 2> = Tds::empty(); let a = candidate - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); let b = candidate - .insert_vertex_with_mapping(vertex!([1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) .unwrap(); let c = candidate - .insert_vertex_with_mapping(vertex!([1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) .unwrap(); let d = candidate - .insert_vertex_with_mapping(vertex!(d_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(d_coords).unwrap(), + ) .unwrap(); let _c1 = candidate - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, c], None).unwrap(), + ) .unwrap(); let _c2 = candidate - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, d], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut candidate).unwrap(); @@ -12914,28 +13443,42 @@ mod tests { init_tracing(); let kernel = AdaptiveKernel::::new(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let d = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let e = tds - .insert_vertex_with_mapping(vertex!([0.3, 0.3, 0.3])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3, 0.3]).unwrap(), + ) .unwrap(); let _c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c, d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, c, d], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c, e], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, c, e], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); @@ -12978,25 +13521,37 @@ mod tests { let mut tds = None; for d_coords in d_candidates { - let mut candidate: Tds = Tds::empty(); + let mut candidate: Tds<(), (), 2> = Tds::empty(); let a = candidate - .insert_vertex_with_mapping(vertex!(a_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(a_coords).unwrap(), + ) .unwrap(); let b = candidate - .insert_vertex_with_mapping(vertex!(b_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(b_coords).unwrap(), + ) .unwrap(); let c = candidate - .insert_vertex_with_mapping(vertex!(c_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(c_coords).unwrap(), + ) .unwrap(); let d = candidate - .insert_vertex_with_mapping(vertex!(d_coords)) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(d_coords).unwrap(), + ) .unwrap(); let _c1 = candidate - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, c], None).unwrap(), + ) .unwrap(); let _c2 = candidate - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a, b, d], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut candidate).unwrap(); @@ -13016,7 +13571,7 @@ mod tests { #[test] fn test_repair_delaunay_with_flips_rejects_unsupported_dimension_1d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 1> = Tds::empty(); let kernel = AdaptiveKernel::::new(); let result = repair_delaunay_with_flips_k2_k3( @@ -13040,24 +13595,38 @@ mod tests { #[test] fn test_flip_k2_robust_kernel_near_degenerate_2d() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let d = tds - .insert_vertex_with_mapping(vertex!([1.0, 1e-9])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1e-9]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, d], None).unwrap()) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(c1, 2); + let facet = FacetHandle::from_validated(c1, 2); let context = build_k2_flip_context(&tds, facet).unwrap(); let _info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); @@ -13069,23 +13638,39 @@ mod tests { #[test] fn test_k2_flip_would_create_degenerate_simplex_degenerate() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); // a, c, d are collinear on the x-axis → replacement simplex {a,c,d} is degenerate - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([0.5, 0.0])).unwrap(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.0]).unwrap(), + ) + .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, d], None).unwrap()) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(c1, 2); + let facet = FacetHandle::from_validated(c1, 2); let context = build_k2_flip_context(&tds, facet).unwrap(); let degenerate = k2_flip_would_create_degenerate_simplex(&tds, &context).unwrap(); @@ -13100,22 +13685,38 @@ mod tests { #[test] fn test_k2_flip_would_create_degenerate_simplex_nondegenerate() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, d], None).unwrap()) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(c1, 2); + let facet = FacetHandle::from_validated(c1, 2); let context = build_k2_flip_context(&tds, facet).unwrap(); assert_context_has_nonzero_robust_orientation(&tds, &context); @@ -13127,37 +13728,51 @@ mod tests { #[test] fn test_flip_k4_4d_four_to_two() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 4> = Tds::empty(); let mut shared_vertices = Vec::with_capacity(4); for i in 0..4 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<4>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<4>(i)).unwrap(), + ) .unwrap(); shared_vertices.push(v); } - let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap(); - let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap(); + let opposite_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 4]).unwrap(), + ) + .unwrap(); + let opposite_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0; 4]).unwrap(), + ) + .unwrap(); let mut vertices_with_first_opposite = shared_vertices.clone(); vertices_with_first_opposite.push(opposite_a); let simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_first_opposite, None).unwrap(), + ) .unwrap(); let mut vertices_with_second_opposite = shared_vertices.clone(); vertices_with_second_opposite.push(opposite_b); let _simplex_b = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_second_opposite, None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(simplex_a, 4); + let facet = FacetHandle::from_validated(simplex_a, 4); let context = build_k2_flip_context(&tds, facet).unwrap(); let _info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); - let edge = EdgeKey::new(opposite_a, opposite_b); + let edge = EdgeKey::from_validated_endpoints(opposite_a, opposite_b); let context_back = build_k2_flip_context_from_edge(&tds, edge).unwrap(); let info_back = apply_bistellar_flip_dynamic(&mut tds, 4, &context_back).unwrap(); @@ -13171,22 +13786,28 @@ mod tests { #[test] fn test_flip_k5_4d_five_to_one() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let origin = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap(); + let mut tds: Tds<(), (), 4> = Tds::empty(); + let origin = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 4]).unwrap(), + ) + .unwrap(); let mut vertices = Vec::with_capacity(5); vertices.push(origin); for i in 0..4 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<4>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<4>(i)).unwrap(), + ) .unwrap(); vertices.push(v); } let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vertices, None).unwrap()) .unwrap(); - let new_vertex = vertex!([0.1; 4]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.1; 4]).unwrap(); let new_uuid = new_vertex.uuid(); let info = apply_bistellar_flip_k1(&mut tds, simplex_key, new_vertex).unwrap(); @@ -13206,53 +13827,74 @@ mod tests { #[test] fn test_flip_k4_5d_four_to_three() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 5> = Tds::empty(); let r0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, 0.5]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, a, b], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, b, c], None).unwrap(), + ) .unwrap(); let _c3 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, c, a], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let ridge = RidgeHandle::new(c1, 4, 5); + let ridge = RidgeHandle::from_validated(c1, 4, 5); let context = build_k3_flip_context(&tds, ridge).unwrap(); let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap(); assert_eq!(info.kind.k, 3); assert_eq!(info.inserted_face_vertices.len(), 3); - let triangle = TriangleHandle::new( + let triangle = TriangleHandle::try_new( info.inserted_face_vertices[0], info.inserted_face_vertices[1], info.inserted_face_vertices[2], - ); + ) + .unwrap(); let context_back = build_k3_flip_context_from_triangle(&tds, triangle).unwrap(); let info_back = apply_bistellar_flip_dynamic(&mut tds, 4, &context_back).unwrap(); @@ -13266,37 +13908,51 @@ mod tests { #[test] fn test_flip_k5_5d_five_to_two() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 5> = Tds::empty(); let mut shared_vertices = Vec::with_capacity(5); for i in 0..5 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<5>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<5>(i)).unwrap(), + ) .unwrap(); shared_vertices.push(v); } - let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 5])).unwrap(); - let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 5])).unwrap(); + let opposite_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 5]).unwrap(), + ) + .unwrap(); + let opposite_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0; 5]).unwrap(), + ) + .unwrap(); let mut vertices_with_first_opposite = shared_vertices.clone(); vertices_with_first_opposite.push(opposite_a); let simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_first_opposite, None).unwrap(), + ) .unwrap(); let mut vertices_with_second_opposite = shared_vertices.clone(); vertices_with_second_opposite.push(opposite_b); let _simplex_b = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_second_opposite, None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(simplex_a, 5); + let facet = FacetHandle::from_validated(simplex_a, 5); let context = build_k2_flip_context(&tds, facet).unwrap(); let _info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); - let edge = EdgeKey::new(opposite_a, opposite_b); + let edge = EdgeKey::from_validated_endpoints(opposite_a, opposite_b); let context_back = build_k2_flip_context_from_edge(&tds, edge).unwrap(); let info_back = apply_bistellar_flip_dynamic(&mut tds, 5, &context_back).unwrap(); @@ -13310,22 +13966,28 @@ mod tests { #[test] fn test_flip_k6_5d_six_to_one() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let origin = tds.insert_vertex_with_mapping(vertex!([0.0; 5])).unwrap(); + let mut tds: Tds<(), (), 5> = Tds::empty(); + let origin = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 5]).unwrap(), + ) + .unwrap(); let mut vertices = Vec::with_capacity(6); vertices.push(origin); for i in 0..5 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<5>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<5>(i)).unwrap(), + ) .unwrap(); vertices.push(v); } let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vertices, None).unwrap()) .unwrap(); - let new_vertex = vertex!([0.1; 5]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.1; 5]).unwrap(); let new_uuid = new_vertex.uuid(); let info = apply_bistellar_flip_k1(&mut tds, simplex_key, new_vertex).unwrap(); @@ -13344,16 +14006,28 @@ mod tests { #[test] fn test_flip_k1_2d_roundtrip() { init_tracing(); - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); - let new_vertex = vertex!([0.2, 0.2]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2]).unwrap(); let new_uuid = new_vertex.uuid(); let info = apply_bistellar_flip_k1(&mut tds, simplex, new_vertex).unwrap(); @@ -13374,33 +14048,47 @@ mod tests { #[test] fn test_repair_queue_inverse_k2_smoke_4d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 4> = Tds::empty(); let mut shared_vertices = Vec::with_capacity(4); for i in 0..4 { let v = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<4>(i))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<4>(i)).unwrap(), + ) .unwrap(); shared_vertices.push(v); } - let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap(); - let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap(); + let opposite_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 4]).unwrap(), + ) + .unwrap(); + let opposite_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0; 4]).unwrap(), + ) + .unwrap(); let mut vertices_with_first_opposite = shared_vertices.clone(); vertices_with_first_opposite.push(opposite_a); let simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_first_opposite, None).unwrap(), + ) .unwrap(); let mut vertices_with_second_opposite = shared_vertices.clone(); vertices_with_second_opposite.push(opposite_b); let _simplex_b = tds - .insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vertices_with_second_opposite, None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let facet = FacetHandle::new(simplex_a, 4); + let facet = FacetHandle::from_validated(simplex_a, 4); let context = build_k2_flip_context(&tds, facet).unwrap(); let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap(); @@ -13421,42 +14109,62 @@ mod tests { #[test] fn test_repair_queue_inverse_k3_smoke_5d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 5> = Tds::empty(); let r0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let r3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, 0.5]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, a, b], None).unwrap(), + ) .unwrap(); let _c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, b, c], None).unwrap(), + ) .unwrap(); let _c3 = tds - .insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![r0, r1, r2, r3, c, a], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); - let ridge = RidgeHandle::new(c1, 4, 5); + let ridge = RidgeHandle::from_validated(c1, 4, 5); let context = build_k3_flip_context(&tds, ridge).unwrap(); let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap(); @@ -14115,7 +14823,7 @@ mod tests { } fn insert_periodic_simplex_with_lifted_vertex( - tds: &mut Tds, + tds: &mut Tds<(), (), D>, vertices: Vec, lifted_vertex: VertexKey, ) -> SimplexKey { @@ -14123,31 +14831,31 @@ mod tests { if let Some(index) = vertices.iter().position(|&vkey| vkey == lifted_vertex) { offsets[index][0] = 1; } - let mut simplex = Simplex::new(vertices, None).unwrap(); + let mut simplex = Simplex::try_new_with_data(vertices, None).unwrap(); simplex.set_periodic_vertex_offsets(offsets).unwrap(); tds.insert_simplex_with_mapping(simplex).unwrap() } fn insert_periodic_simplex_with_offsets( - tds: &mut Tds, + tds: &mut Tds<(), (), D>, vertices: Vec, offsets: Vec<[i8; D]>, ) -> SimplexKey { - let mut simplex = Simplex::new(vertices, None).unwrap(); + let mut simplex = Simplex::try_new_with_data(vertices, None).unwrap(); simplex.set_periodic_vertex_offsets(offsets).unwrap(); tds.insert_simplex_with_mapping(simplex).unwrap() } fn insert_plain_simplex( - tds: &mut Tds, + tds: &mut Tds<(), (), D>, vertices: Vec, ) -> SimplexKey { - tds.insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap()) + tds.insert_simplex_with_mapping(Simplex::try_new_with_data(vertices, None).unwrap()) .unwrap() } fn periodic_helper_vertices( - tds: &mut Tds, + tds: &mut Tds<(), (), D>, count: usize, ) -> Vec { (0..count) @@ -14160,7 +14868,10 @@ mod tests { f64::from(u32::try_from(index + 2).expect("test index fits in u32")), coords[next_index], ); - tds.insert_vertex_with_mapping(vertex!(coords)).unwrap() + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + ) + .unwrap() }) .collect() } @@ -14170,9 +14881,9 @@ mod tests { pastey::paste! { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let lifted_vertex = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(0))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<$dim>(0)).unwrap()) .unwrap(); let mut simplex_vertices = Vec::with_capacity($dim + 1); simplex_vertices.push(lifted_vertex); @@ -14218,12 +14929,12 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let shared_vertex = tds - .insert_vertex_with_mapping(vertex!([0.0; $dim])) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([0.0; $dim]).unwrap()) .unwrap(); let lifted_vertex = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(0))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<$dim>(0)).unwrap()) .unwrap(); let mut target_vertices = Vec::with_capacity($dim + 1); @@ -14259,17 +14970,17 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let shared_a = tds - .insert_vertex_with_mapping(vertex!([0.0; $dim])) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([0.0; $dim]).unwrap()) .unwrap(); let mut shared_b_coords = [0.0; $dim]; shared_b_coords[0] = 0.2; let shared_b = tds - .insert_vertex_with_mapping(vertex!(shared_b_coords)) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(shared_b_coords).unwrap()) .unwrap(); let lifted_vertex = tds - .insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(0))) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::<$dim>(0)).unwrap()) .unwrap(); let mut target_vertices = Vec::with_capacity($dim + 1); @@ -14329,22 +15040,32 @@ mod tests { gen_periodic_lift_helper_tests!(5); fn periodic_inverse_k2_fixture() -> ( - Tds, + Tds<(), (), D>, Vec, VertexKey, VertexKey, SimplexKeyBuffer, ) { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), D> = Tds::empty(); let mut face_vertices = Vec::with_capacity(D); for axis in 0..D { face_vertices.push( - tds.insert_vertex_with_mapping(vertex!(unit_vector::(axis))) - .unwrap(), + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::(axis)).unwrap(), + ) + .unwrap(), ); } - let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; D])).unwrap(); - let opposite_b = tds.insert_vertex_with_mapping(vertex!([0.25; D])).unwrap(); + let opposite_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; D]).unwrap(), + ) + .unwrap(); + let opposite_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.25; D]).unwrap(), + ) + .unwrap(); let lifted_vertex = face_vertices[0]; let mut removed_simplices = SimplexKeyBuffer::new(); @@ -14374,25 +15095,35 @@ mod tests { } fn periodic_inverse_k3_fixture() -> ( - Tds, + Tds<(), (), D>, Vec, Vec, SimplexKeyBuffer, ) { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), D> = Tds::empty(); let mut ridge_vertices = Vec::with_capacity(D - 1); for axis in 0..(D - 1) { ridge_vertices.push( - tds.insert_vertex_with_mapping(vertex!(unit_vector::(axis))) - .unwrap(), + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::(axis)).unwrap(), + ) + .unwrap(), ); } - let a = tds.insert_vertex_with_mapping(vertex!([0.0; D])).unwrap(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0; D]).unwrap(), + ) + .unwrap(); let b = tds - .insert_vertex_with_mapping(vertex!(unit_vector::(D - 1))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(unit_vector::(D - 1)).unwrap(), + ) .unwrap(); let c = tds - .insert_vertex_with_mapping(vertex!(skewed_point::())) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(skewed_point::()).unwrap(), + ) .unwrap(); let triangle_vertices = vec![a, b, c]; @@ -14605,10 +15336,10 @@ mod tests { fn test_repair_run_full_reseed_preserves_mutation_frontier() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.2]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -14637,10 +15368,10 @@ mod tests { fn test_repair_k2_empty_seed_does_not_full_reseed() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.2]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -14668,10 +15399,10 @@ mod tests { fn test_repair_queue_k2_local_seed() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.2]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); diff --git a/src/core/algorithms/incremental_insertion.rs b/src/core/algorithms/incremental_insertion.rs index 08b09ef6..cf49fded 100644 --- a/src/core/algorithms/incremental_insertion.rs +++ b/src/core/algorithms/incremental_insertion.rs @@ -52,8 +52,8 @@ use crate::geometry::point::Point; use crate::geometry::predicates::Orientation; use crate::geometry::robust_predicates::robust_orientation; use crate::geometry::traits::coordinate::{ - CoordinateConversionError, CoordinateConversionValue, CoordinateScalar, CoordinateValues, - InvalidCoordinateValue, + CoordinateConversionError, CoordinateConversionValue, CoordinateValidationError, + CoordinateValues, DEFAULT_TOLERANCE_F64, InvalidCoordinateValue, }; use crate::validation::DelaunayTriangulationValidationError; use std::fmt; @@ -150,7 +150,7 @@ pub enum TdsValidationFailure { /// Coherent orientation was violated between adjacent simplices. #[error( "orientation invariant violated between simplices {simplex1_uuid} and {simplex2_uuid} \ - (facet indices {simplex1_facet_index}/{simplex2_facet_index}, vertex counts \ + (facet indices {simplex1_facet_index}/{simplex2_facet_index} counts \ {facet_vertex_count}/{simplex2_facet_vertex_count}, observed odd permutation \ {observed_odd_permutation}, expected {expected_odd_permutation})" )] @@ -876,6 +876,8 @@ pub enum InsertionErrorKind { MaxSimplicesRemovedExceeded, /// Spatial index construction failed. SpatialIndexConstruction, + /// A perturbation retry produced invalid coordinates. + PerturbedCoordinateInvalid, } /// Nested discriminant preserved by an [`InsertionErrorSummary`]. @@ -1023,6 +1025,9 @@ impl From for InsertionErrorSummary { InsertionError::SpatialIndexConstruction { .. } => { InsertionErrorKind::SpatialIndexConstruction } + InsertionError::PerturbedCoordinateInvalid { .. } => { + InsertionErrorKind::PerturbedCoordinateInvalid + } }; let source_kind = match &source { InsertionError::DelaunayValidationFailed { source } => { @@ -1444,6 +1449,21 @@ pub enum NeighborWiringError { }, } +fn ensure_neighbor_wiring_simplex_arity( + simplex_key: SimplexKey, + vertex_count: usize, +) -> Result<(), NeighborWiringError> { + if vertex_count == D + 1 { + return Ok(()); + } + + Err(NeighborWiringError::WrongSimplexArity { + simplex_key, + expected: D + 1, + found: vertex_count, + }) +} + /// Typed reason a spatial insertion index could not be constructed. /// /// # Examples @@ -1490,15 +1510,12 @@ pub enum SpatialIndexConstructionFailure { }, } -impl From> for SpatialIndexConstructionFailure -where - T: CoordinateScalar, -{ - fn from(source: HashGridIndexError) -> Self { +impl From for SpatialIndexConstructionFailure { + fn from(source: HashGridIndexError) -> Self { match source { HashGridIndexError::NonFiniteCellSize { value } => Self::NonFiniteCellSize { value }, HashGridIndexError::NonPositiveCellSize { value } => Self::NonPositiveCellSize { - value: CoordinateConversionValue::from_numeric_debug(&value), + value: CoordinateConversionValue::from_f64(value), }, } } @@ -1653,13 +1670,18 @@ pub enum InsertionError { #[source] reason: SpatialIndexConstructionFailure, }, + + /// A perturbation retry produced non-finite coordinates. + #[error("Perturbation retry produced invalid coordinates: {source}")] + PerturbedCoordinateInvalid { + /// Structured coordinate validation failure for the perturbed point. + #[source] + source: CoordinateValidationError, + }, } -impl From> for InsertionError -where - T: CoordinateScalar, -{ - fn from(source: HashGridIndexError) -> Self { +impl From for InsertionError { + fn from(source: HashGridIndexError) -> Self { Self::SpatialIndexConstruction { reason: source.into(), } @@ -1773,7 +1795,7 @@ impl InsertionError { Self::TopologyValidation(tds_err) => Self::is_tds_error_retryable(tds_err), // Conflict region errors: only geometry-degeneracy variants are retryable. // Structural variants (InvalidStartSimplex, PredicateError, SimplexDataAccessFailed, - // InternalInconsistency — regardless of which typed + // InvalidSimplexArity, MissingSimplexVertex, InternalInconsistency — regardless of which typed // `InternalInconsistencySite` carries the failure context) represent caller // or implementation errors that perturbation cannot fix, and so fall // through to non-retryable by omission. @@ -1812,7 +1834,8 @@ impl InsertionError { | Self::DuplicateCoordinates { .. } | Self::DuplicateUuid { .. } | Self::MaxSimplicesRemovedExceeded { .. } - | Self::SpatialIndexConstruction { .. } => false, + | Self::SpatialIndexConstruction { .. } + | Self::PerturbedCoordinateInvalid { .. } => false, } } @@ -2032,10 +2055,10 @@ impl InsertionError { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -2048,13 +2071,12 @@ impl InsertionError { /// # Ok(()) /// # } /// ``` -pub fn fill_cavity( - tds: &mut Tds, +pub fn fill_cavity( + tds: &mut Tds, new_vertex_key: VertexKey, boundary_facets: &[FacetHandle], ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2067,13 +2089,12 @@ where } /// Fills a replacement cavity using the transactional replacement insertion path. -pub(crate) fn fill_cavity_replacing_simplices( - tds: &mut Tds, +pub(crate) fn fill_cavity_replacing_simplices( + tds: &mut Tds, new_vertex_key: VertexKey, boundary_facets: &[FacetHandle], ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2086,13 +2107,12 @@ where } /// Fills a caller-validated cavity without per-simplex global insertion scans. -fn fill_cavity_with_prechecked_topology( - tds: &mut Tds, +fn fill_cavity_with_prechecked_topology( + tds: &mut Tds, new_vertex_key: VertexKey, boundary_facets: &[FacetHandle], ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2118,14 +2138,13 @@ enum CavityInsertionTopology { clippy::too_many_lines, reason = "Cavity filling includes detailed debug instrumentation and error handling" )] -fn fill_cavity_impl( - tds: &mut Tds, +fn fill_cavity_impl( + tds: &mut Tds, new_vertex_key: VertexKey, boundary_facets: &[FacetHandle], insertion_topology: CavityInsertionTopology, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2313,7 +2332,7 @@ where // Create and insert the new simplex let new_simplex = - Simplex::new(new_simplex_vertices, None).map_err(CavityFillingError::from)?; + Simplex::try_new(new_simplex_vertices).map_err(CavityFillingError::from)?; let simplex_key = match insertion_topology { CavityInsertionTopology::Checked => { tds.insert_simplex_with_mapping_trusted_vertices(new_simplex) @@ -2331,7 +2350,7 @@ where if std::env::var_os("DELAUNAY_DEBUG_CAVITY").is_some() && let Some(created_simplex) = tds.simplex(simplex_key) { - let simplex_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let simplex_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = created_simplex .vertices() .iter() @@ -2424,7 +2443,7 @@ where /// use delaunay::prelude::tds::Tds; /// /// # fn main() -> Result<(), InsertionError> { -/// let mut tds: Tds = Tds::empty(); +/// let mut tds: Tds<(), (), 3> = Tds::empty(); /// let new_simplices = SimplexKeyBuffer::new(); /// /// wire_cavity_neighbors(&mut tds, &new_simplices, [], None)?; @@ -2435,14 +2454,13 @@ where clippy::too_many_lines, reason = "Neighbor wiring keeps cohesive logic and debug accounting together" )] -pub fn wire_cavity_neighbors( - tds: &mut Tds, +pub fn wire_cavity_neighbors( + tds: &mut Tds, new_simplices: &SimplexKeyBuffer, external_facets: I, conflict_simplices: Option<&SimplexKeyBuffer>, ) -> Result<(), InsertionError> where - T: CoordinateScalar, U: DataType, V: DataType, I: IntoIterator, @@ -2466,15 +2484,7 @@ where .simplex(simplex_key) .ok_or(NeighborWiringError::MissingSimplex { simplex_key })?; let vertex_count = simplex.number_of_vertices(); - let expected = D + 1; - if vertex_count != expected { - return Err(NeighborWiringError::WrongSimplexArity { - simplex_key, - expected, - found: vertex_count, - } - .into()); - } + ensure_neighbor_wiring_simplex_arity::(simplex_key, vertex_count)?; for facet_idx in 0..vertex_count { let mut facet_vkeys = SmallBuffer::::new(); @@ -2783,13 +2793,12 @@ where /// /// This is used to wire new simplices to the pre-existing triangulation without performing a global /// scan over all simplices. -pub(crate) fn external_facets_for_boundary( - tds: &Tds, +pub(crate) fn external_facets_for_boundary( + tds: &Tds, internal_simplices: &SimplexKeyBuffer, boundary_facets: &[FacetHandle], ) -> Result, InsertionError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -2874,7 +2883,7 @@ where facet_index: facet_idx, max: u8::MAX, })?; - external_facets.push(FacetHandle::new(simplex_key, facet_idx_u8)); + external_facets.push(FacetHandle::from_validated(simplex_key, facet_idx_u8)); } } @@ -2882,8 +2891,8 @@ where } /// Helper: Set a single neighbor relationship -fn set_neighbor( - tds: &mut Tds, +fn set_neighbor( + tds: &mut Tds, simplex_key: SimplexKey, facet_idx: u8, neighbor: Option, @@ -2915,8 +2924,8 @@ where } /// Installs a fully assigned neighbor buffer and preserves typed simplex context on arity errors. -fn set_simplex_neighbors_from_keys( - simplex: &mut Simplex, +fn set_simplex_neighbors_from_keys( + simplex: &mut Simplex, neighbors: impl IntoIterator>, ) -> Result<(), InsertionError> { let simplex_id = simplex.uuid(); @@ -2944,14 +2953,7 @@ fn facet_hash_from_sorted_vertices(sorted_vkeys: &[VertexKey]) -> u64 { /// Compute a canonical hash for one simplex facet so local repair can match /// newly exposed facets without scanning the full triangulation. -fn facet_hash_for_simplex( - simplex: &Simplex, - facet_idx: usize, -) -> u64 -where - U: DataType, - V: DataType, -{ +fn facet_hash_for_simplex(simplex: &Simplex, facet_idx: usize) -> u64 { let mut facet_vkeys = SmallBuffer::::new(); for (i, &vkey) in simplex.vertices().iter().enumerate() { if i != facet_idx { @@ -2963,9 +2965,9 @@ where } /// Return whether `neighbor_key` is the simplex incident across `simplex`'s `facet_idx`. -fn neighbor_slot_points_across_facet( - tds: &Tds, - simplex: &Simplex, +fn neighbor_slot_points_across_facet( + tds: &Tds, + simplex: &Simplex, facet_idx: usize, neighbor_key: SimplexKey, ) -> bool @@ -3018,7 +3020,7 @@ where /// # Examples /// /// ```rust -/// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder, vertex}; +/// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::DelaunayTriangulationConstructionError; /// use delaunay::prelude::insertion::{ /// InsertionError, TdsMutationError, repair_neighbor_pointers_local, @@ -3035,10 +3037,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), -/// vertex!([1.0, 1.1]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.1]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 2> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3071,8 +3073,8 @@ where clippy::too_many_lines, reason = "Local neighbor repair keeps affected-set construction, facet indexing, and slot application together" )] -pub fn repair_neighbor_pointers_local( - tds: &mut Tds, +pub fn repair_neighbor_pointers_local( + tds: &mut Tds, seeds: &[SimplexKey], optional_external_simplices: Option<&[SimplexKey]>, ) -> Result @@ -3125,15 +3127,7 @@ where .ok_or(NeighborWiringError::MissingSimplex { simplex_key })?; let vertex_count = simplex.number_of_vertices(); - let expected = D + 1; - if vertex_count != expected { - return Err(NeighborWiringError::WrongSimplexArity { - simplex_key, - expected, - found: vertex_count, - } - .into()); - } + ensure_neighbor_wiring_simplex_arity::(simplex_key, vertex_count)?; for facet_idx in 0..vertex_count { let facet_hash = facet_hash_for_simplex(simplex, facet_idx); @@ -3274,7 +3268,6 @@ where /// ```rust /// use delaunay::prelude::construction::{ /// DelaunayTriangulation, DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, -/// vertex, /// }; /// use delaunay::prelude::insertion::{InsertionError, repair_neighbor_pointers}; /// @@ -3287,9 +3280,9 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 2> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3304,11 +3297,10 @@ where clippy::too_many_lines, reason = "Neighbor rebuild keeps facet indexing + wiring + application cohesive; prefer correctness and debuggability" )] -pub fn repair_neighbor_pointers( - tds: &mut Tds, +pub fn repair_neighbor_pointers( + tds: &mut Tds, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -3341,6 +3333,8 @@ where .ok_or(NeighborWiringError::MissingSimplex { simplex_key })?; let vertex_count = simplex.number_of_vertices(); + ensure_neighbor_wiring_simplex_arity::(simplex_key, vertex_count)?; + let mut neighbors = SmallBuffer::with_capacity(vertex_count); neighbors.resize(vertex_count, None); neighbors_by_simplex.insert(simplex_key, neighbors); @@ -3462,8 +3456,8 @@ where /// # Errors /// Returns `NeighborWiring` if a neighbor pointer references a missing simplex key. #[cfg(debug_assertions)] -fn validate_no_neighbor_cycles( - tds: &Tds, +fn validate_no_neighbor_cycles( + tds: &Tds, ) -> Result<(), InsertionError> where U: DataType, @@ -3529,8 +3523,8 @@ where /// Validate neighbor walks from a local affected set after partial pointer repair. #[cfg(debug_assertions)] -fn validate_no_neighbor_cycles_for_simplices( - tds: &Tds, +fn validate_no_neighbor_cycles_for_simplices( + tds: &Tds, sample_simplices: &FastHashSet, ) -> Result<(), InsertionError> where @@ -3595,8 +3589,8 @@ where /// Check mirror-facet symmetry for neighbor slots touched by local repair. #[cfg(debug_assertions)] -fn validate_neighbor_symmetry_for_simplices( - tds: &Tds, +fn validate_neighbor_symmetry_for_simplices( + tds: &Tds, affected_simplices: &FastHashSet, ) -> Result<(), InsertionError> where @@ -3678,22 +3672,25 @@ where /// use delaunay::prelude::geometry::Coordinate; /// use slotmap::Key; /// -/// let mut tds: Tds = Tds::empty(); +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let mut tds: Tds<(), (), 3> = Tds::empty(); /// let vkey = VertexKey::null(); /// let kernel = FastKernel::::new(); -/// let point = Point::new([2.0, 2.0, 2.0]); +/// let point = Point::try_from([2.0, 2.0, 2.0])?; /// /// let result = extend_hull(&mut tds, &kernel, vkey, &point); /// assert!(result.is_err()); +/// # Ok(()) +/// # } /// ``` pub fn extend_hull( - tds: &mut Tds, + tds: &mut Tds, kernel: &K, new_vertex_key: VertexKey, - point: &Point, + point: &Point, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -3789,12 +3786,11 @@ where Ok(new_simplices) } -fn find_boundary_edge_split_facet( - tds: &Tds, - point: &Point, +fn find_boundary_edge_split_facet( + tds: &Tds, + point: &Point, ) -> Result, InsertionError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -3803,7 +3799,7 @@ where } let mut match_facet: Option = None; - let tol = T::default_tolerance(); + let tol = DEFAULT_TOLERANCE_F64; let boundary_facets = tds .boundary_facets() @@ -3822,8 +3818,8 @@ where }, })?; - let mut edge_points = SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); - let mut opposite_point: Option> = None; + let mut edge_points = SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); + let mut opposite_point: Option> = None; for (i, &vkey) in simplex.vertices().iter().enumerate() { let vertex = tds @@ -3852,7 +3848,7 @@ where }, })?; - let mut simplex_points = SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); + let mut simplex_points = SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); simplex_points.extend(edge_points.iter().copied()); simplex_points.push(opposite_point); @@ -3870,7 +3866,7 @@ where continue; } - let mut edge_line = SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); + let mut edge_line = SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); edge_line.extend(edge_points.iter().copied()); edge_line.push(*point); @@ -3907,7 +3903,7 @@ where && p[1] <= max_y + tol; if on_segment { - let handle = FacetHandle::new(simplex_key, facet_index); + let handle = FacetHandle::from_validated(simplex_key, facet_index); if match_facet.is_some() { return Err(InsertionError::HullExtension { reason: HullExtensionReason::Other { @@ -3953,12 +3949,12 @@ where reason = "Visibility checks and diagnostic summaries are kept in a single routine" )] fn find_visible_boundary_facets( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, ) -> Result, InsertionError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -4017,9 +4013,8 @@ where })?; // Collect points for the simplex in canonical order: facet vertices + opposite vertex. - let mut simplex_points = - SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); - let mut opposite_point: Option> = None; + let mut simplex_points = SmallBuffer::, MAX_PRACTICAL_DIMENSION_SIZE>::new(); + let mut opposite_point: Option> = None; for (i, &vkey) in simplex.vertices().iter().enumerate() { let vertex = tds @@ -4072,7 +4067,7 @@ where let p0 = simplex_points[0].coords(); let p1 = simplex_points[1].coords(); let p = point.coords(); - let tol = K::Scalar::default_tolerance(); + let tol = DEFAULT_TOLERANCE_F64; let (min_x, max_x) = if p0[0] <= p1[0] { (p0[0], p1[0]) } else { @@ -4140,7 +4135,7 @@ where } #[cfg(debug_assertions)] if log_enabled && orientation_with_opposite == 0 && degenerate_facets.len() < 10 { - degenerate_facets.push(FacetHandle::new(simplex_key, facet_index)); + degenerate_facets.push(FacetHandle::from_validated(simplex_key, facet_index)); } #[cfg(debug_assertions)] if detail_enabled { @@ -4156,7 +4151,7 @@ where } if is_visible { - visible_facets.push(FacetHandle::new(simplex_key, facet_index)); + visible_facets.push(FacetHandle::from_validated(simplex_key, facet_index)); } } @@ -4530,16 +4525,16 @@ mod tests { use crate::core::validation::TopologyGuarantee; use crate::geometry::kernel::FastKernel; use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateConversionValue, + CoordinateConversionError, CoordinateConversionValue, CoordinateValidationError, + InvalidCoordinateValue, }; use crate::topology::characteristics::euler::TopologyClassification; - use crate::vertex; use slotmap::KeyData; use std::assert_matches; /// Return one mutual neighbor pair from a test TDS. - fn first_neighbor_pair( - tds: &Tds, + fn first_neighbor_pair( + tds: &Tds, ) -> Option<(SimplexKey, u8, SimplexKey, u8)> where U: DataType, @@ -4583,7 +4578,7 @@ mod tests { // Find the single simplex and create boundary facets (one per face) let simplex_key = tds.simplex_keys().next().unwrap(); let boundary_facets: Vec = (0..=$dim) - .map(|i| FacetHandle::new(simplex_key, i)) + .map(|i| FacetHandle::from_validated(simplex_key, i)) .collect(); // Verify expected number of facets @@ -4629,50 +4624,50 @@ mod tests { test_fill_cavity!( 2, vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ], - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), 3 // D+1 facets for a 2-simplex ); test_fill_cavity!( 3, vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ], - vertex!([0.25, 0.25, 0.25]), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), 4 // D+1 facets for a 3-simplex ); test_fill_cavity!( 4, vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ], - vertex!([0.2, 0.2, 0.2, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), 5 // D+1 facets for a 4-simplex ); test_fill_cavity!( 5, vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ], - vertex!([0.15, 0.15, 0.15, 0.15, 0.15]), + crate::core::vertex::Vertex::<(), _>::try_new([0.15, 0.15, 0.15, 0.15, 0.15]).unwrap(), 6 // D+1 facets for a 5-simplex ); @@ -4681,17 +4676,18 @@ mod tests { #[test] fn test_fill_cavity_with_invalid_vertex_key() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); let invalid_vkey = VertexKey::from(KeyData::from_ffi(u64::MAX)); let simplex_key = tds.simplex_keys().next().unwrap(); - let boundary_facets: Vec = - (0..=2).map(|i| FacetHandle::new(simplex_key, i)).collect(); + let boundary_facets: Vec = (0..=2) + .map(|i| FacetHandle::from_validated(simplex_key, i)) + .collect(); let result = fill_cavity(tds, invalid_vkey, &boundary_facets); assert!( @@ -4708,17 +4704,21 @@ mod tests { #[test] fn test_fill_cavity_with_invalid_facet_simplex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); - let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap(); + let new_vkey = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), + ) + .unwrap(); let invalid_simplex_key = SimplexKey::from(KeyData::from_ffi(u64::MAX)); let invalid_boundary_facets: Vec = (0..=2) - .map(|i| FacetHandle::new(invalid_simplex_key, i)) + .map(|i| FacetHandle::from_validated(invalid_simplex_key, i)) .collect(); let result = fill_cavity(tds, new_vkey, &invalid_boundary_facets); @@ -4734,17 +4734,21 @@ mod tests { #[test] fn test_fill_cavity_with_invalid_facet_index() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); - let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap(); + let new_vkey = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), + ) + .unwrap(); let simplex_key = tds.simplex_keys().next().unwrap(); let original_simplex_count = tds.number_of_simplices(); - let invalid_boundary_facets = vec![FacetHandle::new(simplex_key, 3)]; + let invalid_boundary_facets = vec![FacetHandle::from_validated(simplex_key, 3)]; let result = fill_cavity(tds, new_vkey, &invalid_boundary_facets); @@ -4764,9 +4768,9 @@ mod tests { #[test] fn test_wire_cavity_neighbors_with_invalid_simplices() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); @@ -4787,20 +4791,48 @@ mod tests { #[test] fn test_wire_cavity_neighbors_errors_on_unmatched_external_facet() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); - let v4 = tds.insert_vertex_with_mapping(vertex!([3.0, 2.0])).unwrap(); - let v5 = tds.insert_vertex_with_mapping(vertex!([2.0, 3.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); + let v4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 2.0]).unwrap(), + ) + .unwrap(); + let v5 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 3.0]).unwrap(), + ) + .unwrap(); let new_simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let external_simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![v3, v4, v5], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v3, v4, v5], None).unwrap(), + ) .unwrap(); let mut new_simplices = SimplexKeyBuffer::new(); @@ -4809,7 +4841,7 @@ mod tests { let err = wire_cavity_neighbors( &mut tds, &new_simplices, - [FacetHandle::new(external_simplex, 0)], + [FacetHandle::from_validated(external_simplex, 0)], None, ) .unwrap_err(); @@ -4828,25 +4860,47 @@ mod tests { #[test] fn test_wire_cavity_neighbors_errors_on_already_shared_external_facet() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let v4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + ) .unwrap(); - let v4 = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap(); let new_simplex_a = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let new_simplex_b = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v0, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v0, v3], None).unwrap(), + ) .unwrap(); let external_simplex = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v4], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(), ) .unwrap(); @@ -4857,7 +4911,7 @@ mod tests { let err = wire_cavity_neighbors( &mut tds, &new_simplices, - [FacetHandle::new(external_simplex, 2)], + [FacetHandle::from_validated(external_simplex, 2)], None, ) .unwrap_err(); @@ -4877,11 +4931,11 @@ mod tests { #[test] fn test_external_facets_for_boundary_errors_on_missing_internal_simplex() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); let missing_simplex = SimplexKey::from(KeyData::from_ffi(u64::MAX)); let mut internal_simplices = SimplexKeyBuffer::new(); internal_simplices.push(missing_simplex); - let boundary_facets = [FacetHandle::new(missing_simplex, 0)]; + let boundary_facets = [FacetHandle::from_validated(missing_simplex, 0)]; let err = external_facets_for_boundary(&tds, &internal_simplices, &boundary_facets).unwrap_err(); @@ -4896,13 +4950,27 @@ mod tests { #[test] fn test_external_facets_for_boundary_errors_on_missing_neighbor_simplex() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let missing_neighbor = SimplexKey::from(KeyData::from_ffi(u64::MAX - 1)); tds.simplex_mut(simplex_key) @@ -4912,7 +4980,7 @@ mod tests { let mut internal_simplices = SimplexKeyBuffer::new(); internal_simplices.push(simplex_key); - let boundary_facets = [FacetHandle::new(simplex_key, 0)]; + let boundary_facets = [FacetHandle::from_validated(simplex_key, 0)]; let err = external_facets_for_boundary(&tds, &internal_simplices, &boundary_facets).unwrap_err(); @@ -4928,18 +4996,38 @@ mod tests { #[test] fn test_external_facets_for_boundary_finds_shared_edge_only() { // Two triangles share one edge; only that edge should be returned as an external facet. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v0, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v0, v3], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); @@ -4948,7 +5036,9 @@ mod tests { internal_simplices.push(c1); // Internal set has a single simplex, so all its facets are boundary facets. - let boundary_facets: Vec = (0..=2).map(|i| FacetHandle::new(c1, i)).collect(); + let boundary_facets: Vec = (0..=2) + .map(|i| FacetHandle::from_validated(c1, i)) + .collect(); let external_facets = external_facets_for_boundary(&tds, &internal_simplices, &boundary_facets).unwrap(); @@ -4974,14 +5064,18 @@ mod tests { #[test] fn test_fill_cavity_with_empty_boundary_facets() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); - let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap(); + let new_vkey = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), + ) + .unwrap(); let empty_facets: Vec = vec![]; let result = fill_cavity(tds, new_vkey, &empty_facets); @@ -4992,15 +5086,19 @@ mod tests { #[test] fn test_fill_cavity_errors_on_boundary_simplex_wrong_vertex_count() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); // Insert a new vertex (apex) - let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap(); + let new_vkey = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), + ) + .unwrap(); // Corrupt the single boundary simplex by adding one extra vertex key. let simplex_key = tds.simplex_keys().next().unwrap(); @@ -5009,7 +5107,7 @@ mod tests { .unwrap() .push_vertex_key(extra_vkey); - let boundary_facets = vec![FacetHandle::new(simplex_key, 0)]; + let boundary_facets = vec![FacetHandle::from_validated(simplex_key, 0)]; let err = fill_cavity(tds, new_vkey, &boundary_facets).unwrap_err(); assert_matches!( @@ -5023,25 +5121,47 @@ mod tests { #[test] fn test_wire_cavity_neighbors_reports_non_manifold_topology() { // Three triangles sharing the same edge (v_a,v_b) is non-manifold in 2D. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v_d = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) .unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let c3 = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v_a, v_b, v_e], None).unwrap(), + Simplex::try_new_with_data(vec![v_a, v_b, v_e], None).unwrap(), ) .unwrap(); @@ -5064,9 +5184,9 @@ mod tests { fn test_wire_cavity_neighbors_errors_on_wrong_simplex_arity() { // Force a 2D simplex away from triangle arity so wiring reports the invariant directly. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); @@ -5168,6 +5288,24 @@ mod tests { assert!(!summary.is_retryable()); } + #[test] + fn perturbed_coordinate_error_summary_preserves_kind_and_retryability() { + let source = CoordinateValidationError::InvalidCoordinate { + coordinate_index: 0, + coordinate_value: InvalidCoordinateValue::Nan, + dimension: 2, + }; + let err = InsertionError::PerturbedCoordinateInvalid { source }; + + let summary = InsertionErrorSummary::from(err.clone()); + + assert_eq!(summary.kind, InsertionErrorKind::PerturbedCoordinateInvalid); + assert_eq!(summary.source_kind, None); + assert_eq!(summary.message, err.to_string()); + assert!(!summary.is_retryable()); + assert!(!err.is_retryable()); + } + #[test] fn initial_simplex_unexpected_stage_preserves_retryability() { let reason = CavityFillingError::InitialSimplexConstruction { @@ -5757,6 +5895,21 @@ mod tests { }) .is_retryable() ); + assert!( + !InsertionError::ConflictRegion(ConflictError::InvalidSimplexArity { + simplex_key: SimplexKey::from(KeyData::from_ffi(1)), + expected: 3, + found: 2, + }) + .is_retryable() + ); + assert!( + !InsertionError::ConflictRegion(ConflictError::MissingSimplexVertex { + simplex_key: SimplexKey::from(KeyData::from_ffi(1)), + vertex_key: VertexKey::from(KeyData::from_ffi(999)), + }) + .is_retryable() + ); // InternalInconsistency is not retryable regardless of the typed site: // perturbation cannot fix logic errors. assert!( @@ -5943,46 +6096,46 @@ mod tests { test_repair_neighbors!( 2, vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ] ); test_repair_neighbors!( 3, vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), ] ); test_repair_neighbors!( 4, vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), ] ); test_repair_neighbors!( 5, vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.15, 0.15, 0.15, 0.15, 0.15]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.15, 0.15, 0.15, 0.15, 0.15]).unwrap(), ] ); @@ -5990,10 +6143,10 @@ mod tests { fn test_repair_neighbor_pointers_reconstructs_missing_neighbors() { // Create a simple 2D triangulation with two triangles. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.1]), // break cocircular symmetry + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.1]).unwrap(), // break cocircular symmetry ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); @@ -6052,7 +6205,7 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let shared_vertices = $shared_facet_vertices; let opposite_vertices = $opposite_vertices; @@ -6066,7 +6219,7 @@ mod tests { let opposite_key = tds.insert_vertex_with_mapping(vertex).unwrap(); let mut vertices = shared_keys.clone(); vertices.push(opposite_key); - let simplex = Simplex::new(vertices, None).unwrap(); + let simplex = Simplex::try_new_with_data(vertices, None).unwrap(); let simplex_key = if idx < 2 { tds.insert_simplex_with_mapping(simplex) } else { @@ -6090,95 +6243,98 @@ mod tests { test_repair_neighbor_pointers_local_dimensions!( 2, vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.1]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.1]).unwrap(), ], - vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0])], vec![ - vertex!([0.0, 1.0]), - vertex!([0.0, -1.0]), - vertex!([2.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap() + ], + vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), ] ); test_repair_neighbor_pointers_local_dimensions!( 3, vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), ], vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), ], vec![ - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 0.0, -1.0]), - vertex!([1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, -1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ] ); test_repair_neighbor_pointers_local_dimensions!( 4, vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), ], vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), ], vec![ - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.0, 0.0, 0.0, -1.0]), - vertex!([1.0, 1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, -1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0, 1.0]).unwrap(), ] ); test_repair_neighbor_pointers_local_dimensions!( 5, vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.15, 0.15, 0.15, 0.15, 0.15]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.15, 0.15, 0.15, 0.15, 0.15]).unwrap(), ], vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), ], vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.0, 0.0, 0.0, 0.0, -1.0]), - vertex!([1.0, 1.0, 1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, -1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0, 1.0, 1.0]).unwrap(), ] ); #[test] fn test_repair_neighbor_pointers_local_replaces_stale_neighbor_slot() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.1]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.1]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); @@ -6202,25 +6358,53 @@ mod tests { #[test] fn test_repair_neighbor_pointers_local_replaces_wrong_live_neighbor_slot() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v_d = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_f = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 1.0]).unwrap(), + ) .unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap(); - let v_f = tds.insert_vertex_with_mapping(vertex!([2.0, 1.0])).unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let wrong_live_neighbor = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_e, v_f], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_e, v_f], None).unwrap(), + ) .unwrap(); let shared_facet_idx = 2usize; @@ -6246,11 +6430,11 @@ mod tests { #[test] fn test_repair_neighbor_pointers_local_does_not_scan_unseeded_simplices() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.1]), - vertex!([0.5, 0.35]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.1]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.35]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); @@ -6273,16 +6457,20 @@ mod tests { #[test] fn test_extend_hull_adds_simplices_for_exterior_vertex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); let kernel = FastKernel::::new(); - let p = Point::new([2.0, 2.0]); - let new_vkey = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let p = Point::from_validated_coords([2.0, 2.0]); + let new_vkey = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let new_simplices = extend_hull(tds, &kernel, new_vkey, &p).unwrap(); assert!(!new_simplices.is_empty()); @@ -6292,17 +6480,19 @@ mod tests { #[test] fn test_extend_hull_errors_when_no_visible_facets() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let tds = dt.tds_mut(); let kernel = FastKernel::::new(); - let p = Point::new([0.25, 0.25]); // inside + let p = Point::from_validated_coords([0.25, 0.25]); // inside let new_vkey = tds - .insert_vertex_with_mapping(vertex!([0.25, 0.25])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25]).unwrap(), + ) .unwrap(); let err = extend_hull(tds, &kernel, new_vkey, &p).unwrap_err(); @@ -6317,12 +6507,12 @@ mod tests { #[test] fn test_find_boundary_edge_split_facet_on_segment_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); - let point = Point::new([0.5, 0.0]); // on boundary edge + let point = Point::from_validated_coords([0.5, 0.0]); // on boundary edge let facet = find_boundary_edge_split_facet(dt.tds(), &point).unwrap(); assert!(facet.is_some()); @@ -6331,12 +6521,12 @@ mod tests { #[test] fn test_find_boundary_edge_split_facet_off_segment_returns_none_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); - let point = Point::new([2.0, 0.0]); // collinear with an edge line, outside segment + let point = Point::from_validated_coords([2.0, 0.0]); // collinear with an edge line, outside segment let facet = find_boundary_edge_split_facet(dt.tds(), &point).unwrap(); assert!(facet.is_none()); @@ -6345,13 +6535,13 @@ mod tests { #[test] fn test_find_visible_boundary_facets_inside_returns_empty_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let kernel = FastKernel::::new(); - let point = Point::new([0.2, 0.2]); // inside simplex + let point = Point::from_validated_coords([0.2, 0.2]); // inside simplex let visible = find_visible_boundary_facets(dt.tds(), &kernel, &point).unwrap(); assert!(visible.is_empty()); @@ -6360,13 +6550,13 @@ mod tests { #[test] fn test_find_visible_boundary_facets_outside_returns_non_empty_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap(); let kernel = FastKernel::::new(); - let point = Point::new([3.0, 3.0]); // clearly outside + let point = Point::from_validated_coords([3.0, 3.0]); // clearly outside let visible = find_visible_boundary_facets(dt.tds(), &kernel, &point).unwrap(); assert!(!visible.is_empty()); @@ -6375,7 +6565,7 @@ mod tests { #[test] fn test_set_neighbor_errors_on_missing_simplex() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); let missing = SimplexKey::from(KeyData::from_ffi(u64::MAX)); let err = set_neighbor(&mut tds, missing, 0, None).unwrap_err(); @@ -6389,12 +6579,26 @@ mod tests { #[test] fn test_set_neighbor_errors_on_invalid_facet_index() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let err = set_neighbor(&mut tds, simplex_key, 3, None).unwrap_err(); @@ -6412,7 +6616,7 @@ mod tests { #[test] fn test_external_facets_for_boundary_empty_inputs_returns_empty() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); let internal_simplices = SimplexKeyBuffer::new(); let boundary_facets: Vec = Vec::new(); @@ -6423,7 +6627,7 @@ mod tests { #[test] fn test_repair_neighbor_pointers_empty_tds_returns_zero() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); let fixed = repair_neighbor_pointers(&mut tds).unwrap(); assert_eq!(fixed, 0); } diff --git a/src/core/algorithms/locate.rs b/src/core/algorithms/locate.rs index 65692fb9..77cfdc97 100644 --- a/src/core/algorithms/locate.rs +++ b/src/core/algorithms/locate.rs @@ -26,10 +26,13 @@ use crate::core::collections::{ use crate::core::facet::FacetHandle; use crate::core::tds::{SimplexKey, Tds, VertexKey}; use crate::core::traits::data_type::DataType; -use crate::core::util::canonical_points::{sorted_facet_points_with_extra, sorted_simplex_points}; +use crate::core::util::canonical_points::{ + CanonicalFacetPointError, CanonicalSimplexPointError, sorted_facet_points_with_extra, + sorted_simplex_points, +}; use crate::geometry::kernel::Kernel; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar}; +use crate::geometry::traits::coordinate::CoordinateConversionError; use std::env; use std::fmt::{self, Write as _}; use std::hash::{Hash, Hasher}; @@ -117,6 +120,39 @@ pub enum LocateError { simplex_key: SimplexKey, }, + /// A simplex has the wrong number of vertices for this dimension. + #[error("simplex {simplex_key:?} has {found} vertices for point location; expected {expected}")] + InvalidSimplexArity { + /// Simplex with invalid arity. + simplex_key: SimplexKey, + /// Expected simplex vertex count. + expected: usize, + /// Observed simplex vertex count. + found: usize, + }, + + /// A facet index is outside the simplex's facet range. + #[error( + "facet index {facet_index} is invalid for simplex {simplex_key:?} with {facet_count} facets" + )] + InvalidFacetIndex { + /// Simplex containing the requested facet. + simplex_key: SimplexKey, + /// Requested facet index. + facet_index: usize, + /// Number of facets available on the simplex. + facet_count: usize, + }, + + /// A simplex references a vertex missing from the TDS. + #[error("simplex {simplex_key:?} references missing vertex {vertex_key:?} for point location")] + MissingSimplexVertex { + /// Simplex containing the missing vertex reference. + simplex_key: SimplexKey, + /// Missing vertex key. + vertex_key: VertexKey, + }, + /// Geometric predicate failed. #[error("Predicate error: {source}")] PredicateError { @@ -172,6 +208,30 @@ pub enum ConflictError { message: String, }, + /// A conflict-region simplex has the wrong number of vertices for this dimension. + #[error( + "simplex {simplex_key:?} has {found} vertices for conflict-region predicates; expected {expected}" + )] + InvalidSimplexArity { + /// Simplex with invalid arity. + simplex_key: SimplexKey, + /// Expected simplex vertex count. + expected: usize, + /// Observed simplex vertex count. + found: usize, + }, + + /// A conflict-region simplex references a vertex missing from the TDS. + #[error( + "simplex {simplex_key:?} references missing vertex {vertex_key:?} for conflict-region predicates" + )] + MissingSimplexVertex { + /// Simplex containing the missing vertex reference. + simplex_key: SimplexKey, + /// Missing vertex key. + vertex_key: VertexKey, + }, + /// Internal invariant violation during cavity-boundary extraction. /// /// This is raised when an invariant that must hold by construction does not — @@ -191,7 +251,7 @@ pub enum ConflictError { /// as a typed payload so callers can pattern-match without parsing strings. /// /// [`InsertionError::is_retryable`]: - /// crate::core::algorithms::incremental_insertion::InsertionError::is_retryable + /// crate::prelude::insertion::InsertionError::is_retryable #[error("Internal cavity-boundary inconsistency: {site}")] InternalInconsistency { /// Structured, typed description of the violated invariant — the index, @@ -404,12 +464,8 @@ struct RidgeInfo { extra_facets: Vec, } -fn format_vertex_refs( - tds: &Tds, - vertex_keys: &[VertexKey], -) -> String +fn format_vertex_refs(tds: &Tds, vertex_keys: &[VertexKey]) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -427,12 +483,8 @@ where refs } -fn format_facet_vertices( - tds: &Tds, - handle: FacetHandle, -) -> String +fn format_facet_vertices(tds: &Tds, handle: FacetHandle) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -450,12 +502,11 @@ where format_vertex_refs(tds, &vertex_keys) } -fn format_simplex_vertices( - tds: &Tds, +fn format_simplex_vertices( + tds: &Tds, simplex_key: SimplexKey, ) -> String where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -475,14 +526,13 @@ where /// /// The snapshot captures the shared ridge vertices, the participating boundary /// facets, and the extra simplices that cavity reduction would remove. -fn log_first_ridge_fan_dump( - tds: &Tds, +fn log_first_ridge_fan_dump( + tds: &Tds, conflict_simplices: &SimplexKeyBuffer, boundary_facets: &CavityBoundaryBuffer, info: &RidgeInfo, extra_simplices: &[SimplexKey], ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -725,21 +775,23 @@ pub(crate) struct LocateTrace { /// # Locate(#[from] delaunay::prelude::algorithms::LocateError), /// # #[error(transparent)] /// # Conflict(#[from] delaunay::prelude::algorithms::ConflictError), +/// # #[error(transparent)] +/// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 4D simplex (5 vertices) /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let kernel = FastKernel::::new(); /// /// // Point inside the 4-simplex -/// let inside_point = Point::new([0.2, 0.2, 0.2, 0.2]); +/// let inside_point = Point::try_from([0.2, 0.2, 0.2, 0.2])?; /// let inside = locate(dt.tds(), &kernel, &inside_point, None)?; /// std::assert_matches!( /// inside, @@ -747,7 +799,7 @@ pub(crate) struct LocateTrace { /// ); /// /// // Point outside the convex hull -/// let outside_point = Point::new([2.0, 2.0, 2.0, 2.0]); +/// let outside_point = Point::try_from([2.0, 2.0, 2.0, 2.0])?; /// let outside = locate(dt.tds(), &kernel, &outside_point, None)?; /// std::assert_matches!(outside, LocateResult::Outside); /// # Ok(()) @@ -769,15 +821,17 @@ pub(crate) struct LocateTrace { /// # Locate(#[from] delaunay::prelude::algorithms::LocateError), /// # #[error(transparent)] /// # Conflict(#[from] delaunay::prelude::algorithms::ConflictError), +/// # #[error(transparent)] +/// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 4D simplex /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let kernel = RobustKernel::::default(); @@ -786,7 +840,7 @@ pub(crate) struct LocateTrace { /// let Some(hint_simplex) = dt.tds().simplex_keys().next() else { /// return Ok(()); /// }; -/// let query_point = Point::new([0.15, 0.15, 0.15, 0.15]); +/// let query_point = Point::try_from([0.15, 0.15, 0.15, 0.15])?; /// /// let located = locate(dt.tds(), &kernel, &query_point, Some(hint_simplex))?; /// std::assert_matches!(located, LocateResult::InsideSimplex(_)); @@ -794,13 +848,13 @@ pub(crate) struct LocateTrace { /// # } /// ``` pub fn locate( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, hint: Option, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -832,18 +886,20 @@ where /// # Locate(#[from] delaunay::prelude::algorithms::LocateError), /// # #[error(transparent)] /// # Conflict(#[from] delaunay::prelude::algorithms::ConflictError), +/// # #[error(transparent)] +/// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 2> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let kernel = FastKernel::::new(); /// -/// let query_point = Point::new([0.3, 0.3]); +/// let query_point = Point::try_from([0.3, 0.3])?; /// let (_result, stats) = locate_with_stats(dt.tds(), &kernel, &query_point, None)?; /// /// // In well-conditioned cases, the facet-walk should converge without falling back. @@ -852,13 +908,13 @@ where /// # } /// ``` pub fn locate_with_stats( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, hint: Option, ) -> Result<(LocateResult, LocateStats), LocateError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -872,13 +928,13 @@ where /// before the algorithm concluded. For [`LocateResult::Outside`] without a scan /// fallback, that simplex is adjacent to the hull facet crossed by the query point. pub(crate) fn locate_with_trace( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, hint: Option, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -931,11 +987,11 @@ where })?; let facet_count = simplex.number_of_vertices(); + ensure_locate_simplex_arity::(current_simplex, facet_count)?; let mut found_outside_facet = false; for facet_idx in 0..facet_count { - if is_point_outside_facet(tds, kernel, current_simplex, facet_idx, point)? == Some(true) - { + if is_point_outside_facet(tds, kernel, current_simplex, facet_idx, point)? { if let Some(neighbor_key) = simplex.neighbor_key(facet_idx).flatten() { current_simplex = neighbor_key; found_outside_facet = true; @@ -971,21 +1027,22 @@ where } pub(crate) fn locate_by_scan( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { for (simplex_key, simplex) in tds.simplices() { let mut found_outside_facet = false; let facet_count = simplex.number_of_vertices(); + ensure_locate_simplex_arity::(simplex_key, facet_count)?; for facet_idx in 0..facet_count { - if is_point_outside_facet(tds, kernel, simplex_key, facet_idx, point)? == Some(true) { + if is_point_outside_facet(tds, kernel, simplex_key, facet_idx, point)? { found_outside_facet = true; break; } @@ -1011,29 +1068,31 @@ where /// - `facet_idx` refers to both the facet AND the vertex opposite to that facet /// - `simplex.vertices()[facet_idx]` is the vertex opposite the facet /// - The facet consists of all vertices EXCEPT `vertices[facet_idx]` -/// - This invariant is documented in [`Simplex`](crate::core::simplex::Simplex) and enforced by -/// [`Tds::assign_neighbors`](crate::core::tds::Tds::assign_neighbors). +/// - This invariant is documented in [`Simplex`](crate::prelude::tds::Simplex) and enforced by +/// [`Tds::assign_neighbors`](crate::prelude::tds::Tds::assign_neighbors). /// /// It is validated as part of Level 2 structural validation via -/// [`Tds::is_valid`](crate::core::tds::Tds::is_valid) -/// (or cumulatively via [`Tds::validate`](crate::core::tds::Tds::validate)). +/// [`Tds::is_valid`](crate::prelude::tds::Tds::is_valid) +/// (or cumulatively via [`Tds::validate`](crate::prelude::tds::Tds::validate)). /// /// This correspondence is essential for the canonical ordering used in orientation tests. /// If this invariant is violated, point location will produce incorrect results. /// -/// Returns: -/// - `Some(true)` if point is outside (should cross facet) -/// - `Some(false)` if point is inside (should not cross facet) -/// - `None` for degenerate cases +/// Returns `true` if the point is outside the facet and `false` otherwise. +/// +/// # Errors +/// +/// Returns [`LocateError`] if the simplex or facet cannot provide valid +/// orientation predicate input. fn is_point_outside_facet( - tds: &Tds, + tds: &Tds, kernel: &K, simplex_key: SimplexKey, facet_idx: usize, - query_point: &Point, -) -> Result, LocateError> + query_point: &Point, +) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -1042,19 +1101,26 @@ where .ok_or(LocateError::InvalidSimplex { simplex_key })?; let simplex_vertex_keys = simplex.vertices(); - if simplex_vertex_keys.len() != D + 1 { - return Ok(None); // Degenerate simplex - } - - if facet_idx > D { - return Ok(None); // Out-of-range facet index + let vertex_count = simplex_vertex_keys.len(); + ensure_locate_simplex_arity::(simplex_key, vertex_count)?; + + if facet_idx >= vertex_count { + return Err(LocateError::InvalidFacetIndex { + simplex_key, + facet_index: facet_idx, + facet_count: vertex_count, + }); } // The vertex at facet_idx is opposite the facet let opposite_key = simplex_vertex_keys[facet_idx]; - let Some(opposite_point) = tds.vertex(opposite_key).map(|v| *v.point()) else { - return Ok(None); // Unresolvable vertex → degenerate simplex - }; + let opposite_point = *tds + .vertex(opposite_key) + .ok_or(LocateError::MissingSimplexVertex { + simplex_key, + vertex_key: opposite_key, + })? + .point(); // Facet keys: all vertex keys except the one at facet_idx let facet_keys: SmallBuffer = simplex_vertex_keys @@ -1065,11 +1131,8 @@ where .collect(); // Build facet simplex + opposite vertex in canonical key order. - // If any facet vertex is unresolvable, treat as degenerate. - let Some(canonical_simplex) = sorted_facet_points_with_extra(tds, &facet_keys, opposite_point) - else { - return Ok(None); - }; + let canonical_simplex = sorted_facet_points_with_extra(tds, &facet_keys, opposite_point) + .map_err(|error| locate_facet_points_error(simplex_key, error))?; let simplex_orientation = kernel.orientation(&canonical_simplex)?; @@ -1084,7 +1147,64 @@ where // If orientations differ, query point and opposite vertex are on // opposite sides of the facet → point is "outside" (should cross) // If orientations match, they're on the same side → point is "inside" (should not cross) - Ok(Some(simplex_orientation * query_orientation < 0)) + Ok(simplex_orientation * query_orientation < 0) +} + +const fn ensure_locate_simplex_arity( + simplex_key: SimplexKey, + vertex_count: usize, +) -> Result<(), LocateError> { + if vertex_count == D + 1 { + return Ok(()); + } + + Err(LocateError::InvalidSimplexArity { + simplex_key, + expected: D + 1, + found: vertex_count, + }) +} + +const fn locate_facet_points_error( + simplex_key: SimplexKey, + error: CanonicalFacetPointError, +) -> LocateError { + match error { + CanonicalFacetPointError::InvalidArity { expected, found } => { + LocateError::InvalidSimplexArity { + simplex_key, + expected: expected + 1, + found: found + 1, + } + } + CanonicalFacetPointError::MissingVertex { vertex_key } => { + LocateError::MissingSimplexVertex { + simplex_key, + vertex_key, + } + } + } +} + +const fn conflict_simplex_points_error( + simplex_key: SimplexKey, + error: CanonicalSimplexPointError, +) -> ConflictError { + match error { + CanonicalSimplexPointError::InvalidArity { expected, found } => { + ConflictError::InvalidSimplexArity { + simplex_key, + expected, + found, + } + } + CanonicalSimplexPointError::MissingVertex { vertex_key } => { + ConflictError::MissingSimplexVertex { + simplex_key, + vertex_key, + } + } + } } /// Find all simplices whose circumspheres contain the query point (conflict region). @@ -1126,7 +1246,6 @@ where /// use delaunay::prelude::geometry::FastKernel; /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; -/// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1136,21 +1255,23 @@ where /// # Locate(#[from] delaunay::prelude::algorithms::LocateError), /// # #[error(transparent)] /// # Conflict(#[from] delaunay::prelude::algorithms::ConflictError), +/// # #[error(transparent)] +/// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 4D simplex (5 vertices forming a 4-simplex) /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// let kernel = FastKernel::::new(); /// // Point inside the 4-simplex -/// let query_point = Point::new([0.2, 0.2, 0.2, 0.2]); +/// let query_point = Point::try_from([0.2, 0.2, 0.2, 0.2])?; /// /// // First locate the point /// let location = locate(dt.tds(), &kernel, &query_point, None)?; @@ -1167,13 +1288,13 @@ where reason = "function is long due to complex locate logic and should be split when refactoring" )] pub fn find_conflict_region( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, start_simplex: SimplexKey, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -1239,19 +1360,8 @@ where // Collect simplex vertex points in canonical VertexKey order for consistent // SoS perturbation priority. - let simplex_points = sorted_simplex_points(tds, simplex).ok_or_else(|| { - ConflictError::SimplexDataAccessFailed { - simplex_key, - message: format!("Failed to resolve all {} simplex vertices", D + 1), - } - })?; - - if simplex_points.len() != D + 1 { - return Err(ConflictError::SimplexDataAccessFailed { - simplex_key, - message: format!("Expected {} vertices, got {}", D + 1, simplex_points.len()), - }); - } + let simplex_points = sorted_simplex_points(tds, simplex) + .map_err(|error| conflict_simplex_points_error(simplex_key, error))?; #[cfg(debug_assertions)] if debug_config.log_conflict { @@ -1372,9 +1482,9 @@ where /// use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate, Point}; /// use delaunay::prelude::tds::Tds; /// -/// let tds: Tds = Tds::empty(); +/// let tds: Tds<(), (), 2> = Tds::empty(); /// let kernel = AdaptiveKernel::::new(); -/// let point = Point::new([0.25, 0.25]); +/// let point = Point::try_from([0.25, 0.25])?; /// let bfs_conflicts = SimplexKeyBuffer::new(); /// /// let missed = verify_conflict_region_completeness( @@ -1389,13 +1499,13 @@ where #[cfg(any(feature = "diagnostics", all(test, debug_assertions)))] #[cfg_attr(docsrs, doc(cfg(feature = "diagnostics")))] pub fn verify_conflict_region_completeness( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, bfs_conflict_simplices: &SimplexKeyBuffer, ) -> usize where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -1406,20 +1516,19 @@ where let mut predicate_errors = 0usize; for (simplex_key, simplex) in tds.simplices() { - let Some(simplex_points) = sorted_simplex_points(tds, simplex) else { - malformed_simplices += 1; - tracing::debug!( - simplex_key = ?simplex_key, - vertex_keys = ?simplex.vertices(), - "verify_conflict_region: skipping malformed simplex (sorted_simplex_points returned None)" - ); - continue; + let simplex_points = match sorted_simplex_points(tds, simplex) { + Ok(points) => points, + Err(error) => { + malformed_simplices += 1; + tracing::debug!( + simplex_key = ?simplex_key, + vertex_keys = ?simplex.vertices(), + error = %error, + "verify_conflict_region: skipping malformed simplex" + ); + continue; + } }; - if simplex_points.len() != D + 1 { - malformed_simplices += 1; - continue; - } - let Ok(sign) = kernel.in_sphere(&simplex_points, point) else { predicate_errors += 1; tracing::debug!( @@ -1542,7 +1651,7 @@ where /// use delaunay::prelude::tds::Tds; /// /// # fn main() -> Result<(), delaunay::prelude::algorithms::ConflictError> { -/// let tds: Tds = Tds::empty(); +/// let tds: Tds<(), (), 3> = Tds::empty(); /// let boundary = extract_cavity_boundary(&tds, &SimplexKeyBuffer::new())?; /// assert!(boundary.is_empty()); /// # Ok(()) @@ -1556,7 +1665,6 @@ where /// use delaunay::prelude::geometry::FastKernel; /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; -/// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1566,20 +1674,22 @@ where /// # Locate(#[from] delaunay::prelude::algorithms::LocateError), /// # #[error(transparent)] /// # Conflict(#[from] delaunay::prelude::algorithms::ConflictError), +/// # #[error(transparent)] +/// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 4D simplex /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// let kernel = FastKernel::::new(); -/// let query_point = Point::new([0.2, 0.2, 0.2, 0.2]); +/// let query_point = Point::try_from([0.2, 0.2, 0.2, 0.2])?; /// /// // Locate and find conflict region /// let location = locate(dt.tds(), &kernel, &query_point, None)?; @@ -1599,12 +1709,11 @@ where clippy::too_many_lines, reason = "Long function; keep boundary extraction logic in one place for clarity" )] -pub fn extract_cavity_boundary( - tds: &Tds, +pub fn extract_cavity_boundary( + tds: &Tds, conflict_simplices: &SimplexKeyBuffer, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -1687,7 +1796,7 @@ where facet_to_conflict .entry(facet_hash) .or_default() - .push(FacetHandle::new(simplex_key, facet_idx_u8)); + .push(FacetHandle::from_validated(simplex_key, facet_idx_u8)); } } @@ -1709,7 +1818,7 @@ where let facet_idx_u8 = handle.facet_index(); let boundary_facet_idx = boundary_facets.len(); - boundary_facets.push(FacetHandle::new(simplex_key, facet_idx_u8)); + boundary_facets.push(FacetHandle::from_validated(simplex_key, facet_idx_u8)); #[cfg(debug_assertions)] { boundary_facet_count = boundary_facet_count.saturating_add(1); @@ -2028,9 +2137,7 @@ mod tests { use crate::core::collections::NeighborBuffer; use crate::core::simplex::Simplex; use crate::geometry::kernel::{FastKernel, RobustKernel}; - use crate::geometry::traits::coordinate::Coordinate; use crate::prelude::DelaunayTriangulation; - use crate::vertex; use slotmap::KeyData; use std::assert_matches; @@ -2074,9 +2181,9 @@ mod tests { #[test] fn test_format_vertex_and_simplex_references_include_missing_markers() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tds = dt.tds(); @@ -2091,7 +2198,7 @@ mod tests { let formatted_missing = format_vertex_refs(tds, &[missing_vertex]); assert!(formatted_missing.contains("missing")); - let facet = FacetHandle::new(simplex_key, 0); + let facet = FacetHandle::from_validated(simplex_key, 0); let formatted_facet = format_facet_vertices(tds, facet); assert!(formatted_facet.contains("VertexKey")); @@ -2100,7 +2207,7 @@ mod tests { let missing_simplex = SimplexKey::from(KeyData::from_ffi(999_999)); assert_eq!( - format_facet_vertices(tds, FacetHandle::new(missing_simplex, 0)), + format_facet_vertices(tds, FacetHandle::from_validated(missing_simplex, 0)), "" ); assert_eq!( @@ -2116,11 +2223,11 @@ mod tests { let simplex_c = SimplexKey::from(KeyData::from_ffi(3)); let simplex_d = SimplexKey::from(KeyData::from_ffi(4)); let boundary_facets: CavityBoundaryBuffer = [ - FacetHandle::new(simplex_a, 0), - FacetHandle::new(simplex_b, 1), - FacetHandle::new(simplex_c, 2), - FacetHandle::new(simplex_c, 3), - FacetHandle::new(simplex_d, 0), + FacetHandle::from_validated(simplex_a, 0), + FacetHandle::from_validated(simplex_b, 1), + FacetHandle::from_validated(simplex_c, 2), + FacetHandle::from_validated(simplex_c, 3), + FacetHandle::from_validated(simplex_d, 0), ] .into_iter() .collect(); @@ -2139,57 +2246,111 @@ mod tests { } #[test] + #[expect( + clippy::too_many_lines, + reason = "regression test spells out the multi-fan cavity fixture" + )] fn test_extract_cavity_boundary_accumulates_multiple_ridge_fans_2d() { - let mut tds: Tds = Tds::empty(); - let center_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let a0 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let a1 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let center_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let a0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let a1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let a2 = tds - .insert_vertex_with_mapping(vertex!([-1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, 0.0]).unwrap(), + ) .unwrap(); let a3 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let a4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) .unwrap(); - let a4 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); let a5 = tds - .insert_vertex_with_mapping(vertex!([-1.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, -1.0]).unwrap(), + ) .unwrap(); let center_b = tds - .insert_vertex_with_mapping(vertex!([10.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0]).unwrap(), + ) .unwrap(); let b0 = tds - .insert_vertex_with_mapping(vertex!([11.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 0.0]).unwrap(), + ) .unwrap(); let b1 = tds - .insert_vertex_with_mapping(vertex!([10.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 1.0]).unwrap(), + ) + .unwrap(); + let b2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([9.0, 0.0]).unwrap(), + ) .unwrap(); - let b2 = tds.insert_vertex_with_mapping(vertex!([9.0, 0.0])).unwrap(); let b3 = tds - .insert_vertex_with_mapping(vertex!([10.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, -1.0]).unwrap(), + ) .unwrap(); let b4 = tds - .insert_vertex_with_mapping(vertex!([11.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 1.0]).unwrap(), + ) .unwrap(); let b5 = tds - .insert_vertex_with_mapping(vertex!([9.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([9.0, -1.0]).unwrap(), + ) .unwrap(); let origin_simplices = [ - tds.insert_simplex_with_mapping(Simplex::new(vec![center_a, a0, a1], None).unwrap()) - .unwrap(), - tds.insert_simplex_with_mapping(Simplex::new(vec![center_a, a2, a3], None).unwrap()) - .unwrap(), - tds.insert_simplex_with_mapping(Simplex::new(vec![center_a, a4, a5], None).unwrap()) - .unwrap(), + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center_a, a0, a1], None).unwrap(), + ) + .unwrap(), + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center_a, a2, a3], None).unwrap(), + ) + .unwrap(), + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center_a, a4, a5], None).unwrap(), + ) + .unwrap(), ]; let shifted_simplices = [ - tds.insert_simplex_with_mapping(Simplex::new(vec![center_b, b0, b1], None).unwrap()) - .unwrap(), - tds.insert_simplex_with_mapping(Simplex::new(vec![center_b, b2, b3], None).unwrap()) - .unwrap(), - tds.insert_simplex_with_mapping(Simplex::new(vec![center_b, b4, b5], None).unwrap()) - .unwrap(), + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center_b, b0, b1], None).unwrap(), + ) + .unwrap(), + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center_b, b2, b3], None).unwrap(), + ) + .unwrap(), + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center_b, b4, b5], None).unwrap(), + ) + .unwrap(), ]; let all_simplices = [ @@ -2225,9 +2386,9 @@ mod tests { // Triangle: (0,0), (1,0), (0,1) // Point inside: (0.3, 0.3) let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); @@ -2237,7 +2398,7 @@ mod tests { let simplex = dt.tds().simplex(simplex_key).unwrap(); // Get simplex vertices in order - let simplex_points: Vec> = simplex + let simplex_points: Vec> = simplex .vertices() .iter() .map(|&vkey| *dt.tds().vertex(vkey).unwrap().point()) @@ -2250,13 +2411,13 @@ mod tests { println!("Simplex orientation: {simplex_orientation}"); // Test query point inside - let query_inside = Point::new([0.3, 0.3]); + let query_inside = Point::from_validated_coords([0.3, 0.3]); // For each facet, test if point is outside using the actual function for facet_idx in 0..3 { let result = is_point_outside_facet(dt.tds(), &kernel, simplex_key, facet_idx, &query_inside); - let is_outside = result.unwrap() == Some(true); + let is_outside = result.unwrap(); println!("Facet {facet_idx} (opposite to vertex {facet_idx}): is_outside={is_outside}"); @@ -2268,13 +2429,13 @@ mod tests { } // Test query point outside - let query_outside = Point::new([2.0, 2.0]); + let query_outside = Point::from_validated_coords([2.0, 2.0]); let mut found_outside_facet = false; for facet_idx in 0..3 { let result = is_point_outside_facet(dt.tds(), &kernel, simplex_key, facet_idx, &query_outside); - let is_outside = result.unwrap() == Some(true); + let is_outside = result.unwrap(); println!("Outside point - Facet {facet_idx}: is_outside={is_outside}"); @@ -2292,9 +2453,9 @@ mod tests { #[test] fn test_locate_empty_triangulation() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 3> = Tds::empty(); let kernel = FastKernel::::new(); - let point = Point::new([0.0, 0.0, 0.0]); + let point = Point::from_validated_coords([0.0, 0.0, 0.0]); let result = locate(&tds, &kernel, &point, None); assert_matches!(result, Err(LocateError::EmptyTriangulation)); @@ -2303,15 +2464,15 @@ mod tests { #[test] fn test_locate_point_inside_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); // Point inside the triangle - let point = Point::new([0.3, 0.3]); + let point = Point::from_validated_coords([0.3, 0.3]); let result = locate(dt.tds(), &kernel, &point, None); match result { @@ -2325,16 +2486,16 @@ mod tests { #[test] fn test_locate_point_inside_3d() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); // Point inside the tetrahedron - let point = Point::new([0.25, 0.25, 0.25]); + let point = Point::from_validated_coords([0.25, 0.25, 0.25]); let result = locate(dt.tds(), &kernel, &point, None); match result { @@ -2348,15 +2509,15 @@ mod tests { #[test] fn test_locate_point_outside_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); // Point far outside the triangle - let point = Point::new([10.0, 10.0]); + let point = Point::from_validated_coords([10.0, 10.0]); let result = locate(dt.tds(), &kernel, &point, None); assert_matches!(result, Ok(LocateResult::Outside)); @@ -2365,16 +2526,16 @@ mod tests { #[test] fn test_locate_point_outside_3d() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); // Point far outside the tetrahedron - let point = Point::new([2.0, 2.0, 2.0]); + let point = Point::from_validated_coords([2.0, 2.0, 2.0]); let result = locate(dt.tds(), &kernel, &point, None); assert_matches!(result, Ok(LocateResult::Outside)); @@ -2383,17 +2544,17 @@ mod tests { #[test] fn test_locate_with_hint_simplex() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); // Get a valid simplex as hint let hint_simplex = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new([0.25, 0.25, 0.25]); + let point = Point::from_validated_coords([0.25, 0.25, 0.25]); let result = locate(dt.tds(), &kernel, &point, Some(hint_simplex)); assert_matches!(result, Ok(LocateResult::InsideSimplex(_))); @@ -2402,14 +2563,14 @@ mod tests { #[test] fn test_locate_with_robust_kernel() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = RobustKernel::::default(); - let point = Point::new([0.3, 0.3]); + let point = Point::from_validated_coords([0.3, 0.3]); let result = locate(dt.tds(), &kernel, &point, None); assert_matches!(result, Ok(LocateResult::InsideSimplex(_))); @@ -2421,9 +2582,9 @@ mod tests { // pointers to create a self-loop. This forces facet walking to revisit a simplex, exercising // the cycle-detection fallback path deterministically. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2439,7 +2600,7 @@ mod tests { // Point outside the simplex: walking will attempt to cross a facet, hit the self-loop, // detect a cycle, and fall back to scan. - let point = Point::new([10.0, 10.0]); + let point = Point::from_validated_coords([10.0, 10.0]); let (result, stats) = locate_with_stats(dt.tds(), &kernel, &point, None).unwrap(); assert_matches!(result, LocateResult::Outside); @@ -2459,44 +2620,44 @@ mod tests { #[test] fn test_is_point_outside_facet_inside() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let simplex_key = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new([0.25, 0.25, 0.25]); // Inside tetrahedron + let point = Point::from_validated_coords([0.25, 0.25, 0.25]); // Inside tetrahedron // Test all facets - point should not be outside any of them for facet_idx in 0..4 { let result = is_point_outside_facet(dt.tds(), &kernel, simplex_key, facet_idx, &point); - assert_matches!(result, Ok(Some(false) | None)); + assert_matches!(result, Ok(false)); } } #[test] fn test_is_point_outside_facet_outside() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let simplex_key = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new([2.0, 2.0, 2.0]); // Outside tetrahedron + let point = Point::from_validated_coords([2.0, 2.0, 2.0]); // Outside tetrahedron // At least one facet should show the point as outside let mut found_outside = false; for facet_idx in 0..4 { if matches!( is_point_outside_facet(dt.tds(), &kernel, simplex_key, facet_idx, &point), - Ok(Some(true)) + Ok(true) ) { found_outside = true; break; @@ -2511,15 +2672,15 @@ mod tests { #[test] fn test_locate_near_boundary() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = RobustKernel::::default(); // Point very close to an edge but still inside - let point = Point::new([0.01, 0.01]); + let point = Point::from_validated_coords([0.01, 0.01]); let result = locate(dt.tds(), &kernel, &point, None); // Should either be inside or on the edge, not outside @@ -2533,12 +2694,12 @@ mod tests { macro_rules! test_locate_dimension { ($dim:literal, $inside_point:expr, $($coords:expr),+ $(,)?) => {{ let vertices: Vec<_> = vec![ - $(vertex!($coords)),+ + $(crate::core::vertex::Vertex::<(), _>::try_new($coords).unwrap()),+ ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); - let point = Point::new($inside_point); + let point = Point::from_validated_coords($inside_point); let result = locate(dt.tds(), &kernel, &point, None); assert!( @@ -2597,13 +2758,13 @@ mod tests { macro_rules! test_find_conflict_region_dimension { ($dim:literal, $inside_point:expr, $($coords:expr),+ $(,)?) => {{ let vertices: Vec<_> = vec![ - $(vertex!($coords)),+ + $(crate::core::vertex::Vertex::<(), _>::try_new($coords).unwrap()),+ ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let start_simplex = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new($inside_point); + let point = Point::from_validated_coords($inside_point); let conflict_simplices = find_conflict_region(dt.tds(), &kernel, &point, start_simplex).unwrap(); @@ -2664,16 +2825,16 @@ mod tests { fn test_find_conflict_region_outside_point() { // Point outside - should find zero simplices in conflict let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let start_simplex = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new([10.0, 10.0, 10.0]); // Far outside + let point = Point::from_validated_coords([10.0, 10.0, 10.0]); // Far outside let conflict_simplices = find_conflict_region(dt.tds(), &kernel, &point, start_simplex).unwrap(); @@ -2689,16 +2850,16 @@ mod tests { #[test] fn test_find_conflict_region_invalid_start_simplex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); // Create invalid simplex key let invalid_simplex = SimplexKey::from(KeyData::from_ffi(999_999)); - let point = Point::new([0.3, 0.3]); + let point = Point::from_validated_coords([0.3, 0.3]); let result = find_conflict_region(dt.tds(), &kernel, &point, invalid_simplex); @@ -2712,15 +2873,15 @@ mod tests { fn test_find_conflict_region_with_robust_kernel() { // Test with robust kernel let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = RobustKernel::::default(); let start_simplex = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new([0.3, 0.3]); + let point = Point::from_validated_coords([0.3, 0.3]); let conflict_simplices = find_conflict_region(dt.tds(), &kernel, &point, start_simplex).unwrap(); @@ -2736,7 +2897,7 @@ mod tests { macro_rules! test_cavity_boundary_dimension { ($dim:literal, $expected_facets:expr, $($coords:expr),+ $(,)?) => {{ let vertices: Vec<_> = vec![ - $(vertex!($coords)),+ + $(crate::core::vertex::Vertex::<(), _>::try_new($coords).unwrap()),+ ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2808,9 +2969,9 @@ mod tests { fn test_extract_cavity_boundary_empty_conflict() { // Empty conflict region should produce empty boundary let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2828,13 +2989,13 @@ mod tests { #[test] fn test_locate_with_stats_invalid_hint_uses_arbitrary_start_simplex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); - let point = Point::new([0.25, 0.25]); + let point = Point::from_validated_coords([0.25, 0.25]); let invalid_hint = SimplexKey::from(KeyData::from_ffi(999_999)); let expected_start = dt.tds().simplex_keys().next().unwrap(); @@ -2850,16 +3011,16 @@ mod tests { #[test] fn test_locate_by_scan_inside_and_outside() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let expected_simplex = dt.tds().simplex_keys().next().unwrap(); - let inside = Point::new([0.2, 0.2]); - let outside = Point::new([3.0, 3.0]); + let inside = Point::from_validated_coords([0.2, 0.2]); + let outside = Point::from_validated_coords([3.0, 3.0]); assert_eq!( locate_by_scan(dt.tds(), &kernel, &inside).unwrap(), @@ -2873,24 +3034,46 @@ mod tests { #[test] fn test_extract_cavity_boundary_rejects_nonmanifold_facet_2d() { - let mut tds: Tds = Tds::empty(); - let origin = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let x_axis = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let y_axis = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let upper_right = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - let top_apex = tds.insert_vertex_with_mapping(vertex!([0.5, 1.5])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let origin = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let x_axis = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let y_axis = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let upper_right = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); + let top_apex = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.5]).unwrap(), + ) + .unwrap(); let first_simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![origin, x_axis, y_axis], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![origin, x_axis, y_axis], None).unwrap(), + ) .unwrap(); let second_simplex = tds .insert_simplex_with_mapping( - Simplex::new(vec![origin, x_axis, upper_right], None).unwrap(), + Simplex::try_new_with_data(vec![origin, x_axis, upper_right], None).unwrap(), ) .unwrap(); let third_simplex = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![origin, x_axis, top_apex], None).unwrap(), + Simplex::try_new_with_data(vec![origin, x_axis, top_apex], None).unwrap(), ) .unwrap(); @@ -2911,22 +3094,46 @@ mod tests { #[test] fn test_extract_cavity_boundary_detects_disconnected_boundary_2d() { - let mut tds: Tds = Tds::empty(); - let left_origin = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let left_x = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let left_y = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let right_origin = tds.insert_vertex_with_mapping(vertex!([3.0, 0.0])).unwrap(); - let right_x = tds.insert_vertex_with_mapping(vertex!([4.0, 0.0])).unwrap(); - let right_y = tds.insert_vertex_with_mapping(vertex!([3.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let left_origin = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let left_x = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let left_y = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let right_origin = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 0.0]).unwrap(), + ) + .unwrap(); + let right_x = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + ) + .unwrap(); + let right_y = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 1.0]).unwrap(), + ) + .unwrap(); let left = tds .insert_simplex_with_mapping( - Simplex::new(vec![left_origin, left_x, left_y], None).unwrap(), + Simplex::try_new_with_data(vec![left_origin, left_x, left_y], None).unwrap(), ) .unwrap(); let right = tds .insert_simplex_with_mapping( - Simplex::new(vec![right_origin, right_x, right_y], None).unwrap(), + Simplex::try_new_with_data(vec![right_origin, right_x, right_y], None).unwrap(), ) .unwrap(); @@ -2961,13 +3168,13 @@ mod tests { #[test] fn test_locate_with_stats_valid_hint_marks_used_hint() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); - let point = Point::new([0.2, 0.2]); + let point = Point::from_validated_coords([0.2, 0.2]); let hint = dt.tds().simplex_keys().next().unwrap(); let (result, stats) = locate_with_stats(dt.tds(), &kernel, &point, Some(hint)).unwrap(); @@ -2980,9 +3187,9 @@ mod tests { #[test] fn test_locate_by_scan_empty_returns_outside() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); let kernel = FastKernel::::new(); - let point = Point::new([0.0, 0.0]); + let point = Point::from_validated_coords([0.0, 0.0]); assert_eq!( locate_by_scan(&tds, &kernel, &point).unwrap(), @@ -2993,9 +3200,9 @@ mod tests { #[test] fn test_extract_cavity_boundary_invalid_conflict_simplex_key() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3010,17 +3217,30 @@ mod tests { ); } - /// A simplex with fewer than D+1 vertex keys is detected early by the - /// `simplex_vertex_keys.len() != D + 1` guard and returns `Ok(None)`. + /// A simplex with fewer than D+1 vertex keys is rejected before orientation. #[test] - fn test_is_point_outside_facet_underdimensioned_simplex_returns_none() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + fn test_is_point_outside_facet_underdimensioned_simplex_returns_invalid_arity() { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Shrink simplex to only 2 vertices (D+1 = 3 required for D=2). @@ -3032,26 +3252,89 @@ mod tests { } let kernel = FastKernel::::new(); - let point = Point::new([0.3, 0.3]); + let point = Point::from_validated_coords([0.3, 0.3]); let result = is_point_outside_facet(&tds, &kernel, simplex_key, 0, &point); assert!( - matches!(result, Ok(None)), - "underdimensioned simplex should return Ok(None), got {result:?}" + matches!( + result, + Err(LocateError::InvalidSimplexArity { + simplex_key: key, + expected: 3, + found: 2, + }) if key == simplex_key + ), + "underdimensioned simplex should return InvalidSimplexArity, got {result:?}" + ); + } + + #[test] + fn test_is_point_outside_facet_invalid_facet_index_returns_invalid_facet_index() { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + + let simplex_key = tds + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + + let kernel = FastKernel::::new(); + let point = Point::from_validated_coords([0.3, 0.3]); + let result = is_point_outside_facet(&tds, &kernel, simplex_key, 3, &point); + + assert!( + matches!( + result, + Err(LocateError::InvalidFacetIndex { + simplex_key: key, + facet_index: 3, + facet_count: 3, + }) if key == simplex_key + ), + "invalid facet index should return InvalidFacetIndex, got {result:?}" ); } /// When the vertex at `facet_idx` (the opposite vertex) is unresolvable, - /// `is_point_outside_facet` returns `Ok(None)` before reaching the + /// `is_point_outside_facet` returns a typed error before reaching the /// canonical ordering helpers. #[test] - fn test_is_point_outside_facet_unresolvable_opposite_vertex_returns_none() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + fn test_is_point_outside_facet_unresolvable_opposite_vertex_returns_missing_vertex() { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Replace vertex at index 0 (the opposite vertex for facet_idx=0) @@ -3066,29 +3349,49 @@ mod tests { } let kernel = FastKernel::::new(); - let point = Point::new([0.3, 0.3]); - // facet_idx=0 → opposite_key = missing → unresolvable → Ok(None) + let point = Point::from_validated_coords([0.3, 0.3]); + // facet_idx=0 → opposite_key = missing → unresolvable. let result = is_point_outside_facet(&tds, &kernel, simplex_key, 0, &point); assert!( - matches!(result, Ok(None)), - "unresolvable opposite vertex should return Ok(None), got {result:?}" + matches!( + result, + Err(LocateError::MissingSimplexVertex { + simplex_key: key, + vertex_key, + }) if key == simplex_key && vertex_key == missing + ), + "unresolvable opposite vertex should return MissingSimplexVertex, got {result:?}" ); } /// `is_point_outside_facet` resolves vertex points via the canonical ordering /// helper `sorted_facet_points_with_extra`. A simplex whose vertex-key list - /// contains a key absent from the TDS causes the helper to return `None`, - /// which the function converts to `Ok(None)` (degenerate/unresolvable simplex). + /// contains a key absent from the TDS causes the helper to return an error, + /// which the function preserves as a typed locate error. #[test] - fn test_is_point_outside_facet_degenerate_simplex_missing_vertex_returns_none() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + fn test_is_point_outside_facet_degenerate_simplex_missing_vertex_returns_missing_vertex() { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); // Build a valid simplex first, then mutate its vertex list to include a missing key. let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let existing_vertices = tds.simplex(simplex_key).unwrap().vertices().to_vec(); let missing = VertexKey::from(KeyData::from_ffi(999_999)); @@ -3101,13 +3404,19 @@ mod tests { } let kernel = FastKernel::::new(); - let point = Point::new([0.3_f64, 0.3_f64]); + let point = Point::from_validated_coords([0.3_f64, 0.3_f64]); - // Only 2 of the 3 vertices exist → simplex_vertices.len() (2) != D+1 (3) → Ok(None). + // One facet vertex is missing from the TDS, so canonical point collection fails. let result = is_point_outside_facet(&tds, &kernel, simplex_key, 0, &point); assert!( - matches!(result, Ok(None)), - "degenerate simplex with missing vertex should return Ok(None), got {result:?}" + matches!( + result, + Err(LocateError::MissingSimplexVertex { + simplex_key: key, + vertex_key, + }) if key == simplex_key && vertex_key == missing + ), + "degenerate simplex with missing vertex should return MissingSimplexVertex, got {result:?}" ); } @@ -3116,13 +3425,27 @@ mod tests { /// simplex, returning `SimplexDataAccessFailed` with a "vanished" message. #[test] fn test_find_conflict_region_vanished_neighbor_returns_simplex_data_access_failed() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Wire a neighbor that doesn't exist in the TDS. @@ -3137,7 +3460,7 @@ mod tests { let kernel = FastKernel::::new(); // Point inside the circumcircle so the start simplex is in conflict // and BFS tries to visit the ghost neighbor. - let point = Point::new([0.2, 0.2]); + let point = Point::from_validated_coords([0.2, 0.2]); let result = find_conflict_region(&tds, &kernel, &point, simplex_key); assert!( matches!( @@ -3148,18 +3471,31 @@ mod tests { ); } - /// When a simplex in the BFS has fewer than D+1 vertex keys (all resolvable), - /// `sorted_simplex_points` returns a short buffer and the vertex-count guard - /// fires `SimplexDataAccessFailed`. + /// When a simplex in the BFS has fewer than D+1 vertex keys, canonical point + /// collection rejects it before predicate evaluation. #[test] - fn test_find_conflict_region_underdimensioned_simplex_returns_simplex_data_access_failed() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + fn test_find_conflict_region_underdimensioned_simplex_returns_invalid_simplex_arity() { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Shrink simplex to only 2 vertices (both valid). @@ -3171,30 +3507,48 @@ mod tests { } let kernel = FastKernel::::new(); - let point = Point::new([0.3, 0.3]); + let point = Point::from_validated_coords([0.3, 0.3]); let result = find_conflict_region(&tds, &kernel, &point, simplex_key); assert!( matches!( result, - Err(ConflictError::SimplexDataAccessFailed { simplex_key: ck, .. }) if ck == simplex_key + Err(ConflictError::InvalidSimplexArity { + simplex_key: ck, + expected: 3, + found: 2, + }) if ck == simplex_key ), - "expected SimplexDataAccessFailed for underdimensioned simplex, got {result:?}" + "expected InvalidSimplexArity for underdimensioned simplex, got {result:?}" ); } /// `find_conflict_region` uses `sorted_simplex_points` in the BFS loop; /// a conflict simplex whose vertex-key list contains a key absent from the TDS - /// causes the helper to return `None`, yielding `Err(SimplexDataAccessFailed)`. + /// causes the helper to return an error, yielding `Err(MissingSimplexVertex)`. #[test] - fn test_find_conflict_region_degenerate_simplex_returns_simplex_data_access_failed() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + fn test_find_conflict_region_degenerate_simplex_returns_missing_simplex_vertex() { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); // Build valid simplex then mutate one vertex to a missing key. let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let existing_vertices = tds.simplex(simplex_key).unwrap().vertices().to_vec(); let missing = VertexKey::from(KeyData::from_ffi(999_999)); @@ -3207,13 +3561,19 @@ mod tests { } let kernel = FastKernel::::new(); - let point = Point::new([0.3_f64, 0.3_f64]); + let point = Point::from_validated_coords([0.3_f64, 0.3_f64]); - // BFS visits the simplex; simplex_points.len() == 2 != D+1 == 3 → SimplexDataAccessFailed. + // BFS visits the simplex; canonical point collection rejects the missing vertex. let result = find_conflict_region(&tds, &kernel, &point, simplex_key); assert!( - matches!(result, Err(ConflictError::SimplexDataAccessFailed { simplex_key: ck, .. }) if ck == simplex_key), - "expected SimplexDataAccessFailed for degenerate simplex, got {result:?}" + matches!( + result, + Err(ConflictError::MissingSimplexVertex { + simplex_key: ck, + vertex_key, + }) if ck == simplex_key && vertex_key == missing + ), + "expected MissingSimplexVertex for degenerate simplex, got {result:?}" ); } @@ -3222,13 +3582,13 @@ mod tests { #[test] fn test_locate_with_stats_none_hint_picks_arbitrary_start_simplex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); - let point = Point::new([0.25_f64, 0.25_f64]); + let point = Point::from_validated_coords([0.25_f64, 0.25_f64]); let (result, stats) = locate_with_stats(dt.tds(), &kernel, &point, None).unwrap(); @@ -3250,13 +3610,13 @@ mod tests { macro_rules! test_verify_conflict_region_complete_dimension { ($dim:literal, $inside_point:expr, $($coords:expr),+ $(,)?) => {{ let vertices: Vec<_> = vec![ - $(vertex!($coords)),+ + $(crate::core::vertex::Vertex::<(), _>::try_new($coords).unwrap()),+ ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let start_simplex = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new($inside_point); + let point = Point::from_validated_coords($inside_point); let conflict_simplices = find_conflict_region(dt.tds(), &kernel, &point, start_simplex).unwrap(); assert!( @@ -3338,15 +3698,15 @@ mod tests { #[test] fn test_verify_conflict_region_completeness_empty_bfs_detects_missed() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); // Point inside the circumcircle — the single simplex should be in conflict. - let point = Point::new([0.3, 0.3]); + let point = Point::from_validated_coords([0.3, 0.3]); let empty_bfs = SimplexKeyBuffer::new(); let missed = verify_conflict_region_completeness(dt.tds(), &kernel, &point, &empty_bfs); @@ -3361,16 +3721,16 @@ mod tests { #[test] fn test_verify_conflict_region_completeness_outside_point_zero_missed() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let start_simplex = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new([10.0, 10.0, 10.0]); + let point = Point::from_validated_coords([10.0, 10.0, 10.0]); let conflict_simplices = find_conflict_region(dt.tds(), &kernel, &point, start_simplex).unwrap(); @@ -3399,16 +3759,16 @@ mod tests { // All 4 points are co-circular, so a center-ish query point is strictly // inside both circumcircles → conflict region has 2 simplices. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([4.0, 3.0]), - vertex!([0.0, 3.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 3.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 3.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let kernel = FastKernel::::new(); let start_simplex = dt.tds().simplex_keys().next().unwrap(); - let point = Point::new([2.0, 1.5]); + let point = Point::from_validated_coords([2.0, 1.5]); let full_conflict = find_conflict_region(dt.tds(), &kernel, &point, start_simplex).unwrap(); assert!( @@ -3430,29 +3790,57 @@ mod tests { #[test] fn test_extract_cavity_boundary_rejects_ridge_fan_2d() { - let mut tds: Tds = Tds::empty(); - let center = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let axis_x = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let axis_y = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let center = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let axis_x = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let axis_y = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let axis_neg_x = tds - .insert_vertex_with_mapping(vertex!([-1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, 0.0]).unwrap(), + ) .unwrap(); let axis_neg_y = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let far_x = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + ) + .unwrap(); + let far_y = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), + ) .unwrap(); - let far_x = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap(); - let far_y = tds.insert_vertex_with_mapping(vertex!([0.0, 2.0])).unwrap(); let first_simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![center, axis_x, axis_y], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center, axis_x, axis_y], None).unwrap(), + ) .unwrap(); let second_simplex = tds .insert_simplex_with_mapping( - Simplex::new(vec![center, axis_neg_x, axis_neg_y], None).unwrap(), + Simplex::try_new_with_data(vec![center, axis_neg_x, axis_neg_y], None).unwrap(), ) .unwrap(); let third_simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![center, far_x, far_y], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center, far_x, far_y], None).unwrap(), + ) .unwrap(); let mut conflict_simplices = SimplexKeyBuffer::new(); diff --git a/src/core/algorithms/pl_manifold_repair.rs b/src/core/algorithms/pl_manifold_repair.rs index 7de243df..b893c693 100644 --- a/src/core/algorithms/pl_manifold_repair.rs +++ b/src/core/algorithms/pl_manifold_repair.rs @@ -2,7 +2,7 @@ //! //! This module implements a `pub(crate)` repair algorithm that attempts to bring //! a triangulation closer to satisfying the -//! [`TopologyGuarantee::PLManifold`](crate::core::validation::TopologyGuarantee::PLManifold) +//! [`TopologyGuarantee::PLManifold`](crate::prelude::validation::TopologyGuarantee::PLManifold) //! invariant by removing simplices that cause codimension-1 facet over-sharing //! (facets incident to more than 2 simplices). //! @@ -32,7 +32,6 @@ use crate::core::simplex::Simplex; use crate::core::tds::{SimplexKey, Tds, TdsError, VertexKey}; use crate::core::traits::data_type::DataType; use crate::core::vertex::Vertex; -use crate::geometry::traits::coordinate::CoordinateScalar; use crate::geometry::util::norms::hypot; use crate::topology::manifold::validate_facet_degree; use num_traits::NumCast; @@ -79,7 +78,7 @@ impl Default for PlManifoldRepairConfig { /// ```rust /// use delaunay::prelude::delaunayize::PlManifoldRepairStats; /// -/// let stats = PlManifoldRepairStats::::default(); +/// let stats = PlManifoldRepairStats::<(), (), 3>::default(); /// assert_eq!(stats.iterations, 0); /// assert_eq!(stats.simplices_removed, 0); /// assert!(stats.removed_simplices.is_empty()); @@ -87,25 +86,23 @@ impl Default for PlManifoldRepairConfig { /// assert!(!stats.succeeded); /// ``` #[derive(Debug, Clone)] -pub struct PlManifoldRepairStats { +#[must_use] +pub struct PlManifoldRepairStats { /// Number of repair iterations executed. pub iterations: usize, /// Total number of simplices removed. pub simplices_removed: usize, /// Simplices that were removed, preserving user data for callers that need to /// migrate or inspect it. Identifiable by [`Simplex::uuid()`]. - pub removed_simplices: Vec>, + pub removed_simplices: Vec>, /// Vertices that became isolated after simplex removal and were removed from /// the TDS. Identifiable by [`Vertex::uuid()`]. - pub removed_vertices: Vec>, + pub removed_vertices: Vec>, /// Whether the facet-degree invariant was satisfied at termination. pub succeeded: bool, } -impl PartialEq for PlManifoldRepairStats -where - T: CoordinateScalar, -{ +impl PartialEq for PlManifoldRepairStats { fn eq(&self, other: &Self) -> bool { self.iterations == other.iterations && self.simplices_removed == other.simplices_removed @@ -115,9 +112,9 @@ where } } -impl Eq for PlManifoldRepairStats where T: CoordinateScalar {} +impl Eq for PlManifoldRepairStats {} -impl Default for PlManifoldRepairStats { +impl Default for PlManifoldRepairStats { fn default() -> Self { Self { iterations: 0, @@ -203,12 +200,11 @@ pub enum PlManifoldRepairError { /// - The TDS fails structural validation (Levels 1–2). /// - The iteration or simplex-removal budget is exhausted. /// - A pass finds violations but cannot remove any simplices. -pub fn repair_facet_oversharing( - tds: &mut Tds, +pub fn repair_facet_oversharing( + tds: &mut Tds, config: &PlManifoldRepairConfig, -) -> Result, PlManifoldRepairError> +) -> Result, PlManifoldRepairError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -257,6 +253,7 @@ where if removal_candidates.is_empty() { // We had violations but couldn't pick any simplex — no progress. let remaining = facet_map.values().filter(|h| h.len() > 2).count(); + prepare_error_return_topology(tds); return Err(PlManifoldRepairError::NoProgress { over_shared_facets: remaining, iterations: stats.iterations, @@ -267,6 +264,7 @@ where // Check simplex-removal budget. let batch_size = removal_candidates.len(); if stats.simplices_removed + batch_size > config.max_simplices_removed { + prepare_error_return_topology(tds); return Err(PlManifoldRepairError::BudgetExhausted { iterations: stats.iterations, simplices_removed: stats.simplices_removed, @@ -302,13 +300,12 @@ where if validate_facet_degree(&facet_map).is_ok() { stats.succeeded = true; // Rebuild full neighbor/incidence pointers before returning. - tds.assign_neighbors().map_err(PlManifoldRepairError::Tds)?; - tds.assign_incident_simplices() - .map_err(|e| PlManifoldRepairError::Tds(e.into()))?; + rebuild_success_topology(tds)?; return Ok(stats); } } + prepare_error_return_topology(tds); Err(PlManifoldRepairError::BudgetExhausted { iterations: stats.iterations, simplices_removed: stats.simplices_removed, @@ -317,17 +314,54 @@ where }) } +/// Rebuilds topology metadata before a successful PL-manifold repair result is observed. +/// +/// Successful repair has restored the facet-degree invariant, so neighbor and +/// incident-simplex metadata can be rebuilt strictly and any failure should be +/// surfaced to the caller. +/// +/// # Errors +/// +/// Returns [`PlManifoldRepairError::Tds`] if neighbor assignment or +/// incident-simplex assignment fails while restoring topology metadata. +fn rebuild_success_topology( + tds: &mut Tds, +) -> Result<(), PlManifoldRepairError> { + tds.assign_neighbors().map_err(PlManifoldRepairError::Tds)?; + tds.assign_incident_simplices() + .map_err(|e| PlManifoldRepairError::Tds(e.into())) +} + +/// Best-effort topology metadata repair before returning a non-convergence error. +/// +/// Error exits may still contain over-shared facets, so strict neighbor assignment +/// can fail even though the intended public result is `NoProgress` or +/// `BudgetExhausted`. This helper preserves that primary repair outcome while +/// still repairing incident simplices and rebuilding neighbors when the remaining +/// topology permits it. +fn prepare_error_return_topology(tds: &mut Tds) { + if let Err(error) = tds.assign_incident_simplices() { + tracing::debug!( + ?error, + "PL-manifold repair could not rebuild incident simplices before error return" + ); + } + + if let Err(error) = tds.assign_neighbors() { + tracing::debug!( + ?error, + "PL-manifold repair could not rebuild neighbors before error return" + ); + } +} + /// Deterministic quality score for a simplex: lower = better quality. /// /// Uses an edge-length aspect-ratio metric (max/min edge) that operates /// directly on the `Tds` without requiring a full `Triangulation` or /// circumsphere computation. -fn simplex_quality_score( - tds: &Tds, - simplex_key: SimplexKey, -) -> f64 +fn simplex_quality_score(tds: &Tds, simplex_key: SimplexKey) -> f64 where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -345,13 +379,11 @@ where return f64::MAX; }; - let mut diff = [T::zero(); D]; + let mut diff = [0.0; D]; for (idx, d) in diff.iter_mut().enumerate() { *d = vi.point().coords()[idx] - vj.point().coords()[idx]; } - let Some(len): Option = NumCast::from(hypot(&diff)) else { - return f64::MAX; - }; + let len = hypot(&diff); edge_lengths.push(len); } } @@ -382,12 +414,11 @@ where /// /// Selection order: highest quality score first (worst simplex), then canonicalized /// vertex keys (ascending), then simplex UUID (ascending). -fn pick_worst_simplex( - tds: &Tds, +fn pick_worst_simplex( + tds: &Tds, handles: &[FacetHandle], ) -> Option where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -438,11 +469,10 @@ where /// `stats.removed_vertices` so callers can recover their data. /// /// Vertices are sorted by UUID before removal for deterministic ordering. -fn remove_orphaned_vertices( - tds: &mut Tds, - stats: &mut PlManifoldRepairStats, +fn remove_orphaned_vertices( + tds: &mut Tds, + stats: &mut PlManifoldRepairStats, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -469,7 +499,7 @@ fn remove_orphaned_vertices( mod tests { use super::*; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; + use slotmap::KeyData; // ============================================================================= // HELPER FUNCTIONS @@ -495,8 +525,7 @@ mod tests { fn stats_default_does_not_require_data_type_metadata() { struct NonDataType(String); - let stats: PlManifoldRepairStats = - PlManifoldRepairStats::default(); + let stats: PlManifoldRepairStats = PlManifoldRepairStats::default(); assert_eq!(stats.iterations, 0); assert!(stats.removed_simplices.is_empty()); @@ -514,10 +543,10 @@ mod tests { fn test_already_pl_manifold_is_noop() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -539,10 +568,10 @@ mod tests { fn test_budget_exhaustion_zero_iterations() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -565,10 +594,10 @@ mod tests { fn test_2d_already_pl_manifold() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -589,9 +618,9 @@ mod tests { fn test_stats_populated_on_success() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -615,14 +644,14 @@ mod tests { /// Helper: create a TDS with over-shared facets by duplicating a simplex in a /// multi-simplex triangulation. Interior facets go from degree 2 to degree 3. - fn make_overshared_tds() -> Tds { + fn make_overshared_tds() -> Tds<(), (), 3> { // 5 points let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -635,7 +664,7 @@ mod tests { // Duplicate the first simplex → its facets go from degree 2 to degree 3. let simplex_key = tds.simplex_keys().next().unwrap(); let vkeys = tds.simplex_vertices(simplex_key).unwrap(); - let dup_simplex = Simplex::new(vkeys.to_vec(), None).unwrap(); + let dup_simplex = Simplex::try_new_with_data(vkeys.to_vec(), None).unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test(dup_simplex) .unwrap(); @@ -649,6 +678,48 @@ mod tests { tds } + /// Create a TDS that cannot be fully repaired in a single iteration. + fn make_multi_duplicate_overshared_tds() -> Tds<(), (), 3> { + let mut tds = make_overshared_tds(); + let simplex_key = tds.simplex_keys().next().unwrap(); + let vkeys = tds.simplex_vertices(simplex_key).unwrap(); + + for _ in 0..5 { + let dup_simplex = Simplex::try_new_with_data(vkeys.to_vec(), None).unwrap(); + tds.insert_simplex_bypassing_topology_checks_for_test(dup_simplex) + .unwrap(); + } + + tds + } + + /// Set every vertex incident pointer to a dangling key so repair must rebuild them. + fn poison_incident_simplices(tds: &mut Tds<(), (), 3>) { + let dangling = SimplexKey::from(KeyData::from_ffi(u64::MAX)); + let vertex_keys: Vec<_> = tds.vertex_keys().collect(); + for vertex_key in vertex_keys { + tds.vertex_mut(vertex_key) + .unwrap() + .set_incident_simplex(Some(dangling)); + } + } + + /// Assert that every present incident pointer references a containing simplex. + fn assert_incident_simplices_are_coherent(tds: &Tds<(), (), 3>) { + for (vertex_key, vertex) in tds.vertices() { + let Some(simplex_key) = vertex.incident_simplex() else { + continue; + }; + let simplex = tds + .simplex(simplex_key) + .expect("incident simplex should exist"); + assert!( + simplex.contains_vertex(vertex_key), + "incident simplex {simplex_key:?} should contain vertex {vertex_key:?}" + ); + } + } + /// Create a TDS with over-shared facets and verify that repair removes /// simplices until the facet-degree invariant is satisfied. #[test] @@ -706,6 +777,31 @@ mod tests { ); } + #[test] + fn test_repair_budget_exhausted_after_mutation_rebuilds_incident_simplices() { + init_tracing(); + let mut tds = make_multi_duplicate_overshared_tds(); + poison_incident_simplices(&mut tds); + + let config = PlManifoldRepairConfig { + max_iterations: 1, + max_simplices_removed: 10_000, + }; + let result = repair_facet_oversharing(&mut tds, &config); + + let Err(PlManifoldRepairError::BudgetExhausted { + simplices_removed, .. + }) = result + else { + panic!("Expected BudgetExhausted after partial repair, got: {result:?}"); + }; + assert!( + simplices_removed > 0, + "test should exercise an error return after simplex removal" + ); + assert_incident_simplices_are_coherent(&tds); + } + // ============================================================================= // QUALITY SCORE TESTS // ============================================================================= @@ -716,10 +812,10 @@ mod tests { fn test_simplex_quality_score_finite_and_deterministic() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -758,11 +854,11 @@ mod tests { fn test_deterministic_repeated_runs() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); diff --git a/src/core/boundary.rs b/src/core/boundary.rs index 155a66a4..f6d73fa2 100644 --- a/src/core/boundary.rs +++ b/src/core/boundary.rs @@ -37,7 +37,7 @@ fn number_of_boundary_facets_in_map( /// /// This implementation provides efficient boundary facet identification and analysis /// for d-dimensional triangulations using the triangulation data structure. -impl BoundaryAnalysis for Tds { +impl BoundaryAnalysis for Tds { /// Identifies all boundary facets in the triangulation. /// /// A boundary facet is a facet that belongs to only one simplex, meaning it lies on the @@ -56,13 +56,13 @@ impl BoundaryAnalysis for Tds { /// /// # Returns /// - /// A `Result, TdsError>` containing an iterator over boundary facets. + /// A `Result, TdsError>` containing an iterator over boundary facets. /// The iterator yields facets lazily without pre-allocating vectors, providing better performance. /// /// # Errors /// /// Returns a [`TdsError`] (typically - /// [`crate::core::facet::FacetError`]) if: + /// [`crate::prelude::tds::FacetError`]) if: /// - Any boundary facet cannot be created from the simplices /// - A facet index is out of bounds (indicates data corruption) /// - A referenced simplex is not found in the triangulation (indicates data corruption) @@ -84,10 +84,10 @@ impl BoundaryAnalysis for Tds { /// # fn main() -> Result<(), ExampleError> { /// // Create a simple 3D triangulation (single tetrahedron) /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -100,12 +100,12 @@ impl BoundaryAnalysis for Tds { /// # Ok(()) /// # } /// ``` - fn boundary_facets(&self) -> Result, TdsError> { + fn boundary_facets(&self) -> Result, TdsError> { // Build a map from facet keys to the simplices that contain them let facet_to_simplices = self.build_facet_to_simplices_map()?; // Create the boundary facets iterator - Ok(BoundaryFacetsIter::new(self, facet_to_simplices)) + BoundaryFacetsIter::try_new(self, facet_to_simplices).map_err(TdsError::from) } /// Checks if a specific facet is a boundary facet. @@ -149,10 +149,10 @@ impl BoundaryAnalysis for Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -166,7 +166,7 @@ impl BoundaryAnalysis for Tds { /// # } /// ``` #[inline] - fn is_boundary_facet(&self, facet: &FacetView<'_, T, U, V, D>) -> Result { + fn is_boundary_facet(&self, facet: &FacetView<'_, U, V, D>) -> Result { let facet_to_simplices = self.build_facet_to_simplices_map()?; self.is_boundary_facet_with_map(facet, &facet_to_simplices) } @@ -207,10 +207,10 @@ impl BoundaryAnalysis for Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -228,7 +228,7 @@ impl BoundaryAnalysis for Tds { #[inline] fn is_boundary_facet_with_map( &self, - facet: &FacetView<'_, T, U, V, D>, + facet: &FacetView<'_, U, V, D>, facet_to_simplices: &FacetToSimplicesMap, ) -> Result { // Use FacetView's key() method which is more efficient @@ -278,10 +278,10 @@ impl BoundaryAnalysis for Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -304,7 +304,7 @@ mod tests { use crate::core::query::QueryError; use crate::core::tds::{SimplexKey, TdsError}; use crate::core::vertex::Vertex; - use crate::geometry::{point::Point, traits::coordinate::Coordinate}; + use crate::geometry::point::Point; use crate::triangulation::DelaunayTriangulation; use std::assert_matches; @@ -323,9 +323,9 @@ mod tests { // Test Case 1: 2D triangle - all 3 edges should be boundary facets { let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, 1.0]), ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -358,10 +358,10 @@ mod tests { // Test Case 2: 3D tetrahedron - all 4 faces should be boundary facets { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -394,11 +394,11 @@ mod tests { // Test Case 3: 4D simplex - all 5 tetrahedra should be boundary facets { let points = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -464,10 +464,10 @@ mod tests { // Test case 1: Basic method delegation and error propagation { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -493,11 +493,11 @@ mod tests { // Test case 2: Capacity allocation and vector operations { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), - Point::new([0.5, 0.5, 0.5]), // Interior point + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.5, 0.5, 0.5]), // Interior point ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -581,10 +581,10 @@ mod tests { // Create a valid triangulation let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -631,10 +631,10 @@ mod tests { #[test] fn test_boundary_facet_with_map_rejects_invalid_multiplicity() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -652,9 +652,9 @@ mod tests { facet_to_simplices.insert( facet_key, [ - FacetHandle::new(facet.simplex_key(), 0), - FacetHandle::new(facet.simplex_key(), 1), - FacetHandle::new(facet.simplex_key(), 2), + FacetHandle::from_validated(facet.simplex_key(), 0), + FacetHandle::from_validated(facet.simplex_key(), 1), + FacetHandle::from_validated(facet.simplex_key(), 2), ] .into_iter() .collect(), @@ -677,9 +677,9 @@ mod tests { facet_to_simplices.insert( 0xCAFE, [ - FacetHandle::new(SimplexKey::default(), 0), - FacetHandle::new(SimplexKey::default(), 1), - FacetHandle::new(SimplexKey::default(), 2), + FacetHandle::from_validated(SimplexKey::default(), 0), + FacetHandle::from_validated(SimplexKey::default(), 1), + FacetHandle::from_validated(SimplexKey::default(), 2), ] .into_iter() .collect(), @@ -699,10 +699,10 @@ mod tests { #[test] fn test_boundary_facets_error_propagation_from_build_map() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let vertices = Vertex::from_points(&points); let mut dt: DelaunayTriangulation<_, (), (), 3> = @@ -734,10 +734,10 @@ mod tests { // ensuring the method properly delegates and transforms the result let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let vertices = Vertex::from_points(&points); let dt = DelaunayTriangulation::new(&vertices).unwrap(); diff --git a/src/core/collections/aliases.rs b/src/core/collections/aliases.rs index 27cf1e8f..ff4d7fd5 100644 --- a/src/core/collections/aliases.rs +++ b/src/core/collections/aliases.rs @@ -57,7 +57,7 @@ pub use uuid::Uuid; /// /// ```rust,ignore /// // Internal use - not exposed in public API -/// let vertices: StorageMap> = StorageMap::with_key(); +/// let vertices: StorageMap> = StorageMap::with_key(); /// ``` pub type StorageMap = DenseSlotMap; diff --git a/src/core/collections/buffers.rs b/src/core/collections/buffers.rs index bfbbe64a..64511f11 100644 --- a/src/core/collections/buffers.rs +++ b/src/core/collections/buffers.rs @@ -217,7 +217,7 @@ pub type SimplexVertexUuidBuffer = SmallBuffer = SmallBuffer<[i8; D], MAX_PRACTICAL_DIMENSION_SIZE>; diff --git a/src/core/collections/key_maps.rs b/src/core/collections/key_maps.rs index a1187eab..d66e7d9a 100644 --- a/src/core/collections/key_maps.rs +++ b/src/core/collections/key_maps.rs @@ -1,4 +1,4 @@ -//! Keyed map and set aliases for vertex, simplex, and UUID lookups. +//! Keyed map and set aliases for simplex, and UUID lookups. //! //! These types describe the canonical hash-map shapes used for topology storage, //! validation, and geometric algorithm bookkeeping. @@ -54,10 +54,10 @@ pub type VertexUuidSet = FastHashSet; /// # } /// # fn main() -> Result<(), ReverseLookupExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = DelaunayTriangulationBuilder::new(&vertices) /// .topology_guarantee(TopologyGuarantee::PLManifold) @@ -108,10 +108,10 @@ pub type UuidToVertexKeyMap = FastHashMap; /// # } /// # fn main() -> Result<(), ReverseLookupExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = DelaunayTriangulationBuilder::new(&vertices) /// .topology_guarantee(TopologyGuarantee::PLManifold) diff --git a/src/core/collections/secondary_maps.rs b/src/core/collections/secondary_maps.rs index b356004c..fb17771b 100644 --- a/src/core/collections/secondary_maps.rs +++ b/src/core/collections/secondary_maps.rs @@ -35,10 +35,10 @@ use slotmap::SparseSecondaryMap; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -79,10 +79,10 @@ pub type SimplexSecondaryMap = SparseSecondaryMap; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; diff --git a/src/core/collections/spatial_hash_grid.rs b/src/core/collections/spatial_hash_grid.rs index eb3f207b..3826ea99 100644 --- a/src/core/collections/spatial_hash_grid.rs +++ b/src/core/collections/spatial_hash_grid.rs @@ -19,7 +19,7 @@ use super::{SecureHashMap, SmallBuffer}; use crate::core::tds::VertexKey; -use crate::geometry::traits::coordinate::{CoordinateScalar, InvalidCoordinateValue}; +use crate::geometry::traits::coordinate::{HashCoordinate, InvalidCoordinateValue, OrderedEq}; use std::hash::{Hash, Hasher}; /// Maximum dimension supported by the hash-grid neighborhood walk. @@ -37,7 +37,7 @@ const BUCKET_INLINE_CAPACITY: usize = 8; /// with [`HashGridIndex::is_usable`] and treat as a linear-scan fallback. #[derive(Clone, Debug, thiserror::Error, PartialEq)] #[non_exhaustive] -pub enum HashGridIndexError { +pub enum HashGridIndexError { /// The cell size is non-finite. #[error("invalid hash-grid cell size: {value}; cell size must be finite and positive")] NonFiniteCellSize { @@ -48,22 +48,18 @@ pub enum HashGridIndexError { #[error("invalid hash-grid cell size {value:?}; cell size must be positive")] NonPositiveCellSize { /// The finite non-positive cell size. - value: T, + value: f64, }, } /// Hashable grid-cell key for a D-dimensional grid. /// -/// Internally, this stores integer-valued cell coordinates as the same scalar -/// type used for points. We avoid casting to an integer type to keep the index -/// aligned with the crate's `CoordinateScalar` contract (`f64` today). +/// Internally, this stores integer-valued cell coordinates as finite `f64` +/// values produced by `floor(coord / cell_size)`. #[derive(Clone, Copy, Debug)] -struct GridKey([T; D]); +struct GridKey([f64; D]); -impl PartialEq for GridKey -where - T: CoordinateScalar, -{ +impl PartialEq for GridKey { fn eq(&self, other: &Self) -> bool { self.0 .iter() @@ -72,12 +68,9 @@ where } } -impl Eq for GridKey where T: CoordinateScalar {} +impl Eq for GridKey {} -impl Hash for GridKey -where - T: CoordinateScalar, -{ +impl Hash for GridKey { fn hash(&self, state: &mut H) { for coord in &self.0 { coord.hash_scalar(state); @@ -92,16 +85,16 @@ where /// that the stored cell size is finite and strictly positive, so lookup methods /// only need to handle dimensional support and coordinate keyability. #[derive(Clone, Debug)] -pub struct HashGridIndex { - cell_size: T, +pub struct HashGridIndex { + cell_size: f64, usable: bool, // Grid keys are derived directly from public coordinate input, so this map // uses randomized hashing instead of the crate's fast non-cryptographic // hasher. - cells: SecureHashMap, SmallBuffer>, + cells: SecureHashMap, SmallBuffer>, } -impl HashGridIndex { +impl HashGridIndex { /// Returns whether the index is usable for accelerated lookup. /// /// This is `false` when `D` exceeds the bounded neighborhood walk supported @@ -121,11 +114,8 @@ impl HashGridIndex { /// /// The returned value was parsed by [`HashGridIndex::try_new`] and is /// guaranteed to be finite and strictly positive for supported - /// [`CoordinateScalar`] implementations. - pub const fn cell_size(&self) -> T - where - T: Copy, - { + /// f64 coordinate model. + pub const fn cell_size(&self) -> f64 { self.cell_size } @@ -143,10 +133,7 @@ pub struct HashGridIndexSnapshot { cells: Vec, } -impl HashGridIndex -where - T: CoordinateScalar, -{ +impl HashGridIndex { /// Creates a new grid index with a finite, positive cell size. /// /// Unsupported dimensions produce an unusable index, but invalid cell sizes @@ -158,14 +145,14 @@ where /// Returns [`HashGridIndexError::NonFiniteCellSize`] if `cell_size` is /// `NaN` or infinite, or [`HashGridIndexError::NonPositiveCellSize`] if it /// is finite and not strictly positive. - pub fn try_new(cell_size: T) -> Result> { + pub fn try_new(cell_size: f64) -> Result { if !cell_size.is_finite() { return Err(HashGridIndexError::NonFiniteCellSize { value: InvalidCoordinateValue::from_debug(&cell_size), }); } - if cell_size <= T::zero() { + if cell_size <= 0.0 { return Err(HashGridIndexError::NonPositiveCellSize { value: cell_size }); } @@ -179,7 +166,6 @@ where #[cfg(test)] pub(crate) fn debug_snapshot(&self) -> HashGridIndexSnapshot where - T: std::fmt::Debug, K: std::fmt::Debug, { let mut cells = self @@ -201,7 +187,7 @@ where } /// Returns whether `coords` can be mapped to a stable grid key. - pub fn can_key_coords(&self, coords: &[T; D]) -> bool { + pub fn can_key_coords(&self, coords: &[f64; D]) -> bool { self.key_for_coords(coords).is_some() } @@ -209,7 +195,7 @@ where /// /// If the index is not usable, or the point cannot be keyed robustly, the /// index is disabled (so callers can fall back to linear scans). - pub fn insert_vertex(&mut self, vertex_key: K, coords: &[T; D]) { + pub fn insert_vertex(&mut self, vertex_key: K, coords: &[f64; D]) { if !self.usable { return; } @@ -227,7 +213,7 @@ where /// If the coordinates cannot be keyed, the index is disabled so callers fall /// back to a full scan instead of querying a cache that may contain a stale /// entry we could not remove. - pub fn remove_vertex(&mut self, vertex_key: &K, coords: &[T; D]) + pub fn remove_vertex(&mut self, vertex_key: &K, coords: &[f64; D]) where K: PartialEq, { @@ -254,7 +240,7 @@ where /// /// Returns `true` if the index was used for the query (even if it yielded zero /// candidates). Returns `false` if the index was unusable for this query. - pub fn for_each_candidate_vertex_key(&self, coords: &[T; D], mut f: F) -> bool + pub fn for_each_candidate_vertex_key(&self, coords: &[f64; D], mut f: F) -> bool where K: Copy, F: FnMut(K) -> bool, @@ -285,13 +271,12 @@ where } /// Converts finite coordinates into a grid key when neighbor enumeration remains stable. - fn key_for_coords(&self, coords: &[T; D]) -> Option> { + fn key_for_coords(&self, coords: &[f64; D]) -> Option> { if !self.usable { return None; } - let mut key = [T::zero(); D]; - let one = T::one(); + let mut key = [0.0; D]; for (i, coord) in coords.iter().enumerate() { if !coord.is_finite() { @@ -305,7 +290,7 @@ where // If the cell coordinate is too large to have unit resolution, neighbor // enumeration would be lossy (cell_coord + 1 == cell_coord). Disable. - if (cell_coord + one).ordered_eq(&cell_coord) { + if (cell_coord + 1.0).ordered_eq(&cell_coord) { return None; } @@ -316,16 +301,20 @@ where } /// Recursively visits the Moore neighborhood around `base` and stops early on callback failure. - fn visit_neighbor_cells(axis: usize, base: &[T; D], current: &mut [T; D], f: &mut F) -> bool + fn visit_neighbor_cells( + axis: usize, + base: &[f64; D], + current: &mut [f64; D], + f: &mut F, + ) -> bool where - F: FnMut(GridKey) -> bool, + F: FnMut(GridKey) -> bool, { if axis == D { return f(GridKey(*current)); } - let one = T::one(); - let offsets = [-one, T::zero(), one]; + let offsets = [-1.0, 0.0, 1.0]; for offset in offsets { current[axis] = base[axis] + offset; @@ -349,32 +338,32 @@ mod tests { #[test] fn test_hash_grid_index_try_new_rejects_invalid_cell_size() { - let grid: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let grid: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); assert!(grid.is_usable()); assert_abs_diff_eq!(grid.cell_size(), 1.0, epsilon = f64::EPSILON); assert!(matches!( - HashGridIndex::::try_new(0.0), + HashGridIndex::<2>::try_new(0.0), Err(HashGridIndexError::NonPositiveCellSize { value: 0.0 }) )); assert!(matches!( - HashGridIndex::::try_new(-1.0), + HashGridIndex::<2>::try_new(-1.0), Err(HashGridIndexError::NonPositiveCellSize { value: -1.0 }) )); assert!(matches!( - HashGridIndex::::try_new(f64::NAN), + HashGridIndex::<2>::try_new(f64::NAN), Err(HashGridIndexError::NonFiniteCellSize { value: InvalidCoordinateValue::Nan }) )); assert!(matches!( - HashGridIndex::::try_new(f64::INFINITY), + HashGridIndex::<2>::try_new(f64::INFINITY), Err(HashGridIndexError::NonFiniteCellSize { value: InvalidCoordinateValue::PositiveInfinity }) )); assert!(matches!( - HashGridIndex::::try_new(f64::NEG_INFINITY), + HashGridIndex::<2>::try_new(f64::NEG_INFINITY), Err(HashGridIndexError::NonFiniteCellSize { value: InvalidCoordinateValue::NegativeInfinity }) @@ -387,7 +376,7 @@ mod tests { let v1 = slots.insert(1); let v2 = slots.insert(2); - let mut grid: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut grid: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); grid.insert_vertex(v1, &[0.2, 0.2]); grid.insert_vertex(v2, &[-0.2, 0.2]); @@ -406,7 +395,7 @@ mod tests { #[test] fn test_hash_grid_index_candidate_visit_counts_2d() { let mut slots: SlotMap = SlotMap::default(); - let mut grid: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut grid: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); let mut expected: FastHashSet = FastHashSet::default(); @@ -436,7 +425,7 @@ mod tests { let mut slots: SlotMap = SlotMap::default(); let v1 = slots.insert(1); let v2 = slots.insert(2); - let mut grid: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut grid: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); grid.insert_vertex(v1, &[0.25, 0.25]); grid.insert_vertex(v2, &[0.25, 0.25]); @@ -467,12 +456,12 @@ mod tests { let v1 = slots.insert(1); let v2 = slots.insert(2); - let mut unsupported: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut unsupported: HashGridIndex<6> = HashGridIndex::try_new(1.0).unwrap(); assert!(!unsupported.is_usable()); unsupported.remove_vertex(&v1, &[0.0; 6]); assert!(!unsupported.is_usable()); - let mut grid: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut grid: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); grid.insert_vertex(v1, &[0.25, 0.25]); grid.remove_vertex(&v2, &[20.25, 20.25]); @@ -489,7 +478,7 @@ mod tests { fn test_hash_grid_index_remove_vertex_disables_on_unkeyable_coordinates() { let mut slots: SlotMap = SlotMap::default(); let v = slots.insert(1); - let mut grid: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut grid: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); grid.insert_vertex(v, &[0.25, 0.25]); assert!(grid.is_usable()); @@ -503,7 +492,7 @@ mod tests { #[test] fn test_hash_grid_index_candidate_visit_counts_5d() { let mut slots: SlotMap = SlotMap::default(); - let mut grid: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut grid: HashGridIndex<5> = HashGridIndex::try_new(1.0).unwrap(); let mut expected: FastHashSet = FastHashSet::default(); diff --git a/src/core/construction.rs b/src/core/construction.rs index ead95a82..aa85d963 100644 --- a/src/core/construction.rs +++ b/src/core/construction.rs @@ -1,7 +1,7 @@ //! Generic triangulation construction helpers. //! //! This module owns the generic construction vocabulary for -//! [`Triangulation`](crate::core::triangulation::Triangulation) +//! [`Triangulation`](crate::prelude::triangulation::Triangulation) //! and the initial-simplex bootstrap used before incremental insertion takes //! over. Mutation-heavy insertion and repair orchestration remain implemented //! with the triangulation type until they can be split into narrower modules. @@ -23,7 +23,6 @@ use crate::geometry::predicates::Orientation; use crate::geometry::robust_predicates::robust_orientation; use crate::geometry::traits::coordinate::CoordinateValues; use crate::validation::DelaunayTriangulationValidationError; -use num_traits::NumCast; use thiserror::Error; /// Errors that can occur during triangulation construction. @@ -32,13 +31,13 @@ use thiserror::Error; /// /// ```rust /// use delaunay::prelude::triangulation::{ -/// FastKernel, Triangulation, TriangulationConstructionError, vertex, +/// FastKernel, Triangulation, TriangulationConstructionError, /// }; /// /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let result: Result<_, TriangulationConstructionError> = /// Triangulation::, (), (), 2>::build_initial_simplex(&vertices); @@ -201,8 +200,7 @@ pub enum TriangulationConstructionError { impl Triangulation where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -235,12 +233,12 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::triangulation::{FastKernel, Triangulation, vertex}; + /// use delaunay::prelude::triangulation::{FastKernel, Triangulation}; /// /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices)?; /// assert_eq!(tds.number_of_vertices(), 3); @@ -249,8 +247,8 @@ where /// # Ok::<(), delaunay::prelude::triangulation::TriangulationConstructionError>(()) /// ``` pub fn build_initial_simplex( - vertices: &[Vertex], - ) -> Result, TriangulationConstructionError> { + vertices: &[Vertex], + ) -> Result, TriangulationConstructionError> { if vertices.len() != D + 1 { return Err(TriangulationConstructionError::InsufficientVertices { dimension: D, @@ -273,7 +271,7 @@ where })?; } - let points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = vertices.iter().map(|v| *v.point()).collect(); let exact_orientation = robust_orientation(&points[..]).map_err(|e| { @@ -326,7 +324,7 @@ where } } - let simplex = Simplex::new(vertex_keys, None).map_err(|e| { + let simplex = Simplex::try_new(vertex_keys).map_err(|e| { TriangulationConstructionError::FailedToCreateSimplex { message: format!("Failed to create initial simplex: {e}"), } @@ -347,12 +345,9 @@ where mod tests { use super::*; use crate::core::simplex::NeighborSlot; - use crate::core::vertex::VertexBuilder; use crate::geometry::kernel::FastKernel; - use crate::geometry::traits::coordinate::Coordinate; - use crate::vertex; + use crate::geometry::traits::coordinate::{CoordinateValidationError, InvalidCoordinateValue}; use std::assert_matches; - use uuid::Uuid; #[test] fn internal_inconsistency_display() { @@ -371,8 +366,8 @@ mod tests { pastey::paste! { #[test] fn []() { - let vertices: Vec> = vec![ - $(vertex!($simplex_coords)),+ + let vertices: Vec> = vec![ + $(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+ ]; let expected_vertices = vertices.len(); @@ -437,7 +432,10 @@ mod tests { #[test] fn build_initial_simplex_insufficient_vertices() { - let vertices = vec![vertex!([0.0, 0.0, 0.0]), vertex!([1.0, 0.0, 0.0])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ]; let result = Triangulation::, (), (), 3>::build_initial_simplex(&vertices); @@ -450,10 +448,10 @@ mod tests { #[test] fn build_initial_simplex_too_many_vertices() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let result = Triangulation::, (), (), 2>::build_initial_simplex(&vertices); @@ -464,43 +462,23 @@ mod tests { ); } - fn invalid_initial_simplex_vertices() -> Vec> { - let mut vertices = Vec::with_capacity(D + 1); - vertices.push(vertex!([0.0_f64; D])); - - let mut invalid_coords = [0.0_f64; D]; - invalid_coords[0] = 1.0; - invalid_coords[1] = f64::NAN; - vertices.push(Vertex::new_with_uuid( - Point::new(invalid_coords), - Uuid::new_v4(), - None, - )); - - for axis in 1..D { - let mut coords = [0.0_f64; D]; - coords[axis] = 1.0; - vertices.push(vertex!(coords)); - } - - vertices - } - - macro_rules! test_build_initial_simplex_rejects_invalid_vertex_dimensions { + macro_rules! test_build_initial_simplex_rejects_non_finite_vertex_coordinate_dimensions { ($($dim:expr),+ $(,)?) => { pastey::paste! { $( #[test] - fn []() { - let vertices = invalid_initial_simplex_vertices::<$dim>(); - - let result = Triangulation::, (), (), $dim>::build_initial_simplex(&vertices); + fn []() { + let mut invalid_coords = [0.0_f64; $dim]; + invalid_coords[0] = 1.0; + invalid_coords[1] = f64::NAN; assert_matches!( - result, - Err(TriangulationConstructionError::Tds( - TdsConstructionError::ValidationError(TdsError::InvalidVertex { .. }) - )) + Point::<$dim>::try_new(invalid_coords), + Err(CoordinateValidationError::InvalidCoordinate { + coordinate_index: 1, + coordinate_value: InvalidCoordinateValue::Nan, + dimension: $dim, + }) ); } )+ @@ -508,25 +486,13 @@ mod tests { }; } - test_build_initial_simplex_rejects_invalid_vertex_dimensions!(2, 3, 4, 5); + test_build_initial_simplex_rejects_non_finite_vertex_coordinate_dimensions!(2, 3, 4, 5); #[test] fn build_initial_simplex_with_user_data() { - let v1 = VertexBuilder::default() - .point(Point::new([0.0, 0.0])) - .data(42_usize) - .build() - .unwrap(); - let v2 = VertexBuilder::default() - .point(Point::new([1.0, 0.0])) - .data(43_usize) - .build() - .unwrap(); - let v3 = VertexBuilder::default() - .point(Point::new([0.0, 1.0])) - .data(44_usize) - .build() - .unwrap(); + let v1 = Vertex::try_new_with_data([0.0, 0.0], 42_usize).unwrap(); + let v2 = Vertex::try_new_with_data([1.0, 0.0], 43_usize).unwrap(); + let v3 = Vertex::try_new_with_data([0.0, 1.0], 44_usize).unwrap(); let vertices = vec![v1, v2, v3]; let tds = Triangulation::, usize, (), 2>::build_initial_simplex(&vertices) @@ -549,9 +515,9 @@ mod tests { #[test] fn build_initial_simplex_rejects_collinear_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([2.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), ]; let result = Triangulation::, (), (), 2>::build_initial_simplex(&vertices); @@ -565,10 +531,10 @@ mod tests { #[test] fn build_initial_simplex_rejects_coplanar_3d() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.5, 0.5, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.0]).unwrap(), ]; let result = Triangulation::, (), (), 3>::build_initial_simplex(&vertices); diff --git a/src/core/edge.rs b/src/core/edge.rs index c77cd289..ca00dd10 100644 --- a/src/core/edge.rs +++ b/src/core/edge.rs @@ -4,7 +4,7 @@ //! triangulation topology. Since edges are not stored explicitly (they are inferred from //! maximal simplices), we expose a lightweight `EdgeKey` that: //! -//! - identifies an edge purely by its two endpoint [`VertexKey`]s +//! - identifies an edge by two live endpoint [`VertexKey`]s that share a simplex //! - canonicalizes endpoint ordering so `(a, b)` and `(b, a)` map to the same edge //! - is `Copy`/`Hash`/`Ord` for fast use in sets and maps //! @@ -16,22 +16,67 @@ #![forbid(unsafe_code)] -use crate::core::tds::VertexKey; +use crate::core::tds::{Tds, VertexKey}; use slotmap::Key; +use thiserror::Error; + +/// Error returned when constructing an [`EdgeKey`] from invalid endpoints. +#[derive(Copy, Clone, Debug, Error, PartialEq, Eq)] +#[non_exhaustive] +pub enum EdgeKeyError { + /// Both endpoints refer to the same vertex. + #[error("Edge endpoints must be distinct, got {endpoint:?} twice")] + DuplicateEndpoint { + /// The repeated vertex key. + endpoint: VertexKey, + }, + /// One endpoint is not present in the TDS. + #[error("Edge endpoint {endpoint:?} is not present in the TDS")] + MissingEndpoint { + /// The missing endpoint key. + endpoint: VertexKey, + }, + /// The endpoints are live vertices but do not form an edge in any simplex. + #[error("Vertices {v0:?} and {v1:?} are not joined by an edge in the TDS")] + EdgeNotFound { + /// First endpoint. + v0: VertexKey, + /// Second endpoint. + v1: VertexKey, + }, +} /// Canonical identifier for an (undirected) edge. /// /// # Examples /// /// ```rust +/// use delaunay::prelude::*; /// use delaunay::prelude::tds::EdgeKey; -/// use delaunay::prelude::tds::VertexKey; -/// use slotmap::KeyData; /// -/// let a = VertexKey::from(KeyData::from_ffi(1)); -/// let b = VertexKey::from(KeyData::from_ffi(2)); -/// let edge = EdgeKey::new(a, b); +/// # #[derive(Debug, thiserror::Error)] +/// # enum ExampleError { +/// # #[error(transparent)] +/// # Construction(#[from] DelaunayTriangulationConstructionError), +/// # #[error(transparent)] +/// # Edge(#[from] delaunay::prelude::tds::EdgeKeyError), +/// # } +/// # fn main() -> Result<(), ExampleError> { +/// let vertices = [ +/// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), +/// ]; +/// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; +/// let Some((_simplex_key, simplex)) = dt.simplices().next() else { +/// return Ok(()); +/// }; +/// let a = simplex.vertices()[0]; +/// let b = simplex.vertices()[1]; +/// let edge = EdgeKey::try_new(dt.tds(), a, b)?; /// assert_eq!(edge.endpoints(), (edge.v0(), edge.v1())); +/// # Ok(()) +/// # } /// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct EdgeKey { @@ -42,36 +87,80 @@ pub struct EdgeKey { impl EdgeKey { /// Creates a new canonical edge key. /// - /// The endpoints are reordered so that `v0 <= v1` under the internal key order. + /// The endpoints must be distinct, present in `tds`, and co-incident in at + /// least one simplex. Endpoints are reordered so that `v0 <= v1` under the + /// internal key order. + /// + /// # Errors + /// + /// Returns [`EdgeKeyError::DuplicateEndpoint`] if both endpoints are the + /// same vertex, [`EdgeKeyError::MissingEndpoint`] if either endpoint is not + /// live in `tds`, or [`EdgeKeyError::EdgeNotFound`] if no simplex contains + /// both endpoints. /// /// # Examples /// /// ```rust /// use delaunay::prelude::*; + /// use delaunay::prelude::tds::EdgeKey; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Edge(#[from] delaunay::prelude::tds::EdgeKeyError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let vertices = [ + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; - /// let dt: DelaunayTriangulation<_, (), (), 2> = - /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; - /// let tri = dt.as_triangulation(); + /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; + /// let Some((_simplex_key, simplex)) = dt.simplices().next() else { + /// return Ok(()); + /// }; + /// let a = simplex.vertices()[0]; + /// let b = simplex.vertices()[1]; /// - /// let mut it = tri.vertices(); - /// let Some((a, _)) = it.next() else { return Ok(()); }; - /// let Some((b, _)) = it.next() else { return Ok(()); }; - /// - /// let e1 = EdgeKey::new(a, b); - /// let e2 = EdgeKey::new(b, a); + /// let e1 = EdgeKey::try_new(dt.tds(), a, b)?; + /// let e2 = EdgeKey::try_new(dt.tds(), b, a)?; /// assert_eq!(e1, e2); /// assert!(e1.v0() <= e1.v1()); /// # Ok(()) /// # } /// ``` + pub fn try_new( + tds: &Tds, + a: VertexKey, + b: VertexKey, + ) -> Result { + if a == b { + return Err(EdgeKeyError::DuplicateEndpoint { endpoint: a }); + } + if tds.vertex(a).is_none() { + return Err(EdgeKeyError::MissingEndpoint { endpoint: a }); + } + if tds.vertex(b).is_none() { + return Err(EdgeKeyError::MissingEndpoint { endpoint: b }); + } + if !tds + .simplices() + .any(|(_simplex_key, simplex)| simplex.contains_vertex(a) && simplex.contains_vertex(b)) + { + return Err(EdgeKeyError::EdgeNotFound { v0: a, v1: b }); + } + + Ok(Self::from_validated_endpoints(a, b)) + } + + /// Creates a canonical edge key from endpoints already known to be distinct. + #[inline] #[must_use] - pub fn new(a: VertexKey, b: VertexKey) -> Self { + pub(crate) fn from_validated_endpoints(a: VertexKey, b: VertexKey) -> Self { + debug_assert_ne!(a, b, "validated edge endpoints must be distinct"); + // Use the raw slotmap key representation for ordering to avoid relying on // any higher-level semantic ordering. let a_raw = a.data().as_ffi(); @@ -90,22 +179,29 @@ impl EdgeKey { /// /// ```rust /// use delaunay::prelude::*; + /// use delaunay::prelude::tds::EdgeKey; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Edge(#[from] delaunay::prelude::tds::EdgeKeyError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let vertices = [ + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; - /// let dt: DelaunayTriangulation<_, (), (), 2> = - /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; - /// let tri = dt.as_triangulation(); + /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; + /// let Some((_simplex_key, simplex)) = dt.simplices().next() else { + /// return Ok(()); + /// }; + /// let a = simplex.vertices()[1]; + /// let b = simplex.vertices()[0]; /// - /// let mut it = tri.vertices(); - /// let Some((a, _)) = it.next() else { return Ok(()); }; - /// let Some((b, _)) = it.next() else { return Ok(()); }; - /// - /// let e = EdgeKey::new(a, b); + /// let e = EdgeKey::try_new(dt.tds(), a, b)?; /// let v0 = e.v0(); /// let v1 = e.v1(); /// assert!(v0 <= v1); @@ -124,22 +220,29 @@ impl EdgeKey { /// /// ```rust /// use delaunay::prelude::*; + /// use delaunay::prelude::tds::EdgeKey; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Edge(#[from] delaunay::prelude::tds::EdgeKeyError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let vertices = [ + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; - /// let dt: DelaunayTriangulation<_, (), (), 2> = - /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; - /// let tri = dt.as_triangulation(); + /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; + /// let Some((_simplex_key, simplex)) = dt.simplices().next() else { + /// return Ok(()); + /// }; + /// let a = simplex.vertices()[0]; + /// let b = simplex.vertices()[1]; /// - /// let mut it = tri.vertices(); - /// let Some((a, _)) = it.next() else { return Ok(()); }; - /// let Some((b, _)) = it.next() else { return Ok(()); }; - /// - /// let e = EdgeKey::new(a, b); + /// let e = EdgeKey::try_new(dt.tds(), a, b)?; /// let v0 = e.v0(); /// let v1 = e.v1(); /// assert!(v0 <= v1); @@ -158,22 +261,29 @@ impl EdgeKey { /// /// ```rust /// use delaunay::prelude::*; + /// use delaunay::prelude::tds::EdgeKey; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Edge(#[from] delaunay::prelude::tds::EdgeKeyError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let vertices = [ + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; - /// let dt: DelaunayTriangulation<_, (), (), 2> = - /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; - /// let tri = dt.as_triangulation(); - /// - /// let mut it = tri.vertices(); - /// let Some((a, _)) = it.next() else { return Ok(()); }; - /// let Some((b, _)) = it.next() else { return Ok(()); }; + /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; + /// let Some((_simplex_key, simplex)) = dt.simplices().next() else { + /// return Ok(()); + /// }; + /// let a = simplex.vertices()[0]; + /// let b = simplex.vertices()[1]; /// - /// let e = EdgeKey::new(a, b); + /// let e = EdgeKey::try_new(dt.tds(), a, b)?; /// let (v0, v1) = e.endpoints(); /// assert_eq!(v0, e.v0()); /// assert_eq!(v1, e.v1()); @@ -188,67 +298,116 @@ impl EdgeKey { } } -impl From<(VertexKey, VertexKey)> for EdgeKey { - #[inline] - fn from((a, b): (VertexKey, VertexKey)) -> Self { - Self::new(a, b) - } -} - #[cfg(test)] mod tests { use super::*; - use slotmap::SlotMap; + use crate::prelude::{DelaunayTriangulationBuilder, Vertex}; use std::collections::{BTreeSet, HashSet}; + fn with_triangle_tds(test: impl FnOnce(&Tds<(), (), 2>, [VertexKey; 3])) { + let vertices = [ + Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ]; + let dt = DelaunayTriangulationBuilder::new(&vertices) + .build::<()>() + .unwrap(); + let simplex = dt.simplices().next().unwrap().1; + let vertices = simplex.vertices(); + let simplex_vertices = [vertices[0], vertices[1], vertices[2]]; + test(dt.tds(), simplex_vertices); + } + #[test] fn edge_key_is_canonical() { - let mut vertices: SlotMap = SlotMap::with_key(); - let a = vertices.insert(()); - let b = vertices.insert(()); + with_triangle_tds(|tds, [a, b, _c]| { + let e1 = EdgeKey::try_new(tds, a, b).unwrap(); + let e2 = EdgeKey::try_new(tds, b, a).unwrap(); - let e1 = EdgeKey::new(a, b); - let e2 = EdgeKey::new(b, a); + assert_eq!(e1, e2); - assert_eq!(e1, e2); - - // Ensure the ordering invariant holds. - assert!(e1.v0().data().as_ffi() <= e1.v1().data().as_ffi()); + // Ensure the ordering invariant holds. + assert!(e1.v0().data().as_ffi() <= e1.v1().data().as_ffi()); + }); } #[test] fn edge_key_endpoints_roundtrip() { - let mut vertices: SlotMap = SlotMap::with_key(); - let a = vertices.insert(()); - let b = vertices.insert(()); + with_triangle_tds(|tds, [a, b, _c]| { + let e = EdgeKey::try_new(tds, b, a).unwrap(); + let (v0, v1) = e.endpoints(); - let e = EdgeKey::new(b, a); - let (v0, v1) = e.endpoints(); + assert_eq!(v0, e.v0()); + assert_eq!(v1, e.v1()); + assert!(v0.data().as_ffi() <= v1.data().as_ffi()); + assert_eq!(EdgeKey::try_new(tds, a, b).unwrap(), e); + }); + } - assert_eq!(v0, e.v0()); - assert_eq!(v1, e.v1()); - assert!(v0.data().as_ffi() <= v1.data().as_ffi()); + #[test] + fn edge_key_rejects_duplicate_endpoint() { + with_triangle_tds(|tds, [a, _b, _c]| { + assert_eq!( + EdgeKey::try_new(tds, a, a), + Err(EdgeKeyError::DuplicateEndpoint { endpoint: a }) + ); + }); + } - assert_eq!(EdgeKey::from((a, b)), EdgeKey::new(a, b)); + #[test] + fn edge_key_rejects_missing_endpoint() { + with_triangle_tds(|tds, [a, _b, _c]| { + let missing = VertexKey::default(); + assert_eq!( + EdgeKey::try_new(tds, a, missing), + Err(EdgeKeyError::MissingEndpoint { endpoint: missing }) + ); + }); + } + + #[test] + fn edge_key_rejects_live_vertices_without_edge() { + let vertices = [ + Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ]; + let dt = DelaunayTriangulationBuilder::new(&vertices) + .build::<()>() + .unwrap(); + let keys: Vec = dt.tds().vertex_keys().collect(); + let missing_edge = keys.iter().enumerate().find_map(|(i, &a)| { + keys.iter().skip(i + 1).copied().find_map(|b| { + matches!( + EdgeKey::try_new(dt.tds(), a, b), + Err(EdgeKeyError::EdgeNotFound { .. }) + ) + .then_some((a, b)) + }) + }); + let (a, b) = missing_edge.expect("square triangulation should have one missing diagonal"); + assert_eq!( + EdgeKey::try_new(dt.tds(), a, b), + Err(EdgeKeyError::EdgeNotFound { v0: a, v1: b }) + ); } #[test] fn edge_key_is_hashable_and_orderable() { - let mut vertices: SlotMap = SlotMap::with_key(); - let a = vertices.insert(()); - let b = vertices.insert(()); - let c = vertices.insert(()); - - let mut hash_set: HashSet = HashSet::new(); - hash_set.insert(EdgeKey::new(a, b)); - hash_set.insert(EdgeKey::new(b, a)); - hash_set.insert(EdgeKey::new(a, c)); - assert_eq!(hash_set.len(), 2); - - let mut btree_set: BTreeSet = BTreeSet::new(); - btree_set.insert(EdgeKey::new(a, b)); - btree_set.insert(EdgeKey::new(b, a)); - btree_set.insert(EdgeKey::new(a, c)); - assert_eq!(btree_set.len(), 2); + with_triangle_tds(|tds, [a, b, c]| { + let mut hash_set: HashSet = HashSet::new(); + hash_set.insert(EdgeKey::try_new(tds, a, b).unwrap()); + hash_set.insert(EdgeKey::try_new(tds, b, a).unwrap()); + hash_set.insert(EdgeKey::try_new(tds, a, c).unwrap()); + assert_eq!(hash_set.len(), 2); + + let mut btree_set: BTreeSet = BTreeSet::new(); + btree_set.insert(EdgeKey::try_new(tds, a, b).unwrap()); + btree_set.insert(EdgeKey::try_new(tds, b, a).unwrap()); + btree_set.insert(EdgeKey::try_new(tds, a, c).unwrap()); + assert_eq!(btree_set.len(), 2); + }); } } diff --git a/src/core/facet.rs b/src/core/facet.rs index a9d777d7..e4f160b6 100644 --- a/src/core/facet.rs +++ b/src/core/facet.rs @@ -28,7 +28,7 @@ //! construction and validation phases. //! //! For a comprehensive discussion of all topological invariants in Delaunay triangulations, -//! see the [Topological Invariants](crate::core::tds#topological-invariants) +//! see the [Topological Invariants](crate::prelude::tds#topological-invariants) //! section in the triangulation data structure documentation. //! //! # Examples @@ -49,10 +49,10 @@ //! # fn main() -> Result<(), ExampleError> { //! // Create vertices for a tetrahedron //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! //! // Create a 3D triangulation @@ -62,7 +62,7 @@ //! }; //! //! // Create a facet view (facet 0 excludes vertex 0) -//! let facet = FacetView::new(dt.tds(), simplex_key, 0)?; +//! let facet = FacetView::try_new(dt.tds(), simplex_key, 0)?; //! assert_eq!(facet.vertices()?.count(), 3); // Facet (triangle) in 3D has 3 vertices //! # Ok(()) //! # } @@ -175,6 +175,14 @@ pub enum FacetError { /// The number of facets available. facet_count: usize, }, + /// Dimension exceeds the maximum representable facet index. + #[error("Dimension {dimension} exceeds maximum {max_dimension} for u8 facet indices")] + FacetIndexCapacityExceeded { + /// The triangulation dimension. + dimension: usize, + /// The maximum supported dimension with the current facet-index representation. + max_dimension: usize, + }, /// Simplex was not found in the triangulation. #[error("Simplex not found in triangulation (potential data corruption)")] SimplexNotFoundInTriangulation, @@ -249,10 +257,10 @@ pub enum FacetError { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { @@ -260,10 +268,10 @@ pub enum FacetError { /// }; /// /// // Create a facet handle -/// let handle = FacetHandle::new(simplex_key, 0); +/// let handle = FacetHandle::try_new(dt.tds(), simplex_key, 0)?; /// /// // Use it to create a FacetView -/// let facet = FacetView::new(dt.tds(), handle.simplex_key(), handle.facet_index())?; +/// let facet = FacetView::try_new(dt.tds(), handle.simplex_key(), handle.facet_index())?; /// # let _ = facet; /// # Ok(()) /// # } @@ -276,7 +284,7 @@ pub struct FacetHandle { } impl FacetHandle { - /// Creates a new facet handle. + /// Creates a new facet handle after validating it against a TDS. /// /// # Arguments /// @@ -289,24 +297,57 @@ impl FacetHandle { /// use delaunay::prelude::*; /// use delaunay::prelude::tds::FacetHandle; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Facet(#[from] delaunay::prelude::tds::FacetError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let vertices = [ + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; - /// - /// let handle = FacetHandle::new(simplex_key, 0); - /// assert_eq!(handle.simplex_key(), simplex_key); + /// let handle = FacetHandle::try_new(dt.tds(), simplex_key, 0)?; /// assert_eq!(handle.facet_index(), 0); /// # Ok(()) /// # } /// ``` - pub const fn new(simplex_key: SimplexKey, facet_index: u8) -> Self { + /// + /// # Errors + /// + /// Returns [`FacetError::SimplexNotFoundInTriangulation`] if `simplex_key` + /// is not present in `tds`, or [`FacetError::InvalidFacetIndex`] if + /// `facet_index` is outside the simplex facet range. + pub fn try_new( + tds: &Tds, + simplex_key: SimplexKey, + facet_index: u8, + ) -> Result { + let simplex = tds + .simplex(simplex_key) + .ok_or(FacetError::SimplexNotFoundInTriangulation)?; + let facet_count = simplex.number_of_vertices(); + if usize::from(facet_index) >= facet_count { + return Err(FacetError::InvalidFacetIndex { + index: facet_index, + facet_count, + }); + } + + Ok(Self::from_validated(simplex_key, facet_index)) + } + + /// Creates a facet handle from a simplex key and facet index already + /// proven valid by the caller. + #[inline] + pub(crate) const fn from_validated(simplex_key: SimplexKey, facet_index: u8) -> Self { Self { simplex_key, facet_index, @@ -321,18 +362,24 @@ impl FacetHandle { /// use delaunay::prelude::*; /// use delaunay::prelude::tds::FacetHandle; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Facet(#[from] delaunay::prelude::tds::FacetError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let vertices = [ + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; - /// - /// let handle = FacetHandle::new(simplex_key, 0); + /// let handle = FacetHandle::try_new(dt.tds(), simplex_key, 0)?; /// assert_eq!(handle.simplex_key(), simplex_key); /// # Ok(()) /// # } @@ -350,18 +397,24 @@ impl FacetHandle { /// use delaunay::prelude::*; /// use delaunay::prelude::tds::FacetHandle; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Facet(#[from] delaunay::prelude::tds::FacetError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let vertices = [ + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; - /// - /// let handle = FacetHandle::new(simplex_key, 1); + /// let handle = FacetHandle::try_new(dt.tds(), simplex_key, 1)?; /// assert_eq!(handle.facet_index(), 1); /// # Ok(()) /// # } @@ -387,7 +440,7 @@ impl FacetHandle { /// /// # Memory Efficiency /// -/// Compared to the original `Facet`: +/// Compared to the original `Facet`: /// - **Original**: Stores complete Simplex + Vertex objects (~hundreds of bytes) /// - **`FacetView`**: Stores TDS reference + `SimplexKey` + `facet_index` (~17 bytes) /// - **Memory reduction: ~18x smaller** @@ -395,7 +448,6 @@ impl FacetHandle { /// # Type Parameters /// /// - `'tds`: Lifetime of the triangulation data structure -/// - `T`: Coordinate scalar type /// - `U`: Vertex data type /// - `V`: Simplex data type /// - `D`: Spatial dimension @@ -409,11 +461,11 @@ impl FacetHandle { /// // This is a conceptual example showing FacetView usage /// // In practice, tds and simplex_key would come from your triangulation /// fn example_usage<'a>( -/// tds: &'a Tds, +/// tds: &'a Tds<(), (), 3>, /// simplex_key: SimplexKey, /// ) -> Result<(), FacetError> { /// // Create a facet view for the first facet of a simplex -/// let facet_view = FacetView::new(tds, simplex_key, 0)?; +/// let facet_view = FacetView::try_new(tds, simplex_key, 0)?; /// /// // Access vertices through the view (lazy evaluation) /// for vertex in facet_view.vertices()? { @@ -429,9 +481,9 @@ impl FacetHandle { /// } /// ``` #[must_use] -pub struct FacetView<'tds, T, U, V, const D: usize> { +pub struct FacetView<'tds, U, V, const D: usize> { /// Reference to the triangulation data structure. - tds: &'tds Tds, + tds: &'tds Tds, /// Key of the simplex containing this facet. simplex_key: SimplexKey, /// Index of this facet within the simplex (0 <= `facet_index` < D+1). @@ -442,7 +494,7 @@ pub struct FacetView<'tds, T, U, V, const D: usize> { facet_index: u8, } -impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { +impl<'tds, U, V, const D: usize> FacetView<'tds, U, V, D> { /// Returns the simplex key for this facet. #[inline] #[must_use] @@ -460,12 +512,12 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { /// Returns the TDS reference. #[inline] #[must_use] - pub const fn tds(&self) -> &'tds Tds { + pub const fn tds(&self) -> &'tds Tds { self.tds } } -impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { +impl<'tds, U, V, const D: usize> FacetView<'tds, U, V, D> { /// Creates a new `FacetView` for the specified facet of a simplex. /// /// # Arguments @@ -480,9 +532,10 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { /// /// # Errors /// - /// Returns `FacetError` if: - /// - `simplex_key` is not found in the TDS - /// - `facet_index` is out of bounds (>= D+1) + /// Returns [`FacetError::SimplexNotFoundInTriangulation`] if + /// `simplex_key` is not present in the TDS, or + /// [`FacetError::InvalidFacetIndex`] if `facet_index` is not a valid facet + /// of the simplex. /// /// # Examples /// @@ -501,23 +554,23 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; - /// let facet = FacetView::new(dt.tds(), simplex_key, 0)?; + /// let facet = FacetView::try_new(dt.tds(), simplex_key, 0)?; /// assert_eq!(facet.facet_index(), 0); /// # Ok(()) /// # } /// ``` - pub fn new( - tds: &'tds Tds, + pub fn try_new( + tds: &'tds Tds, simplex_key: SimplexKey, facet_index: u8, ) -> Result { @@ -578,22 +631,22 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// if let Some((simplex_key, _)) = dt.simplices().next() { - /// let facet = FacetView::new(dt.tds(), simplex_key, 0)?; + /// let facet = FacetView::try_new(dt.tds(), simplex_key, 0)?; /// let vertex_iter = facet.vertices()?; /// assert_eq!(vertex_iter.count(), 3); // 3D facet has 3 vertices /// } /// # Ok(()) /// # } /// ``` - pub fn vertices(&self) -> Result>, FacetError> { + pub fn vertices(&self) -> Result>, FacetError> { let simplex = self .tds .simplex(self.simplex_key) @@ -602,7 +655,7 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { // Collect first so missing vertex keys become an error, not silent drops. // Use SmallBuffer for stack allocation (D vertices fit on stack for D ≤ 7) - let mut refs: SmallBuffer<&'tds Vertex, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut refs: SmallBuffer<&'tds Vertex, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(simplex.number_of_vertices().saturating_sub(1)); for (i, &vkey) in simplex.vertices().iter().enumerate() { if i == facet_index { @@ -647,23 +700,23 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; /// - /// let facet = FacetView::new(dt.tds(), simplex_key, 1)?; + /// let facet = FacetView::try_new(dt.tds(), simplex_key, 1)?; /// let opposite = facet.opposite_vertex()?; /// assert_eq!(opposite.point().coords().len(), 3); /// # Ok(()) /// # } /// ``` - pub fn opposite_vertex(&self) -> Result<&'tds Vertex, FacetError> { + pub fn opposite_vertex(&self) -> Result<&'tds Vertex, FacetError> { let simplex = self .tds .simplex(self.simplex_key) @@ -712,23 +765,23 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; /// - /// let facet = FacetView::new(dt.tds(), simplex_key, 2)?; + /// let facet = FacetView::try_new(dt.tds(), simplex_key, 2)?; /// let simplex = facet.simplex()?; /// assert_eq!(simplex.number_of_vertices(), 4); /// # Ok(()) /// # } /// ``` - pub fn simplex(&self) -> Result<&'tds Simplex, FacetError> { + pub fn simplex(&self) -> Result<&'tds Simplex, FacetError> { self.tds .simplex(self.simplex_key) .ok_or(FacetError::SimplexNotFoundInTriangulation) @@ -765,17 +818,17 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; /// - /// let facet = FacetView::new(dt.tds(), simplex_key, 0)?; + /// let facet = FacetView::try_new(dt.tds(), simplex_key, 0)?; /// let facet_key = facet.key()?; /// let map = dt.tds().build_facet_to_simplices_map()?; /// assert!(map.contains_key(&facet_key)); @@ -792,7 +845,7 @@ impl<'tds, T, U, V, const D: usize> FacetView<'tds, T, U, V, D> { } // Trait implementations for FacetView -impl Debug for FacetView<'_, T, U, V, D> { +impl Debug for FacetView<'_, U, V, D> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FacetView") .field("simplex_key", &self.simplex_key) @@ -806,7 +859,7 @@ impl Debug for FacetView<'_, T, U, V, D> { clippy::non_canonical_clone_impl, reason = "facet clone intentionally preserves cached view fields" )] -impl Clone for FacetView<'_, T, U, V, D> { +impl Clone for FacetView<'_, U, V, D> { fn clone(&self) -> Self { Self { tds: self.tds, @@ -816,9 +869,9 @@ impl Clone for FacetView<'_, T, U, V, D> { } } -impl Copy for FacetView<'_, T, U, V, D> {} +impl Copy for FacetView<'_, U, V, D> {} -impl PartialEq for FacetView<'_, T, U, V, D> { +impl PartialEq for FacetView<'_, U, V, D> { fn eq(&self, other: &Self) -> bool { // Two facet views are equal if they reference the same facet std::ptr::eq(self.tds, other.tds) @@ -827,7 +880,7 @@ impl PartialEq for FacetView<'_, T, U, V, D> { } } -impl Eq for FacetView<'_, T, U, V, D> {} +impl Eq for FacetView<'_, U, V, D> {} /// Utility function to create multiple `FacetView`s for all facets of a simplex. /// @@ -866,10 +919,10 @@ impl Eq for FacetView<'_, T, U, V, D> {} /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { @@ -881,10 +934,10 @@ impl Eq for FacetView<'_, T, U, V, D> {} /// # Ok(()) /// # } /// ``` -pub fn all_facets_for_simplex( - tds: &Tds, +pub fn all_facets_for_simplex( + tds: &Tds, simplex_key: SimplexKey, -) -> Result>, FacetError> { +) -> Result>, FacetError> { let simplex = tds .simplex(simplex_key) .ok_or(FacetError::SimplexNotFoundInTriangulation)?; @@ -894,7 +947,7 @@ pub fn all_facets_for_simplex( for facet_index in 0..vertex_count { let idx = facet_index; // usize - let facet_view = FacetView::new(tds, simplex_key, usize_to_u8(idx, vertex_count)?)?; + let facet_view = FacetView::try_new(tds, simplex_key, usize_to_u8(idx, vertex_count)?)?; facet_views.push(facet_view); } @@ -913,36 +966,43 @@ pub fn all_facets_for_simplex( /// use delaunay::prelude::tds::AllFacetsIter; /// use delaunay::prelude::*; /// -/// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { +/// # #[derive(Debug, thiserror::Error)] +/// # enum ExampleError { +/// # #[error(transparent)] +/// # Construction(#[from] DelaunayTriangulationConstructionError), +/// # #[error(transparent)] +/// # Facet(#[from] delaunay::prelude::tds::FacetError), +/// # } +/// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// -/// let count = AllFacetsIter::new(dt.tds()).count(); +/// let count = AllFacetsIter::try_new(dt.tds())?.count(); /// assert_eq!(count, 4); /// # Ok(()) /// # } /// ``` #[derive(Clone)] -pub struct AllFacetsIter<'tds, T, U, V, const D: usize> { - tds: &'tds Tds, +pub struct AllFacetsIter<'tds, U, V, const D: usize> { + tds: &'tds Tds, simplex_keys: std::vec::IntoIter, current_simplex_key: Option, current_facet_index: usize, current_simplex_facet_count: usize, } -impl<'tds, T, U, V, const D: usize> AllFacetsIter<'tds, T, U, V, D> { +impl<'tds, U, V, const D: usize> AllFacetsIter<'tds, U, V, D> { /// Creates a new iterator over all facets in the TDS. /// - /// # Panics + /// # Errors /// - /// Panics if `D > 255`, since facet indices are stored as `u8`. - /// This check is performed at runtime but optimizes away since `D` is a compile-time constant. + /// Returns [`FacetError::FacetIndexCapacityExceeded`] if `D > 255`, since + /// facet indices are stored as `u8`. /// /// # Examples /// @@ -950,27 +1010,36 @@ impl<'tds, T, U, V, const D: usize> AllFacetsIter<'tds, T, U, V, D> { /// use delaunay::prelude::tds::AllFacetsIter; /// use delaunay::prelude::*; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Facet(#[from] delaunay::prelude::tds::FacetError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// - /// let mut iter = AllFacetsIter::new(dt.tds()); + /// let mut iter = AllFacetsIter::try_new(dt.tds())?; /// assert!(iter.next().is_some()); /// # Ok(()) /// # } /// ``` - #[must_use] - pub fn new(tds: &'tds Tds) -> Self { + pub fn try_new(tds: &'tds Tds) -> Result { // Dimension check: facets per simplex = D+1, so D must be <= 255 - assert!( - D <= 255, - "Dimension D={D} exceeds maximum of 255 for u8 facet indices" - ); + if D > usize::from(u8::MAX) { + return Err(FacetError::FacetIndexCapacityExceeded { + dimension: D, + max_dimension: usize::from(u8::MAX), + }); + } + // We collect here because we need an owned iterator to store in the struct // SimplexKey is just u64, so this is efficient #[expect( @@ -978,18 +1047,18 @@ impl<'tds, T, U, V, const D: usize> AllFacetsIter<'tds, T, U, V, D> { reason = "iterator owns a stable snapshot of simplex keys before yielding facets" )] let simplex_keys: Vec = tds.simplex_keys().collect(); - Self { + Ok(Self { tds, simplex_keys: simplex_keys.into_iter(), current_simplex_key: None, current_facet_index: 0, current_simplex_facet_count: 0, - } + }) } } -impl<'tds, T, U, V, const D: usize> Iterator for AllFacetsIter<'tds, T, U, V, D> { - type Item = FacetView<'tds, T, U, V, D>; +impl<'tds, U, V, const D: usize> Iterator for AllFacetsIter<'tds, U, V, D> { + type Item = FacetView<'tds, U, V, D>; fn next(&mut self) -> Option { loop { @@ -1007,7 +1076,7 @@ impl<'tds, T, U, V, const D: usize> Iterator for AllFacetsIter<'tds, T, U, V, D> // If D can exceed 255, widen the index type. return None; }; - if let Ok(facet_view) = FacetView::new(self.tds, simplex_key, facet_u8) { + if let Ok(facet_view) = FacetView::try_new(self.tds, simplex_key, facet_u8) { return Some(facet_view); } } @@ -1038,7 +1107,6 @@ impl<'tds, T, U, V, const D: usize> Iterator for AllFacetsIter<'tds, T, U, V, D> /// # Examples /// /// ```rust -/// use delaunay::prelude::tds::BoundaryFacetsIter; /// use delaunay::prelude::*; /// /// # #[derive(Debug, thiserror::Error)] @@ -1052,32 +1120,34 @@ impl<'tds, T, U, V, const D: usize> Iterator for AllFacetsIter<'tds, T, U, V, D> /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; -/// let facet_map = dt.tds().build_facet_to_simplices_map()?; -/// -/// let count = BoundaryFacetsIter::new(dt.tds(), facet_map).count(); +/// let count = dt.tds().boundary_facets()?.count(); /// assert_eq!(count, 4); /// # Ok(()) /// # } /// ``` #[derive(Clone)] -pub struct BoundaryFacetsIter<'tds, T, U, V, const D: usize> { - all_facets: AllFacetsIter<'tds, T, U, V, D>, +pub struct BoundaryFacetsIter<'tds, U, V, const D: usize> { + all_facets: AllFacetsIter<'tds, U, V, D>, facet_to_simplices_map: crate::core::collections::FacetToSimplicesMap, } -impl<'tds, T, U, V, const D: usize> BoundaryFacetsIter<'tds, T, U, V, D> { +impl<'tds, U, V, const D: usize> BoundaryFacetsIter<'tds, U, V, D> { /// Creates a new iterator over boundary facets. /// + /// # Errors + /// + /// Returns [`FacetError::FacetIndexCapacityExceeded`] if this dimension + /// cannot be represented by the current `u8` facet-index storage. + /// /// # Examples /// /// ```rust - /// use delaunay::prelude::tds::BoundaryFacetsIter; /// use delaunay::prelude::*; /// /// # #[derive(Debug, thiserror::Error)] @@ -1091,30 +1161,30 @@ impl<'tds, T, U, V, const D: usize> BoundaryFacetsIter<'tds, T, U, V, D> { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; - /// let facet_map = dt.tds().build_facet_to_simplices_map()?; - /// - /// let mut iter = BoundaryFacetsIter::new(dt.tds(), facet_map); + /// let mut iter = dt.tds().boundary_facets()?; /// assert!(iter.next().is_some()); /// # Ok(()) /// # } /// ``` - #[must_use] - pub fn new(tds: &'tds Tds, facet_to_simplices_map: FacetToSimplicesMap) -> Self { - Self { - all_facets: AllFacetsIter::new(tds), + pub(crate) fn try_new( + tds: &'tds Tds, + facet_to_simplices_map: FacetToSimplicesMap, + ) -> Result { + Ok(Self { + all_facets: AllFacetsIter::try_new(tds)?, facet_to_simplices_map, - } + }) } } -impl<'tds, T, U, V, const D: usize> Iterator for BoundaryFacetsIter<'tds, T, U, V, D> { - type Item = FacetView<'tds, T, U, V, D>; +impl<'tds, U, V, const D: usize> Iterator for BoundaryFacetsIter<'tds, U, V, D> { + type Item = FacetView<'tds, U, V, D>; fn next(&mut self) -> Option { // Find the next boundary facet @@ -1222,12 +1292,11 @@ mod tests { use crate::construction::{ ConstructionOptions, InitialSimplexStrategy, InsertionOrderStrategy, }; - use crate::core::tds::VertexKey; + use crate::core::tds::{Tds, VertexKey}; use crate::core::validation::TopologyGuarantee; use crate::core::vertex::Vertex; use crate::geometry::kernel::AdaptiveKernel; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use slotmap::SlotMap; use std::assert_matches; use std::{collections::HashSet, mem}; @@ -1282,13 +1351,16 @@ mod tests { #[test] fn test_facet_error_handling() { // Create a 1D triangulation (2 vertices forming an edge) - let vertices = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Test invalid facet index (should be 0 or 1 for 1D, facet_index >= 2 is invalid) assert_matches!( - FacetView::new(dt.tds(), simplex_key, 99), + FacetView::try_new(dt.tds(), simplex_key, 99), Err(FacetError::InvalidFacetIndex { .. }) ); } @@ -1297,38 +1369,31 @@ mod tests { fn facet_new() { // Create a 3D triangulation with a tetrahedron let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Create facet view for facet 0 (excludes vertex 0) - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); assert_eq!(facet.simplex_key(), simplex_key); assert_eq!(facet.facet_index(), 0); - - // Human readable output for cargo test -- --nocapture - println!( - "FacetView: simplex_key={:?}, facet_index={}", - facet.simplex_key(), - facet.facet_index() - ); } #[test] fn test_facet_new_success_coverage() { // Test 2D case: Create a triangle (2D simplex with 3 vertices) let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), ]; let dt_2d = DelaunayTriangulation::new(&vertices_2d).unwrap(); let simplex_key_2d = dt_2d.simplices().next().unwrap().0; - let result_2d = FacetView::new(dt_2d.tds(), simplex_key_2d, 0); + let result_2d = FacetView::try_new(dt_2d.tds(), simplex_key_2d, 0); // Assert that the result is Ok assert!(result_2d.is_ok()); @@ -1336,10 +1401,13 @@ mod tests { assert_eq!(facet_2d.vertices().unwrap().count(), 2); // 2D facet should have 2 vertices // Test 1D case: Create an edge (1D simplex with 2 vertices) - let vertices_1d = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices_1d = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let dt_1d = DelaunayTriangulation::new(&vertices_1d).unwrap(); let simplex_key_1d = dt_1d.simplices().next().unwrap().0; - let result_1d = FacetView::new(dt_1d.tds(), simplex_key_1d, 0); + let result_1d = FacetView::try_new(dt_1d.tds(), simplex_key_1d, 0); // Assert that the result is Ok assert!(result_1d.is_ok()); @@ -1351,43 +1419,33 @@ mod tests { fn facet_new_with_incorrect_vertex() { // Create a 3D triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Test invalid facet index (3D simplex has vertices 0-3, facet index 4 is invalid) - assert!(FacetView::new(dt.tds(), simplex_key, 4).is_err()); + assert!(FacetView::try_new(dt.tds(), simplex_key, 4).is_err()); } #[test] fn facet_vertices() { // Create a 3D triangulation with a tetrahedron let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Create facet view for facet 0 (excludes vertex 0) - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet_vertices: Vec<_> = facet.vertices().unwrap().collect(); - - assert_eq!(facet_vertices.len(), 3); - // Facet 0 should contain vertices 1, 2, 3 (all except vertex 0) - - // Human readable output for cargo test -- --nocapture - println!( - "FacetView: facet_index={}, vertex_count={}", - facet.facet_index(), - facet_vertices.len() - ); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + assert_eq!(facet.vertices().unwrap().count(), 3); } // ============================================================================= @@ -1398,40 +1456,36 @@ mod tests { fn facet_partial_eq() { // Create a 3D triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Create facet views with same facet index (should be equal) - let facet1 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet2 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet3 = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); assert_eq!(facet1, facet2); assert_ne!(facet1, facet3); } - // Note: PartialOrd is not implemented for FacetView as facet ordering - // doesn't have semantic meaning in triangulation operations. - // The old Facet::partial_ord test has been removed. - #[test] fn facet_clone() { // Create a 3D triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); let cloned_facet = facet; // Verify clones are equal @@ -1453,15 +1507,15 @@ mod tests { fn facet_debug() { // Create a 3D triangulation with a non-degenerate tetrahedron let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); let debug_str = format!("{facet:?}"); assert!(debug_str.contains("FacetView")); @@ -1477,11 +1531,11 @@ mod tests { #[test] fn facet_with_typed_data() { // Create 3D triangulation with typed vertex data - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0], 1), - vertex!([1.0, 0.0, 0.0], 2), - vertex!([0.0, 1.0, 0.0], 3), - vertex!([0.0, 0.0, 1.0], 4), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 2).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 3).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 4).unwrap(), ]; let options = ConstructionOptions::default() .with_insertion_order(InsertionOrderStrategy::Input) @@ -1497,7 +1551,7 @@ mod tests { let simplex_key = dt.simplices().next().unwrap().0; // Create facet view for facet 0 (excludes vertex 0) - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); let facet_vertices: Vec<_> = facet.vertices().unwrap().collect(); assert_eq!(facet_vertices.len(), 3); // 3D facet should have 3 vertices (D) @@ -1525,7 +1579,7 @@ mod tests { /// /// ```ignore /// test_facet_dimensions! { - /// facet_2d => 2 => "triangle" => 2 => vec![vertex!([0.0, 0.0]), ...], + /// facet_2d => 2 => "triangle" => 2 => vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), ...], /// } /// ``` macro_rules! test_facet_dimensions { @@ -1541,7 +1595,7 @@ mod tests { let simplex_key = dt.simplices().next().unwrap().0; // Create facet view for facet 0 (excludes vertex 0) - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); // Facet of D-dimensional simplex is (D-1)-dimensional with D vertices assert_eq!(facet.vertices().unwrap().count(), $expected_facet_vertices, @@ -1557,14 +1611,14 @@ mod tests { let simplex_key = dt.simplices().next().unwrap().0; // Create same facet twice - let facet1 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet2 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); assert_eq!(facet1.key().unwrap(), facet2.key().unwrap(), "Same facet should produce same key"); // Create different facet - let facet3 = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); assert_ne!(facet1.key().unwrap(), facet3.key().unwrap(), "Different facets should produce different keys"); } @@ -1576,9 +1630,9 @@ mod tests { let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet1 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet2 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet3 = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); assert!(facet1 == facet2, "Same facet should be equal"); assert!(facet1 != facet3, "Different facets should not be equal"); @@ -1596,7 +1650,7 @@ mod tests { let mut facet_keys = HashSet::new(); for i in 0..expected_facets { - let facet = FacetView::new(dt.tds(), simplex_key, u8::try_from(i).unwrap()).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, u8::try_from(i).unwrap()).unwrap(); facet_keys.insert(facet.key().unwrap()); } @@ -1611,30 +1665,30 @@ mod tests { // Generate tests for dimensions 2D through 5D test_facet_dimensions! { facet_2d_triangle => 2 => "triangle" => 2 => vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), ], facet_3d_tetrahedron => 3 => "tetrahedron" => 3 => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ], facet_4d_simplex => 4 => "4-simplex" => 4 => vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ], facet_5d_simplex => 5 => "5-simplex" => 5 => vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ], } @@ -1642,7 +1696,10 @@ mod tests { #[test] fn facet_1d_edge() { // Create 1D triangulation (edge with 2 vertices) - let vertices = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let options = ConstructionOptions::default() .with_insertion_order(InsertionOrderStrategy::Input) .with_initial_simplex_strategy(InitialSimplexStrategy::First); @@ -1650,12 +1707,28 @@ mod tests { let simplex_key = dt.simplices().next().unwrap().0; // Create facet view for facet 0 (excludes vertex 0) - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); // Facet of 1D edge is a point (0D) with 1 vertex assert_eq!(facet.vertices().unwrap().count(), 1); } + #[test] + fn all_facets_iter_rejects_dimension_above_u8_facet_index_capacity() { + let tds: Tds<(), (), 256> = Tds::empty(); + let Err(err) = AllFacetsIter::try_new(&tds) else { + panic!("D=256 cannot fit facet indices in u8"); + }; + + assert_matches!( + err, + FacetError::FacetIndexCapacityExceeded { + dimension: 256, + max_dimension: 255, + } + ); + } + // ============================================================================= // ERROR HANDLING TESTS // ============================================================================= @@ -1683,18 +1756,18 @@ mod tests { fn test_facet_key_consistency() { // Create 3D triangulation with a tetrahedron let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Create facet views for different facets - let facet1 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); // excludes vertex 0 - let facet2 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); // same facet - let facet3 = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); // excludes vertex 1 (different facet) + let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); // excludes vertex 0 + let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); // same facet + let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); // excludes vertex 1 (different facet) // Both facet1 and facet2 reference the same facet, so same key assert_eq!( @@ -1714,16 +1787,19 @@ mod tests { #[test] fn facet_vertices_empty_simplex() { // Test edge case of minimal simplex (1D edge with 2 vertices) - let vertices = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Create facet with vertex 0 as opposite - should have only vertex 1 in facet - let facet = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); assert_eq!(facet.vertices().unwrap().count(), 1); // Test the opposite case - vertex 1 as opposite should have only vertex 0 in facet - let other_facet = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let other_facet = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); assert_eq!(other_facet.vertices().unwrap().count(), 1); } @@ -1731,16 +1807,16 @@ mod tests { fn facet_vertices_ordering() { // Test that vertices are filtered correctly let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Create facet view for facet 2 (excludes vertex 2) - let facet = FacetView::new(dt.tds(), simplex_key, 2).unwrap(); + let facet = FacetView::try_new(dt.tds(), simplex_key, 2).unwrap(); // Should have all vertices except vertex at index 2 assert_eq!(facet.vertices().unwrap().count(), 3); @@ -1751,18 +1827,18 @@ mod tests { fn facet_eq_different_vertices() { // Create a 3D triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet1 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet2 = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); - let facet3 = FacetView::new(dt.tds(), simplex_key, 2).unwrap(); - let facet4 = FacetView::new(dt.tds(), simplex_key, 3).unwrap(); + let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet2 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); + let facet3 = FacetView::try_new(dt.tds(), simplex_key, 2).unwrap(); + let facet4 = FacetView::try_new(dt.tds(), simplex_key, 3).unwrap(); // All facets should be different because they have different facet indices // (i.e., different opposite vertices) @@ -1774,27 +1850,24 @@ mod tests { assert_ne!(facet3, facet4); } - // Note: Hash is not implemented for FacetView as it contains a reference. - // Use FacetView::key() to get a hashable u64 key for facet identity. - // The old Facet::hash test has been removed. #[test] fn facet_key_hash() { // Create a 3D triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Create two facet views that reference the same facet - let facet1 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet2 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); // Create a different facet - let facet3 = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); // Test that facet keys are consistent for the same facet assert_eq!(facet1.key().unwrap(), facet2.key().unwrap()); @@ -1847,44 +1920,44 @@ mod tests { } // ============================================================================= - // PHASE 3: FACET VIEW TESTS + // FACET VIEW TESTS // ============================================================================= #[test] fn test_facet_view_creation() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; // Test valid facet creation - let facet_view = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet_view = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); assert_eq!(facet_view.simplex_key(), simplex_key); assert_eq!(facet_view.facet_index(), 0); // Test invalid facet index - let result = FacetView::new(dt.tds(), simplex_key, 10); + let result = FacetView::try_new(dt.tds(), simplex_key, 10); assert_matches!(result, Err(FacetError::InvalidFacetIndex { .. })); } #[test] fn test_facet_view_vertices_iteration() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet_view = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet_view = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); // Facet opposite to vertex 0 should have 3 vertices (D vertices in D-1 facet) let facet_vertices: Vec<_> = facet_view.vertices().unwrap().collect(); @@ -1907,16 +1980,16 @@ mod tests { #[test] fn test_facet_view_opposite_vertex() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet_view = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let facet_view = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); let opposite = facet_view.opposite_vertex().unwrap(); // The opposite vertex should be the vertex at index 1 @@ -1932,16 +2005,16 @@ mod tests { #[test] fn test_facet_view_key_computation() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet_view = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); + let facet_view = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); let key = facet_view.key().unwrap(); // Key should be non-zero for valid facet @@ -1951,10 +2024,10 @@ mod tests { #[test] fn test_all_facets_for_simplex() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1978,18 +2051,18 @@ mod tests { #[test] fn test_facet_view_equality() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet_view1 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet_view2 = FacetView::new(dt.tds(), simplex_key, 0).unwrap(); - let facet_view3 = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let facet_view1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet_view2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); + let facet_view3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); // Same facet should be equal assert_eq!(facet_view1, facet_view2); @@ -2001,16 +2074,16 @@ mod tests { #[test] fn test_facet_view_debug() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; - let facet_view = FacetView::new(dt.tds(), simplex_key, 1).unwrap(); + let facet_view = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap(); let debug_str = format!("{facet_view:?}"); assert!(debug_str.contains("FacetView")); @@ -2021,17 +2094,10 @@ mod tests { #[test] fn test_facet_view_memory_efficiency() { - // This test demonstrates the memory efficiency of FacetView - // The deprecated heavyweight Facet struct has been removed. - let lightweight_size = mem::size_of::>(); - - println!("Lightweight FacetView size: {lightweight_size} bytes"); + let lightweight_size = mem::size_of::>(); // FacetView should be around 17 bytes (8 byte ref + 8 byte SimplexKey + 1 byte facet_index) // Allow for some padding/alignment assert!(lightweight_size <= 24); - - // Document actual size for reference - // On 64-bit systems: typically 17 bytes (reference + SimplexKey + u8) } } diff --git a/src/core/insertion.rs b/src/core/insertion.rs index f6210726..83ff9280 100644 --- a/src/core/insertion.rs +++ b/src/core/insertion.rs @@ -2,7 +2,7 @@ //! //! This module owns transactional vertex insertion, duplicate-coordinate //! detection, perturbation retry, conflict-region shaping, cavity insertion, -//! and insertion telemetry for [`Triangulation`](crate::core::triangulation::Triangulation). +//! and insertion telemetry for [`Triangulation`](crate::prelude::triangulation::Triangulation). use crate::core::algorithms::incremental_insertion::{ CavityFillingError, CavityRepairStage, HullExtensionReason, InsertionError, extend_hull, @@ -31,16 +31,13 @@ use crate::core::triangulation::Triangulation; use crate::core::vertex::Vertex; use crate::geometry::kernel::Kernel; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionValue, CoordinateScalar, CoordinateValues, -}; +use crate::geometry::traits::coordinate::{CoordinateValues, DEFAULT_TOLERANCE_F64}; use crate::locality::{ append_live_unique_simplex_seeds, collect_local_exterior_conflict_seed_simplices, replace_simplices_and_record_removed, retain_simplices_and_record_removed, }; #[cfg(debug_assertions)] use crate::topology::manifold::validate_ridge_links; -use num_traits::{Float, NumCast, One, Zero}; use std::borrow::Cow; use std::env; use std::sync::{ @@ -184,6 +181,22 @@ fn cavity_conflict_error_summary(error: &ConflictError) -> String { } => { format!("simplex_data_access_failed simplex_key={simplex_key:?} message={message}") } + ConflictError::InvalidSimplexArity { + simplex_key, + expected, + found, + } => { + format!( + "invalid_simplex_arity simplex_key={simplex_key:?} expected={expected} \ + found={found}" + ) + } + ConflictError::MissingSimplexVertex { + simplex_key, + vertex_key, + } => { + format!("missing_simplex_vertex simplex_key={simplex_key:?} vertex_key={vertex_key:?}") + } ConflictError::InternalInconsistency { site } => { format!("internal_inconsistency site={site}") } @@ -365,8 +378,7 @@ pub(crate) struct DetailedInsertionResult { impl Triangulation where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -429,11 +441,11 @@ where /// their local repair frontier without changing the public insertion API. pub(crate) fn insert_with_statistics_seeded_indexed_detailed( &mut self, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, perturbation_seed: u64, - index: Option<&mut HashGridIndex>, + index: Option<&mut HashGridIndex>, bulk_index: Option, ) -> Result { self.insert_with_statistics_seeded_indexed_detailed_with_telemetry( @@ -458,11 +470,11 @@ where )] pub(crate) fn insert_with_statistics_seeded_indexed_detailed_with_telemetry( &mut self, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, perturbation_seed: u64, - index: Option<&mut HashGridIndex>, + index: Option<&mut HashGridIndex>, bulk_index: Option, telemetry_mode: InsertionTelemetryMode, ) -> Result { @@ -490,12 +502,12 @@ where )] fn insert_transactional_detailed( &mut self, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, max_perturbation_attempts: usize, perturbation_seed: u64, - mut index: Option<&mut HashGridIndex>, + mut index: Option<&mut HashGridIndex>, bulk_index: Option, telemetry_mode: InsertionTelemetryMode, ) -> Result { @@ -504,10 +516,6 @@ where let original_coords = *vertex.point().coords(); let original_uuid = vertex.uuid(); let mut current_vertex = vertex; - // Preserve the last retryable failure so an exhausted perturbation loop can - // explain why the vertex was skipped instead of reporting a generic error. - let mut last_retryable_error: Option = None; - // Reuse the caller's spatial index as a locate-hint source when batch insertion did // not already provide a better hint. This keeps retries and bulk runs on the same // point-location path. @@ -526,12 +534,8 @@ where self.estimate_duplicate_coordinate_tolerance(&original_coords, hint); self.ensure_duplicate_index_cell_size(index.as_deref_mut(), duplicate_tolerance); - // Base perturbation epsilon: ≈ √machine_epsilon for the scalar type. - let epsilon_value: f64 = if K::Scalar::mantissa_digits() <= 24 { - 1e-4 - } else { - 1e-8 - }; + // Base perturbation epsilon: ≈ √machine_epsilon for f64. + let epsilon_value: f64 = 1e-8; for attempt in 0..=max_perturbation_attempts { stats.attempts = attempt + 1; @@ -550,33 +554,15 @@ where reason = "attempt is at most DEFAULT_PERTURBATION_RETRIES (3), fits in i32" )] let scale_factor = 10.0_f64.powi(attempt as i32); - let Some(epsilon) = ::from(epsilon_value * scale_factor) - else { - // We failed to convert the perturbation scale into the scalar type. - // - // This should not happen for the supported scalar type (`f64`), but if it - // does (e.g. with a custom scalar), we degrade gracefully by skipping this vertex - // rather than aborting the whole insertion. - stats.result = InsertionResult::SkippedDegeneracy; - let error = last_retryable_error.unwrap_or_else(|| { - CavityFillingError::PerturbationScaleConversion { - value: CoordinateConversionValue::from_f64(epsilon_value), - } - .into() - }); - return Ok(DetailedInsertionResult { - outcome: InsertionOutcome::Skipped { error }, - stats, - telemetry, - repair_seed_simplices: SimplexKeyBuffer::new(), - delaunay_repair_required: false, - }); - }; + let epsilon = epsilon_value * scale_factor; let perturbation_scale = epsilon * local_scale; for (idx, coord) in perturbed_coords.iter_mut().enumerate() { - let coord_scale = - ::from(idx + 1).unwrap_or_else(K::Scalar::one); + #[expect( + clippy::cast_precision_loss, + reason = "D is a const-generic triangulation dimension and practical dimensions are tiny" + )] + let coord_scale = (idx + 1) as f64; let signed_perturbation = if perturbation_seed == 0 { if (attempt + idx) % 2 == 0 { perturbation_scale @@ -599,8 +585,10 @@ where // Preserve the caller-provided vertex UUID across perturbation retries. // This ensures the inserted vertex retains its original identity even if we have // to retry with perturbed coordinates. + let perturbed_point = Point::try_new(perturbed_coords) + .map_err(|source| InsertionError::PerturbedCoordinateInvalid { source })?; current_vertex = - Vertex::new_with_uuid(Point::new(perturbed_coords), original_uuid, vertex.data); + Vertex::new_with_uuid(perturbed_point, original_uuid, current_vertex.data); } // Duplicate coordinate detection uses the hash grid when available; otherwise it @@ -740,7 +728,6 @@ where } if is_retryable && attempt < max_perturbation_attempts { - last_retryable_error = Some(e.clone()); #[cfg(debug_assertions)] tracing::debug!( "RETRYING: Attempt {} failed with: {e}. Applying perturbation...", @@ -790,10 +777,10 @@ where fn select_locate_hint_from_hash_grid( &self, - coords: &[K::Scalar; D], - index: &HashGridIndex, + coords: &[f64; D], + index: &HashGridIndex, ) -> Option { - let mut best: Option<(K::Scalar, SimplexKey)> = None; + let mut best: Option<(f64, SimplexKey)> = None; index.for_each_candidate_vertex_key(coords, |vkey| { let Some(vertex) = self.tds.vertex(vkey) else { @@ -809,10 +796,10 @@ where } let vcoords = vertex.point().coords(); - let mut dist_sq = K::Scalar::zero(); + let mut dist_sq = 0.0; for i in 0..D { let diff = vcoords[i] - coords[i]; - dist_sq += diff * diff; + dist_sq = diff.mul_add(diff, dist_sq); } match best { @@ -828,23 +815,18 @@ where best.map(|(_, simplex_key)| simplex_key) } - /// Chooses the relative duplicate-coordinate tolerance for the scalar precision. - fn duplicate_relative_tolerance() -> K::Scalar { - let value = if K::Scalar::mantissa_digits() <= 24 { - 1e-6_f64 - } else { - 1e-10_f64 - }; - ::from(value).unwrap_or_else(K::Scalar::default_tolerance) + /// Returns the f64 relative tolerance used for duplicate-coordinate detection. + const fn duplicate_relative_tolerance() -> f64 { + 1e-10_f64 } /// Keeps duplicate-scale estimates tied to existing geometry rather than /// hard-coding a scalar-unit epsilon. fn include_duplicate_scale_reference( - point_coords: &[K::Scalar; D], - axis_min: &mut [K::Scalar; D], - axis_max: &mut [K::Scalar; D], - magnitude_scale: &mut K::Scalar, + point_coords: &[f64; D], + axis_min: &mut [f64; D], + axis_max: &mut [f64; D], + magnitude_scale: &mut f64, saw_reference: &mut bool, ) { *saw_reference = true; @@ -868,12 +850,12 @@ where /// a small ULP-scaled floor for translated coordinate systems. fn estimate_duplicate_coordinate_tolerance( &self, - coords: &[K::Scalar; D], + coords: &[f64; D], hint: Option, - ) -> K::Scalar { + ) -> f64 { let mut axis_min = *coords; let mut axis_max = *coords; - let mut magnitude_scale = K::Scalar::zero(); + let mut magnitude_scale = 0.0; let mut saw_reference = false; let mut local_feature_scale = None; @@ -902,7 +884,7 @@ where if !saw_reference { let local_scale = self.estimate_local_perturbation_scale(coords, None); - if local_scale.is_finite() && local_scale > K::Scalar::zero() { + if local_scale.is_finite() && local_scale > 0.0 { if local_scale > magnitude_scale { magnitude_scale = local_scale; } @@ -911,23 +893,22 @@ where } let feature_scale = local_feature_scale.unwrap_or_else(|| { - let mut span_sq = K::Scalar::zero(); + let mut span_sq = 0.0; for i in 0..D { let span = axis_max[i] - axis_min[i]; - span_sq += span * span; + span_sq = span.mul_add(span, span_sq); } span_sq.sqrt() }); let relative_tolerance = Self::duplicate_relative_tolerance() * feature_scale; - let ulp_factor = ::from(16.0_f64).unwrap_or_else(K::Scalar::one); - let ulp_tolerance = K::Scalar::epsilon() * ulp_factor * magnitude_scale; + let ulp_tolerance = f64::EPSILON * 16.0 * magnitude_scale; let mut tolerance = if relative_tolerance > ulp_tolerance { relative_tolerance } else { ulp_tolerance }; - if !tolerance.is_finite() || tolerance <= K::Scalar::zero() { + if !tolerance.is_finite() || tolerance <= 0.0 { tolerance = Self::duplicate_relative_tolerance(); } @@ -938,16 +919,13 @@ where /// the current grid cell size, preserving complete candidate coverage. fn ensure_duplicate_index_cell_size( &self, - index: Option<&mut HashGridIndex>, - tolerance: K::Scalar, + index: Option<&mut HashGridIndex>, + tolerance: f64, ) { let Some(index) = index else { return; }; - if !HashGridIndex::::supports_dimension() - || !tolerance.is_finite() - || tolerance <= K::Scalar::zero() - { + if !HashGridIndex::::supports_dimension() || !tolerance.is_finite() || tolerance <= 0.0 { return; } if index.cell_size() >= tolerance { @@ -965,7 +943,7 @@ where /// Compares a squared distance against the duplicate tolerance without /// overflowing the tolerance square on extreme coordinate scales. - fn duplicate_distance_within_tolerance(dist_sq: K::Scalar, tolerance: K::Scalar) -> bool { + fn duplicate_distance_within_tolerance(dist_sq: f64, tolerance: f64) -> bool { let tolerance_sq = tolerance * tolerance; if tolerance_sq.is_finite() { dist_sq <= tolerance_sq @@ -978,9 +956,9 @@ where /// linear-scan fallback (O(n·D) per insertion) if the index is unavailable/unusable. fn duplicate_coordinates_error( &self, - coords: &[K::Scalar; D], - tolerance: K::Scalar, - index: Option<&HashGridIndex>, + coords: &[f64; D], + tolerance: f64, + index: Option<&HashGridIndex>, ) -> Option { let mut duplicate_found = false; let make_duplicate_error = || { @@ -999,10 +977,10 @@ where }; let vcoords = vertex.point().coords(); - let mut dist_sq = K::Scalar::zero(); + let mut dist_sq = 0.0; for i in 0..D { let diff = vcoords[i] - coords[i]; - dist_sq += diff * diff; + dist_sq = diff.mul_add(diff, dist_sq); } if Self::duplicate_distance_within_tolerance(dist_sq, tolerance) { @@ -1027,10 +1005,10 @@ where for (_, existing_vertex) in self.tds.vertices() { let existing_coords = existing_vertex.point().coords(); - let mut dist_sq = K::Scalar::zero(); + let mut dist_sq = 0.0; for i in 0..D { let diff = coords[i] - existing_coords[i]; - dist_sq += diff * diff; + dist_sq = diff.mul_add(diff, dist_sq); } if Self::duplicate_distance_within_tolerance(dist_sq, tolerance) { @@ -1053,18 +1031,17 @@ where /// proportional to local feature size. fn estimate_local_perturbation_scale( &self, - coords: &[K::Scalar; D], + coords: &[f64; D], hint: Option, - ) -> K::Scalar { - let mut min_dist_sq: Option = None; + ) -> f64 { + let mut min_dist_sq: Option = None; - let consider_vertex = |vertex: &Vertex, - min_dist_sq: &mut Option| { + let consider_vertex = |vertex: &Vertex, min_dist_sq: &mut Option| { let vcoords = vertex.point().coords(); - let mut dist_sq = K::Scalar::zero(); + let mut dist_sq = 0.0; for i in 0..D { let diff = vcoords[i] - coords[i]; - dist_sq += diff * diff; + dist_sq = diff.mul_add(diff, dist_sq); } match min_dist_sq { Some(current) => { @@ -1094,9 +1071,9 @@ where } } - let mut scale = min_dist_sq.map_or_else(K::Scalar::one, num_traits::Float::sqrt); + let mut scale = min_dist_sq.map_or(1.0, f64::sqrt); - let min_scale = K::Scalar::default_tolerance(); + let min_scale = DEFAULT_TOLERANCE_F64; if scale < min_scale { scale = min_scale; } @@ -1112,11 +1089,11 @@ where )] fn try_insert_with_topology_safety_net( &mut self, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, attempt: usize, - tds_snapshot: &Tds, + tds_snapshot: &Tds, telemetry: &mut InsertionTelemetry, telemetry_mode: InsertionTelemetryMode, ) -> Result { @@ -1165,7 +1142,7 @@ where /// - We re-run Level 3 validation after the fallback to avoid "recovering" into an invalid state. fn try_star_split_fallback_after_topology_failure( &mut self, - vertex: Vertex, + vertex: Vertex, hint: Option, attempt: usize, validation_err: InvariantError, @@ -1256,7 +1233,7 @@ where fn star_split_boundary_facets(start_simplex: SimplexKey) -> CavityBoundaryBuffer { (0..=D) .map(|i| { - FacetHandle::new( + FacetHandle::from_validated( start_simplex, u8::try_from(i).expect("facet index must fit in u8"), ) @@ -1490,7 +1467,7 @@ where fn insert_with_conflict_region( &mut self, v_key: VertexKey, - point: &Point, + point: &Point, mut conflict_simplices: SimplexKeyBuffer, fallback_simplex: Option, suspicion: &mut SuspicionFlags, @@ -1963,7 +1940,7 @@ where fn collect_exterior_repair_seed_simplices( &self, - point: &Point, + point: &Point, terminal_simplex: SimplexKey, locate_stats: &LocateStats, telemetry_mode: InsertionTelemetryMode, @@ -2004,7 +1981,7 @@ where )] fn try_insert_impl( &mut self, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, telemetry: &mut InsertionTelemetry, @@ -2685,14 +2662,13 @@ mod tests { use crate::core::algorithms::locate::InternalInconsistencySite; use crate::core::collections::spatial_hash_grid::HashGridIndex; use crate::core::simplex::Simplex; - use crate::core::vertex::VertexBuilder; use crate::geometry::kernel::{AdaptiveKernel, FastKernel}; use crate::geometry::point::Point; use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateConversionValue, CoordinateScalar, + CoordinateConversionError, CoordinateConversionValue, DEFAULT_TOLERANCE_F64, + F64_MANTISSA_DIGITS, }; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use std::assert_matches; use slotmap::KeyData; @@ -2727,13 +2703,12 @@ mod tests { fn insert( tri: &mut Triangulation, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, ) -> Result<(VertexKey, Option), InsertionError> where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -2755,13 +2730,12 @@ mod tests { fn insert_with_statistics( tri: &mut Triangulation, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, ) -> Result<(InsertionOutcome, InsertionStatistics), InsertionError> where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -2783,17 +2757,16 @@ mod tests { )] fn insert_transactional( tri: &mut Triangulation, - vertex: Vertex, + vertex: Vertex, conflict_simplices: Option<&SimplexKeyBuffer>, hint: Option, max_perturbation_attempts: usize, perturbation_seed: u64, - index: Option<&mut HashGridIndex>, + index: Option<&mut HashGridIndex>, bulk_index: Option, ) -> Result<(InsertionOutcome, InsertionStatistics), InsertionError> where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -3005,25 +2978,25 @@ mod tests { assert!(after.grid_candidates >= before.grid_candidates + 3); } - fn unit_simplex_vertices() -> Vec> { + fn unit_simplex_vertices() -> Vec> { let mut vertices = Vec::with_capacity(D + 1); - vertices.push(vertex!([0.0_f64; D])); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new([0.0_f64; D]).unwrap()); for axis in 0..D { let mut coords = [0.0_f64; D]; coords[axis] = 1.0; - vertices.push(vertex!(coords)); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap()); } vertices } /// Build a simplex whose feature length is controlled by one shared axis scale. - fn axis_scaled_simplex_vertices(scale: f64) -> Vec> { + fn axis_scaled_simplex_vertices(scale: f64) -> Vec> { let mut vertices = Vec::with_capacity(D + 1); - vertices.push(vertex!([0.0_f64; D])); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new([0.0_f64; D]).unwrap()); for axis in 0..D { let mut coords = [0.0_f64; D]; coords[axis] = scale; - vertices.push(vertex!(coords)); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap()); } vertices } @@ -3038,16 +3011,16 @@ mod tests { #[test] fn test_select_locate_hint_from_hash_grid_returns_incident_simplex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); let tri = Triangulation::, (), (), 2>::new_with_tds(FastKernel::new(), tds); let simplex_key = tri.tds.simplex_keys().next().unwrap(); - let mut index: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut index: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); for (vkey, vertex) in tri.tds.vertices() { index.insert_vertex(vkey, vertex.point().coords()); } @@ -3062,14 +3035,16 @@ mod tests { Triangulation::new_empty(FastKernel::new()); let vkey = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); { let vertex = tri.tds.vertex_mut(vkey).unwrap(); vertex.set_incident_simplex(Some(SimplexKey::default())); } - let mut index: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut index: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); index.insert_vertex(vkey, &[0.0, 0.0]); let hint = tri.select_locate_hint_from_hash_grid(&[0.0, 0.0], &index); @@ -3082,10 +3057,12 @@ mod tests { Triangulation::new_empty(FastKernel::new()); let vkey = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); - let mut index: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut index: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); index.insert_vertex(vkey, &[0.0, 0.0]); let tol = 1e-10_f64; @@ -3099,10 +3076,12 @@ mod tests { Triangulation::new_empty(FastKernel::new()); let _ = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); - let mut index: HashGridIndex = HashGridIndex::try_new(1.0).unwrap(); + let mut index: HashGridIndex<2> = HashGridIndex::try_new(1.0).unwrap(); index.remove_vertex(&VertexKey::default(), &[f64::NAN, 0.0]); let tol = 1e-10_f64; let err = tri.duplicate_coordinates_error(&[0.0, 0.0], tol, Some(&index)); @@ -3114,7 +3093,10 @@ mod tests { Triangulation::new_empty(FastKernel::new()); let _ = tri .tds - .insert_vertex_with_mapping(vertex!(coords_with_first::(1.0e-6))) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(coords_with_first::(1.0e-6)) + .unwrap(), + ) .unwrap(); let candidate = coords_with_first::(1.0e-6 + 1.0e-11); @@ -3158,7 +3140,7 @@ mod tests { let hint = tri.tds.simplex_keys().next(); let candidate = [1.0_f64; D]; let tolerance = tri.estimate_duplicate_coordinate_tolerance(&candidate, hint); - let mut index: HashGridIndex = HashGridIndex::try_new(1.0e-10).unwrap(); + let mut index: HashGridIndex = HashGridIndex::try_new(1.0e-10).unwrap(); for (vkey, vertex) in tri.tds.vertices() { index.insert_vertex(vkey, vertex.point().coords()); } @@ -3216,9 +3198,9 @@ mod tests { #[test] fn test_estimate_local_perturbation_scale_uses_hint_simplex_vertices() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -3235,11 +3217,13 @@ mod tests { Triangulation::new_empty(FastKernel::new()); let _ = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); let scale = tri.estimate_local_perturbation_scale(&[0.0, 0.0], None); - let min_scale = ::default_tolerance(); + let min_scale = DEFAULT_TOLERANCE_F64; approx::assert_abs_diff_eq!(scale, min_scale, epsilon = f64::EPSILON); } @@ -3249,17 +3233,33 @@ mod tests { Triangulation::new_empty(FastKernel::new()); // First insertion succeeds. - insert(&mut tri, vertex!([0.0, 0.0, 0.0]), None, None) - .expect("first insertion should succeed"); + insert( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + None, + None, + ) + .expect("first insertion should succeed"); assert_eq!(tri.number_of_vertices(), 1); // Second insertion at same coordinates: insert() returns Err; the internal // statistics helper reports Skipped so telemetry can classify the no-op. - let err = insert(&mut tri, vertex!([0.0, 0.0, 0.0]), None, None).unwrap_err(); + let err = insert( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + None, + None, + ) + .unwrap_err(); assert_matches!(err, InsertionError::DuplicateCoordinates { .. }); - let (outcome, stats) = - insert_with_statistics(&mut tri, vertex!([0.0, 0.0, 0.0]), None, None).unwrap(); + let (outcome, stats) = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + None, + None, + ) + .unwrap(); assert!(stats.skipped()); assert_matches!(outcome, InsertionOutcome::Skipped { .. }); @@ -3269,17 +3269,26 @@ mod tests { #[test] fn test_insert_duplicate_uuid_is_non_retryable_and_rolls_back() { - // Insert a vertex, then attempt to insert another vertex with the same UUID. + // Insert a then attempt to insert another vertex with the same UUID. let mut tri: Triangulation, (), (), 3> = Triangulation::new_empty(FastKernel::new()); - insert(&mut tri, vertex!([0.0, 0.0, 0.0]), None, None) - .expect("first insertion should succeed"); + insert( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + None, + None, + ) + .expect("first insertion should succeed"); assert_eq!(tri.number_of_vertices(), 1); let existing_uuid = tri.tds.vertices().next().unwrap().1.uuid(); - let mut dup = vertex!([1.0, 0.0, 0.0]); - dup.set_uuid(existing_uuid).unwrap(); + let dup = Vertex::try_new_with_uuid( + Point::from_validated_coords([1.0, 0.0, 0.0]), + existing_uuid, + None, + ) + .unwrap(); let err = insert(&mut tri, dup, None, None).unwrap_err(); assert!( @@ -3301,8 +3310,13 @@ mod tests { Triangulation::new_empty(FastKernel::new()); // Insert first vertex - let (outcome, stats) = insert_with_statistics(&mut tri, vertex!([0.0, 0.0]), None, None) - .expect("insertion should succeed"); + let (outcome, stats) = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + None, + None, + ) + .expect("insertion should succeed"); assert_matches!(outcome, InsertionOutcome::Inserted { hint: None, .. }); assert_eq!(stats.attempts, 1); @@ -3319,10 +3333,10 @@ mod tests { // Insert D+1 vertices to create initial simplex let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; for (i, v) in vertices.into_iter().enumerate() { @@ -3355,13 +3369,19 @@ mod tests { [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], ] { - insert_with_statistics(&mut tri, vertex!(coords), None, None).unwrap(); + insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + None, + None, + ) + .unwrap(); } let hint = tri.simplices().next().map(|(simplex_key, _)| simplex_key); let detail = tri .insert_with_statistics_seeded_indexed_detailed( - vertex!([2.0, 2.0, 2.0]), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).unwrap(), None, hint, 0, @@ -3404,14 +3424,20 @@ mod tests { [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], ] { - insert_with_statistics(&mut tri, vertex!(coords), None, None).unwrap(); + insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + None, + None, + ) + .unwrap(); } let hint = tri.simplices().next().map(|(simplex_key, _)| simplex_key); let empty_conflicts = SimplexKeyBuffer::new(); let detail = tri .insert_with_statistics_seeded_indexed_detailed( - vertex!([2.0, 2.0, 2.0]), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).unwrap(), Some(&empty_conflicts), hint, 0, @@ -3443,9 +3469,9 @@ mod tests { #[test] fn triangulation_caller_conflicts_do_not_force_delaunay_repair() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -3461,7 +3487,7 @@ mod tests { conflict_simplices.push(start_simplex); let detail = tri .insert_with_statistics_seeded_indexed_detailed( - vertex!([0.25, 0.25]), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25]).unwrap(), Some(&conflict_simplices), Some(start_simplex), 0, @@ -3489,14 +3515,24 @@ mod tests { if i > 0 { coords[i - 1] = 1.0; } - insert_with_statistics(&mut tri, vertex!(coords), None, None).unwrap(); + insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + None, + None, + ) + .unwrap(); } // Insert with explicit hint let hint_simplex = tri.simplices().next().map(|(key, _)| key); - let (outcome, stats) = - insert_with_statistics(&mut tri, vertex!([0.2, 0.2, 0.2, 0.2]), None, hint_simplex) - .unwrap(); + let (outcome, stats) = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), + None, + hint_simplex, + ) + .unwrap(); assert_matches!(outcome, InsertionOutcome::Inserted { hint: Some(_), .. }); assert_eq!(stats.attempts, 1); @@ -3509,10 +3545,21 @@ mod tests { Triangulation::new_empty(FastKernel::new()); // Insert first vertex - insert_with_statistics(&mut tri, vertex!([1.0, 2.0, 3.0]), None, None).unwrap(); + insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(), + None, + None, + ) + .unwrap(); // Try duplicate - should be skipped - let result = insert_with_statistics(&mut tri, vertex!([1.0, 2.0, 3.0]), None, None); + let result = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(), + None, + None, + ); assert_matches!( result, @@ -3531,11 +3578,11 @@ mod tests { Triangulation::new_empty(FastKernel::new()); let points = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), - vertex!([0.3, 0.3]), - vertex!([0.7, 0.3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.7, 0.3]).unwrap(), ]; let mut all_succeeded = true; @@ -3564,8 +3611,13 @@ mod tests { Triangulation::new_empty(FastKernel::new()); // Test Inserted variant - let (outcome, _) = - insert_with_statistics(&mut tri, vertex!([0.0, 0.0]), None, None).unwrap(); + let (outcome, _) = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + None, + None, + ) + .unwrap(); match outcome { InsertionOutcome::Inserted { vertex_key, hint } => { @@ -3589,8 +3641,13 @@ mod tests { coords[i - 1] = 1.0; } - let (outcome, stats) = - insert_with_statistics(&mut tri, vertex!(coords), None, None).unwrap(); + let (outcome, stats) = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + None, + None, + ) + .unwrap(); assert_matches!(outcome, InsertionOutcome::Inserted { .. }); assert_eq!(stats.attempts, 1); @@ -3607,15 +3664,38 @@ mod tests { Triangulation::new_empty(FastKernel::new()); // Build simplex - insert_with_statistics(&mut tri, vertex!([0.0, 0.0]), None, None).unwrap(); - insert_with_statistics(&mut tri, vertex!([1.0, 0.0]), None, None).unwrap(); - insert_with_statistics(&mut tri, vertex!([0.5, 1.0]), None, None).unwrap(); + insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + None, + None, + ) + .unwrap(); + insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + None, + None, + ) + .unwrap(); + insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + None, + None, + ) + .unwrap(); let simplices_before = tri.number_of_simplices(); // Insert interior point - might trigger repair - let (_outcome, stats) = - insert_with_statistics(&mut tri, vertex!([0.5, 0.3]), None, None).unwrap(); + let (_outcome, stats) = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.3]).unwrap(), + None, + None, + ) + .unwrap(); let simplices_after = tri.number_of_simplices(); @@ -3652,45 +3732,63 @@ mod tests { // Two triangles that share no vertices (→ DisconnectedBoundary on extraction). let v0 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tri .tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + ) .unwrap(); let v3 = tri .tds - .insert_vertex_with_mapping(vertex!([5.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([5.0, 0.0]).unwrap(), + ) .unwrap(); let v4 = tri .tds - .insert_vertex_with_mapping(vertex!([6.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([6.0, 0.0]).unwrap(), + ) .unwrap(); let v5 = tri .tds - .insert_vertex_with_mapping(vertex!([5.5, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([5.5, 1.0]).unwrap(), + ) .unwrap(); let simplex_a = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let simplex_b = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v3, v4, v5], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v3, v4, v5], None).unwrap(), + ) .unwrap(); // Neither simplex has any neighbour pointers, so `simplices_to_add` will be empty on // the first iteration and the `else { break; }` arm fires immediately. let new_v = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 0.3])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.3]).unwrap(), + ) .unwrap(); - let point = Point::new([0.5_f64, 0.3_f64]); + let point = Point::from_validated_coords([0.5_f64, 0.3_f64]); let mut conflict_simplices = SimplexKeyBuffer::new(); conflict_simplices.push(simplex_a); @@ -3725,50 +3823,68 @@ mod tests { let v0 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tri .tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); // Three distinct fourth vertices that all pair with the {v0,v1,v2} face. let v3 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v4 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, -1.0]).unwrap(), + ) .unwrap(); let v5 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), + ) .unwrap(); let simplex1 = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let simplex2 = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v4], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v4], None).unwrap(), + ) .unwrap(); let simplex3 = tri .tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v2, v5], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v2, v5], None).unwrap(), ) .unwrap(); let new_v = tri .tds - .insert_vertex_with_mapping(vertex!([0.1, 0.1, 0.1])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.1, 0.1]).unwrap(), + ) .unwrap(); - let point = Point::new([0.1_f64, 0.1_f64, 0.1_f64]); + let point = Point::from_validated_coords([0.1_f64, 0.1_f64, 0.1_f64]); let mut conflict_simplices = SimplexKeyBuffer::new(); conflict_simplices.push(simplex1); @@ -3799,6 +3915,10 @@ mod tests { /// /// Covers: `RidgeFan` SHRINK body (lines 3434-3442) and re-extraction (line 3514). #[test] + #[expect( + clippy::too_many_lines, + reason = "regression test keeps the ridge-fan shrink fixture explicit" + )] fn test_cavity_reduction_ridge_fan_shrink_fires_for_4_conflict_simplices_2d() { let mut tri: Triangulation, (), (), 2> = Triangulation::new_empty(FastKernel::new()); @@ -3806,63 +3926,91 @@ mod tests { // `center` appears in 8 boundary edges (2 per simplex × 4 simplices) → RidgeFan. let center = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); let va = tri .tds - .insert_vertex_with_mapping(vertex!([-1.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, 2.0]).unwrap(), + ) .unwrap(); let vb = tri .tds - .insert_vertex_with_mapping(vertex!([1.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), + ) .unwrap(); let vc = tri .tds - .insert_vertex_with_mapping(vertex!([-3.0, -2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-3.0, -2.0]).unwrap(), + ) .unwrap(); let vd = tri .tds - .insert_vertex_with_mapping(vertex!([-2.0, -3.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-2.0, -3.0]).unwrap(), + ) .unwrap(); let ve = tri .tds - .insert_vertex_with_mapping(vertex!([3.0, -2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([3.0, -2.0]).unwrap(), + ) .unwrap(); let vf = tri .tds - .insert_vertex_with_mapping(vertex!([2.0, -3.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, -3.0]).unwrap(), + ) .unwrap(); let vg = tri .tds - .insert_vertex_with_mapping(vertex!([-4.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-4.0, 1.0]).unwrap(), + ) .unwrap(); let vh = tri .tds - .insert_vertex_with_mapping(vertex!([-4.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-4.0, -1.0]).unwrap(), + ) .unwrap(); let simplex1 = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![center, va, vb], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center, va, vb], None).unwrap(), + ) .unwrap(); let simplex2 = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![center, vc, vd], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center, vc, vd], None).unwrap(), + ) .unwrap(); let simplex3 = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![center, ve, vf], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center, ve, vf], None).unwrap(), + ) .unwrap(); let simplex4 = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![center, vg, vh], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![center, vg, vh], None).unwrap(), + ) .unwrap(); let new_v = tri .tds - .insert_vertex_with_mapping(vertex!([0.3, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 1.0]).unwrap(), + ) .unwrap(); - let point = Point::new([0.3_f64, 1.0_f64]); + let point = Point::from_validated_coords([0.3_f64, 1.0_f64]); let mut conflict_simplices = SimplexKeyBuffer::new(); conflict_simplices.push(simplex1); @@ -3912,6 +4060,10 @@ mod tests { /// Covers: EXPAND body (lines 3470-3480), SHRINK-fallback (lines 3481-3491), /// and re-extraction after each reshape (line 3514). #[test] + #[expect( + clippy::too_many_lines, + reason = "regression test keeps the disconnected cavity fixture explicit" + )] fn test_cavity_reduction_disconnected_expand_then_shrink_2d() { let mut tri: Triangulation, (), (), 2> = Triangulation::new_empty(FastKernel::new()); @@ -3919,55 +4071,79 @@ mod tests { // Group A: simplex_a = {v0,v1,v2} shares edge {v0,v1} with simplex_c = {v0,v1,v6}. let v0 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tri .tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + ) .unwrap(); let v6 = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, -1.0]).unwrap(), + ) .unwrap(); // Group B: simplex_b = {v3,v4,v5} shares edge {v3,v4} with simplex_d = {v3,v4,v7}. let v3 = tri .tds - .insert_vertex_with_mapping(vertex!([5.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([5.0, 0.0]).unwrap(), + ) .unwrap(); let v4 = tri .tds - .insert_vertex_with_mapping(vertex!([6.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([6.0, 0.0]).unwrap(), + ) .unwrap(); let v5 = tri .tds - .insert_vertex_with_mapping(vertex!([5.5, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([5.5, 1.0]).unwrap(), + ) .unwrap(); let v7 = tri .tds - .insert_vertex_with_mapping(vertex!([5.5, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([5.5, -1.0]).unwrap(), + ) .unwrap(); let simplex_a = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // simplex_c is a non-conflict neighbour of simplex_a (not initially in conflict_simplices). let simplex_c = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v6], None).unwrap(), + ) .unwrap(); let simplex_b = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v3, v4, v5], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v3, v4, v5], None).unwrap(), + ) .unwrap(); // simplex_d is a non-conflict neighbour of simplex_b. let simplex_d = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v3, v4, v7], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v3, v4, v7], None).unwrap(), + ) .unwrap(); // Wire neighbours so EXPAND discovers simplex_c via simplex_a and simplex_d via simplex_b. @@ -3986,9 +4162,11 @@ mod tests { let new_v = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 0.3])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.3]).unwrap(), + ) .unwrap(); - let point = Point::new([0.5_f64, 0.3_f64]); + let point = Point::from_validated_coords([0.5_f64, 0.3_f64]); let mut conflict_simplices = SimplexKeyBuffer::new(); conflict_simplices.push(simplex_a); @@ -4054,25 +4232,15 @@ mod tests { // ========================================================================= /// Helper: build a set of D+1 affinely independent vertices for dimension D. - fn simplex_vertices() -> Vec> { + fn simplex_vertices() -> Vec> { let mut verts = Vec::with_capacity(D + 1); // Origin - verts.push( - VertexBuilder::default() - .point(Point::new([0.0; D])) - .build() - .unwrap(), - ); + verts.push(Vertex::try_new([0.0; D]).unwrap()); // Unit vectors along each axis for i in 0..D { let mut coords = [0.0; D]; coords[i] = 1.0; - verts.push( - VertexBuilder::default() - .point(Point::new(coords)) - .build() - .unwrap(), - ); + verts.push(Vertex::try_new(coords).unwrap()); } verts } @@ -4131,10 +4299,7 @@ mod tests { for c in interior.iter_mut() { *c = 1.0 / (>::from($dim + 1) * 2.0); } - let interior_vertex = VertexBuilder::default() - .point(Point::new(interior)) - .build() - .unwrap(); + let interior_vertex = Vertex::try_new(interior).unwrap(); let (_, hint) = insert(&mut tri, interior_vertex, None, None).unwrap(); assert!(hint.is_some(), "{}D: hint returned after D+2 insertion", $dim); @@ -4161,8 +4326,13 @@ mod tests { let points = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.25, 0.25]]; for coords in &points { - let (outcome, stats) = - insert_with_statistics(&mut tri, vertex!(*coords), None, None).unwrap(); + let (outcome, stats) = insert_with_statistics( + &mut tri, + crate::core::vertex::Vertex::<(), _>::try_new(*coords).unwrap(), + None, + None, + ) + .unwrap(); assert!(stats.success()); assert_matches!(outcome, InsertionOutcome::Inserted { .. }); assert_eq!(stats.attempts, 1); @@ -4181,7 +4351,9 @@ mod tests { Triangulation::new_empty(FastKernel::new()); let _ = tri .tds - .insert_vertex_with_mapping(vertex!([3.0, 4.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 4.0]).unwrap(), + ) .unwrap(); let tol = 1e-10_f64; @@ -4230,9 +4402,16 @@ mod tests { [0.0, 1.0, 1.0], [0.5, 0.5, 0.5], ]; - let vertices: Vec> = base_coords + let vertices: Vec> = base_coords .iter() - .map(|c| vertex!([c[0] * scale, c[1] * scale, c[2] * scale])) + .map(|c| { + crate::core::vertex::Vertex::<(), _>::try_new([ + c[0] * scale, + c[1] * scale, + c[2] * scale, + ]) + .unwrap() + }) .collect(); let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -4278,18 +4457,17 @@ mod tests { fn test_perturbation_epsilon_selection_and_retry() { // Assert the supported scalar's mantissa-digits branch. assert_eq!( - f64::mantissa_digits(), - 53, + F64_MANTISSA_DIGITS, 53, "f64 should take the 1e-8 epsilon path" ); // Build a 2D triangulation, then insert a point exactly on an existing edge. // This near-degenerate configuration exercises the full insert_transactional path // including epsilon_value computation. - let initial_f64: Vec> = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0_f64, 0.0]), - vertex!([0.0_f64, 1.0]), + let initial_f64: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0_f64, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0_f64, 1.0]).unwrap(), ]; let tds_f64 = Triangulation::, (), (), 2>::build_initial_simplex(&initial_f64) @@ -4299,8 +4477,13 @@ mod tests { tds_f64, ); - let (outcome_f64, stats_f64) = - insert_with_statistics(&mut tri_f64, vertex!([0.5_f64, 0.0]), None, None).unwrap(); + let (outcome_f64, stats_f64) = insert_with_statistics( + &mut tri_f64, + crate::core::vertex::Vertex::<(), _>::try_new([0.5_f64, 0.0]).unwrap(), + None, + None, + ) + .unwrap(); assert!( stats_f64.attempts >= 1, "f64 insertion must execute at least 1 attempt" @@ -4327,127 +4510,127 @@ mod tests { clippy::too_many_lines, reason = "Literal 4D repro point set keeps retry-path coverage deterministic" )] - fn perturbation_retry_repro_points_4d() -> [Point; 20] { + fn perturbation_retry_repro_points_4d() -> [Point<4>; 20] { // Fixed adversarial insertion sequence captured from the former // randomized sweep (seed 4, index 19). The final insertion exhausts // perturbation retries in the current 4D path, so this keeps retry // coverage deterministic without looping over random seeds. [ - Point::new([ + Point::from_validated_coords([ 0.660_063_804_566_304_3, 3.139_352_812_821_116, 1.460_437_437_858_557_2, 1.683_976_950_416_514_7, ]), - Point::new([ + Point::from_validated_coords([ 2.451_966_162_957_145, 9.547_229_335_697_903, 3.306_128_696_560_687_5, -3.722_166_730_957_705_6, ]), - Point::new([ + Point::from_validated_coords([ -2.344_360_378_074_79, -2.755_831_029_562_339, -1.275_699_073_649_171_6, 7.667_812_493_160_508, ]), - Point::new([ + Point::from_validated_coords([ -8.633_692_230_033_44, 1.995_093_685_275_964_6, 7.993_316_108_703_105, -3.310_780_098_197_376_7, ]), - Point::new([ + Point::from_validated_coords([ 9.710_410_828_147_591, -9.675_293_457_452_888, -7.169_080_272_753_141, 5.405_946_111_675_925_5, ]), - Point::new([ + Point::from_validated_coords([ 2.266_246_031_487_613, 2.481_673_939_102_995, 3.039_413_140_674_462, 4.441_464_307_622_285, ]), - Point::new([ + Point::from_validated_coords([ 2.565_731_492_709_954, 8.916_218_617_699_3, -3.878_340_784_199_263_4, -9.518_720_806_139_726, ]), - Point::new([ + Point::from_validated_coords([ -2.067_801_258_479_087_2, -5.739_002_626_992_522, 7.554_154_642_458_165, -2.983_334_995_469_171_2, ]), - Point::new([ + Point::from_validated_coords([ 7.592_645_474_686_005, -3.326_646_745_715_216, -3.259_537_116_123_248, -4.935_000_398_073_641, ]), - Point::new([ + Point::from_validated_coords([ -5.931_807_896_262_18, 8.897_268_005_841_394, 0.324_049_126_782_281_15, -8.328_532_028_712_647, ]), - Point::new([ + Point::from_validated_coords([ -8.182_644_118_410_867, 5.373_925_359_941_506, -9.015_837_749_827_128, -1.703_973_344_007_208, ]), - Point::new([ + Point::from_validated_coords([ 1.455_467_619_488_706_2, 9.869_985_381_801_74, 8.605_618_759_378_327, -1.050_236_122_559_873_3, ]), - Point::new([ + Point::from_validated_coords([ -5.687_160_826_499_058, 6.504_655_423_433_022, 8.941_590_411_569_816, 9.543_547_641_077_382, ]), - Point::new([ + Point::from_validated_coords([ 8.975_549_245_653_312, -8.089_655_037_805_944, 9.936_284_142_216_682, -7.816_992_427_475_977, ]), - Point::new([ + Point::from_validated_coords([ 5.825_845_324_524_742, -7.639_141_597_632_388, 1.549_524_653_880_336_4, 4.563_088_344_949_309, ]), - Point::new([ + Point::from_validated_coords([ 7.387_141_055_690_918, 6.194_972_387_680_284, -5.764_015_058_796_046, 9.298_338_336_238_999, ]), - Point::new([ + Point::from_validated_coords([ -1.597_916_740_077_209_9, -4.938_008_036_006_716, 7.414_979_546_687_874, -7.718_146_418_588_452, ]), - Point::new([ + Point::from_validated_coords([ -2.414_045_007_912_424_3, 8.888_648_260_600_007, -5.859_329_894_512_815, 3.268_096_825_406_147, ]), - Point::new([ + Point::from_validated_coords([ -8.294_250_893_230_837, 3.083_275_278_154_95, 8.020_989_920_767_69, 8.155_291_219_012_977, ]), - Point::new([ + Point::from_validated_coords([ 6.718_748_825_685_814_6, -4.640_634_945_941_695, 2.283_644_483_657_752_7, @@ -4464,12 +4647,12 @@ mod tests { /// retry exhaustion. #[test] fn test_perturbation_retry_and_exhaustion_4d() { - let initial_vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + let initial_vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 4>::build_initial_simplex( &initial_vertices, @@ -4481,10 +4664,7 @@ mod tests { ); let _guard = ForceNextRetryableInsertionFailureGuard::enable(); - let retry_success_vertex = VertexBuilder::default() - .point(Point::new([0.2, 0.2, 0.2, 0.2])) - .build() - .unwrap(); + let retry_success_vertex = Vertex::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(); let (_outcome, retry_success_stats) = insert_with_statistics(&mut retry_success_tri, retry_success_vertex, None, None) .unwrap(); @@ -4495,7 +4675,7 @@ mod tests { let mut saw_exhausted_skip = false; for point in perturbation_retry_repro_points_4d() { - let v = VertexBuilder::default().point(point).build().unwrap(); + let v = Vertex::from_validated_point(point); let (outcome, stats) = insert_with_statistics(&mut exhaustion_tri, v, None, None).unwrap(); @@ -4529,7 +4709,7 @@ mod tests { Triangulation::new_empty(AdaptiveKernel::new()); for point in perturbation_retry_repro_points_4d() { - let v = VertexBuilder::default().point(point).build().unwrap(); + let v = Vertex::from_validated_point(point); let (_outcome, stats) = insert_transactional( &mut tri, v, diff --git a/src/core/orientation.rs b/src/core/orientation.rs index 4f562ea3..9c566939 100644 --- a/src/core/orientation.rs +++ b/src/core/orientation.rs @@ -15,12 +15,11 @@ use crate::geometry::kernel::Kernel; use crate::geometry::point::Point; use crate::geometry::predicates::Orientation; use crate::geometry::robust_predicates::robust_orientation; -use crate::geometry::traits::coordinate::Coordinate; use crate::topology::traits::global_topology_model::GlobalTopologyModel; impl Triangulation where - K: Kernel, + K: Kernel, { /// Collect simplex points for orientation evaluation. /// @@ -29,9 +28,9 @@ where fn collect_simplex_points_for_orientation( &self, simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, purpose: &str, - ) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, TdsError> { + ) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, TdsError> { let topology_model = self.global_topology.model(); let periodic_offsets = simplex.periodic_vertex_offsets(); if let Some(offsets) = periodic_offsets { @@ -60,7 +59,7 @@ where } } - let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(simplex.number_of_vertices()); for (vertex_idx, &vertex_key) in simplex.vertices().iter().enumerate() { @@ -83,7 +82,14 @@ where ), })?; - points.push(Point::new(lifted_coords)); + let lifted_point = + Point::try_new(lifted_coords).map_err(|source| TdsError::InconsistentDataStructure { + message: format!( + "Lifted coordinates for vertex key {vertex_key:?} at slot {vertex_idx} in simplex {:?} (key {simplex_key:?}) during {purpose} violated point invariants: {source}", + simplex.uuid(), + ), + })?; + points.push(lifted_point); } Ok(points) @@ -96,7 +102,7 @@ where pub(crate) fn evaluate_simplex_orientation_for_context( &self, simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, purpose: &str, predicate_failure_prefix: &str, ) -> Result { @@ -510,7 +516,6 @@ mod tests { use crate::topology::traits::topological_space::{ GlobalTopology, ToroidalConstructionMode, ToroidalDomainError, }; - use crate::vertex; use std::assert_matches; /// Regression test: a negatively oriented but topologically valid simplex @@ -518,9 +523,9 @@ mod tests { #[test] fn negative_oriented_simplex_topology_only() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -543,10 +548,10 @@ mod tests { #[test] fn local_geometric_orientation_validation_errors_on_missing_scope_simplex() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices).unwrap(); @@ -567,9 +572,9 @@ mod tests { #[test] fn is_valid_rejects_negative_geometric_simplex_orientation() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -591,9 +596,9 @@ mod tests { #[test] fn validate_geometric_simplex_orientation_returns_enriched_error_on_negative() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -619,9 +624,9 @@ mod tests { #[test] fn simplices_require_positive_orientation_promotion_detects_negative_without_mutating() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -645,9 +650,9 @@ mod tests { #[test] fn simplices_require_positive_orientation_promotion_false_for_positive_without_mutating() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -668,9 +673,9 @@ mod tests { #[test] fn periodic_geometric_orientation_validation_uses_lifted_coordinates() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([0.8, 0.0]), - vertex!([0.0, 0.8]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.8]).unwrap(), ]; let mut tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -704,9 +709,9 @@ mod tests { #[test] fn periodic_geometric_orientation_validation_requires_toroidal_metadata() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([0.8, 0.0]), - vertex!([0.0, 0.8]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.8]).unwrap(), ]; let mut tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -729,9 +734,9 @@ mod tests { #[test] fn periodic_geometric_orientation_validation_rejects_offset_count_mismatch() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([0.8, 0.0]), - vertex!([0.0, 0.8]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.8]).unwrap(), ]; let mut tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); diff --git a/src/core/query.rs b/src/core/query.rs index 53236b6c..68f17d81 100644 --- a/src/core/query.rs +++ b/src/core/query.rs @@ -2,7 +2,7 @@ //! //! This module owns zero-mutation accessors and topology traversal helpers for //! the generic triangulation layer. Mutation APIs stay with the construction and -//! editing modules; validation orchestration stays in [`crate::core::validation`]. +//! editing modules; validation orchestration stays in [`crate::prelude::validation`]. use crate::core::adjacency::{AdjacencyIndex, AdjacencyIndexBuildError}; use crate::core::collections::{ @@ -42,10 +42,10 @@ static VERTEX_TO_SIMPLICES_SPILL_EVENTS: AtomicU64 = AtomicU64::new(0); /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -79,11 +79,18 @@ where /// ```rust /// use delaunay::prelude::*; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Query(#[from] delaunay::query::QueryError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tri = dt.as_triangulation(); @@ -96,7 +103,7 @@ where /// # Ok(()) /// # } /// ``` - pub fn simplices(&self) -> impl Iterator)> { + pub fn simplices(&self) -> impl Iterator)> { self.tds.simplices() } @@ -109,11 +116,18 @@ where /// ```rust /// use delaunay::prelude::*; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Query(#[from] delaunay::query::QueryError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tri = dt.as_triangulation(); @@ -126,7 +140,7 @@ where /// # Ok(()) /// # } /// ``` - pub fn vertices(&self) -> impl Iterator)> { + pub fn vertices(&self) -> impl Iterator)> { self.tds.vertices() } @@ -137,12 +151,19 @@ where /// ```rust /// use delaunay::prelude::*; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Query(#[from] delaunay::query::QueryError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.as_triangulation().number_of_vertices(), 4); @@ -161,12 +182,19 @@ where /// ```rust /// use delaunay::prelude::*; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Query(#[from] delaunay::query::QueryError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.as_triangulation().number_of_simplices(), 1); // Single tetrahedron @@ -194,10 +222,10 @@ where /// /// // 3D tetrahedron has dimension 3 /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.as_triangulation().dim(), 3); @@ -219,28 +247,42 @@ where /// /// An iterator yielding `FacetView` objects for all facets in the triangulation. /// + /// # Errors + /// + /// Returns [`QueryError::TriangulationCorrupted`] if the facet iterator cannot + /// represent facet indices for this dimension. + /// /// # Examples /// /// ```rust /// use delaunay::prelude::*; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Query(#[from] delaunay::query::QueryError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// // Iterate over all facets - /// let facet_count = dt.as_triangulation().facets().count(); + /// let facet_count = dt.as_triangulation().facets()?.count(); /// assert_eq!(facet_count, 4); // Tetrahedron has 4 facets /// # Ok(()) /// # } /// ``` - pub fn facets(&self) -> AllFacetsIter<'_, K::Scalar, U, V, D> { - AllFacetsIter::new(&self.tds) + pub fn facets(&self) -> Result, QueryError> { + AllFacetsIter::try_new(&self.tds) + .map_err(TdsError::from) + .map_err(|source| QueryError::TriangulationCorrupted { source }) } /// Returns an iterator over boundary (hull) facets in the triangulation. @@ -266,10 +308,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -284,14 +326,14 @@ where /// Returns [`QueryError::TriangulationCorrupted`] if facet-map construction /// detects invalid simplex or facet bookkeeping. The variant preserves the /// underlying [`TdsError`] so callers can inspect the structural failure. - pub fn boundary_facets( - &self, - ) -> Result, QueryError> { + pub fn boundary_facets(&self) -> Result, QueryError> { let facet_map = self .tds .build_facet_to_simplices_map() .map_err(|source| QueryError::TriangulationCorrupted { source })?; - Ok(BoundaryFacetsIter::new(&self.tds, facet_map)) + BoundaryFacetsIter::try_new(&self.tds, facet_map) + .map_err(TdsError::from) + .map_err(|source| QueryError::TriangulationCorrupted { source }) } #[inline] @@ -331,10 +373,10 @@ where /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// // A single 3D tetrahedron has 6 unique edges. /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -368,10 +410,10 @@ where /// # fn main() -> Result<(), ExampleError> { /// // A single 3D tetrahedron has 6 unique edges. /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -403,9 +445,9 @@ where /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// // A single 2D triangle has 3 unique edges. /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 2> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -437,10 +479,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// # let vertices = vec![ - /// # vertex!([0.0, 0.0, 0.0]), - /// # vertex!([1.0, 0.0, 0.0]), - /// # vertex!([0.0, 1.0, 0.0]), - /// # vertex!([0.0, 0.0, 1.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// # ]; /// # let dt: DelaunayTriangulation<_, (), (), 3> = /// # DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -577,7 +619,7 @@ where /// /// This is a zero-allocation accessor. If `v` is not present, returns `None`. #[must_use] - pub fn vertex_coords(&self, v: VertexKey) -> Option<&[K::Scalar]> { + pub fn vertex_coords(&self, v: VertexKey) -> Option<&[f64]> { self.tds .vertex(v) .map(|vertex| &vertex.point().coords()[..]) @@ -672,7 +714,7 @@ where for i in 0..vertices.len() { for j in (i + 1)..vertices.len() { - let edge = EdgeKey::new(vertices[i], vertices[j]); + let edge = EdgeKey::from_validated_endpoints(vertices[i], vertices[j]); if !seen_edges.insert(edge) { continue; } @@ -708,7 +750,7 @@ where let vertices = simplex.vertices(); for i in 0..vertices.len() { for j in (i + 1)..vertices.len() { - edges.insert(EdgeKey::new(vertices[i], vertices[j])); + edges.insert(EdgeKey::from_validated_endpoints(vertices[i], vertices[j])); } } } @@ -729,7 +771,7 @@ where if other == v { continue; } - edges.insert(EdgeKey::new(v, other)); + edges.insert(EdgeKey::from_validated_endpoints(v, other)); } } @@ -742,7 +784,6 @@ mod tests { use super::*; use crate::geometry::kernel::FastKernel; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use slotmap::KeyData; use std::collections::HashSet; @@ -760,11 +801,11 @@ mod tests { assert_eq!(empty.dim(), -1); assert_eq!(empty.simplices().count(), 0); assert_eq!(empty.vertices().count(), 0); - assert_eq!(empty.facets().count(), 0); + assert_eq!(empty.facets().unwrap().count(), 0); assert_eq!(empty.boundary_facets().unwrap().count(), 0); let vertices = vec![ - $(vertex!($simplex_coords)),+ + $(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+ ]; let expected_vertex_count = vertices.len(); @@ -781,7 +822,7 @@ mod tests { assert_eq!(tri.dim(), $dim as i32); assert_eq!(tri.simplices().count(), 1); assert_eq!(tri.vertices().count(), expected_vertex_count); - assert_eq!(tri.facets().count(), expected_vertex_count); + assert_eq!(tri.facets().unwrap().count(), expected_vertex_count); assert_eq!(tri.boundary_facets().unwrap().count(), expected_vertex_count); } } @@ -823,9 +864,9 @@ mod tests { #[test] fn test_boundary_facets_reports_corrupted_facet_map() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -853,9 +894,9 @@ mod tests { #[test] fn topology_edges_triangle_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -883,11 +924,11 @@ mod tests { #[test] fn topology_edges_and_incident_edges_double_tetrahedron_3d() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), - vertex!([1.0, 0.7, 1.5]), - vertex!([1.0, 0.7, -1.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -957,9 +998,9 @@ mod tests { #[test] fn topology_queries_missing_keys_are_empty_or_none() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1007,9 +1048,9 @@ mod tests { #[test] fn topology_geometry_accessors_round_trip() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -1031,11 +1072,11 @@ mod tests { #[test] fn build_adjacency_index_basic_invariants() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), - vertex!([1.0, 0.7, 1.5]), - vertex!([1.0, 0.7, -1.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -1078,9 +1119,9 @@ mod tests { #[test] fn build_adjacency_index_includes_isolated_vertex_entries() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -1089,7 +1130,9 @@ mod tests { let isolated_vertex = tri .tds - .insert_vertex_with_mapping(vertex!([10.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0]).unwrap(), + ) .unwrap(); let index = tri.build_adjacency_index().unwrap(); @@ -1110,9 +1153,9 @@ mod tests { #[test] fn build_adjacency_index_errors_on_missing_neighbor_simplex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -1147,9 +1190,9 @@ mod tests { #[test] fn simplex_neighbors_filters_missing_neighbor_simplex() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -1171,9 +1214,9 @@ mod tests { #[test] fn build_adjacency_index_errors_on_missing_vertex_key() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices).unwrap(); @@ -1211,11 +1254,11 @@ mod tests { #[test] fn topology_queries_on_two_tet_triangulation() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1226,7 +1269,7 @@ mod tests { assert_eq!(edges_collected.len(), edge_count); assert!(edge_count >= 6); - assert!(tri.facets().next().is_some()); + assert!(tri.facets().unwrap().next().is_some()); assert!(tri.boundary_facets().unwrap().next().is_some()); let (simplex_key, _) = tri.simplices().next().unwrap(); @@ -1261,11 +1304,11 @@ mod tests { #[test] fn adjacency_index_with_index_methods() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); diff --git a/src/core/repair.rs b/src/core/repair.rs index 399180ac..9c0e1f00 100644 --- a/src/core/repair.rs +++ b/src/core/repair.rs @@ -1,7 +1,7 @@ //! Local topology repair for generic triangulations. //! //! This module owns local facet issue detection/repair, stale incident-simplex -//! repair, and vertex-removal cavity retriangulation for [`Triangulation`](crate::core::triangulation::Triangulation). +//! repair, and vertex-removal cavity retriangulation for [`Triangulation`](crate::prelude::triangulation::Triangulation). use crate::core::algorithms::incremental_insertion::{ CavityFillingError, CavityRepairStage, InsertionError, external_facets_for_boundary, @@ -20,9 +20,6 @@ use crate::core::triangulation::Triangulation; use crate::core::validation::{TriangulationValidationError, insertion_error_to_invariant_error}; use crate::geometry::kernel::Kernel; use crate::geometry::quality::{QualityError, QualitySimplexVerticesError, radius_ratio}; -use crate::geometry::util::safe_scalar_to_f64; -use core::ops::Div; -use num_traits::NumCast; use std::env; use std::hash::{Hash, Hasher}; use std::sync::OnceLock; @@ -98,7 +95,7 @@ pub(crate) struct LocalFacetRepairOutcome { impl Triangulation where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -243,8 +240,7 @@ where impl Triangulation where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -874,7 +870,6 @@ where /// /// ```rust /// use delaunay::prelude::construction::DelaunayTriangulationBuilder; - /// use delaunay::prelude::triangulation::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -886,9 +881,9 @@ where /// # fn main() -> Result<(), ExampleError> { /// // A single simplex has no over-shared facets. /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -964,10 +959,7 @@ where fn simplices_for_local_facet_issue_repair( &self, issues: &FacetIssuesMap, - ) -> Result - where - K::Scalar: Div, - { + ) -> Result { let mut simplices_to_remove = SimplexKeySet::default(); // For each over-shared facet, select simplices to remove @@ -987,19 +979,12 @@ where // Propagate quality evaluation errors let ratio = radius_ratio(self, simplex_key) .map_err(|error| quality_error_to_tds_error(simplex_key, error))?; - let ratio_f64 = - safe_scalar_to_f64(ratio).map_err(|_| TdsError::InconsistentDataStructure { - message: format!( - "Quality ratio conversion failed for simplex {simplex_key:?}" - ), - })?; - - if ratio_f64.is_finite() { - simplex_qualities.push((simplex_key, ratio_f64, uuid)); + if ratio.is_finite() { + simplex_qualities.push((simplex_key, ratio, uuid)); } else { return Err(TdsError::InconsistentDataStructure { message: format!( - "Non-finite quality ratio {ratio_f64} for simplex {simplex_key:?}" + "Non-finite quality ratio {ratio} for simplex {simplex_key:?}" ), }); } @@ -1077,10 +1062,7 @@ where &mut self, issues: &FacetIssuesMap, max_simplices_removed: usize, - ) -> Result - where - K::Scalar: Div, - { + ) -> Result { let to_remove = self .simplices_for_local_facet_issue_repair(issues) .map_err(InsertionError::TopologyValidation)?; @@ -1149,7 +1131,7 @@ where /// DelaunayTriangulation, DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::insertion::InsertionError; - /// use delaunay::prelude::triangulation::{FacetIssuesMap, vertex}; + /// use delaunay::prelude::triangulation::{FacetIssuesMap}; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1161,9 +1143,9 @@ where /// # fn main() -> Result<(), ExampleError> { /// // Start with a valid 2D simplex. /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 2> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1183,10 +1165,7 @@ where &mut self, issues: &FacetIssuesMap, max_simplices_removed: usize, - ) -> Result - where - K::Scalar: Div, - { + ) -> Result { let tds_snapshot = self.tds.clone_for_rollback(); let repair_result = (|| -> Result { let outcome = @@ -1226,7 +1205,6 @@ mod tests { use crate::core::tds::Tds; use crate::core::vertex::Vertex; use crate::geometry::kernel::FastKernel; - use crate::vertex; use std::assert_matches; use slotmap::KeyData; @@ -1243,24 +1221,34 @@ mod tests { let v0 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tri .tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let ck = tri .tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); for vk in [v0, v1, v2, v3] { @@ -1283,25 +1271,47 @@ mod tests { VertexKey, VertexKey, ) { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v_d = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) .unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let c3 = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v_a, v_b, v_e], None).unwrap(), + Simplex::try_new_with_data(vec![v_a, v_b, v_e], None).unwrap(), ) .unwrap(); @@ -1331,8 +1341,8 @@ mod tests { pastey::paste! { #[test] fn []() { - let vertices: Vec> = vec![ - $(vertex!($simplex_coords)),+ + let vertices: Vec> = vec![ + $(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+ ]; let tds = Triangulation::, (), (), $dim>::build_initial_simplex(&vertices) @@ -1368,8 +1378,8 @@ mod tests { #[test] fn []() { - let vertices: Vec> = vec![ - $(vertex!($simplex_coords)),+ + let vertices: Vec> = vec![ + $(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+ ]; let tds = Triangulation::, (), (), $dim>::build_initial_simplex(&vertices) @@ -1396,9 +1406,9 @@ mod tests { #[test] fn []() { // Build triangulation with D+1 simplex vertices + 1 interior point - let vertices: Vec> = { - let mut v = vec![$(vertex!($simplex_coords)),+]; - v.push(vertex!($interior_point)); + let vertices: Vec> = { + let mut v = vec![$(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+]; + v.push(crate::core::vertex::Vertex::<(), _>::try_new($interior_point).unwrap()); v }; @@ -1615,7 +1625,9 @@ mod tests { // Insert a vertex that is NOT referenced by any simplex. let iso = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 0.5, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), + ) .unwrap(); let result = tri.repair_stale_incident_simplices(); @@ -1641,10 +1653,10 @@ mod tests { #[test] fn test_detect_local_facet_issues_none_for_valid_triangulation() { let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1672,7 +1684,7 @@ mod tests { fn test_pick_fan_apex_preserves_missing_boundary_simplex() { let (tri, _, _) = build_single_tet(); let missing_simplex = SimplexKey::from(KeyData::from_ffi(0xBAD)); - let facets = [FacetHandle::new(missing_simplex, 0)]; + let facets = [FacetHandle::from_validated(missing_simplex, 0)]; assert_matches!( tri.pick_fan_apex(&facets), @@ -1722,7 +1734,7 @@ mod tests { candidate_simplices.push(simplex_key); candidate_simplices.push(missing_simplex); candidate_simplices.push(simplex_key); - let external_facets = [FacetHandle::new(simplex_key, 0)]; + let external_facets = [FacetHandle::from_validated(simplex_key, 0)]; let live_simplices = tri.live_simplices_from(&candidate_simplices); let validation_scope = tri.vertex_removal_validation_scope( @@ -1819,14 +1831,18 @@ mod tests { #[test] fn test_fan_boundary_facets_excluding_apex_keeps_only_facets_without_apex() { let (tri, vkeys, simplex_key) = build_single_tet(); - let boundary_facets: CavityBoundaryBuffer = - (0..=3).map(|i| FacetHandle::new(simplex_key, i)).collect(); + let boundary_facets: CavityBoundaryBuffer = (0..=3) + .map(|i| FacetHandle::from_validated(simplex_key, i)) + .collect(); let fan_facets = tri .fan_boundary_facets_excluding_apex(vkeys[0], &boundary_facets) .unwrap(); - assert_eq!(fan_facets.as_slice(), &[FacetHandle::new(simplex_key, 0)]); + assert_eq!( + fan_facets.as_slice(), + &[FacetHandle::from_validated(simplex_key, 0)] + ); } #[test] @@ -1880,10 +1896,10 @@ mod tests { fn test_remove_vertex_retriangulates_cavity_2d() { // Build 2D triangulation with 4 vertices, remove one, verify valid. let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1909,9 +1925,9 @@ mod tests { // When we remove a vertex from a single-simplex triangulation, // the empty boundary case triggers Tds::remove_vertex fallback. let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1946,8 +1962,9 @@ mod tests { // Use vkeys[0] as apex; construct boundary facets that ALL include vkeys[0]. // In a tet, facet 0 is opposite vkeys[0] (does NOT include it), // but facets 1,2,3 each include vkeys[0]. - let boundary_facets: CavityBoundaryBuffer = - (1..=3).map(|i| FacetHandle::new(ck, i)).collect(); + let boundary_facets: CavityBoundaryBuffer = (1..=3) + .map(|i| FacetHandle::from_validated(ck, i)) + .collect(); let result = tri.fan_fill_cavity(vkeys[0], &boundary_facets); // All facets include vkeys[0], so no simplices should be created. @@ -1963,10 +1980,10 @@ mod tests { // Build 2D triangulation with enough simplices to have interior facets, // then artificially create an over-shared facet by duplicating a simplex. let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1975,7 +1992,7 @@ mod tests { // Add a duplicate simplex with the same vertices as an existing simplex. let (_, existing_simplex) = tri.tds.simplices().next().unwrap(); let vkeys: Vec<_> = existing_simplex.vertices().to_vec(); - let dup_simplex = Simplex::new(vkeys, None).unwrap(); + let dup_simplex = Simplex::try_new_with_data(vkeys, None).unwrap(); let _ = tri .tds .insert_simplex_bypassing_topology_checks_for_test(dup_simplex) @@ -2001,11 +2018,7 @@ mod tests { } /// Return the facet index opposite the vertex not on the tested shared edge. - fn shared_edge_facet_index( - simplex: &Simplex, - v_a: VertexKey, - v_b: VertexKey, - ) -> usize { + fn shared_edge_facet_index(simplex: &Simplex<(), 2>, v_a: VertexKey, v_b: VertexKey) -> usize { simplex .vertices() .iter() diff --git a/src/core/simplex.rs b/src/core/simplex.rs index 118cc32a..7fc14392 100644 --- a/src/core/simplex.rs +++ b/src/core/simplex.rs @@ -5,9 +5,8 @@ //! //! # Key Features //! -//! - **Geometry when you need it**: the `Simplex` type does not require `T: CoordinateScalar` -//! at the type level, but geometric operations, validation, and serialization are only -//! available when `T: CoordinateScalar` (`f64` in this crate) +//! - **Topology-only storage**: the `Simplex` type stores vertex keys rather than +//! coordinate-bearing vertices, so it is generic only over simplex data and dimension //! - **Unique Identification**: Each simplex has a UUID for consistent identification //! - **Vertices Management**: Stores vertices that form the simplex //! - **Neighbor Tracking**: Maintains references to neighboring simplices @@ -23,10 +22,10 @@ //! # fn main() -> Result<(), DelaunayTriangulationConstructionError> { //! // Create vertices for a tetrahedron //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! //! // Create a 3D triangulation with simplices @@ -48,20 +47,19 @@ // IMPORTS // ============================================================================= -use super::vertex::Vertex; +use super::vertex::{Vertex, VertexValidationError}; use super::{ facet::{FacetError, FacetView}, - tds::{SimplexKey, Tds, VertexKey}, + tds::{EntityKind, SimplexKey, Tds, TdsConstructionError, VertexKey}, traits::{DataDeserialize, DataSerialize}, util::{UuidValidationError, make_uuid, usize_to_u8, validate_uuid}, - vertex::VertexValidationError, }; use crate::core::collections::{ FastHashMap, FastHashSet, NeighborBuffer, PeriodicOffsetBuffer, SimplexVertexKeyBuffer, - SimplexVertexUuidBuffer, + SimplexVertexUuidBuffer, fast_hash_map_with_capacity, }; use crate::geometry::matrix::StackMatrixDispatchError; -use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar}; +use crate::geometry::traits::coordinate::CoordinateConversionError; use serde::{ Deserialize, Deserializer, Serialize, de::{self, IgnoredAny, MapAccess, Visitor}, @@ -176,23 +174,12 @@ impl From for SimplexValidationError { } } -fn total_cmp_for_coordinate(left: &T, right: &T) -> cmp::Ordering -where - T: CoordinateScalar, -{ - left.ordered_partial_cmp(right) - .expect("CoordinateScalar::ordered_partial_cmp must define a total order") -} - -fn compare_vertices_by_coordinates( - left: &Vertex, - right: &Vertex, -) -> cmp::Ordering -where - T: CoordinateScalar, -{ +fn compare_vertices_by_coordinates( + left: &Vertex, + right: &Vertex, +) -> cmp::Ordering { for (left_coord, right_coord) in left.point().coords().iter().zip(right.point().coords()) { - let ordering = total_cmp_for_coordinate(left_coord, right_coord); + let ordering = left_coord.total_cmp(right_coord); if ordering != cmp::Ordering::Equal { return ordering; } @@ -295,7 +282,7 @@ impl NeighborSlot { /// - `uuid`: Universally unique identifier for the simplex. /// - `neighbors`: Optional keys to neighboring simplices (opposite each vertex). Access via `neighbors()` method. /// - `data`: Optional user data associated with the simplex. Read via [`data()`](Self::data), -/// mutate via [`Tds::set_simplex_data`](crate::core::tds::Tds::set_simplex_data). +/// mutate via [`Tds::set_simplex_data`](crate::prelude::tds::Tds::set_simplex_data). /// /// # Accessing Vertices /// @@ -307,9 +294,9 @@ impl NeighborSlot { /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// // Create a triangulation with some vertices /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -329,7 +316,7 @@ impl NeighborSlot { /// # Ok(()) /// # } /// ``` -pub struct Simplex { +pub struct Simplex { /// Keys to the vertices forming this simplex. /// /// - Zero heap allocation for D ≤ 7 (stack-allocated) @@ -367,10 +354,6 @@ pub struct Simplex { /// `periodic_vertex_offsets[i]` corresponds to `vertices[i]`. /// Offsets are omitted from serialization and are reconstructed by periodic builders. pub(crate) periodic_vertex_offsets: Option>, - - /// Phantom data to maintain type parameters T and U for coordinate and vertex data types. - /// These are needed because simplices store keys to vertices, not the vertices themselves. - _phantom: PhantomData<(T, U)>, } // ============================================================================= @@ -379,15 +362,16 @@ pub struct Simplex { /// Manual implementation of Serialize for Simplex. /// -/// This implementation handles serialization of Simplex fields. The `vertices` and `neighbors` -/// fields are skipped as they contain keys that are only valid within the current `SlotMap`. -/// During deserialization, these are reconstructed by the TDS. +/// This implementation deliberately omits vertex keys because `VertexKey` +/// values are slotmap-local. `Tds` serialization stores a simplex UUID → vertex +/// UUID mapping and resolves those UUIDs back to live keys during TDS +/// deserialization. /// /// **Field Count Optimization**: We dynamically adjust the field count and conditionally /// serialize `data` to omit it from JSON when None (reducing output size). The second /// `is_some()` check matches the field count logic—both could be removed to always /// serialize "data": null, but tests explicitly verify the field is omitted when None. -impl Serialize for Simplex +impl Serialize for Simplex where V: DataSerialize, { @@ -410,8 +394,18 @@ where // DESERIALIZATION IMPLEMENTATION // ============================================================================= -/// Manual implementation of Deserialize for Simplex -impl<'de, T, U, V, const D: usize> Deserialize<'de> for Simplex +/// Raw serialized simplex record used by `Tds` deserialization. +/// +/// TDS serialization carries simplex-to-vertex relationships separately as +/// vertex UUIDs. This DTO intentionally does not construct a `Simplex`; the TDS +/// deserializer resolves those UUIDs to live vertex keys and then calls +/// `Simplex::try_new_with_uuid`. +pub(crate) struct SerializedSimplex { + pub(crate) uuid: Uuid, + pub(crate) data: Option, +} + +impl<'de, V> Deserialize<'de> for SerializedSimplex where V: DataDeserialize, { @@ -419,32 +413,32 @@ where where De: Deserializer<'de>, { - struct SimplexVisitor + struct SerializedSimplexVisitor where V: DataDeserialize, { - _phantom: PhantomData<(T, U, V)>, + _phantom: PhantomData, } - impl<'de, T, U, V, const D: usize> Visitor<'de> for SimplexVisitor + impl<'de, V> Visitor<'de> for SerializedSimplexVisitor where V: DataDeserialize, { - type Value = Simplex; + type Value = SerializedSimplex; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a Simplex struct") + formatter.write_str("a serialized Simplex record") } - fn visit_map(self, mut map: A) -> Result, A::Error> + fn visit_map(self, mut map: A) -> Result, A::Error> where A: MapAccess<'de>, { let mut uuid = None; let mut data = None; - while let Some(key) = map.next_key()? { - match key { + while let Some(key) = map.next_key::()? { + match key.as_str() { "uuid" => { if uuid.is_some() { return Err(de::Error::duplicate_field("uuid")); @@ -457,9 +451,12 @@ where } data = Some(map.next_value()?); } + "vertices" | "neighbors" | "periodic_vertex_offsets" => { + return Err(de::Error::custom(format!( + "{key} is storage-local simplex state and must not be deserialized; deserialize Tds so UUID relationships can be reconstructed", + ))); + } _ => { - // Ignore any other fields (vertices, neighbors, etc.) - // These are skipped during serialization and reconstructed by TDS let _ = map.next_value::()?; } } @@ -468,27 +465,69 @@ where let uuid: Uuid = uuid.ok_or_else(|| de::Error::missing_field("uuid"))?; validate_uuid(&uuid) .map_err(|e| de::Error::custom(format!("invalid uuid: {e}")))?; - // data is Option>: None if field missing, Some(inner) if present - // flatten() converts None -> None and Some(inner) -> inner - let data = data.flatten(); - - // Vertices and neighbors are not serialized. They are reconstructed - // by TDS deserialization using: - // - vertices: rebuilt by the TDS using its serialized simplex→vertex mapping - // - neighbors: rebuilt by the TDS via assign_neighbors() - let vertices = SimplexVertexKeyBuffer::new(); - - Ok(Simplex { - vertices, + + Ok(SerializedSimplex { uuid, - neighbors: None, // Will be reconstructed by TDS - data, - periodic_vertex_offsets: None, - _phantom: PhantomData, + data: data.flatten(), }) } } + const FIELDS: &[&str] = &["uuid", "data", "vertices"]; + deserializer.deserialize_struct( + "SerializedSimplex", + FIELDS, + SerializedSimplexVisitor { + _phantom: PhantomData, + }, + ) + } +} + +/// Manual implementation of Deserialize for Simplex. +/// +/// A standalone simplex cannot be reconstructed safely from serialized data +/// because its vertex keys are slotmap-local. Deserialize the containing +/// [`Tds`] so simplex vertex UUIDs can be resolved to +/// live keys first. +impl<'de, V, const D: usize> Deserialize<'de> for Simplex +where + V: DataDeserialize, +{ + fn deserialize(deserializer: De) -> Result + where + De: Deserializer<'de>, + { + struct SimplexVisitor + where + V: DataDeserialize, + { + _phantom: PhantomData, + } + + impl<'de, V, const D: usize> Visitor<'de> for SimplexVisitor + where + V: DataDeserialize, + { + type Value = Simplex; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a Simplex struct") + } + + fn visit_map(self, map: A) -> Result, A::Error> + where + A: MapAccess<'de>, + { + let _record = SerializedSimplex::::deserialize( + de::value::MapAccessDeserializer::new(map), + )?; + Err(de::Error::custom( + "standalone Simplex deserialization is unsupported; deserialize Tds so simplex vertex UUIDs can be resolved to live keys", + )) + } + } + const FIELDS: &[&str] = &["uuid", "data"]; deserializer.deserialize_struct( "Simplex", @@ -505,24 +544,34 @@ where // ============================================================================= // Minimal trait bounds impl block -impl Simplex { - /// Internal constructor for TDS use only. +impl Simplex { + /// Tries to create a simplex from vertex keys without user data. /// - /// Creates a simplex with the given vertex keys and optional data. - /// This constructor is `pub(crate)` to restrict usage to within the crate, - /// ensuring simplices are always created through proper TDS methods. + /// This constructor is `pub(crate)` because live [`VertexKey`] values are + /// TDS-local handles. Public callers should construct triangulations from + /// vertices or explicit index lists so the owning TDS can validate and map + /// those handles. /// - /// # Arguments + /// # Errors /// - /// * `vertices` - Keys to the vertices forming this simplex (must be D+1 keys) - /// * `data` - Optional simplex data + /// Returns [`SimplexValidationError::InsufficientVertices`] if `vertices` + /// does not contain exactly `D + 1` keys, or + /// [`SimplexValidationError::DuplicateVertices`] if a key appears more than + /// once. + pub(crate) fn try_new( + vertices: impl Into, + ) -> Result { + Self::try_new_with_data(vertices, None) + } + + /// Tries to create a simplex from vertex keys with user data. /// /// # Errors /// /// Returns: /// - `SimplexValidationError::InsufficientVertices` if `vertices` doesn't have exactly D+1 elements. /// - `SimplexValidationError::DuplicateVertices` if any vertex key appears more than once. - pub(crate) fn new( + pub(crate) fn try_new_with_data( vertices: impl Into, data: Option, ) -> Result { @@ -552,10 +601,34 @@ impl Simplex { neighbors: None, data, periodic_vertex_offsets: None, - _phantom: PhantomData, }) } + /// Tries to create a simplex with a caller-provided UUID. + /// + /// This crate-internal constructor is used by TDS deserialization after + /// serialized vertex UUID relationships have been resolved back to live + /// [`VertexKey`] handles. Public callers construct simplices through the + /// owning TDS so key locality and UUID validity are established together. + /// + /// # Errors + /// + /// Returns [`SimplexValidationError::InvalidUuid`] if `uuid` is nil, + /// [`SimplexValidationError::InsufficientVertices`] if `vertices` does not + /// contain exactly `D + 1` keys, or + /// [`SimplexValidationError::DuplicateVertices`] if any key appears more + /// than once. + pub(crate) fn try_new_with_uuid( + vertices: impl Into, + uuid: Uuid, + data: Option, + ) -> Result { + validate_uuid(&uuid)?; + let mut simplex = Self::try_new_with_data(vertices, data)?; + simplex.uuid = uuid; + Ok(simplex) + } + /// Checks if this simplex contains the given vertex key. /// /// This is a cheap operation (O(D)) that only compares keys. @@ -575,9 +648,9 @@ impl Simplex { /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((_, simplex)) = dt.simplices().next() else { @@ -615,10 +688,10 @@ impl Simplex { /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), - /// vertex!([1.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let mut simplices_iter = dt.simplices().map(|(_, simplex)| simplex); @@ -653,9 +726,9 @@ impl Simplex { /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((_, simplex)) = dt.simplices().next() else { @@ -694,9 +767,9 @@ impl Simplex { /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((_, simplex)) = dt.simplices().next() else { @@ -780,9 +853,9 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let mut tds = dt.tds().clone(); @@ -836,9 +909,9 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((_, simplex)) = dt.simplices().next() else { @@ -936,29 +1009,16 @@ impl Simplex { mirror_idx } - /// Adds a vertex key to this simplex. - /// - /// # Internal Use Only - /// - /// This method is used internally by TDS deserialization to rebuild simplex vertex keys. - /// It should not be used outside of TDS serialization/deserialization code. - /// - /// # Arguments - /// - /// * `vertex_key` - The vertex key to add + /// Adds a vertex key in tests that deliberately construct invalid topology. + #[cfg(test)] #[inline] pub(crate) fn push_vertex_key(&mut self, vertex_key: VertexKey) { self.vertices.push(vertex_key); self.periodic_vertex_offsets = None; } - /// Clears all vertex keys from this simplex. - /// - /// # Internal Use Only - /// - /// This method is used internally by TDS deserialization to clear stale vertex keys - /// before rebuilding them from the serialized `simplex_vertices` mapping. - /// It should not be used outside of TDS serialization/deserialization code. + /// Clears vertex keys in tests that deliberately construct invalid topology. + #[cfg(test)] #[inline] pub(crate) fn clear_vertex_keys(&mut self) { self.vertices.clear(); @@ -1039,7 +1099,7 @@ impl Simplex { } // Standard read-only and validation impl block -impl Simplex { +impl Simplex { /// The function returns the number of vertices in the [Simplex]. /// /// # Returns @@ -1064,10 +1124,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { @@ -1111,10 +1171,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 1.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1151,9 +1211,9 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::()?; /// let Some((_, simplex)) = dt.simplices().next() else { @@ -1169,36 +1229,11 @@ impl Simplex { self.data.as_ref() } - /// Sets the simplex UUID with validation. - /// - /// This is a test-only utility for creating simplices with specific UUIDs - /// to test error handling (e.g., duplicate UUID detection). - /// - /// # Arguments - /// - /// * `uuid` - The new UUID to set for this simplex - /// - /// # Returns - /// - /// Returns `Ok(())` if the UUID is valid and was set successfully, - /// otherwise returns a `SimplexValidationError::InvalidUuid` if the UUID - /// is nil or has an invalid version. - /// - /// # Errors - /// - /// Returns `SimplexValidationError::InvalidUuid` if the UUID is nil or invalid. - #[cfg(test)] - pub(crate) fn set_uuid(&mut self, uuid: Uuid) -> Result<(), SimplexValidationError> { - validate_uuid(&uuid)?; - self.uuid = uuid; - Ok(()) - } - /// Clears the neighbors of the [Simplex]. /// /// **Internal API**: This method is `pub(crate)` to enforce that all neighbor /// modifications go through validated TDS methods. External code should use - /// [`Tds::clear_all_neighbors()`](crate::core::tds::Tds::clear_all_neighbors) + /// [`Tds::clear_all_neighbors()`](crate::prelude::tds::Tds::clear_all_neighbors) /// which properly invalidates caches and maintains triangulation consistency. /// /// This method sets the `neighbors` field to `None`, effectively removing all @@ -1222,9 +1257,9 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// # let vertices = vec![ - /// # vertex!([0.0, 0.0]), - /// # vertex!([1.0, 0.0]), - /// # vertex!([0.0, 1.0]), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// # ]; /// # let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// // Note: clear_all_neighbors() is a Tds method, not on DelaunayTriangulation @@ -1281,10 +1316,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { @@ -1300,9 +1335,9 @@ impl Simplex { /// # } /// ``` #[inline] - pub fn vertex_uuids( + pub fn vertex_uuids( &self, - tds: &Tds, + tds: &Tds, ) -> Result { self.vertices .iter() @@ -1349,9 +1384,9 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { @@ -1367,9 +1402,9 @@ impl Simplex { /// # } /// ``` #[inline] - pub fn vertex_uuid_iter<'a>( + pub fn vertex_uuid_iter<'a, U>( &'a self, - tds: &'a Tds, + tds: &'a Tds, ) -> impl ExactSizeIterator> + 'a { self.vertices.iter().map(move |&vkey| { tds.vertex(vkey) @@ -1403,10 +1438,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 1.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1453,20 +1488,22 @@ impl Simplex { /// # Simplex(#[from] delaunay::prelude::tds::SimplexValidationError), /// # #[error(transparent)] /// # Facet(#[from] delaunay::prelude::tds::FacetError), + /// # #[error(transparent)] + /// # TdsConstruction(#[from] delaunay::prelude::tds::TdsConstructionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create two separate triangulations /// let vertices1 = vec![ - /// vertex!([0.0, 0.0, 1.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), /// ]; /// let vertices2 = vec![ - /// vertex!([1.0, 1.0, 1.0]), - /// vertex!([2.0, 1.0, 1.0]), - /// vertex!([1.0, 2.0, 1.0]), - /// vertex!([1.0, 1.0, 2.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([2.0, 1.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 2.0]).expect("finite vertex coordinates"), /// ]; /// let dt1: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices1).build::<()>()?; @@ -1484,7 +1521,7 @@ impl Simplex { /// let uuid1 = simplex1.uuid(); /// let uuid2 = simplex2.uuid(); /// - /// let simplex_map = Simplex::into_hashmap([simplex1, simplex2]); + /// let simplex_map = Simplex::try_into_hashmap([simplex1, simplex2])?; /// /// // Access simplices by their UUIDs /// assert_eq!(simplex_map.get(&uuid1).map(Simplex::uuid), Some(uuid1)); @@ -1498,22 +1535,40 @@ impl Simplex { /// use delaunay::prelude::tds::Simplex; /// /// // Empty collection produces empty FastHashMap - /// let empty_map = Simplex::::into_hashmap([]); + /// let empty_map = Simplex::<(), 3>::try_into_hashmap([])?; /// assert!(empty_map.is_empty()); + /// # Ok::<(), delaunay::prelude::tds::TdsConstructionError>(()) /// ``` - #[must_use] - pub fn into_hashmap(simplices: I) -> FastHashMap + /// + /// # Errors + /// + /// Returns [`TdsConstructionError::DuplicateUuid`] if the input contains + /// duplicate simplex UUIDs. + pub fn try_into_hashmap( + simplices: I, + ) -> Result, TdsConstructionError> where I: IntoIterator, { - simplices.into_iter().map(|c| (c.uuid, c)).collect() + let iter = simplices.into_iter(); + let mut map = fast_hash_map_with_capacity(iter.size_hint().0); + for simplex in iter { + let uuid = simplex.uuid(); + if map.insert(uuid, simplex).is_some() { + return Err(TdsConstructionError::DuplicateUuid { + entity: EntityKind::Simplex, + uuid, + }); + } + } + Ok(map) } /// The function `is_valid` checks if a [Simplex] is valid. /// /// # Type Parameters /// - /// This method relies on capabilities implied by `T: CoordinateScalar` + /// This method relies on f64-backed vertex coordinate comparisons /// (finite, comparable, and hashable coordinates suitable for geometric checks). /// /// # Returns @@ -1558,10 +1613,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 1.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1616,7 +1671,7 @@ impl Simplex { } // Advanced implementation block for Simplex methods -impl Simplex { +impl Simplex { /// Returns all facets (faces) of the simplex. /// /// A facet is a (D-1)-dimensional face of a D-dimensional simplex, obtained by removing @@ -1648,7 +1703,7 @@ impl Simplex { /// /// # Returns /// - /// A `Result>, FacetError>` containing all facets of the simplex. + /// A `Result>, FacetError>` containing all facets of the simplex. /// The returned vector has exactly D+1 facets, where each facet contains D vertices /// (one fewer than the original simplex's D+1 vertices). /// @@ -1707,10 +1762,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1728,10 +1783,10 @@ impl Simplex { /// # Ok(()) /// # } /// ``` - pub fn facet_views_from_tds( - tds: &Tds, + pub fn facet_views_from_tds( + tds: &Tds, simplex_key: SimplexKey, - ) -> Result>, FacetError> { + ) -> Result>, FacetError> { // Get the simplex from the TDS using the key let simplex = tds .simplex(simplex_key) @@ -1748,7 +1803,7 @@ impl Simplex { let mut facet_views = Vec::with_capacity(vertex_count); for idx in 0..vertex_count { let facet_index = usize_to_u8(idx, vertex_count)?; - facet_views.push(FacetView::new(tds, simplex_key, facet_index)?); + facet_views.push(FacetView::try_new(tds, simplex_key, facet_index)?); } Ok(facet_views) } @@ -1790,9 +1845,9 @@ impl Simplex { /// # fn main() -> Result<(), ExampleError> { /// // Example 1: Comparing simplices from different TDS instances with same coordinates /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt1: DelaunayTriangulation<_, _, _, 2> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1831,14 +1886,14 @@ impl Simplex { /// # fn main() -> Result<(), ExampleError> { /// // Example 2: Comparing simplices with different coordinates returns false /// let vertices1 = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let vertices2 = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([2.0, 0.0]), // Different coordinate - /// vertex!([0.0, 2.0]), // Different coordinate + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0]).expect("finite vertex coordinates"), // Different coordinate + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 2.0]).expect("finite vertex coordinates"), // Different coordinate /// ]; /// let dt1: DelaunayTriangulation<_, _, _, 2> = /// DelaunayTriangulationBuilder::new(&vertices1).build::<()>()?; @@ -1859,15 +1914,12 @@ impl Simplex { /// # Ok(()) /// # } /// ``` - pub fn eq_by_vertices( + pub fn eq_by_vertices( &self, - self_tds: &Tds, + self_tds: &Tds, other: &Self, - other_tds: &Tds, - ) -> bool - where - T: CoordinateScalar, - { + other_tds: &Tds, + ) -> bool { // Get vertices for both simplices let self_vertices: Option> = self .vertices() @@ -1941,10 +1993,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1984,10 +2036,10 @@ impl Simplex { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -2004,13 +2056,11 @@ impl Simplex { /// # Ok(()) /// # } /// ``` - pub fn facet_view_iter( - tds: &Tds, + pub fn facet_view_iter( + tds: &Tds, simplex_key: SimplexKey, - ) -> Result< - impl ExactSizeIterator, FacetError>>, - FacetError, - > { + ) -> Result, FacetError>>, FacetError> + { // Get the simplex from the TDS using the key let simplex = tds .simplex(simplex_key) @@ -2027,7 +2077,7 @@ impl Simplex { // Return a simple range-based iterator that maps indices to FacetView creation Ok((0..vertex_count).map(move |idx| { let facet_index = usize_to_u8(idx, vertex_count)?; - FacetView::new(tds, simplex_key, facet_index) + FacetView::try_new(tds, simplex_key, facet_index) })) } } @@ -2043,7 +2093,7 @@ impl Simplex { /// /// **Note**: This compares simplices within the same TDS context. For cross-TDS /// comparison by coordinates, use [`Simplex::eq_by_vertices`]. -impl PartialEq for Simplex { +impl PartialEq for Simplex { #[inline] fn eq(&self, other: &Self) -> bool { // Fast comparison using vertex keys (just u64 comparisons) @@ -2060,7 +2110,7 @@ impl PartialEq for Simplex { /// /// This provides a consistent ordering for simplices based on their vertex keys. /// Fast (O(D log D)) and doesn't require TDS access. -impl PartialOrd for Simplex { +impl PartialOrd for Simplex { #[inline] fn partial_cmp(&self, other: &Self) -> Option { // Fast comparison using vertex keys @@ -2081,7 +2131,7 @@ impl PartialOrd for Simplex { /// /// Maintains the Eq contract with `PartialEq`: simplices with the same vertex keys /// are considered equal. -impl Eq for Simplex {} +impl Eq for Simplex {} /// Custom Hash implementation for Simplex using sorted vertex keys. /// @@ -2092,7 +2142,7 @@ impl Eq for Simplex {} /// /// **Note**: UUID, neighbors, and data are excluded from hashing to match /// the `PartialEq` implementation which only compares vertex keys. -impl Hash for Simplex { +impl Hash for Simplex { fn hash(&self, state: &mut H) { // Hash sorted vertex keys for consistent ordering // Use SimplexVertexKeyBuffer for stack allocation (D+1 keys fit on stack for D ≤ 7) @@ -2115,7 +2165,6 @@ mod tests { use super::*; use crate::builder::DelaunayTriangulationBuilder; use crate::core::facet::FacetError; - use crate::core::vertex::vertex; use crate::geometry::kernel::AdaptiveKernel; use crate::geometry::matrix::MAX_STACK_MATRIX_DIM; use crate::geometry::point::Point; @@ -2130,15 +2179,15 @@ mod tests { hash::Hasher, }; // Type aliases for commonly used types to reduce repetition - type TestVertex3D = Vertex; - type TestVertex2D = Vertex; + type TestVertex3D = Vertex<(), 3>; + type TestVertex2D = Vertex<(), 2>; struct NonDataType(String); fn simplex_with_non_data_type_metadata( vertex_ids: [u64; D], data: NonDataType, - ) -> Simplex { + ) -> Simplex { Simplex { vertices: vertex_ids .into_iter() @@ -2148,7 +2197,6 @@ mod tests { neighbors: None, data: Some(data), periodic_vertex_offsets: None, - _phantom: PhantomData, } } @@ -2170,7 +2218,7 @@ mod tests { /// /// ```ignore /// test_simplex_dimensions! { - /// simplex_2d => 2 => vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])], + /// simplex_2d => 2 => vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")], /// } /// ``` macro_rules! test_simplex_dimensions { @@ -2214,9 +2262,9 @@ mod tests { let serialized = serde_json::to_string(&simplex).unwrap(); assert!(serialized.contains("\"data\":")); - let deserialized: Simplex = serde_json::from_str(&serialized).unwrap(); - assert_eq!(deserialized.data, Some(99)); - assert_eq!(deserialized.uuid(), simplex.uuid()); + let err = serde_json::from_str::>(&serialized) + .expect_err("standalone Simplex deserialization should require TDS UUID mapping"); + assert!(err.to_string().contains("standalone Simplex deserialization")); // Test serialization with None data - use simple constructor let vertices = $vertices; @@ -2225,8 +2273,9 @@ mod tests { let serialized = serde_json::to_string(&simplex).unwrap(); assert!(!serialized.contains("\"data\":")); - let deserialized: Simplex, $dim> = serde_json::from_str(&serialized).unwrap(); - assert_eq!(deserialized.data, None); + let err = serde_json::from_str::, $dim>>(&serialized) + .expect_err("standalone Simplex deserialization should require TDS UUID mapping"); + assert!(err.to_string().contains("standalone Simplex deserialization")); } #[test] @@ -2276,30 +2325,30 @@ mod tests { // Generate tests for dimensions 2D through 5D test_simplex_dimensions! { simplex_2d => 2 => vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ], simplex_3d => 3 => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ], simplex_4d => 4 => vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ], simplex_5d => 5 => vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ], } @@ -2308,8 +2357,8 @@ mod tests { // ============================================================================= /// Simplified helper function to test basic simplex properties - fn assert_simplex_properties( - simplex: &Simplex, + fn assert_simplex_properties( + simplex: &Simplex, expected_vertices: usize, expected_dim: usize, ) { @@ -2321,18 +2370,18 @@ mod tests { // Helper functions for creating common test data fn create_test_vertices_3d() -> Vec { vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ] } fn create_test_vertices_2d() -> Vec { vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ] } @@ -2343,10 +2392,10 @@ mod tests { fn simplex_macro_without_data() { // Test the simplex! macro without data (explicit type annotation required) let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), // Need 4 vertices for 3D simplex + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // Need 4 vertices for 3D simplex ]; // Create DT to get a simplex with TDS context. @@ -2385,9 +2434,6 @@ mod tests { "Input vertex {original_coords:?} not found in simplex" ); } - - // Human readable output for cargo test -- --nocapture - println!("Simplex without data: {simplex:?}"); } // ============================================================================= @@ -2396,14 +2442,12 @@ mod tests { #[test] fn test_eq_by_vertices_same_coordinates_different_tds() { - println!("Testing eq_by_vertices with same coordinates across different TDS"); - // Create two separate TDS instances with identical vertex coordinates let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt1 = DelaunayTriangulation::new(&vertices).unwrap(); let dt2 = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2414,17 +2458,14 @@ mod tests { // Despite different DT instances (and thus different vertex keys), // simplices should be equal by coordinates assert!(simplex1.eq_by_vertices(dt1.tds(), simplex2, dt2.tds())); - println!(" ✓ Simplices from different DT with same coordinates are equal"); } #[test] fn test_eq_by_vertices_2d() { - println!("Testing eq_by_vertices in 2D"); - let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt1 = DelaunayTriangulation::new(&vertices).unwrap(); let dt2 = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2433,17 +2474,16 @@ mod tests { let simplex2 = dt2.simplices().next().unwrap().1; assert!(simplex1.eq_by_vertices(dt1.tds(), simplex2, dt2.tds())); - println!(" ✓ 2D simplices with same coordinates are equal"); } #[test] fn simplex_macro_with_data() { // Test the simplex! macro with data by creating TDS, cloning simplex, and modifying let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; // Build DT with integer simplex data type @@ -2486,9 +2526,6 @@ mod tests { "Input vertex {original_coords:?} not found in simplex" ); } - - // Human readable output for cargo test -- --nocapture - println!("Simplex with data: {simplex:?}"); } #[test] @@ -2496,10 +2533,10 @@ mod tests { // Test simplices with vertex data through TDS. // Don't use simplex! macro since we need TDS context for vertex data access let vertices = vec![ - vertex!([0.0, 0.0, 0.0], 1), - vertex!([1.0, 0.0, 0.0], 2), - vertex!([0.0, 1.0, 0.0], 3), - vertex!([0.0, 0.0, 1.0], 4), // Need 4 vertices for 3D simplex + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 2).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 3).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 4).unwrap(), // Need 4 vertices for 3D simplex ]; // Create DT with vertex data - simple constructor works since simplex data is () @@ -2535,10 +2572,10 @@ mod tests { fn simplex_partial_eq() { // Test PartialEq using simplices from TDS. let vertices = vec![ - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex1) = dt.simplices().next().unwrap(); @@ -2558,11 +2595,11 @@ mod tests { fn simplex_partial_ord() { // Test PartialOrd using TDS with multiple simplices. let all_vertices = vec![ - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&all_vertices).unwrap(); let simplices: Vec<_> = dt.simplices().map(|(_, simplex)| simplex).collect(); @@ -2584,10 +2621,10 @@ mod tests { #[test] fn simplex_hash() { let vertices = vec![ - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex1) = dt.simplices().next().unwrap(); @@ -2609,10 +2646,10 @@ mod tests { #[test] fn simplex_clone() { let vertices = vec![ - vertex!([0.0, 0.0, 1.0], 1), - vertex!([0.0, 1.0, 0.0], 1), - vertex!([1.0, 0.0, 0.0], 1), - vertex!([1.0, 1.0, 1.0], 2), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 1.0, 1.0], 2).unwrap(), ]; let dt: DelaunayTriangulation, i32, i32, 3> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap(); @@ -2627,10 +2664,10 @@ mod tests { #[test] fn simplex_ordering_edge_cases() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex1) = dt.simplices().next().unwrap(); @@ -2669,10 +2706,10 @@ mod tests { fn simplex_mirror_facet_index_shared_facet_2d() { // Four points in convex position should yield two triangles that share an edge. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.1]), // break cocircular symmetry + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.1]).unwrap(), // break cocircular symmetry ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2744,11 +2781,11 @@ mod tests { fn simplex_mirror_facet_index_returns_none_when_simplices_do_not_share_facet_2d() { // Add a point strictly inside the convex hull to yield multiple triangles. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2802,10 +2839,14 @@ mod tests { #[test] fn simplex_contains_vertex() { - let vertex1: Vertex = vertex!([0.0, 0.0, 1.0], 1); - let vertex2 = vertex!([0.0, 1.0, 0.0], 1); - let vertex3 = vertex!([1.0, 0.0, 0.0], 1); - let vertex4 = vertex!([1.0, 1.0, 1.0], 2); + let vertex1: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 1).unwrap(); + let vertex2 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(); + let vertex3 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(); + let vertex4 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 1.0, 1.0], 2).unwrap(); // Create DT to get VertexKeys - use generic constructor for vertex data let vertices = vec![vertex1, vertex2, vertex3, vertex4]; @@ -2822,19 +2863,16 @@ mod tests { assert!(simplex.contains_vertex(vertex_keys[1])); assert!(simplex.contains_vertex(vertex_keys[2])); assert!(simplex.contains_vertex(vertex_keys[3])); - - // Human readable output for cargo test -- --nocapture - println!("Simplex: {simplex:?}"); } #[test] fn simplex_has_vertex_in_common() { // Test has_vertex_in_common (replacement for deprecated contains_vertex_of) let vertices1 = vec![ - vertex!([0.0, 0.0, 1.0], 1), - vertex!([0.0, 1.0, 0.0], 1), - vertex!([1.0, 0.0, 0.0], 1), - vertex!([1.0, 1.0, 1.0], 2), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 1.0, 1.0], 2).unwrap(), ]; let tds1: DelaunayTriangulation, i32, i32, 3> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices1).unwrap(); @@ -2843,10 +2881,10 @@ mod tests { simplex.data = Some(42); let vertices2 = vec![ - vertex!([0.0, 0.0, 1.0], 1), - vertex!([0.0, 1.0, 0.0], 1), - vertex!([1.0, 0.0, 0.0], 1), - vertex!([0.0, 0.0, 0.0], 0), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 0).unwrap(), ]; let tds2: DelaunayTriangulation, i32, i32, 3> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices2).unwrap(); @@ -2855,13 +2893,8 @@ mod tests { simplex2.data = Some(43); assert!(simplex.has_vertex_in_common(&simplex2)); - - // Human readable output for cargo test -- --nocapture - println!("Simplex: {simplex:?}"); } - // Removed: simplex_facets_contains - redundant with simplex_facet_views_comprehensive - // ============================================================================= // VERTEX_UUIDS METHOD TESTS // ============================================================================= @@ -2870,10 +2903,14 @@ mod tests { #[test] fn test_vertex_uuids_success() { // Test the vertex_uuids method returns correct vertex UUIDs vector - let vertex1 = vertex!([0.0, 0.0, 0.0], 10); - let vertex2 = vertex!([1.0, 0.0, 0.0], 20); - let vertex3 = vertex!([0.0, 1.0, 0.0], 30); - let vertex4 = vertex!([0.0, 0.0, 1.0], 40); + let vertex1 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 10).unwrap(); + let vertex2 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 20).unwrap(); + let vertex3 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 30).unwrap(); + let vertex4 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 40).unwrap(); let vertices = vec![vertex1, vertex2, vertex3, vertex4]; let dt: DelaunayTriangulation, i32, (), 3> = @@ -2901,8 +2938,6 @@ mod tests { for uuid in simplex.vertex_uuid_iter(dt.tds()) { assert_ne!(uuid.unwrap(), Uuid::nil()); } - - println!("✓ vertex_uuids method returns correct vertex UUIDs"); } #[test] @@ -2910,22 +2945,24 @@ mod tests { // Test that TDS creation fails gracefully with insufficient vertices for dimension // Tested through TDS, which is the user-facing API. - let vertices = vec![vertex!([0.0, 0.0, 0.0])]; + let vertices = + vec![crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap()]; let result = DelaunayTriangulation::new(&vertices); assert!(result.is_err()); let error = result.unwrap_err(); assert!(error.to_string().contains("Insufficient vertices")); - - println!("✓ DT construction properly validates vertex count"); } #[test] fn test_vertex_uuids_2d_simplex() { // Test vertex_uuids with a 2D simplex (triangle) - let vertex1 = vertex!([0.0, 0.0], 1); - let vertex2 = vertex!([1.0, 0.0], 2); - let vertex3 = vertex!([0.5, 1.0], 3); + let vertex1 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 1).unwrap(); + let vertex2 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 2).unwrap(); + let vertex3 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.5, 1.0], 3).unwrap(); let vertices = vec![vertex1, vertex2, vertex3]; let dt: DelaunayTriangulation, i32, (), 2> = @@ -2952,19 +2989,22 @@ mod tests { for uuid in simplex.vertex_uuid_iter(dt.tds()) { assert_ne!(uuid.unwrap(), Uuid::nil()); } - - println!("✓ vertex_uuids works correctly for 2D simplices"); } #[test] fn test_vertex_uuids_4d_simplex() { // Test vertex_uuids with a 4D simplex (4-simplex) using integer data let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0], 1), - vertex!([1.0, 0.0, 0.0, 0.0], 2), - vertex!([0.0, 1.0, 0.0, 0.0], 3), - vertex!([0.0, 0.0, 1.0, 0.0], 4), - vertex!([0.0, 0.0, 0.0, 1.0], 5), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 1) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0, 0.0], 2) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0, 0.0], 3) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0, 0.0], 4) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 1.0], 5) + .unwrap(), ]; let dt: DelaunayTriangulation, i32, (), 4> = @@ -3005,8 +3045,6 @@ mod tests { for uuid in simplex.vertex_uuid_iter(dt.tds()) { assert_ne!(uuid.unwrap(), Uuid::nil()); } - - println!("✓ vertex_uuids works correctly for 4D simplices"); } // ============================================================================= @@ -3021,7 +3059,10 @@ mod tests { // Keep 1D test separate as it's less common #[test] fn simplex_1d() { - let vertices = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex) = dt.simplices().next().unwrap(); assert_simplex_properties(simplex, 2, 1); @@ -3031,7 +3072,8 @@ mod tests { fn simplex_single_vertex() { // Test that creating a 3D triangulation with insufficient vertices fails validation // Tested through TDS, which is the user-facing API. - let vertices = vec![vertex!([0.0, 0.0, 0.0])]; + let vertices = + vec![crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap()]; let result = DelaunayTriangulation::new(&vertices); assert!(result.is_err()); @@ -3044,10 +3086,10 @@ mod tests { #[test] fn simplex_neighbors_none_by_default() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex) = dt.simplices().next().unwrap(); @@ -3059,10 +3101,10 @@ mod tests { #[test] fn simplex_data_none_by_default() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex) = dt.simplices().next().unwrap(); @@ -3073,10 +3115,10 @@ mod tests { #[test] fn simplex_data_can_be_set() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation, (), i32, 3> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap(); @@ -3090,7 +3132,7 @@ mod tests { #[test] fn simplex_into_hashmap_empty() { - let hashmap = Simplex::::into_hashmap([]); + let hashmap = Simplex::<(), 3>::try_into_hashmap([]).unwrap(); assert!(hashmap.is_empty()); } @@ -3098,11 +3140,11 @@ mod tests { #[test] fn simplex_into_hashmap_multiple() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3118,26 +3160,50 @@ mod tests { let uuid1 = simplex1.uuid(); let uuid2 = simplex2.uuid(); - let hashmap = Simplex::into_hashmap( + let hashmap = Simplex::try_into_hashmap( std::iter::once(simplex1) .chain(std::iter::once(simplex2)) .chain(simplices_iter), - ); + ) + .unwrap(); assert!(hashmap.len() >= 2); assert!(hashmap.contains_key(&uuid1)); assert!(hashmap.contains_key(&uuid2)); } + #[test] + fn simplex_try_into_hashmap_rejects_duplicate_uuid() { + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ]; + let dt = DelaunayTriangulation::new(&vertices).unwrap(); + let (_, simplex_ref) = dt.simplices().next().unwrap(); + let simplex = simplex_ref.clone(); + let uuid = simplex.uuid(); + let duplicate = + Simplex::try_new_with_uuid(simplex.vertices().to_vec(), uuid, None).unwrap(); + + assert_matches!( + Simplex::try_into_hashmap([simplex, duplicate]), + Err(TdsConstructionError::DuplicateUuid { + entity: EntityKind::Simplex, + uuid: duplicate_uuid, + }) if duplicate_uuid == uuid + ); + } + #[test] fn simplex_debug_format() { // Use a simple non-degenerate 3D tetrahedron so `DelaunayTriangulation::new` can construct // a valid simplex for debug-format testing. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation, (), i32, 3> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap(); @@ -3167,10 +3233,10 @@ mod tests { // Use a non-degenerate 3D tetrahedron so `DelaunayTriangulation::new` can construct a // valid initial simplex. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3180,7 +3246,7 @@ mod tests { assert!(serialized.contains("simplices")); // Deserialize back to DT via try_from_tds (AdaptiveKernel has no auto-Deserialize). - let tds: Tds = serde_json::from_str(&serialized).unwrap(); + let tds: Tds<(), (), 3> = serde_json::from_str(&serialized).unwrap(); let deserialized = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new()) .expect("serialized Delaunay TDS should validate"); @@ -3195,8 +3261,6 @@ mod tests { assert_eq!(simplex.dim(), 3); assert_eq!(simplex.number_of_vertices(), 4); } - - println!("TDS serialization/deserialization test passed"); } #[test] @@ -3205,8 +3269,7 @@ mod tests { // Test missing required field (uuid) let invalid_json_missing_uuid = r#"{"data": null}"#; - let result: Result, _> = - serde_json::from_str(invalid_json_missing_uuid); + let result: Result, _> = serde_json::from_str(invalid_json_missing_uuid); assert!(result.is_err(), "Missing UUID should cause error"); let error = result.unwrap_err().to_string(); assert!( @@ -3216,54 +3279,74 @@ mod tests { // Test invalid UUID format let invalid_json_bad_uuid = r#"{"uuid": "not-a-valid-uuid"}"#; - let result: Result, _> = - serde_json::from_str(invalid_json_bad_uuid); + let result: Result, _> = serde_json::from_str(invalid_json_bad_uuid); assert!(result.is_err(), "Invalid UUID format should cause error"); // Test completely invalid JSON syntax let invalid_json_syntax = r"{this is not valid JSON}"; - let result: Result, _> = serde_json::from_str(invalid_json_syntax); + let result: Result, _> = serde_json::from_str(invalid_json_syntax); assert!(result.is_err(), "Invalid JSON syntax should cause error"); // Test empty JSON object (missing required uuid) let empty_json = r"{}"; - let result: Result, _> = serde_json::from_str(empty_json); + let result: Result, _> = serde_json::from_str(empty_json); assert!(result.is_err(), "Empty JSON should fail (missing uuid)"); - // Test deserialization with unknown fields (should succeed - ignored) + // Test deserialization with unknown fields still rejects standalone simplex records. let json_unknown_field = r#"{ "uuid": "550e8400-e29b-41d4-a716-446655440000", "unknown_field": "value", "another_unknown": 123 }"#; - let result: Result, _> = serde_json::from_str(json_unknown_field); - assert!(result.is_ok(), "Unknown fields should be ignored"); + let result: Result, _> = serde_json::from_str(json_unknown_field); + let err = result.expect_err("standalone simplex records should not deserialize"); + assert!( + err.to_string() + .contains("standalone Simplex deserialization") + ); + + let json_with_slotmap_vertices = r#"{ + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "vertices": [{"idx": 1, "version": 1}] + }"#; + let result: Result, _> = serde_json::from_str(json_with_slotmap_vertices); + let err = result.expect_err("slotmap vertex keys must be rejected"); + assert!( + err.to_string().contains("storage-local simplex state"), + "unexpected error for serialized slotmap vertex keys: {err}" + ); + + let json_with_neighbors = r#"{ + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "neighbors": [null, null, null, null] + }"#; + let result: Result, _> = serde_json::from_str(json_with_neighbors); + let err = result.expect_err("slotmap neighbor keys must be rejected"); + assert!( + err.to_string().contains("storage-local simplex state"), + "unexpected error for serialized slotmap neighbor keys: {err}" + ); } #[test] fn simplex_serialization_data_field_handling() { // Comprehensive test for data field serialization behavior - // Test 1: Minimal valid JSON (only uuid required) + // Test 1: Minimal simplex record is valid only as a TDS reconstruction DTO. let minimal_valid_json = r#"{"uuid": "550e8400-e29b-41d4-a716-446655440000"}"#; - let result: Result, _> = serde_json::from_str(minimal_valid_json); + let result: Result, _> = serde_json::from_str(minimal_valid_json); + let err = result.expect_err("minimal standalone simplex record should fail"); assert!( - result.is_ok(), - "Minimal valid JSON with just UUID should succeed" + err.to_string() + .contains("standalone Simplex deserialization") ); - let simplex = result.unwrap(); - assert_eq!( - simplex.uuid().to_string(), - "550e8400-e29b-41d4-a716-446655440000" - ); - assert!(simplex.data.is_none()); // Test 2: Serialization with Some(data) includes field let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation, (), i32, 3> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap(); @@ -3276,8 +3359,12 @@ mod tests { "Some(data) should include data field" ); assert!(serialized.contains("42")); - let deserialized: Simplex = serde_json::from_str(&serialized).unwrap(); - assert_eq!(deserialized.data, Some(42)); + let err = serde_json::from_str::>(&serialized) + .expect_err("standalone Simplex deserialization should require TDS UUID mapping"); + assert!( + err.to_string() + .contains("standalone Simplex deserialization") + ); // Test 3: Serialization with None data omits field (optimization) let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3287,15 +3374,21 @@ mod tests { !serialized_none.contains("\"data\":"), "None should omit data field" ); - let deserialized_none: Simplex, 3> = - serde_json::from_str(&serialized_none).unwrap(); - assert_eq!(deserialized_none.data, None); + let err = serde_json::from_str::, 3>>(&serialized_none) + .expect_err("standalone Simplex deserialization should require TDS UUID mapping"); + assert!( + err.to_string() + .contains("standalone Simplex deserialization") + ); - // Test 4: Backward compatibility - explicit "data": null works + // Test 4: Explicit "data": null still rejects outside TDS reconstruction. let json_with_null = r#"{"uuid":"550e8400-e29b-41d4-a716-446655440000","data":null}"#; - let simplex_explicit_null: Simplex, 3> = - serde_json::from_str(json_with_null).unwrap(); - assert_eq!(simplex_explicit_null.data, None); + let err = serde_json::from_str::, 3>>(json_with_null) + .expect_err("standalone simplex record should fail"); + assert!( + err.to_string() + .contains("standalone Simplex deserialization") + ); } // ============================================================================= @@ -3309,10 +3402,10 @@ mod tests { // Negative coordinates: fully in negative octant let negative_vertices = vec![ - vertex!([-1.0, -1.0, -1.0]), - vertex!([-2.0, -1.0, -1.0]), - vertex!([-1.0, -2.0, -1.0]), - vertex!([-1.0, -1.0, -2.0]), + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, -1.0, -1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([-2.0, -1.0, -1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, -2.0, -1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, -1.0, -2.0]).unwrap(), ]; let dt_neg = DelaunayTriangulation::new(&negative_vertices).unwrap(); let (_, simplex_neg) = dt_neg.tds().simplices().next().unwrap(); @@ -3321,10 +3414,10 @@ mod tests { // Large coordinates: large-magnitude coordinates let large_vertices = vec![ - vertex!([1e6, 1e6, 1e6]), - vertex!([2e6, 1e6, 1e6]), - vertex!([1e6, 2e6, 1e6]), - vertex!([1e6, 1e6, 2e6]), + crate::core::vertex::Vertex::<(), _>::try_new([1e6, 1e6, 1e6]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2e6, 1e6, 1e6]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e6, 2e6, 1e6]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e6, 1e6, 2e6]).unwrap(), ]; let dt_large = DelaunayTriangulation::new(&large_vertices).unwrap(); let (_, simplex_large) = dt_large.tds().simplices().next().unwrap(); @@ -3333,10 +3426,10 @@ mod tests { // Small coordinates: scaled-down tetrahedron without degeneracy let small_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1e-3, 0.0, 0.0]), - vertex!([0.0, 1e-3, 0.0]), - vertex!([0.0, 0.0, 1e-3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-3, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e-3, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e-3]).unwrap(), ]; let dt_small = DelaunayTriangulation::new(&small_vertices).unwrap(); let (_, simplex_small) = dt_small.tds().simplices().next().unwrap(); @@ -3346,9 +3439,9 @@ mod tests { #[test] fn simplex_circumradius_2d() { - let vertex1 = vertex!([0.0, 0.0]); - let vertex2 = vertex!([1.0, 0.0]); - let vertex3 = vertex!([0.0, 1.0]); + let vertex1 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(); + let vertex2 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(); + let vertex3 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(); let vertices = vec![vertex1, vertex2, vertex3]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3356,7 +3449,7 @@ mod tests { let simplex = &dt.tds().simplex(simplex_key).unwrap(); // Resolve VertexKeys to actual vertices - let vertex_points: Vec> = simplex + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vk| *dt.tds().vertex(*vk).unwrap().point()) @@ -3370,11 +3463,12 @@ mod tests { #[test] fn simplex_contains_vertex_false() { - let vertex1 = vertex!([0.0, 0.0, 0.0]); - let vertex2 = vertex!([1.0, 0.0, 0.0]); - let vertex3 = vertex!([0.0, 1.0, 0.0]); - let vertex4 = vertex!([0.0, 0.0, 1.0]); // 4th vertex to complete 3D simplex - let vertex_outside: Vertex = vertex!([2.0, 2.0, 2.0]); + let vertex1 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); + let vertex2 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(); + let vertex3 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(); + let vertex4 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(); // 4th vertex to complete 3D simplex + let vertex_outside: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).unwrap(); let vertices = vec![vertex1, vertex2, vertex3, vertex4]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3390,10 +3484,14 @@ mod tests { fn simplex_circumsphere_contains_vertex_determinant() { // Test the matrix determinant method for circumsphere containment // Use a simple, well-known case: unit tetrahedron - let vertex1 = vertex!([0.0, 0.0, 0.0], 1); - let vertex2 = vertex!([1.0, 0.0, 0.0], 1); - let vertex3 = vertex!([0.0, 1.0, 0.0], 1); - let vertex4 = vertex!([0.0, 0.0, 1.0], 2); + let vertex1 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1).unwrap(); + let vertex2 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(); + let vertex3 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(); + let vertex4 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 2).unwrap(); let vertices = vec![vertex1, vertex2, vertex3, vertex4]; let dt: DelaunayTriangulation, i32, (), 3> = @@ -3402,9 +3500,10 @@ mod tests { let simplex = &dt.tds().simplex(simplex_key).unwrap(); // Test vertex clearly outside circumsphere - let vertex_far_outside: Vertex = vertex!([10.0, 10.0, 10.0], 4); + let vertex_far_outside: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([10.0, 10.0, 10.0], 4).unwrap(); // Just check that the method runs without error for now - let vertex_points: Vec> = simplex + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vk| *dt.tds().vertex(*vk).unwrap().point()) @@ -3413,8 +3512,9 @@ mod tests { assert!(result.is_ok()); // Test with origin (should be inside or on boundary) - let origin: Vertex = vertex!([0.0, 0.0, 0.0], 3); - let vertex_points: Vec> = simplex + let origin: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 3).unwrap(); + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vk| *dt.tds().vertex(*vk).unwrap().point()) @@ -3426,9 +3526,9 @@ mod tests { #[test] fn simplex_circumsphere_contains_vertex_2d() { // Test 2D case for circumsphere containment using determinant method - let vertex1 = vertex!([0.0, 0.0]); - let vertex2 = vertex!([1.0, 0.0]); - let vertex3 = vertex!([0.0, 1.0]); + let vertex1 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(); + let vertex2 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(); + let vertex3 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(); let vertices = vec![vertex1, vertex2, vertex3]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3436,8 +3536,9 @@ mod tests { let simplex = &dt.tds().simplex(simplex_key).unwrap(); // Test vertex far outside circumcircle - let vertex_far_outside: Vertex = vertex!([10.0, 10.0]); - let vertex_points: Vec> = simplex + let vertex_far_outside: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0]).unwrap(); + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vk| *dt.tds().vertex(*vk).unwrap().point()) @@ -3446,8 +3547,9 @@ mod tests { assert!(result.is_ok()); // Test with center of triangle (should be inside) - let center: Vertex = vertex!([0.33, 0.33]); - let vertex_points: Vec> = simplex + let center: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([0.33, 0.33]).unwrap(); + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vk| *dt.tds().vertex(*vk).unwrap().point()) @@ -3459,17 +3561,17 @@ mod tests { #[test] fn simplex_circumradius_with_center() { // Test the circumradius_with_center method - let vertex1 = vertex!([0.0, 0.0, 0.0]); - let vertex2 = vertex!([1.0, 0.0, 0.0]); - let vertex3 = vertex!([0.0, 1.0, 0.0]); - let vertex4 = vertex!([0.0, 0.0, 1.0]); + let vertex1 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); + let vertex2 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(); + let vertex3 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(); + let vertex4 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(); let vertices = vec![vertex1, vertex2, vertex3, vertex4]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; let simplex = &dt.tds().simplex(simplex_key).unwrap(); - let vertex_points: Vec> = simplex + let vertex_points: Vec> = simplex .vertices() .iter() .map(|vk| *dt.tds().vertex(*vk).unwrap().point()) @@ -3487,10 +3589,10 @@ mod tests { #[test] fn simplex_facet_views_comprehensive() { // Test comprehensive facet view functionality - let vertex1 = vertex!([0.0, 0.0, 0.0]); - let vertex2 = vertex!([1.0, 0.0, 0.0]); - let vertex3 = vertex!([0.0, 1.0, 0.0]); - let vertex4 = vertex!([0.0, 0.0, 1.0]); + let vertex1 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); + let vertex2 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(); + let vertex3 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(); + let vertex4 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(); let vertices = vec![vertex1, vertex2, vertex3, vertex4]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3534,10 +3636,10 @@ mod tests { #[test] fn test_facet_vertex_uniqueness() { // Test that facet vertices are unique and don't include the opposite vertex - let vertex1 = vertex!([0.0, 0.0, 1.0]); - let vertex2 = vertex!([0.0, 1.0, 0.0]); - let vertex3 = vertex!([1.0, 0.0, 0.0]); - let vertex4 = vertex!([1.0, 1.0, 1.0]); + let vertex1 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(); + let vertex2 = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(); + let vertex3 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(); + let vertex4 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(); let vertices = vec![vertex1, vertex2, vertex3, vertex4]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3581,12 +3683,18 @@ mod tests { #[test] fn simplex_high_dimensional() { // Test with higher dimensions (5D) - let vertex1 = vertex!([0.0, 0.0, 0.0, 0.0, 0.0]); - let vertex2 = vertex!([1.0, 0.0, 0.0, 0.0, 0.0]); - let vertex3 = vertex!([0.0, 1.0, 0.0, 0.0, 0.0]); - let vertex4 = vertex!([0.0, 0.0, 1.0, 0.0, 0.0]); - let vertex5 = vertex!([0.0, 0.0, 0.0, 1.0, 0.0]); - let vertex6 = vertex!([0.0, 0.0, 0.0, 0.0, 1.0]); + let vertex1 = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(); + let vertex2 = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(); + let vertex3 = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(); + let vertex4 = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(); + let vertex5 = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(); + let vertex6 = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(); let vertices = vec![vertex1, vertex2, vertex3, vertex4, vertex5, vertex6]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3606,10 +3714,14 @@ mod tests { #[test] fn simplex_vertex_data_consistency() { // Test simplices with vertices that have different data types - let vertex1 = vertex!([0.0, 0.0, 0.0], 1); - let vertex2 = vertex!([1.0, 0.0, 0.0], 2); - let vertex3 = vertex!([0.0, 1.0, 0.0], 3); - let vertex4 = vertex!([0.0, 0.0, 1.0], 4); // Need 4 vertices for 3D simplex + let vertex1 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1).unwrap(); + let vertex2 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 2).unwrap(); + let vertex3 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 3).unwrap(); + let vertex4 = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 4).unwrap(); // Need 4 vertices for 3D simplex let vertices = vec![vertex1, vertex2, vertex3, vertex4]; let mut dt: DelaunayTriangulation, i32, u32, 3> = @@ -3668,10 +3780,10 @@ mod tests { // Valid 3D simplex with correct vertex count (D+1 = 4) let vertices_3d = vec![ - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices_3d).unwrap(); let (_, simplex_3d) = dt.simplices().next().unwrap(); @@ -3682,9 +3794,9 @@ mod tests { // Valid 2D simplex with correct vertex count (D+1 = 3) let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices_2d).unwrap(); let (_, simplex_ref) = dt.simplices().next().unwrap(); @@ -3717,10 +3829,10 @@ mod tests { // Invalid UUID (nil) let vertices = vec![ - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex_ref) = dt.simplices().next().unwrap(); @@ -3735,7 +3847,10 @@ mod tests { ); // Insufficient vertices (TDS construction fails) - let insufficient_vertices = vec![vertex!([0.0, 0.0, 1.0]), vertex!([0.0, 1.0, 0.0])]; + let insufficient_vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ]; let result = DelaunayTriangulation::new(&insufficient_vertices); assert!( result.is_err(), @@ -3744,9 +3859,9 @@ mod tests { // Invalid neighbors length (too few) let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices_2d).unwrap(); let (_, simplex_ref) = dt.simplices().next().unwrap(); @@ -3786,17 +3901,17 @@ mod tests { #[test] fn simplex_new_rejects_insufficient_and_duplicate_vertices() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let vkeys: Vec<_> = dt.tds().vertices().map(|(k, _)| k).collect(); // Too few vertices for a 3D simplex (D+1 = 4) - let err = - Simplex::::new(vec![vkeys[0], vkeys[1], vkeys[2]], None).unwrap_err(); + let err = Simplex::<(), 3>::try_new_with_data(vec![vkeys[0], vkeys[1], vkeys[2]], None) + .unwrap_err(); assert_matches!( err, SimplexValidationError::InsufficientVertices { @@ -3808,7 +3923,7 @@ mod tests { // Duplicate vertex keys are rejected let err = - Simplex::::new(vec![vkeys[0], vkeys[1], vkeys[2], vkeys[0]], None) + Simplex::<(), 3>::try_new_with_data(vec![vkeys[0], vkeys[1], vkeys[2], vkeys[0]], None) .unwrap_err(); assert_matches!(err, SimplexValidationError::DuplicateVertices); } @@ -3816,10 +3931,10 @@ mod tests { #[test] fn simplex_is_valid_rejects_insufficient_and_duplicate_vertices() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex_ref) = dt.simplices().next().unwrap(); @@ -3844,9 +3959,9 @@ mod tests { #[test] fn simplex_ensure_neighbors_buffer_mut_initializes_and_reuses() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (simplex_key, simplex_ref) = dt.simplices().next().unwrap(); @@ -3868,9 +3983,9 @@ mod tests { #[test] fn simplex_neighbor_views_distinguish_unassigned_boundary_and_neighbor_slots() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (simplex_key, simplex_ref) = dt.simplices().next().unwrap(); @@ -3902,9 +4017,9 @@ mod tests { #[test] fn simplex_validation_rejects_unassigned_slot_inside_assigned_neighbors() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex_ref) = dt.simplices().next().unwrap(); @@ -3922,9 +4037,9 @@ mod tests { #[test] fn simplex_swap_vertex_slots_swaps_vertices_neighbors_and_offsets() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (simplex_key, simplex_ref) = dt.simplices().next().unwrap(); @@ -3958,9 +4073,9 @@ mod tests { #[should_panic(expected = "neighbors index out of bounds")] fn simplex_swap_vertex_slots_panics_when_neighbors_shorter_than_vertices() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let (_, simplex_ref) = dt.simplices().next().unwrap(); @@ -3975,10 +4090,10 @@ mod tests { #[test] fn simplex_facet_view_helpers_reject_excessive_vertex_count() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.tds().simplex_keys().next().unwrap(); @@ -4026,40 +4141,37 @@ mod tests { #[test] fn simplex_deserialize_rejects_missing_uuid_and_duplicate_fields_and_invalid_uuid() { // Expecting() should surface for non-map inputs. - let err = serde_json::from_str::>("null").unwrap_err(); + let err = serde_json::from_str::>("null").unwrap_err(); assert!(err.to_string().contains("a Simplex struct")); // Missing required field. - let err = serde_json::from_str::>("{\"data\":1}").unwrap_err(); + let err = serde_json::from_str::>("{\"data\":1}").unwrap_err(); assert!(err.to_string().contains("missing field `uuid`")); // Duplicate uuid. let uuid = uuid::Uuid::new_v4(); let json = format!("{{\"uuid\":\"{uuid}\",\"uuid\":\"{uuid}\"}}"); - let err = serde_json::from_str::>(&json).unwrap_err(); + let err = serde_json::from_str::>(&json).unwrap_err(); assert!(err.to_string().contains("duplicate field `uuid`")); // Duplicate data. let uuid = uuid::Uuid::new_v4(); let json = format!("{{\"uuid\":\"{uuid}\",\"data\":1,\"data\":2}}"); - let err = serde_json::from_str::>(&json).unwrap_err(); + let err = serde_json::from_str::>(&json).unwrap_err(); assert!(err.to_string().contains("duplicate field `data`")); // Invalid uuid (nil) should be rejected by validate_uuid. let json = "{\"uuid\":\"00000000-0000-0000-0000-000000000000\"}"; - let err = serde_json::from_str::>(json).unwrap_err(); + let err = serde_json::from_str::>(json).unwrap_err(); assert!(err.to_string().contains("invalid uuid")); - // Unknown fields are ignored. + // Unknown fields are ignored by the raw DTO parser, then rejected at the standalone boundary. let uuid = uuid::Uuid::new_v4(); - let json = format!("{{\"uuid\":\"{uuid}\",\"data\":5,\"neighbors\":[1,2,3]}}"); - let simplex = serde_json::from_str::>(&json).unwrap(); - assert_eq!(simplex.data, Some(5)); - assert!(simplex.neighbors.is_none()); - assert_eq!( - simplex.number_of_vertices(), - 0, - "vertices are not serialized" + let json = format!("{{\"uuid\":\"{uuid}\",\"data\":5,\"extra\":[1,2,3]}}"); + let err = serde_json::from_str::>(&json).unwrap_err(); + assert!( + err.to_string() + .contains("standalone Simplex deserialization") ); } @@ -4085,9 +4197,9 @@ mod tests { fn test_simplex_partial_eq_different_dimensions() { // Test equality for simplices of different dimensions let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt1 = DelaunayTriangulation::new(&vertices_2d).unwrap(); let (_, simplex_2d) = dt1.tds().simplices().next().unwrap(); @@ -4099,18 +4211,16 @@ mod tests { simplex_2d, simplex_2d_copy, "Identical 2D simplices should be equal" ); - - println!("✓ 2D simplices work correctly with PartialEq"); } #[test] fn test_facet_view_iter() { // Test the iterator-based facet_view_iter method let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -4172,18 +4282,14 @@ mod tests { vec_facets.len(), "Iterator and Vec methods should return same count" ); - - println!("✓ facet_view_iter zero-allocation iterator works correctly"); } - // Removed: test_facet_view_memory_efficiency_comparison - covered by test_facet_view_iter - #[test] fn test_simplex_data_accessor() { let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) .build::() @@ -4197,68 +4303,4 @@ mod tests { dt.set_simplex_data(key, Some(99)); assert_eq!(dt.tds().simplex(key).unwrap().data(), Some(&99)); } - - #[test] - fn test_clear_vertex_keys() { - // Test the clear_vertex_keys method used in deserialization - let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - ]; - let dt = DelaunayTriangulation::new(&vertices).unwrap(); - - // Get a simplex and clone it (since we need mutable access) - let (_simplex_key, original_simplex) = dt.simplices().next().unwrap(); - let mut test_simplex = original_simplex.clone(); - - // Verify the simplex has 4 vertices initially - assert_eq!( - test_simplex.number_of_vertices(), - 4, - "Simplex should start with 4 vertices" - ); - - // Clear the vertex keys - test_simplex.clear_vertex_keys(); - - // Verify the simplex now has 0 vertices - assert_eq!( - test_simplex.number_of_vertices(), - 0, - "Simplex should have 0 vertices after clearing" - ); - assert_eq!( - test_simplex.vertices().len(), - 0, - "Vertices slice should be empty after clearing" - ); - - // Test that we can rebuild vertex keys after clearing (simulating deserialization) - for &vkey in original_simplex.vertices() { - test_simplex.push_vertex_key(vkey); - } - - // Verify we've restored the correct number of vertices - assert_eq!( - test_simplex.number_of_vertices(), - 4, - "Simplex should have 4 vertices after rebuilding" - ); - - // Verify the vertex keys match the original - for (original_vkey, rebuilt_vkey) in original_simplex - .vertices() - .iter() - .zip(test_simplex.vertices().iter()) - { - assert_eq!( - original_vkey, rebuilt_vkey, - "Rebuilt vertex keys should match original keys" - ); - } - - println!("✓ clear_vertex_keys() correctly clears and allows rebuilding of vertex keys"); - } } diff --git a/src/core/tds.rs b/src/core/tds.rs index dd9c827b..fefa3acd 100644 --- a/src/core/tds.rs +++ b/src/core/tds.rs @@ -22,8 +22,8 @@ //! - **Neighbor Relationships**: Maintains adjacency information between simplices for efficient //! topological traversal //! - **Validation Support**: Structural invariant validation (Level 2) plus cumulative element -//! validation (Levels 1–2; requires `T: CoordinateScalar`) -//! - **Serialization Support**: Serde support for persistence (available when `T: CoordinateScalar`) +//! validation (Levels 1–2) +//! - **Serialization Support**: Serde support for persistence //! - **Optimized Storage**: Internal key-based storage with UUIDs for external identity //! //! # Geometric Structure @@ -138,10 +138,10 @@ //! # } //! # fn main() -> Result<(), ExampleError> { //! let vertices = [ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -167,9 +167,9 @@ //! See [`docs/validation.md`](https://github.com/acgetchell/delaunay/blob/main/docs/validation.md) //! for a comprehensive validation guide. //! -//! [`Simplex::is_valid()`]: crate::core::simplex::Simplex::is_valid -//! [`Vertex::is_valid()`]: crate::core::vertex::Vertex::is_valid -//! [`Triangulation::is_valid()`]: crate::core::triangulation::Triangulation::is_valid +//! [`Simplex::is_valid()`]: crate::prelude::tds::Simplex::is_valid +//! [`Vertex::is_valid()`]: crate::prelude::Vertex::is_valid +//! [`Triangulation::is_valid()`]: crate::prelude::triangulation::Triangulation::is_valid //! [`DelaunayTriangulation::is_valid()`]: crate::DelaunayTriangulation::is_valid //! [`DelaunayTriangulation::validation_report()`]: crate::DelaunayTriangulation::validation_report //! @@ -202,10 +202,10 @@ //! # fn main() -> Result<(), ExampleError> { //! // Create vertices for a tetrahedron //! let vertices = [ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! //! // Create Delaunay triangulation @@ -247,16 +247,16 @@ //! # fn main() -> Result<(), ExampleError> { //! // Start with initial vertices //! let initial_vertices = [ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! //! let mut dt = DelaunayTriangulationBuilder::new(&initial_vertices).build::<()>()?; //! //! // Add a new vertex -//! let new_vertex = vertex!([0.2, 0.2, 0.2]); +//! let new_vertex = delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).expect("finite vertex coordinates"); //! dt.insert(new_vertex)?; //! //! assert_eq!(dt.number_of_vertices(), 5); @@ -292,11 +292,11 @@ //! # fn main() -> Result<(), ExampleError> { //! // Create 4D triangulation with 5 vertices (needed for a 4-simplex) //! let vertices_4d = [ -//! vertex!([0.0, 0.0, 0.0, 0.0]), // Origin -//! vertex!([1.0, 0.0, 0.0, 0.0]), // Unit vector along first dimension -//! vertex!([0.0, 1.0, 0.0, 0.0]), // Unit vector along second dimension -//! vertex!([0.0, 0.0, 1.0, 0.0]), // Unit vector along third dimension -//! vertex!([0.0, 0.0, 0.0, 1.0]), // Unit vector along fourth dimension +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), // Origin +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), // Unit vector along first dimension +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), // Unit vector along second dimension +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), // Unit vector along third dimension +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), // Unit vector along fourth dimension //! ]; //! //! let dt_4d = DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; @@ -319,7 +319,7 @@ use super::{ facet::{FacetHandle, facet_key_from_vertices}, - simplex::{NeighborSlot, Simplex, SimplexValidationError}, + simplex::{NeighborSlot, SerializedSimplex, Simplex, SimplexValidationError}, traits::data_type::DataType, util::{ deduplication::coords_equal_exact, periodic_facet_key_from_lifted_vertices, usize_to_u8, @@ -334,7 +334,6 @@ use crate::core::collections::{ fast_hash_map_with_capacity, }; use crate::core::validation::TriangulationValidationError; -use crate::geometry::traits::coordinate::CoordinateScalar; use crate::validation::DelaunayTriangulationValidationError; use serde::{ Deserialize, Deserializer, Serialize, @@ -517,10 +516,10 @@ pub enum GeometricError { /// # fn main() -> Result<(), ExampleError> { /// // Build a simple 3D triangulation /// let vertices = [ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.number_of_vertices(), 4); @@ -1565,9 +1564,9 @@ new_key_type! { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some(key) = dt.tds().vertex_keys().next() else { @@ -1605,9 +1604,9 @@ new_key_type! { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some(key) = dt.tds().simplex_keys().next() else { @@ -1627,7 +1626,7 @@ new_key_type! { /// # Properties /// /// - `vertices`: A storage map that stores vertices with stable keys for efficient access. -/// Each [`Vertex`] has a point of type T, vertex data of type U, and a constant D representing the dimension. +/// Each [`Vertex`] has validated coordinates, optional data of type `U`, and a constant `D` dimension. /// - `simplices`: A storage map that stores maximal [`Simplex`] objects with stable keys. /// Each [`Simplex`] stores [`VertexKey`]s (keys into `vertices`) and optional neighbor [`SimplexKey`]s, /// plus simplex data of type `V`. @@ -1647,13 +1646,13 @@ new_key_type! { /// A similar pattern holds for higher dimensions. /// /// In typical usage, vertices carry coordinates in D-dimensional Euclidean space. -/// However, the core `Tds` API is designed to be **combinatorial**: most methods do -/// not require `T: CoordinateScalar` and operate purely on keys and adjacency. +/// However, the core `Tds` API is designed to be **combinatorial**: most methods +/// operate purely on keys and adjacency. /// /// # Usage /// /// `Tds` is the low-level topology container used by -/// [`Triangulation`](crate::core::triangulation::Triangulation) and +/// [`Triangulation`](crate::prelude::triangulation::Triangulation) and /// [`crate::DelaunayTriangulation`]. /// /// Most users should construct triangulations via `DelaunayTriangulation` and access the @@ -1685,9 +1684,9 @@ new_key_type! { /// # fn main() -> Result<(), ExampleError> { /// // Create vertices for a 2D triangulation /// let vertices = [ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.5, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// // Create a new triangulation @@ -1699,12 +1698,12 @@ new_key_type! { /// # Ok(()) /// # } /// ``` -pub struct Tds { +pub struct Tds { /// Storage map for vertices, allowing stable keys and efficient access. - vertices: StorageMap>, + vertices: StorageMap>, /// Storage map for simplices, providing stable keys and efficient access. - simplices: StorageMap>, + simplices: StorageMap>, /// Fast mapping from Vertex UUIDs to their `VertexKeys` for efficient UUID → Key lookups. /// This optimizes the common operation of looking up vertex keys by UUID. @@ -1761,9 +1760,8 @@ enum SimplexInsertionTopologyCheck { Prechecked, } -impl Clone for Tds +impl Clone for Tds where - T: Clone, U: Clone, V: Clone, { @@ -1780,9 +1778,8 @@ where } } -impl Tds +impl Tds where - T: Clone, U: Clone, V: Clone, { @@ -1815,16 +1812,16 @@ where // Following CGAL's Triangulation_data_structure pattern, these methods operate // on topology independently of geometry. // -impl Tds { +impl Tds { #[inline] - fn allows_periodic_self_neighbor(simplex: &Simplex) -> bool { + fn allows_periodic_self_neighbor(simplex: &Simplex) -> bool { let Some(offsets) = simplex.periodic_vertex_offsets() else { return false; }; !offsets.is_empty() && offsets.len() == simplex.number_of_vertices() } fn periodic_facet_key_from_simplex_vertices( - simplex: &Simplex, + simplex: &Simplex, vertices: &[VertexKey], facet_index: usize, ) -> Result { @@ -1917,7 +1914,7 @@ impl Tds { fn lifted_vertex_identities( simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, ) -> Result, TdsError> { let vertices = simplex.vertices(); let periodic_offsets = simplex.periodic_vertex_offsets(); @@ -1944,8 +1941,8 @@ impl Tds { } fn matching_lifted_facet_index( - simplex: &Simplex, - neighbor: &Simplex, + simplex: &Simplex, + neighbor: &Simplex, ) -> Result, TdsError> { let simplex_vertices = simplex.vertices(); let neighbor_vertices = neighbor.vertices(); @@ -1973,9 +1970,9 @@ impl Tds { /// Finds the neighbor facet that matches a source facet in lifted periodic coordinates. fn matching_lifted_mirror_facet_index( - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, - neighbor: &Simplex, + neighbor: &Simplex, context: &str, ) -> Result { let simplex_facet_key = @@ -2035,7 +2032,7 @@ impl Tds { /// /// **Internal use only**: This method rebuilds ALL neighbor pointers from scratch, which is /// inefficient for most use cases. For external use, prefer - /// [`repair_neighbor_pointers`](crate::core::algorithms::incremental_insertion::repair_neighbor_pointers), + /// [`repair_neighbor_pointers`](crate::prelude::insertion::repair_neighbor_pointers), /// which rebuilds neighbor pointers from facet incidence. /// /// # Errors @@ -2159,7 +2156,7 @@ impl Tds { /// /// # Returns /// - /// An iterator over `(SimplexKey, &Simplex)` pairs. + /// An iterator over `(SimplexKey, &Simplex)` pairs. /// /// # Example /// @@ -2187,10 +2184,10 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -2200,7 +2197,7 @@ impl Tds { /// # Ok(()) /// # } /// ``` - pub fn simplices(&self) -> impl Iterator)> { + pub fn simplices(&self) -> impl Iterator)> { self.simplices.iter() } @@ -2212,7 +2209,7 @@ impl Tds { /// /// # Returns /// - /// An iterator over `&Simplex` references. + /// An iterator over `&Simplex` references. /// /// # Example /// @@ -2240,10 +2237,10 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -2253,7 +2250,7 @@ impl Tds { /// # Ok(()) /// # } /// ``` - pub fn simplices_values(&self) -> impl Iterator> { + pub fn simplices_values(&self) -> impl Iterator> { self.simplices.values() } @@ -2271,7 +2268,7 @@ impl Tds { /// /// # Returns /// - /// An iterator over `(VertexKey, &Vertex)` pairs. + /// An iterator over `(VertexKey, &Vertex)` pairs. /// /// # Example /// @@ -2280,6 +2277,7 @@ impl Tds { /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { + /// # #[error(transparent)] Coordinates(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error(transparent)] Construction(#[from] delaunay::DelaunayTriangulationConstructionError), /// # #[error(transparent)] Insertion(#[from] delaunay::prelude::insertion::InsertionError), /// # #[error(transparent)] Tds(#[from] delaunay::prelude::tds::TdsError), @@ -2291,9 +2289,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.5, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -2303,7 +2301,7 @@ impl Tds { /// # Ok(()) /// # } /// ``` - pub fn vertices(&self) -> impl Iterator)> { + pub fn vertices(&self) -> impl Iterator)> { self.vertices.iter() } @@ -2320,6 +2318,7 @@ impl Tds { /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { + /// # #[error(transparent)] Coordinates(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error(transparent)] Construction(#[from] delaunay::DelaunayTriangulationConstructionError), /// # #[error(transparent)] Insertion(#[from] delaunay::prelude::insertion::InsertionError), /// # #[error(transparent)] Tds(#[from] delaunay::prelude::tds::TdsError), @@ -2331,9 +2330,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let keys: Vec<_> = dt.tds().vertex_keys().collect(); @@ -2358,6 +2357,7 @@ impl Tds { /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { + /// # #[error(transparent)] Coordinates(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error(transparent)] Construction(#[from] delaunay::DelaunayTriangulationConstructionError), /// # #[error(transparent)] Insertion(#[from] delaunay::prelude::insertion::InsertionError), /// # #[error(transparent)] Tds(#[from] delaunay::prelude::tds::TdsError), @@ -2369,9 +2369,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let keys: Vec<_> = dt.tds().simplex_keys().collect(); @@ -2407,9 +2407,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -2424,7 +2424,7 @@ impl Tds { /// # } /// ``` #[must_use] - pub fn simplex(&self, key: SimplexKey) -> Option<&Simplex> { + pub fn simplex(&self, key: SimplexKey) -> Option<&Simplex> { self.simplices.get(key) } @@ -2452,9 +2452,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -2484,7 +2484,7 @@ impl Tds { /// ``` /// use delaunay::prelude::tds::Tds; /// - /// let tds = Tds::::empty(); + /// let tds = Tds::<(), (), 3>::empty(); /// assert_eq!(tds.number_of_vertices(), 0); /// ``` /// @@ -2505,8 +2505,8 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::empty(); - /// let vertex1: Vertex = vertex!([1.0, 2.0, 3.0]); - /// let vertex2: Vertex = vertex!([4.0, 5.0, 6.0]); + /// let vertex1: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).expect("finite vertex coordinates"); + /// let vertex2: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([4.0, 5.0, 6.0]).expect("finite vertex coordinates"); /// /// dt.insert(vertex1)?; /// assert_eq!(dt.number_of_vertices(), 1); @@ -2531,16 +2531,17 @@ impl Tds { /// # #[error(transparent)] Invariant(#[from] delaunay::prelude::tds::InvariantError), /// # #[error(transparent)] Facet(#[from] delaunay::prelude::tds::FacetError), /// # #[error(transparent)] Simplex(#[from] delaunay::prelude::tds::SimplexValidationError), + /// # #[error(transparent)] Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// let points = [ - /// Point::new([0.0, 0.0, 0.0]), - /// Point::new([1.0, 0.0, 0.0]), - /// Point::new([0.0, 1.0, 0.0]), - /// Point::new([0.0, 0.0, 1.0]), + /// Point::try_from([0.0, 0.0, 0.0])?, + /// Point::try_from([1.0, 0.0, 0.0])?, + /// Point::try_from([0.0, 1.0, 0.0])?, + /// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// - /// let vertices = Vertex::::from_points(&points); + /// let vertices = Vertex::<(), 3>::from_points(&points); /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.number_of_vertices(), 4); /// # Ok(()) @@ -2567,7 +2568,7 @@ impl Tds { /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; /// - /// let tds = Tds::::empty(); + /// let tds = Tds::<(), (), 3>::empty(); /// assert_eq!(tds.dim(), -1); // Empty triangulation /// ``` /// @@ -2578,6 +2579,7 @@ impl Tds { /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { + /// # #[error(transparent)] Coordinates(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error(transparent)] Construction(#[from] delaunay::DelaunayTriangulationConstructionError), /// # #[error(transparent)] Insertion(#[from] delaunay::prelude::insertion::InsertionError), /// # #[error(transparent)] Tds(#[from] delaunay::prelude::tds::TdsError), @@ -2593,19 +2595,19 @@ impl Tds { /// assert_eq!(dt.dim(), -1); /// /// // Add vertices incrementally - /// let vertex1: Vertex = vertex!([0.0, 0.0, 0.0]); + /// let vertex1: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?; /// dt.insert(vertex1)?; /// assert_eq!(dt.dim(), 0); /// - /// let vertex2: Vertex = vertex!([1.0, 0.0, 0.0]); + /// let vertex2: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?; /// dt.insert(vertex2)?; /// assert_eq!(dt.dim(), 1); /// - /// let vertex3: Vertex = vertex!([0.0, 1.0, 0.0]); + /// let vertex3: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?; /// dt.insert(vertex3)?; /// assert_eq!(dt.dim(), 2); /// - /// let vertex4: Vertex = vertex!([0.0, 0.0, 1.0]); + /// let vertex4: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?; /// dt.insert(vertex4)?; /// assert_eq!(dt.number_of_vertices(), 4); /// assert_eq!(dt.dim(), 3); @@ -2627,27 +2629,28 @@ impl Tds { /// # #[error(transparent)] Invariant(#[from] delaunay::prelude::tds::InvariantError), /// # #[error(transparent)] Facet(#[from] delaunay::prelude::tds::FacetError), /// # #[error(transparent)] Simplex(#[from] delaunay::prelude::tds::SimplexValidationError), + /// # #[error(transparent)] Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// // 2D triangulation /// let points_2d = [ - /// Point::new([0.0, 0.0]), - /// Point::new([1.0, 0.0]), - /// Point::new([0.5, 1.0]), + /// Point::try_from([0.0, 0.0])?, + /// Point::try_from([1.0, 0.0])?, + /// Point::try_from([0.5, 1.0])?, /// ]; - /// let vertices_2d = Vertex::::from_points(&points_2d); + /// let vertices_2d = Vertex::<(), 2>::from_points(&points_2d); /// let dt_2d = DelaunayTriangulationBuilder::new(&vertices_2d).build::<()>()?; /// assert_eq!(dt_2d.dim(), 2); /// /// // 4D triangulation with 5 vertices (minimum for 4D simplex) /// let points_4d = [ - /// Point::new([0.0, 0.0, 0.0, 0.0]), - /// Point::new([1.0, 0.0, 0.0, 0.0]), - /// Point::new([0.0, 1.0, 0.0, 0.0]), - /// Point::new([0.0, 0.0, 1.0, 0.0]), - /// Point::new([0.0, 0.0, 0.0, 1.0]), + /// Point::try_from([0.0, 0.0, 0.0, 0.0])?, + /// Point::try_from([1.0, 0.0, 0.0, 0.0])?, + /// Point::try_from([0.0, 1.0, 0.0, 0.0])?, + /// Point::try_from([0.0, 0.0, 1.0, 0.0])?, + /// Point::try_from([0.0, 0.0, 0.0, 1.0])?, /// ]; - /// let vertices_4d = Vertex::::from_points(&points_4d); + /// let vertices_4d = Vertex::<(), 4>::from_points(&points_4d); /// let dt_4d = DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; /// assert_eq!(dt_4d.dim(), 4); /// # Ok(()) @@ -2684,16 +2687,17 @@ impl Tds { /// # #[error(transparent)] Invariant(#[from] delaunay::prelude::tds::InvariantError), /// # #[error(transparent)] Facet(#[from] delaunay::prelude::tds::FacetError), /// # #[error(transparent)] Simplex(#[from] delaunay::prelude::tds::SimplexValidationError), + /// # #[error(transparent)] Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// let points = [ - /// Point::new([0.0, 0.0, 0.0]), - /// Point::new([1.0, 0.0, 0.0]), - /// Point::new([0.0, 1.0, 0.0]), - /// Point::new([0.0, 0.0, 1.0]), + /// Point::try_from([0.0, 0.0, 0.0])?, + /// Point::try_from([1.0, 0.0, 0.0])?, + /// Point::try_from([0.0, 1.0, 0.0])?, + /// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// - /// let vertices = Vertex::::from_points(&points); + /// let vertices = Vertex::<(), 3>::from_points(&points); /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.number_of_simplices(), 1); // Simplices are automatically created via triangulation /// # Ok(()) @@ -2714,16 +2718,17 @@ impl Tds { /// # #[error(transparent)] Invariant(#[from] delaunay::prelude::tds::InvariantError), /// # #[error(transparent)] Facet(#[from] delaunay::prelude::tds::FacetError), /// # #[error(transparent)] Simplex(#[from] delaunay::prelude::tds::SimplexValidationError), + /// # #[error(transparent)] Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { /// let points = [ - /// Point::new([0.0, 0.0, 0.0]), - /// Point::new([1.0, 0.0, 0.0]), - /// Point::new([0.0, 1.0, 0.0]), - /// Point::new([0.0, 0.0, 1.0]), + /// Point::try_from([0.0, 0.0, 0.0])?, + /// Point::try_from([1.0, 0.0, 0.0])?, + /// Point::try_from([0.0, 1.0, 0.0])?, + /// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// - /// let vertices = Vertex::::from_points(&points); + /// let vertices = Vertex::<(), 3>::from_points(&points); /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.number_of_simplices(), 1); // One tetrahedron for 4 points in 3D /// # Ok(()) @@ -2735,7 +2740,7 @@ impl Tds { /// ``` /// use delaunay::prelude::tds::Tds; /// - /// let tds = Tds::::empty(); + /// let tds = Tds::<(), (), 3>::empty(); /// assert_eq!(tds.number_of_simplices(), 0); // No simplices for empty input /// ``` #[must_use] @@ -2756,7 +2761,7 @@ impl Tds { /// Time complexity: O(N · D), where N is the number of simplices (each simplex has at most /// D+1 neighbors, so the BFS visits at most N·(D+1) edges). /// - /// [`Triangulation::is_valid`]: crate::core::triangulation::Triangulation::is_valid + /// [`Triangulation::is_valid`]: crate::prelude::triangulation::Triangulation::is_valid /// /// # Examples /// @@ -2775,10 +2780,10 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert!(dt.tds().is_connected()); @@ -2856,7 +2861,7 @@ impl Tds { /// ``` /// use delaunay::prelude::tds::Tds; /// - /// let tds: Tds = Tds::empty(); + /// let tds: Tds<(), (), 2> = Tds::empty(); /// assert_eq!(tds.generation(), 0); /// ``` #[inline] @@ -2881,7 +2886,7 @@ impl Tds { } } -impl Tds { +impl Tds { // ========================================================================= // QUERY OPERATIONS // ========================================================================= @@ -2919,7 +2924,7 @@ impl Tds { /// See the unit tests for usage examples of this pub(crate) method. pub(crate) fn insert_vertex_with_mapping( &mut self, - vertex: Vertex, + vertex: Vertex, ) -> Result { let vertex_uuid = vertex.uuid(); @@ -2968,7 +2973,7 @@ impl Tds { /// See the unit tests for usage examples of this pub(crate) method. pub(crate) fn insert_simplex_with_mapping( &mut self, - simplex: Simplex, + simplex: Simplex, ) -> Result { self.insert_simplex_with_mapping_impl(simplex, SimplexInsertionTopologyCheck::Checked) } @@ -2988,7 +2993,7 @@ impl Tds { /// referenced vertex key is not present in this TDS. pub(crate) fn insert_simplex_with_mapping_trusted_vertices( &mut self, - simplex: Simplex, + simplex: Simplex, ) -> Result { self.insert_simplex_with_mapping_impl(simplex, SimplexInsertionTopologyCheck::Checked) } @@ -3000,7 +3005,7 @@ impl Tds { /// immediately wiring local neighbors and validating the affected topology. pub(crate) fn insert_simplex_with_mapping_prechecked_topology( &mut self, - simplex: Simplex, + simplex: Simplex, ) -> Result { self.insert_simplex_with_mapping_impl(simplex, SimplexInsertionTopologyCheck::Prechecked) } @@ -3008,7 +3013,7 @@ impl Tds { /// Shared checked simplex-insertion path used by public and trusted internal wrappers. fn insert_simplex_with_mapping_impl( &mut self, - simplex: Simplex, + simplex: Simplex, topology_check: SimplexInsertionTopologyCheck, ) -> Result { if simplex.number_of_vertices() != D + 1 { @@ -3048,7 +3053,7 @@ impl Tds { /// Verifies that inserting `simplex` would not violate local topology invariants. fn validate_simplex_topology_safe_for_insertion( &self, - simplex: &Simplex, + simplex: &Simplex, ) -> Result<(), TdsConstructionError> { self.validate_no_duplicate_simplex_on_insert(simplex)?; self.validate_facet_sharing_on_insert(simplex)?; @@ -3058,7 +3063,7 @@ impl Tds { /// Builds the duplicate-simplex identity for a not-yet-inserted simplex. fn candidate_periodic_vertex_uuid_offsets( &self, - simplex: &Simplex, + simplex: &Simplex, ) -> Result, TdsError> { let vertices = simplex.vertices(); let periodic_offsets = simplex.periodic_vertex_offsets(); @@ -3098,7 +3103,7 @@ impl Tds { /// Rejects a candidate simplex that duplicates an existing maximal simplex. fn validate_no_duplicate_simplex_on_insert( &self, - simplex: &Simplex, + simplex: &Simplex, ) -> Result<(), TdsError> { let candidate_identity = self.candidate_periodic_vertex_uuid_offsets(simplex)?; @@ -3121,10 +3126,7 @@ impl Tds { } /// Rejects a candidate simplex whose facets would be incident to a third simplex. - fn validate_facet_sharing_on_insert( - &self, - simplex: &Simplex, - ) -> Result<(), TdsError> { + fn validate_facet_sharing_on_insert(&self, simplex: &Simplex) -> Result<(), TdsError> { let vertices = simplex.vertices(); for candidate_facet_idx in 0..vertices.len() { let candidate_facet_key = Self::periodic_facet_key_from_simplex_vertices( @@ -3165,7 +3167,7 @@ impl Tds { /// Verifies every vertex key referenced by `simplex` is live in this TDS. fn validate_simplex_vertices_exist( &self, - simplex: &Simplex, + simplex: &Simplex, ) -> Result<(), TdsConstructionError> { for &vkey in simplex.vertices() { if !self.vertices.contains_key(vkey) { @@ -3184,13 +3186,13 @@ impl Tds { #[cfg(test)] pub(crate) fn insert_simplex_bypassing_topology_checks_for_test( &mut self, - simplex: Simplex, + simplex: Simplex, ) -> Result { self.insert_simplex_with_mapping_impl(simplex, SimplexInsertionTopologyCheck::Prechecked) } } -impl Tds { +impl Tds { /// Gets validated vertex keys for a simplex. /// /// This performs O(D) validation and copying of the requested simplex's @@ -3240,9 +3242,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -3316,10 +3318,10 @@ impl Tds { /// # fn main() -> Result<(), ExampleError> { /// // Create a triangulation with some vertices /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3344,7 +3346,7 @@ impl Tds { /// use delaunay::prelude::tds::Tds; /// use uuid::Uuid; /// - /// let tds: Tds = Tds::empty(); + /// let tds: Tds<(), (), 3> = Tds::empty(); /// let random_uuid = Uuid::new_v4(); /// /// let result = tds.simplex_key_from_uuid(&random_uuid); @@ -3391,10 +3393,10 @@ impl Tds { /// # fn main() -> Result<(), ExampleError> { /// // Create a triangulation with some vertices /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3419,7 +3421,7 @@ impl Tds { /// use delaunay::prelude::tds::Tds; /// use uuid::Uuid; /// - /// let tds: Tds = Tds::empty(); + /// let tds: Tds<(), (), 3> = Tds::empty(); /// let random_uuid = Uuid::new_v4(); /// /// let result = tds.vertex_key_from_uuid(&random_uuid); @@ -3466,10 +3468,10 @@ impl Tds { /// # fn main() -> Result<(), ExampleError> { /// // Create a triangulation with some vertices /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3506,10 +3508,10 @@ impl Tds { /// # fn main() -> Result<(), ExampleError> { /// // Create a triangulation with some vertices /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3573,10 +3575,10 @@ impl Tds { /// # fn main() -> Result<(), ExampleError> { /// // Create a triangulation with some vertices /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3613,10 +3615,10 @@ impl Tds { /// # fn main() -> Result<(), ExampleError> { /// // Create a triangulation with some vertices /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -3667,10 +3669,7 @@ impl Tds { /// #[inline] #[must_use] - pub(crate) fn simplex_mut( - &mut self, - simplex_key: SimplexKey, - ) -> Option<&mut Simplex> { + pub(crate) fn simplex_mut(&mut self, simplex_key: SimplexKey) -> Option<&mut Simplex> { self.simplices.get_mut(simplex_key) } @@ -3704,9 +3703,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -3719,7 +3718,7 @@ impl Tds { /// ``` #[inline] #[must_use] - pub fn vertex(&self, vertex_key: VertexKey) -> Option<&Vertex> { + pub fn vertex(&self, vertex_key: VertexKey) -> Option<&Vertex> { self.vertices.get(vertex_key) } @@ -3735,11 +3734,11 @@ impl Tds { /// #[inline] #[must_use] - pub(crate) fn vertex_mut(&mut self, vertex_key: VertexKey) -> Option<&mut Vertex> { + pub(crate) fn vertex_mut(&mut self, vertex_key: VertexKey) -> Option<&mut Vertex> { self.vertices.get_mut(vertex_key) } - /// Sets the auxiliary data on a vertex, returning the previous value. + /// Sets the auxiliary data on a returning the previous value. /// /// This is a safe O(1) operation that modifies only the user-data field. /// It does not affect geometry, topology, or Delaunay invariants. @@ -3770,10 +3769,10 @@ impl Tds { /// # #[error(transparent)] Simplex(#[from] delaunay::prelude::tds::SimplexValidationError), /// # } /// # fn main() -> Result<(), ExampleError> { - /// let vertices: [Vertex; 3] = [ - /// vertex!([0.0, 0.0], 10i32), - /// vertex!([1.0, 0.0], 20), - /// vertex!([0.0, 1.0], 30), + /// let vertices: [Vertex; 3] = [ + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 10i32).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 20).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 30).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let mut tds = dt.tds().clone(); @@ -3838,9 +3837,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::()?; /// let mut tds = dt.tds().clone(); @@ -3900,9 +3899,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -3946,9 +3945,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -3966,7 +3965,7 @@ impl Tds { } } -impl Tds { +impl Tds { /// Removes multiple simplices by their keys in a batch operation. /// /// This method performs a **local** topology update: @@ -4011,9 +4010,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let mut tds = dt.tds().clone(); @@ -4060,61 +4059,6 @@ impl Tds { removed_count } - /// Repairs locally degenerate simplices by removing them and clearing dangling references - /// (neighbor back-references + `incident_simplex` pointers). - /// - /// This is a narrowly scoped repair primitive intended for test-only usage. - /// - /// A simplex is treated as degenerate if it: - /// - fails basic per-simplex validity (`Simplex::is_valid()`), - /// - references a missing vertex key, or - /// - contains a neighbor pointer to a missing simplex key. - /// - /// This method does **not** retriangulate cavities, insert new simplices, or attempt to repair - /// geometric degeneracy. It only removes simplices and relies on [`Tds::remove_simplices_by_keys`] - /// to clear neighbor back-references and repair `incident_simplex` pointers. - /// - /// Returns the number of simplices removed. - #[cfg(test)] - pub(crate) fn repair_degenerate_simplices(&mut self) -> usize { - if self.simplices.is_empty() { - return 0; - } - - // Collect keys first (cannot mutate while iterating). - let mut to_remove: Vec = Vec::new(); - - for (simplex_key, simplex) in self.simplices() { - if simplex.is_valid().is_err() { - to_remove.push(simplex_key); - continue; - } - - if simplex - .vertices() - .iter() - .any(|&vkey| !self.vertices.contains_key(vkey)) - { - to_remove.push(simplex_key); - continue; - } - - if simplex.neighbor_keys().is_some_and(|neighbors| { - neighbors - .flatten() - .any(|neighbor_key| !self.simplices.contains_key(neighbor_key)) - }) { - to_remove.push(simplex_key); - } - } - - if to_remove.is_empty() { - return 0; - } - - self.remove_simplices_by_keys(&to_remove) - } - fn collect_removal_frontier_and_clear_neighbor_back_references( &mut self, simplex_keys: &[SimplexKey], @@ -4424,10 +4368,10 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), - /// vertex!([1.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -4536,9 +4480,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -4801,7 +4745,7 @@ impl Tds { } fn set_simplex_neighbors_normalized( - simplex: &mut Simplex, + simplex: &mut Simplex, neighbors: &[Option], ) -> Result<(), TdsError> { let simplex_id = simplex.uuid(); @@ -4811,7 +4755,7 @@ impl Tds { } fn ensure_neighbor_buffer( - simplex: &mut Simplex, + simplex: &mut Simplex, ) -> Result<&mut SmallBuffer, TdsError> { if simplex.neighbor_slots().is_none() { let simplex_id = simplex.uuid(); @@ -4833,7 +4777,7 @@ impl Tds { } fn set_neighbor_slot( - simplex: &mut Simplex, + simplex: &mut Simplex, facet_idx: usize, neighbor: Option, ) -> Result<(), TdsError> { @@ -4886,7 +4830,7 @@ impl Tds { fn collect_stale_reciprocal_neighbor_updates( &self, simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, old_neighbors: &[Option], new_neighbors: &[Option], reciprocal_updates: &mut Vec<(SimplexKey, usize, Option)>, @@ -4938,7 +4882,7 @@ impl Tds { fn collect_new_reciprocal_neighbor_updates( &self, simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, neighbors: &[Option], reciprocal_updates: &mut Vec<(SimplexKey, usize, Option)>, ) -> Result<(), TdsError> { @@ -5044,9 +4988,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let mut tds = dt.tds().clone(); @@ -5140,9 +5084,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -5167,7 +5111,7 @@ impl Tds { /// Assigns incident simplices to vertices in the triangulation. /// /// This method establishes a mapping from each vertex to one of the simplices that contains it, - /// which is useful for various geometric queries and traversals. For each vertex, an arbitrary + /// which is useful for various geometric queries and traversals. For each an arbitrary /// incident simplex is selected from the simplices that contain that vertex. /// /// Note: Many topology-mutating operations (like [`Tds::remove_simplices_by_keys`](Self::remove_simplices_by_keys)) @@ -5217,9 +5161,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let mut tds = dt.tds().clone(); @@ -5242,7 +5186,7 @@ impl Tds { // Reset incident_simplex for all vertices before rebuilding the mapping. This // ensures vertices that no longer belong to any simplex do not retain stale - // incident_simplex pointers after topology changes (e.g., vertex or simplex removal). + // incident_simplex pointers after topology changes (e.g. or simplex removal). for vertex in self.vertices.values_mut() { vertex.set_incident_simplex(None); } @@ -5292,7 +5236,7 @@ impl Tds { /// use delaunay::prelude::tds::Tds; /// use delaunay::prelude::tds::TriangulationConstructionState; /// - /// let tds: Tds = Tds::empty(); + /// let tds: Tds<(), (), 3> = Tds::empty(); /// assert_eq!(tds.number_of_vertices(), 0); /// assert_eq!(tds.number_of_simplices(), 0); /// assert_eq!(tds.dim(), -1); @@ -5341,10 +5285,10 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -5602,7 +5546,7 @@ impl Tds { } } -impl Tds { +impl Tds { /// Builds a `FacetToSimplicesMap` with strict error handling. /// /// This method returns an error if any simplex has missing vertex keys, ensuring @@ -5645,9 +5589,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -5686,7 +5630,7 @@ impl Tds { facet_to_simplices .entry(facet_key) .or_default() - .push(FacetHandle::new(simplex_id, facet_index_u8)); + .push(FacetHandle::from_validated(simplex_id, facet_index_u8)); } } @@ -5694,7 +5638,7 @@ impl Tds { } } -impl Tds { +impl Tds { /// Removes duplicate simplices with identical vertex sets. /// /// Returns the number of duplicate simplices that were removed. @@ -5724,7 +5668,7 @@ impl Tds { /// use delaunay::prelude::tds::{Tds, TdsMutationError}; /// /// # fn main() -> Result<(), TdsMutationError> { - /// let mut tds: Tds = Tds::empty(); + /// let mut tds: Tds<(), (), 2> = Tds::empty(); /// let removed = tds.remove_duplicate_simplices()?; /// assert_eq!(removed, 0); /// # Ok(()) @@ -5732,7 +5676,6 @@ impl Tds { /// ``` pub fn remove_duplicate_simplices(&mut self) -> Result where - T: Clone, U: Clone, V: Clone, { @@ -5786,8 +5729,8 @@ impl Tds { // ========================================================================= // VALIDATION & CONSISTENCY CHECKS // ========================================================================= - // Note: Structural validation is topology-only. Only Level-1 element validation (coordinates) - // requires `T: CoordinateScalar`. + // Note: Structural validation is topology-only. Level-1 element validation + // checks validated f64 coordinate storage. /// Validates the consistency of vertex UUID-to-key mappings. /// @@ -6059,7 +6002,7 @@ impl Tds { /// Validates that no simplex contains vertices with identical coordinates. /// - /// This is a geometric-level check complementing [`Simplex::new()`]'s vertex-key uniqueness + /// This is a geometric-level check complementing [`Simplex::try_new()`]'s vertex-key uniqueness /// check. Two different vertex keys can reference geometrically identical points, producing /// a zero-volume simplex that is catastrophic for `SoS` orientation and Pachner moves. /// @@ -6069,10 +6012,7 @@ impl Tds { /// /// Returns [`TdsError::DuplicateCoordinatesInSimplex`] on the first simplex found /// containing two vertices with identical coordinates. - fn validate_simplex_coordinate_uniqueness(&self) -> Result<(), TdsError> - where - T: CoordinateScalar, - { + fn validate_simplex_coordinate_uniqueness(&self) -> Result<(), TdsError> { for (_simplex_key, simplex) in &self.simplices { let vkeys = simplex.vertices(); // O(D²) pairwise comparison per simplex — acceptable since D is small (≤ 6). @@ -6156,9 +6096,9 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 2> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -6287,9 +6227,9 @@ impl Tds { /// Resolves the mirror facet for orientation validation without forcing periodic parity checks. fn orientation_mirror_facet_index( - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, context: &str, ) -> Result<(usize, bool), TdsError> { let uses_periodic_offsets = simplex.periodic_vertex_offsets().is_some() @@ -6312,7 +6252,7 @@ impl Tds { } fn facet_vertices_in_simplex_order( - simplex: &Simplex, + simplex: &Simplex, omit_idx: usize, ) -> Result, TdsError> { if omit_idx >= simplex.number_of_vertices() { @@ -6341,7 +6281,7 @@ impl Tds { /// global translation. The anchor is selected lexicographically by /// `(vertex_key_value, offset)`. fn facet_vertex_identities_in_simplex_order( - simplex: &Simplex, + simplex: &Simplex, omit_idx: usize, ) -> Result, TdsError> { if omit_idx >= simplex.number_of_vertices() { @@ -6408,9 +6348,9 @@ impl Tds { /// The expected odd parity follows the coherent boundary-orientation convention: /// odd is expected exactly when `(facet_idx + mirror_idx)` is even. fn facet_permutation_parity( - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, mirror_idx: usize, ) -> Result<(bool, bool, bool), TdsError> { let simplex_facet_identities = @@ -6562,11 +6502,11 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices_4d = [ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 4> = /// DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; @@ -6628,11 +6568,11 @@ impl Tds { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices_4d = [ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 4> = /// DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; @@ -6642,10 +6582,7 @@ impl Tds { /// # Ok(()) /// # } /// ``` - pub fn validate(&self) -> Result<(), TdsError> - where - T: CoordinateScalar, - { + pub fn validate(&self) -> Result<(), TdsError> { for (_vertex_key, vertex) in &self.vertices { if let Err(source) = (*vertex).is_valid() { return Err(TdsError::InvalidVertex { @@ -6710,10 +6647,7 @@ impl Tds { clippy::too_many_lines, reason = "validation report aggregation is intentionally linear and simplex nomenclature makes existing names longer" )] - pub(crate) fn validation_report(&self) -> Result<(), TriangulationValidationReport> - where - T: CoordinateScalar, - { + pub(crate) fn validation_report(&self) -> Result<(), TriangulationValidationReport> { let mut violations = Vec::new(); // 1. Mapping consistency (vertex + simplex UUID↔key mappings) @@ -7158,8 +7092,8 @@ impl Tds { } fn validate_shared_facet_count( - simplex: &Simplex, - neighbor_simplex: &Simplex, + simplex: &Simplex, + neighbor_simplex: &Simplex, this_vertices: &VertexKeySet, neighbor_vertices: &VertexKeySet, ) -> Result<(), TdsError> { @@ -7176,9 +7110,9 @@ impl Tds { } fn verified_mirror_facet_index( - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, this_vertices: &VertexKeySet, ) -> Result { let mirror_idx = simplex @@ -7216,8 +7150,8 @@ impl Tds { } fn expected_mirror_facet_index( - simplex: &Simplex, - neighbor_simplex: &Simplex, + simplex: &Simplex, + neighbor_simplex: &Simplex, this_vertices: &VertexKeySet, ) -> Result { let mut expected_mirror_idx: Option = None; @@ -7245,9 +7179,9 @@ impl Tds { } fn validate_shared_facet_vertices( - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, mirror_idx: usize, this_vertices: &VertexKeySet, neighbor_vertices: &VertexKeySet, @@ -7293,10 +7227,10 @@ impl Tds { fn validate_mutual_neighbor_back_reference( simplex_key: SimplexKey, - simplex: &Simplex, + simplex: &Simplex, facet_idx: usize, neighbor_key: SimplexKey, - neighbor_simplex: &Simplex, + neighbor_simplex: &Simplex, mirror_idx: usize, ) -> Result<(), TdsError> { let Some(back_ref) = neighbor_simplex.neighbor_key(mirror_idx) else { @@ -7338,16 +7272,14 @@ impl Tds { // ============================================================================= type SimplexUuidSortKey = Vec<(Uuid, [i8; D])>; -type SimplexUuidSortEntry<'a, T, U, V, const D: usize> = - (SimplexUuidSortKey, &'a Simplex); +type SimplexUuidSortEntry<'a, V, const D: usize> = (SimplexUuidSortKey, &'a Simplex); /// Builds stable simplex sort keys once so equality does not hide dangling /// vertex references or allocate sort keys repeatedly during comparison. -fn simplex_uuid_sort_entries( - tds: &Tds, -) -> Option>> +fn simplex_uuid_sort_entries( + tds: &Tds, +) -> Option>> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -7384,9 +7316,8 @@ where /// The triangulation validates coordinates at construction time to ensure no NaN values are present. /// /// Note: Buffer fields are ignored since they are transient data structures. -impl PartialEq for Tds +impl PartialEq for Tds where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -7406,7 +7337,7 @@ where let mut other_vertices: Vec<_> = other.vertices.values().collect(); // Sort vertices by their coordinates for consistent comparison - // CoordinateScalar guarantees PartialOrd; NaN validation occurs at construction time + // f64-backed vertices provide PartialOrd; NaN validation occurs at construction time self_vertices.sort_by(|a, b| { let a_coords = *a.point().coords(); let b_coords = *b.point().coords(); @@ -7467,9 +7398,8 @@ where /// This is a marker trait implementation that relies on the `PartialEq` implementation. /// Since Tds represents a well-defined mathematical structure (triangulation), /// the `PartialEq` relation is indeed an equivalence relation. -impl Eq for Tds +impl Eq for Tds where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -7480,9 +7410,8 @@ where /// Simplices store vertex keys that cannot be serialized directly, so TDS /// serialization includes a simplex UUID → vertex UUID mapping that lets /// deserialization rebuild the vertex keys for each simplex. -impl Serialize for Tds +impl Serialize for Tds where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -7492,31 +7421,39 @@ where { // Build simplex UUID → vertex UUIDs mapping // Note: Using Vec for serde compatibility (SmallVec isn't natively serializable) - let simplex_vertices: FastHashMap> = self - .simplices - .iter() - .map(|(_simplex_key, simplex)| { - let simplex_uuid = simplex.uuid(); - let vertex_uuids = simplex - .vertex_uuids(self) - .map_err(serde::ser::Error::custom)?; - // Convert SimplexVertexUuidBuffer (SmallVec) to Vec for serde - Ok((simplex_uuid, vertex_uuids.to_vec())) - }) - .collect::>()?; + let mut simplex_vertices: FastHashMap> = + fast_hash_map_with_capacity(self.simplices.len()); + let mut simplex_vertex_offsets: FastHashMap>> = + fast_hash_map_with_capacity(self.simplices.len()); + for (_simplex_key, simplex) in &self.simplices { + let simplex_uuid = simplex.uuid(); + let vertex_uuids = simplex + .vertex_uuids(self) + .map_err(serde::ser::Error::custom)?; + simplex_vertices.insert(simplex_uuid, vertex_uuids.to_vec()); + if let Some(offsets) = simplex.periodic_vertex_offsets() { + simplex_vertex_offsets.insert( + simplex_uuid, + offsets.iter().map(|offset| offset.to_vec()).collect(), + ); + } + } - let mut state = serializer.serialize_struct("Tds", 3)?; - state.serialize_field("vertices", &self.vertices)?; - state.serialize_field("simplices", &self.simplices)?; + let vertices: Vec<_> = self.vertices.values().collect(); + let simplices: Vec<_> = self.simplices.values().collect(); + + let mut state = serializer.serialize_struct("Tds", 4)?; + state.serialize_field("vertices", &vertices)?; + state.serialize_field("simplices", &simplices)?; state.serialize_field("simplex_vertices", &simplex_vertices)?; + state.serialize_field("simplex_vertex_offsets", &simplex_vertex_offsets)?; state.end() } } /// Manual implementation of Deserialize for Tds to handle trait bound conflicts -impl<'de, T, U, V, const D: usize> Deserialize<'de> for Tds +impl<'de, U, V, const D: usize> Deserialize<'de> for Tds where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -7524,140 +7461,288 @@ where where D2: Deserializer<'de>, { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "snake_case")] - enum Field { - Vertices, - Simplices, - SimplexVertices, - } + deserializer.deserialize_struct("Tds", TDS_FIELDS, TdsVisitor(PhantomData)) + } +} - struct TdsVisitor(PhantomData<(T, U, V)>); +// ============================================================================= +// SERDE HELPERS +// ============================================================================= - impl<'de, T, U, V, const D: usize> Visitor<'de> for TdsVisitor - where - T: CoordinateScalar, - U: DataType, - V: DataType, - { - type Value = Tds; +// UUID-to-key mappings are skipped during serialization and reconstructed during +// deserialization from the vertices and simplices data. - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct Tds") - } +const TDS_FIELDS: &[&str] = &[ + "vertices", + "simplices", + "simplex_vertices", + "simplex_vertex_offsets", +]; + +#[derive(Deserialize)] +#[serde(field_identifier, rename_all = "snake_case")] +enum SerializedTdsField { + Vertices, + Simplices, + SimplexVertices, + SimplexVertexOffsets, +} - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut vertices: Option>> = None; - let mut simplices: Option>> = None; - let mut simplex_vertices: Option>> = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Vertices => { - if vertices.is_some() { - return Err(de::Error::duplicate_field("vertices")); - } - vertices = Some(map.next_value()?); - } - Field::Simplices => { - if simplices.is_some() { - return Err(de::Error::duplicate_field("simplices")); - } - simplices = Some(map.next_value()?); - } - Field::SimplexVertices => { - if simplex_vertices.is_some() { - return Err(de::Error::duplicate_field("simplex_vertices")); - } - simplex_vertices = Some(map.next_value()?); - } - } - } +struct TdsVisitor(PhantomData<(U, V)>); - let vertices = vertices.ok_or_else(|| de::Error::missing_field("vertices"))?; - let mut simplices = - simplices.ok_or_else(|| de::Error::missing_field("simplices"))?; - let simplex_vertices = - simplex_vertices.ok_or_else(|| de::Error::missing_field("simplex_vertices"))?; +impl<'de, U, V, const D: usize> Visitor<'de> for TdsVisitor +where + U: DataType, + V: DataType, +{ + type Value = Tds; - // Rebuild UUID→Key mappings from the deserialized data - let mut uuid_to_vertex_key = fast_hash_map_with_capacity(vertices.len()); - for (vertex_key, vertex) in &vertices { - uuid_to_vertex_key.insert(vertex.uuid(), vertex_key); - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Tds") + } - let mut uuid_to_simplex_key = fast_hash_map_with_capacity(simplices.len()); - for (simplex_key, simplex) in &simplices { - uuid_to_simplex_key.insert(simplex.uuid(), simplex_key); - } + fn visit_map(self, map: A) -> Result + where + A: MapAccess<'de>, + { + rebuild_deserialized_tds(read_deserialized_tds_parts(map)?) + } +} - // Rebuild simplex vertex keys from the simplex_vertices mapping. - for (_simplex_key, simplex) in &mut simplices { - let simplex_uuid = simplex.uuid(); - if let Some(vertex_uuids) = simplex_vertices.get(&simplex_uuid) { - // Clear stale vertex keys from serialized data before rebuilding - // This prevents duplication: serialized keys + reconstructed keys - simplex.clear_vertex_keys(); - - // Convert vertex UUIDs to vertex keys - for &vertex_uuid in vertex_uuids { - if let Some(&vertex_key) = uuid_to_vertex_key.get(&vertex_uuid) { - simplex.push_vertex_key(vertex_key); - } else { - return Err(de::Error::custom(format!( - "Vertex UUID {vertex_uuid} referenced by simplex {simplex_uuid} not found in vertices" - ))); - } - } - } else { - return Err(de::Error::custom(format!( - "No vertex UUIDs found for simplex {simplex_uuid}" - ))); - } +struct SerializedTdsParts { + vertices: Vec>, + simplices: Vec>, + simplex_vertices: FastHashMap>, + simplex_vertex_offsets: FastHashMap>>, +} + +fn read_deserialized_tds_parts<'de, A, U, V, const D: usize>( + mut map: A, +) -> Result, A::Error> +where + A: MapAccess<'de>, + U: DataType, + V: DataType, +{ + let mut vertices: Option>> = None; + let mut simplices: Option>> = None; + let mut simplex_vertices: Option>> = None; + let mut simplex_vertex_offsets: Option>>> = None; + + while let Some(key) = map.next_key()? { + match key { + SerializedTdsField::Vertices => { + if vertices.is_some() { + return Err(de::Error::duplicate_field("vertices")); + } + vertices = Some(map.next_value()?); + } + SerializedTdsField::Simplices => { + if simplices.is_some() { + return Err(de::Error::duplicate_field("simplices")); } + simplices = Some(map.next_value()?); + } + SerializedTdsField::SimplexVertices => { + if simplex_vertices.is_some() { + return Err(de::Error::duplicate_field("simplex_vertices")); + } + simplex_vertices = Some(map.next_value()?); + } + SerializedTdsField::SimplexVertexOffsets => { + if simplex_vertex_offsets.is_some() { + return Err(de::Error::duplicate_field("simplex_vertex_offsets")); + } + simplex_vertex_offsets = Some(map.next_value()?); + } + } + } - // Rebuild incident_simplex mappings and neighbors since keys are not serialized. - let mut tds = Tds { - vertices, - simplices, - uuid_to_vertex_key, - uuid_to_simplex_key, - // Initialize construction state to default when deserializing - // Since only constructed triangulations should be serialized, - // we assume the deserialized triangulation is constructed - construction_state: TriangulationConstructionState::Constructed, - // Initialize generation counter to 0 when deserializing - // NOTE: Generation counter reset on deserialization means cache generation - // comparisons across serialize/deserialize boundaries will be invalidated. - // This ensures cached data from before serialization is not incorrectly - // considered valid after deserialization. - generation: Arc::new(AtomicU64::new(0)), - identity: Arc::new(Uuid::new_v4()), - }; + Ok(SerializedTdsParts { + vertices: vertices.ok_or_else(|| de::Error::missing_field("vertices"))?, + simplices: simplices.ok_or_else(|| de::Error::missing_field("simplices"))?, + simplex_vertices: simplex_vertices + .ok_or_else(|| de::Error::missing_field("simplex_vertices"))?, + simplex_vertex_offsets: simplex_vertex_offsets.unwrap_or_default(), + }) +} + +struct DeserializedVertexStorage { + vertices: StorageMap>, + uuid_to_vertex_key: FastHashMap, +} - // Rebuild topology; fail fast on any inconsistency. - // Order: neighbors first, then incident simplices (consistent with other call sites). - tds.assign_neighbors().map_err(de::Error::custom)?; - tds.assign_incident_simplices().map_err(de::Error::custom)?; +fn rebuild_deserialized_vertices( + serialized_vertices: Vec>, +) -> Result, E> +where + U: DataType, + E: de::Error, +{ + let mut vertices = StorageMap::with_capacity_and_key(serialized_vertices.len()); + let mut uuid_to_vertex_key = fast_hash_map_with_capacity(serialized_vertices.len()); - Ok(tds) + for vertex in serialized_vertices { + let vertex_uuid = vertex.uuid(); + match uuid_to_vertex_key.entry(vertex_uuid) { + std::collections::hash_map::Entry::Occupied(_) => { + return Err(de::Error::custom(format!( + "Duplicate vertex UUID {vertex_uuid} in serialized vertices" + ))); + } + std::collections::hash_map::Entry::Vacant(entry) => { + let vertex_key = vertices.insert(vertex); + entry.insert(vertex_key); } } - - const FIELDS: &[&str] = &["vertices", "simplices", "simplex_vertices"]; - deserializer.deserialize_struct("Tds", FIELDS, TdsVisitor(PhantomData)) } + + Ok(DeserializedVertexStorage { + vertices, + uuid_to_vertex_key, + }) } -// ============================================================================= -// SERDE HELPERS -// ============================================================================= +fn rebuild_deserialized_tds( + parts: SerializedTdsParts, +) -> Result, E> +where + U: DataType, + V: DataType, + E: de::Error, +{ + let DeserializedVertexStorage { + vertices, + uuid_to_vertex_key, + } = rebuild_deserialized_vertices(parts.vertices)?; + + let DeserializedSimplexStorage { + simplices, + uuid_to_simplex_key, + } = rebuild_deserialized_simplices( + &uuid_to_vertex_key, + parts.simplices, + &parts.simplex_vertices, + &parts.simplex_vertex_offsets, + )?; + + let mut tds = Tds { + vertices, + simplices, + uuid_to_vertex_key, + uuid_to_simplex_key, + construction_state: TriangulationConstructionState::Constructed, + generation: Arc::new(AtomicU64::new(0)), + identity: Arc::new(Uuid::new_v4()), + }; + + tds.assign_neighbors().map_err(de::Error::custom)?; + tds.assign_incident_simplices().map_err(de::Error::custom)?; + tds.validate().map_err(de::Error::custom)?; + + Ok(tds) +} -// UUID-to-key mappings are skipped during serialization and reconstructed during -// deserialization from the vertices and simplices data. +struct DeserializedSimplexStorage { + simplices: StorageMap>, + uuid_to_simplex_key: FastHashMap, +} + +fn rebuild_deserialized_simplices( + uuid_to_vertex_key: &FastHashMap, + serialized_simplices: Vec>, + simplex_vertices: &FastHashMap>, + simplex_vertex_offsets: &FastHashMap>>, +) -> Result, E> +where + V: DataType, + E: de::Error, +{ + let mut simplices = StorageMap::with_capacity_and_key(serialized_simplices.len()); + let mut uuid_to_simplex_key = fast_hash_map_with_capacity(serialized_simplices.len()); + + // Slotmap keys are storage-local, so serialized simplex records remain DTOs + // until their vertex UUIDs resolve against the freshly deserialized vertices. + for serialized_simplex in serialized_simplices { + let simplex_uuid = serialized_simplex.uuid; + let vertex_uuids = simplex_vertices.get(&simplex_uuid).ok_or_else(|| { + de::Error::custom(format!("No vertex UUIDs found for simplex {simplex_uuid}")) + })?; + + let vertex_keys = vertex_uuids + .iter() + .map(|&vertex_uuid| { + uuid_to_vertex_key.get(&vertex_uuid).copied().ok_or_else(|| { + de::Error::custom(format!( + "Vertex UUID {vertex_uuid} referenced by simplex {simplex_uuid} not found in vertices" + )) + }) + }) + .collect::, E>>()?; + + let mut simplex = + Simplex::try_new_with_uuid(vertex_keys, simplex_uuid, serialized_simplex.data) + .map_err(de::Error::custom)?; + if let Some(offsets) = simplex_vertex_offsets.get(&simplex_uuid) { + let offsets = parse_deserialized_periodic_offsets::(simplex_uuid, offsets)?; + simplex + .set_periodic_vertex_offsets(offsets) + .map_err(de::Error::custom)?; + } + match uuid_to_simplex_key.entry(simplex_uuid) { + std::collections::hash_map::Entry::Occupied(_) => { + return Err(de::Error::custom(format!( + "Duplicate simplex UUID {simplex_uuid} in serialized simplices" + ))); + } + std::collections::hash_map::Entry::Vacant(entry) => { + let simplex_key = simplices.insert(simplex); + entry.insert(simplex_key); + } + } + } + + for &simplex_uuid in simplex_vertices.keys() { + if !uuid_to_simplex_key.contains_key(&simplex_uuid) { + return Err(de::Error::custom(format!( + "Vertex UUID mapping provided for unknown simplex {simplex_uuid}" + ))); + } + } + for &simplex_uuid in simplex_vertex_offsets.keys() { + if !uuid_to_simplex_key.contains_key(&simplex_uuid) { + return Err(de::Error::custom(format!( + "Periodic offset mapping provided for unknown simplex {simplex_uuid}" + ))); + } + } + + Ok(DeserializedSimplexStorage { + simplices, + uuid_to_simplex_key, + }) +} + +fn parse_deserialized_periodic_offsets( + simplex_uuid: Uuid, + offsets: &[Vec], +) -> Result, E> +where + E: de::Error, +{ + offsets + .iter() + .enumerate() + .map(|(offset_index, offset)| { + offset.clone().try_into().map_err(|offset: Vec| { + de::Error::custom(format!( + "Periodic offset {offset_index} for simplex {simplex_uuid} has dimension {}, expected {D}", + offset.len() + )) + }) + }) + .collect() +} // ============================================================================= // TESTS @@ -7670,18 +7755,14 @@ mod tests { use crate::builder::DelaunayTriangulationBuilder; use crate::core::algorithms::flips::DelaunayRepairError; use crate::core::algorithms::incremental_insertion::InsertionError; - use crate::core::collections::NeighborBuffer; use crate::core::facet::FacetError; use crate::core::simplex::Simplex; use crate::core::util::uuid::UuidValidationError; use crate::core::validation::TriangulationValidationError; - use crate::core::vertex::VertexBuilder; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::Coordinate; use crate::repair::DelaunayRepairOperation; use crate::topology::characteristics::euler::TopologyClassification; use crate::validation::DelaunayTriangulationValidationError; - use crate::vertex; use slotmap::KeyData; use std::assert_matches; use std::sync::Arc; @@ -7690,47 +7771,56 @@ mod tests { // TEST HELPER FUNCTIONS // ============================================================================= - /// Test helper for a vertex with a specific UUID for collision testing. - /// This is only used in tests to create specific scenarios. - #[cfg(test)] - fn vertex_with_uuid( - point: Point, + fn vertex_with_uuid( + point: Point, uuid: Uuid, data: Option, - ) -> Vertex + ) -> Vertex where - T: CoordinateScalar, U: DataType, { - let mut vertex = data.map_or_else( - || { - VertexBuilder::default() - .point(point) - .build() - .expect("Failed to build vertex") - }, - |data_value| { - VertexBuilder::default() - .point(point) - .data(data_value) - .build() - .expect("Failed to build vertex") - }, - ); - - vertex.set_uuid(uuid).expect("Failed to set UUID"); - vertex + Vertex::try_new_with_uuid(point, uuid, data).expect("Failed to build vertex") } - fn initial_simplex_vertices_3d() -> [Vertex; 4] { + fn initial_simplex_vertices_3d() -> [Vertex<(), 3>; 4] { [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ] } + fn periodic_offset_tds_2d() -> (Tds<(), (), 2>, Uuid, Vec<[i8; 2]>) { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let offsets = vec![[0_i8, 0_i8], [1_i8, 0_i8], [0_i8, 1_i8]]; + let mut simplex = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); + simplex + .set_periodic_vertex_offsets(offsets.clone()) + .unwrap(); + let simplex_uuid = simplex.uuid(); + tds.insert_simplex_with_mapping(simplex).unwrap(); + tds.construction_state = TriangulationConstructionState::Constructed; + tds.assign_neighbors().unwrap(); + tds.assign_incident_simplices().unwrap(); + (tds, simplex_uuid, offsets) + } + fn assert_tds_error_kind(source: &TdsError, expected: TdsErrorKind) { assert_eq!(TdsErrorKind::from(source), expected); } @@ -8010,13 +8100,27 @@ mod tests { #[test] fn test_facet_key_for_simplex_facet_maps_periodic_derivation_errors() { - let mut tds: Tds = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); tds.simplex_mut(simplex_key) .unwrap() @@ -8034,13 +8138,27 @@ mod tests { #[test] fn test_facet_vertex_identities_anchor_uses_lexicographic_key_offset() { - let mut tds: Tds = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); // Deliberately corrupt for regression coverage: duplicate v_a with a smaller @@ -8055,7 +8173,7 @@ mod tests { let simplex = tds.simplex(simplex_key).unwrap(); let identities = - Tds::::facet_vertex_identities_in_simplex_order(simplex, 2).unwrap(); + Tds::<(), (), 2>::facet_vertex_identities_in_simplex_order(simplex, 2).unwrap(); assert_eq!(identities.len(), 3); let mut offsets_for_a: Vec<[i16; 2]> = identities @@ -8074,33 +8192,60 @@ mod tests { #[test] fn test_facet_permutation_parity_derives_coherent_and_incoherent_cases() { - let mut tds: Tds = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); // Shared edge for facet_idx=2 is (v_a, v_b). - let simplex: Simplex = Simplex::new(vec![v_a, v_b, v_c], None).unwrap(); + let simplex: Simplex<(), 2> = + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(); // Coherent case: neighbor lists the shared edge in opposite order. - let coherent_neighbor: Simplex = - Simplex::new(vec![v_b, v_a, v_d], None).unwrap(); + let coherent_neighbor: Simplex<(), 2> = + Simplex::try_new_with_data(vec![v_b, v_a, v_d], None).unwrap(); let coherent_mirror_idx = simplex.mirror_facet_index(2, &coherent_neighbor).unwrap(); let (currently_coherent, observed_odd_permutation, expected_odd_permutation) = - Tds::facet_permutation_parity(&simplex, 2, &coherent_neighbor, coherent_mirror_idx) - .unwrap(); + Tds::<(), (), 2>::facet_permutation_parity( + &simplex, + 2, + &coherent_neighbor, + coherent_mirror_idx, + ) + .unwrap(); assert!(currently_coherent); assert!(observed_odd_permutation); assert!(expected_odd_permutation); // Incoherent case: neighbor lists the shared edge in the same order. - let incoherent_neighbor: Simplex = - Simplex::new(vec![v_a, v_b, v_d], None).unwrap(); + let incoherent_neighbor: Simplex<(), 2> = + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(); let incoherent_mirror_idx = simplex.mirror_facet_index(2, &incoherent_neighbor).unwrap(); let (currently_coherent, observed_odd_permutation, expected_odd_permutation) = - Tds::facet_permutation_parity(&simplex, 2, &incoherent_neighbor, incoherent_mirror_idx) - .unwrap(); + Tds::<(), (), 2>::facet_permutation_parity( + &simplex, + 2, + &incoherent_neighbor, + incoherent_mirror_idx, + ) + .unwrap(); assert!(!currently_coherent); assert!(!observed_odd_permutation); assert!(expected_odd_permutation); @@ -8108,48 +8253,70 @@ mod tests { #[test] fn test_facet_permutation_parity_smoke_4d() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 4> = Tds::empty(); let v_a = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_b = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_c = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v_d = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v_e = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v_f = tds - .insert_vertex_with_mapping(vertex!([1.0, 1.0, 1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0, 1.0]).unwrap(), + ) .unwrap(); // Shared 3-face for facet_idx=4 is [v_a, v_b, v_c, v_d]. - let simplex: Simplex = - Simplex::new(vec![v_a, v_b, v_c, v_d, v_e], None).unwrap(); + let simplex: Simplex<(), 4> = + Simplex::try_new_with_data(vec![v_a, v_b, v_c, v_d, v_e], None).unwrap(); // Coherent case: odd permutation of the shared face. - let coherent_neighbor: Simplex = - Simplex::new(vec![v_b, v_a, v_c, v_d, v_f], None).unwrap(); + let coherent_neighbor: Simplex<(), 4> = + Simplex::try_new_with_data(vec![v_b, v_a, v_c, v_d, v_f], None).unwrap(); let coherent_mirror_idx = simplex.mirror_facet_index(4, &coherent_neighbor).unwrap(); let (currently_coherent, observed_odd_permutation, expected_odd_permutation) = - Tds::facet_permutation_parity(&simplex, 4, &coherent_neighbor, coherent_mirror_idx) - .unwrap(); + Tds::<(), (), 4>::facet_permutation_parity( + &simplex, + 4, + &coherent_neighbor, + coherent_mirror_idx, + ) + .unwrap(); assert!(currently_coherent); assert!(observed_odd_permutation); assert!(expected_odd_permutation); // Incoherent case: identity ordering of the shared face. - let incoherent_neighbor: Simplex = - Simplex::new(vec![v_a, v_b, v_c, v_d, v_f], None).unwrap(); + let incoherent_neighbor: Simplex<(), 4> = + Simplex::try_new_with_data(vec![v_a, v_b, v_c, v_d, v_f], None).unwrap(); let incoherent_mirror_idx = simplex.mirror_facet_index(4, &incoherent_neighbor).unwrap(); let (currently_coherent, observed_odd_permutation, expected_odd_permutation) = - Tds::facet_permutation_parity(&simplex, 4, &incoherent_neighbor, incoherent_mirror_idx) - .unwrap(); + Tds::<(), (), 4>::facet_permutation_parity( + &simplex, + 4, + &incoherent_neighbor, + incoherent_mirror_idx, + ) + .unwrap(); assert!(!currently_coherent); assert!(!observed_odd_permutation); assert!(expected_odd_permutation); @@ -8159,74 +8326,12 @@ mod tests { // VERTEX ADDITION TESTS - CONSOLIDATED // ============================================================================= - #[test] - fn test_repair_degenerate_simplices() { - // Exercise the repair primitive by creating a simplex with a neighbor pointer - // to a missing simplex key. - - let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - ]; - let mut tds: Tds = Tds::empty(); - - let v_keys: Vec<_> = vertices - .iter() - .copied() - .map(|v| tds.insert_vertex_with_mapping(v).unwrap()) - .collect(); - - // A valid simplex that should remain after repair. - let good_simplex = Simplex::new(vec![v_keys[0], v_keys[1], v_keys[2]], None).unwrap(); - let good_simplex_key = tds.insert_simplex_with_mapping(good_simplex).unwrap(); - - // A second simplex that will be made degenerate. - let bad_simplex = Simplex::new(vec![v_keys[0], v_keys[1], v_keys[3]], None).unwrap(); - let bad_simplex_key = tds.insert_simplex_with_mapping(bad_simplex).unwrap(); - - // Insert and then remove a third simplex so we get a real SimplexKey that no longer exists. - // Removing *after* inserting bad_simplex avoids key reuse affecting the test. - let removed_target_simplex = - Simplex::new(vec![v_keys[1], v_keys[2], v_keys[3]], None).unwrap(); - let removed_target_key = tds - .insert_simplex_with_mapping(removed_target_simplex) - .unwrap(); - assert_eq!(tds.remove_simplices_by_keys(&[removed_target_key]), 1); - - // Inject a dangling neighbor pointer using simplex_mut() (violates invariants deliberately). - { - let bad_simplex_mut = tds.simplex_mut(bad_simplex_key).unwrap(); - let mut neighbors = NeighborBuffer::new(); - neighbors.push(Some(removed_target_key)); - neighbors.push(None); - neighbors.push(None); - bad_simplex_mut.set_neighbors_from_keys(neighbors).unwrap(); - } - - let removed_count = tds.repair_degenerate_simplices(); - assert_eq!( - removed_count, 1, - "Expected exactly 1 degenerate simplex removed (bad_simplex with dangling neighbor), got {removed_count}", - ); - - assert_eq!(tds.number_of_simplices(), 1); - assert!(tds.simplices.contains_key(good_simplex_key)); - assert!(!tds.simplices.contains_key(bad_simplex_key)); - - assert_eq!(tds.repair_degenerate_simplices(), 0); - assert!(tds.is_valid().is_ok()); - - println!("✓ repair_degenerate_simplices removes simplices with dangling neighbor pointers"); - } - #[test] fn test_add_vertex_basic_insertion_succeeds() { let initial_vertices = initial_simplex_vertices_3d(); let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap(); - let vertex = vertex!([1.0, 2.0, 3.0]); + let vertex = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let result = dt.insert(vertex); assert!(result.is_ok(), "Basic vertex addition should succeed"); @@ -8238,15 +8343,15 @@ mod tests { let initial_vertices = initial_simplex_vertices_3d(); let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap(); - let vertex = vertex!([1.0, 2.0, 3.0]); - let duplicate = vertex!([1.0, 2.0, 3.0]); + let vertex = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); + let duplicate = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); dt.insert(vertex).unwrap(); - // Same coordinates again (distinct UUID, constructed via vertex! macro) + // Same coordinates again (distinct UUID, constructed via Vertex smart constructors) let result = dt.insert(duplicate); assert!( matches!(result, Err(InsertionError::DuplicateCoordinates { .. })), - "insert() should reject duplicate coordinates created via vertex! (before UUID), got: {result:?}" + "insert() should reject duplicate coordinates created via Vertex::try_new (before UUID), got: {result:?}" ); } @@ -8255,11 +8360,11 @@ mod tests { let initial_vertices = initial_simplex_vertices_3d(); let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap(); - let vertex1 = vertex!([1.0, 2.0, 3.0]); + let vertex1 = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let uuid1 = vertex1.uuid(); dt.insert(vertex1).unwrap(); - let vertex2 = vertex_with_uuid(Point::new([4.0, 5.0, 6.0]), uuid1, None); + let vertex2 = vertex_with_uuid(Point::from_validated_coords([4.0, 5.0, 6.0]), uuid1, None); let result = dt.insert(vertex2); assert!( matches!( @@ -8279,7 +8384,7 @@ mod tests { let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap(); let initial_simplex_count = dt.number_of_simplices(); - let new_vertex = vertex!([0.5, 0.5, 0.5]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(); dt.insert(new_vertex).unwrap(); assert_eq!(dt.number_of_vertices(), 5); @@ -8298,7 +8403,7 @@ mod tests { let initial_vertices = initial_simplex_vertices_3d(); let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap(); - let vertex = vertex!([1.0, 2.0, 3.0]); + let vertex = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let uuid = vertex.uuid(); dt.insert(vertex).unwrap(); @@ -8340,10 +8445,10 @@ mod tests { // Test that remove_vertex properly clears dangling neighbor references // Create a triangulation with multiple simplices let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), - vertex!([1.5, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.5, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -8353,7 +8458,7 @@ mod tests { assert_eq!(initial_vertices, 4); assert!(initial_simplices > 0); - // Get a vertex to remove (not a corner vertex, to ensure we have remaining simplices) + // Get a vertex to remove (not a corner to ensure we have remaining simplices) let (vertex_key, vertex_ref) = dt.vertices().next().unwrap(); let vertex_uuid = vertex_ref.uuid(); @@ -8413,9 +8518,9 @@ mod tests { fn test_remove_vertex_nonexistent() { // Test removing a vertex that doesn't exist let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -8448,10 +8553,10 @@ mod tests { // With the VertexKey API, callers can hold a key after removal and reuse it. // Double-remove must be a no-op returning Ok(0). let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -8481,10 +8586,10 @@ mod tests { // 2D test { let vertices_2d = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt_2d: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_2d).unwrap(); @@ -8497,11 +8602,11 @@ mod tests { // 3D test { let vertices_3d = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(), ]; let mut dt_3d: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices_3d).unwrap(); @@ -8524,12 +8629,12 @@ mod tests { // 4D test { let vertices_4d = [ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), ]; let mut dt_4d: DelaunayTriangulation<_, (), (), 4> = DelaunayTriangulation::new(&vertices_4d).unwrap(); @@ -8560,12 +8665,12 @@ mod tests { // 3. All remaining incident_simplex pointers are valid let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // Interior vertex to remove; offset from circumcenter to avoid degenerate configuration - vertex!([0.2, 0.2, 0.2]), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -8655,11 +8760,11 @@ mod tests { fn test_find_simplices_containing_vertex() { // Test the helper function that finds all simplices containing a specific vertex let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), // Interior vertex + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), // Interior vertex ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -8720,13 +8825,25 @@ mod tests { #[test] fn test_assign_neighbors_errors_on_missing_vertex_key() { - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); // Corrupt the simplex by inserting a vertex key that doesn't exist in the TDS. @@ -8742,21 +8859,43 @@ mod tests { #[test] fn test_assign_neighbors_errors_on_non_manifold_facet_sharing() { // Three triangles sharing the same edge (v_a,v_b) is non-manifold in 2D. - let mut tds: Tds = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + ) + .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) + .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) + .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v_a, v_b, v_e], None).unwrap(), + Simplex::try_new_with_data(vec![v_a, v_b, v_e], None).unwrap(), ) .unwrap(); @@ -8766,7 +8905,7 @@ mod tests { #[test] fn test_remove_simplices_by_keys_empty_is_noop() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); let gen_before = tds.generation(); assert_eq!(tds.remove_simplices_by_keys(&[]), 0); assert_eq!(tds.generation(), gen_before); @@ -8775,17 +8914,33 @@ mod tests { #[test] fn test_remove_simplices_by_keys_clears_neighbor_pointers() { // Two triangles sharing an edge. - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let simplex2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![b, d, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![b, d, c], None).unwrap()) .unwrap(); // Build neighbor pointers based on facet sharing. @@ -8817,18 +8972,34 @@ mod tests { fn test_remove_simplices_by_keys_repairs_incident_simplices_for_affected_vertices() { // Two triangles sharing an edge (B,C). Remove one and ensure incident_simplex pointers are // updated without requiring a full assign_incident_simplices() rebuild. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let simplex2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![b, d, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![b, d, c], None).unwrap()) .unwrap(); tds.assign_neighbors().unwrap(); @@ -8876,14 +9047,14 @@ mod tests { #[test] fn test_tds_remove_vertex_returns_zero_when_vertex_not_in_mapping() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); let missing_key = VertexKey::from(KeyData::from_ffi(u64::MAX)); assert_eq!(tds.remove_vertex(missing_key).unwrap(), 0); } #[test] fn test_remove_isolated_vertex_noop_on_missing_key() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); let missing = VertexKey::from(KeyData::from_ffi(u64::MAX)); let gen_before = tds.generation(); tds.remove_isolated_vertex(missing); @@ -8892,8 +9063,12 @@ mod tests { #[test] fn test_remove_isolated_vertex_noop_when_incident_simplex_set() { - let mut tds: Tds = Tds::empty(); - let vk = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let vk = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); // Manually set an incident simplex so the vertex appears non-isolated. let fake_simplex_key = SimplexKey::from(KeyData::from_ffi(1)); @@ -8913,8 +9088,12 @@ mod tests { #[test] fn test_remove_isolated_vertex_removes_truly_isolated() { - let mut tds: Tds = Tds::empty(); - let vk = tds.insert_vertex_with_mapping(vertex!([1.0, 2.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let vk = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), + ) + .unwrap(); let uuid = tds.vertex(vk).unwrap().uuid(); // No incident simplex set → truly isolated. @@ -8936,21 +9115,37 @@ mod tests { // - simplex1 is removed // - neighbor back-references in simplex2 are cleared // - incident_simplex pointers remain valid for remaining vertices without a full rebuild - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let origin_key = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let east_key = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let north_key = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let diagonal_key = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let origin_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let east_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let north_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let diagonal_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1 = tds .insert_simplex_with_mapping( - Simplex::new(vec![origin_key, east_key, north_key], None).unwrap(), + Simplex::try_new_with_data(vec![origin_key, east_key, north_key], None).unwrap(), ) .unwrap(); let simplex2 = tds .insert_simplex_with_mapping( - Simplex::new(vec![east_key, diagonal_key, north_key], None).unwrap(), + Simplex::try_new_with_data(vec![east_key, diagonal_key, north_key], None).unwrap(), ) .unwrap(); @@ -9001,7 +9196,7 @@ mod tests { #[test] fn test_find_neighbors_by_key_returns_none_buffer_for_missing_simplex() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); let missing = SimplexKey::from(KeyData::from_ffi(u64::MAX)); let neighbors = tds.find_neighbors_by_key(missing); assert_eq!(neighbors.len(), 3); @@ -9010,21 +9205,45 @@ mod tests { #[test] fn test_set_neighbors_by_key_rejects_non_neighbor_and_wrong_slot() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let simplex1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); // A simplex that shares only one vertex with simplex1 => not a neighbor in 2D. let simplex_far = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_d, v_e], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_d, v_e], None).unwrap(), + ) .unwrap(); let err = tds .set_neighbors_by_key(simplex1, &[Some(simplex_far), None, None]) @@ -9034,7 +9253,9 @@ mod tests { // A true facet-neighbor (shares {v_a,v_b}) placed at the wrong facet index. let simplex2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_b, v_a, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_b, v_a, v_d], None).unwrap(), + ) .unwrap(); let err = tds .set_neighbors_by_key(simplex1, &[Some(simplex2), None, None]) @@ -9045,18 +9266,38 @@ mod tests { #[test] fn test_set_neighbors_by_key_updates_reciprocal_back_reference() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); tds.set_neighbors_by_key(simplex1, &[None, None, Some(simplex2)]) @@ -9077,18 +9318,38 @@ mod tests { #[test] fn test_set_neighbors_by_key_rejects_unpaired_interior_facet() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) - .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v_b, v_a, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_b, v_a, v_d], None).unwrap(), + ) + .unwrap(); tds.assign_neighbors().unwrap(); let err = tds @@ -9105,18 +9366,38 @@ mod tests { #[test] fn test_build_simplex_vertex_sets_success() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_c, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_c, v_d], None).unwrap(), + ) .unwrap(); let map = tds.build_simplex_vertex_sets().unwrap(); @@ -9131,14 +9412,28 @@ mod tests { #[test] fn test_build_simplex_vertex_sets_errors_on_missing_vertex_key() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); // Corrupt the simplex by inserting a vertex key that doesn't exist in the TDS. @@ -9153,18 +9448,38 @@ mod tests { #[test] fn test_validate_shared_facet_count_ok_for_true_neighbors() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9174,7 +9489,7 @@ mod tests { let neighbor_vertices: VertexKeySet = [v_a, v_b, v_d].into_iter().collect(); assert!( - Tds::validate_shared_facet_count( + Tds::<(), (), 2>::validate_shared_facet_count( simplex1, simplex2, &this_vertices, @@ -9186,19 +9501,43 @@ mod tests { #[test] fn test_validate_shared_facet_count_errors_for_non_neighbors() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex_far_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_d, v_e], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_d, v_e], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9210,9 +9549,13 @@ mod tests { let simplex1_uuid = simplex1.uuid(); let simplex_far_uuid = simplex_far.uuid(); - let err = - Tds::validate_shared_facet_count(simplex1, simplex_far, &this_vertices, &far_vertices) - .unwrap_err(); + let err = Tds::<(), (), 2>::validate_shared_facet_count( + simplex1, + simplex_far, + &this_vertices, + &far_vertices, + ) + .unwrap_err(); assert_matches!( err, @@ -9223,19 +9566,39 @@ mod tests { #[test] fn test_expected_mirror_facet_index_returns_unique_vertex_index() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); // Put the unique vertex at index 0 to ensure we test the returned index. let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_d, v_a, v_b], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_d, v_a, v_b], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9243,26 +9606,51 @@ mod tests { let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect(); - let idx = Tds::expected_mirror_facet_index(simplex1, simplex2, &this_vertices).unwrap(); + let idx = Tds::<(), (), 2>::expected_mirror_facet_index(simplex1, simplex2, &this_vertices) + .unwrap(); assert_eq!(idx, 0); } #[test] fn test_expected_mirror_facet_index_errors_when_ambiguous() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_e = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); // Shares only v_a -> differs by 2 vertices -> ambiguous mirror facet. let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_d, v_e], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_d, v_e], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9270,7 +9658,8 @@ mod tests { let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect(); - let err = Tds::expected_mirror_facet_index(simplex1, simplex2, &this_vertices).unwrap_err(); + let err = Tds::<(), (), 2>::expected_mirror_facet_index(simplex1, simplex2, &this_vertices) + .unwrap_err(); assert_matches!( err, @@ -9282,19 +9671,33 @@ mod tests { #[test] fn test_expected_mirror_facet_index_errors_when_duplicate_simplices() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); // Duplicate by vertices (different UUID) -> no unique vertex to identify mirror facet. let simplex2_key = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v_a, v_b, v_c], None).unwrap(), + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), ) .unwrap(); @@ -9303,7 +9706,8 @@ mod tests { let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect(); - let err = Tds::expected_mirror_facet_index(simplex1, simplex2, &this_vertices).unwrap_err(); + let err = Tds::<(), (), 2>::expected_mirror_facet_index(simplex1, simplex2, &this_vertices) + .unwrap_err(); assert_matches!( err, @@ -9315,18 +9719,38 @@ mod tests { #[test] fn test_verified_mirror_facet_index_ok() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9336,24 +9760,45 @@ mod tests { // Shared edge is (v_a, v_b). In simplex1, that's opposite vertex index 2 (v_c). let mirror_idx = - Tds::verified_mirror_facet_index(simplex1, 2, simplex2, &this_vertices).unwrap(); + Tds::<(), (), 2>::verified_mirror_facet_index(simplex1, 2, simplex2, &this_vertices) + .unwrap(); assert_eq!(mirror_idx, 2); } #[test] fn test_verified_mirror_facet_index_errors_when_no_shared_facet() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9363,7 +9808,8 @@ mod tests { // facet_idx=0 corresponds to edge (v_b, v_c) in simplex1, which is not shared with simplex2. let err = - Tds::verified_mirror_facet_index(simplex1, 0, simplex2, &this_vertices).unwrap_err(); + Tds::<(), (), 2>::verified_mirror_facet_index(simplex1, 0, simplex2, &this_vertices) + .unwrap_err(); assert_matches!( err, @@ -9375,18 +9821,38 @@ mod tests { #[test] fn test_verified_mirror_facet_index_errors_on_mismatch_with_cross_check() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9396,8 +9862,13 @@ mod tests { // This is a unit-level test of the helper's defensive cross-check behavior. let this_vertices_wrong: VertexKeySet = [v_a, v_c, v_d].into_iter().collect(); - let err = Tds::verified_mirror_facet_index(simplex1, 2, simplex2, &this_vertices_wrong) - .unwrap_err(); + let err = Tds::<(), (), 2>::verified_mirror_facet_index( + simplex1, + 2, + simplex2, + &this_vertices_wrong, + ) + .unwrap_err(); assert_matches!( err, @@ -9416,21 +9887,37 @@ mod tests { // with the simplex's actual vertex buffer (e.g., a bug/corruption in the precompute step). To // simulate that scenario deterministically, we build the map normally and then corrupt the // entry for one simplex before running the validation loop. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let origin_key = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let east_key = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let north_key = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let diagonal_key = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let origin_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let east_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let north_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let diagonal_key = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds .insert_simplex_with_mapping( - Simplex::new(vec![origin_key, east_key, north_key], None).unwrap(), + Simplex::try_new_with_data(vec![origin_key, east_key, north_key], None).unwrap(), ) .unwrap(); let _simplex2_key = tds .insert_simplex_with_mapping( - Simplex::new(vec![origin_key, east_key, diagonal_key], None).unwrap(), + Simplex::try_new_with_data(vec![origin_key, east_key, diagonal_key], None).unwrap(), ) .unwrap(); @@ -9458,18 +9945,38 @@ mod tests { #[test] fn test_validate_shared_facet_vertices_ok() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9479,7 +9986,7 @@ mod tests { let neighbor_vertices: VertexKeySet = [v_a, v_b, v_d].into_iter().collect(); assert!( - Tds::validate_shared_facet_vertices( + Tds::<(), (), 2>::validate_shared_facet_vertices( simplex1, 2, // opposite v_c => shared edge {v_a,v_b} simplex2, @@ -9493,18 +10000,38 @@ mod tests { #[test] fn test_validate_shared_facet_vertices_errors_when_mirror_index_wrong() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); let simplex1 = tds.simplex(simplex1_key).unwrap(); @@ -9514,8 +10041,8 @@ mod tests { let neighbor_vertices: VertexKeySet = [v_a, v_b, v_d].into_iter().collect(); // mirror_idx=0 is intentionally wrong here; it treats vertex v_a as the "opposite" - // vertex, which makes v_d incorrectly part of the "shared facet". - let err = Tds::validate_shared_facet_vertices( + // which makes v_d incorrectly part of the "shared facet". + let err = Tds::<(), (), 2>::validate_shared_facet_vertices( simplex1, 2, // correct for simplex1 simplex2, @@ -9535,18 +10062,38 @@ mod tests { #[test] fn test_validate_mutual_neighbor_back_reference_ok() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); // Build neighbor pointers so mutual back-references exist. @@ -9556,7 +10103,7 @@ mod tests { let simplex2 = tds.simplex(simplex2_key).unwrap(); assert!( - Tds::validate_mutual_neighbor_back_reference( + Tds::<(), (), 2>::validate_mutual_neighbor_back_reference( simplex1_key, simplex1, 2, // opposite v_c => shared edge {v_a,v_b} @@ -9564,31 +10111,51 @@ mod tests { simplex2, 2, // opposite v_d => shared edge {v_a,v_b} ) - .is_ok() - ); - } - - #[test] - fn test_validate_mutual_neighbor_back_reference_errors_when_neighbor_has_no_neighbors() { - let mut tds: Tds = Tds::empty(); - - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + .is_ok() + ); + } + + #[test] + fn test_validate_mutual_neighbor_back_reference_errors_when_neighbor_has_no_neighbors() { + let mut tds: Tds<(), (), 2> = Tds::empty(); + + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); // NOTE: We intentionally do NOT call assign_neighbors(), so neighbor_simplex.neighbors is None. let simplex1 = tds.simplex(simplex1_key).unwrap(); let simplex2 = tds.simplex(simplex2_key).unwrap(); - let err = Tds::validate_mutual_neighbor_back_reference( + let err = Tds::<(), (), 2>::validate_mutual_neighbor_back_reference( simplex1_key, simplex1, 2, @@ -9608,18 +10175,38 @@ mod tests { #[test] fn test_validate_mutual_neighbor_back_reference_errors_when_back_reference_missing() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); // Build neighbor pointers first, then deliberately corrupt the back-reference. @@ -9637,7 +10224,7 @@ mod tests { let simplex1 = tds.simplex(simplex1_key).unwrap(); let simplex2 = tds.simplex(simplex2_key).unwrap(); - let err = Tds::validate_mutual_neighbor_back_reference( + let err = Tds::<(), (), 2>::validate_mutual_neighbor_back_reference( simplex1_key, simplex1, 2, @@ -9657,14 +10244,28 @@ mod tests { #[test] fn test_orientation_validation_rejects_non_periodic_self_neighbor() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); { @@ -9675,7 +10276,7 @@ mod tests { } let simplex = tds.simplex(simplex_key).unwrap(); - assert!(!Tds::allows_periodic_self_neighbor(simplex)); + assert!(!Tds::<(), (), 2>::allows_periodic_self_neighbor(simplex)); let err = tds.validate_coherent_orientation().unwrap_err(); assert_matches!( @@ -9698,14 +10299,28 @@ mod tests { #[test] fn test_orientation_validation_allows_periodic_self_neighbor() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); { @@ -9719,7 +10334,7 @@ mod tests { } let simplex = tds.simplex(simplex_key).unwrap(); - assert!(Tds::allows_periodic_self_neighbor(simplex)); + assert!(Tds::<(), (), 2>::allows_periodic_self_neighbor(simplex)); assert!(tds.validate_coherent_orientation().is_ok()); assert!(tds.is_coherently_oriented()); assert!(tds.normalize_coherent_orientation().is_ok()); @@ -9727,18 +10342,38 @@ mod tests { #[test] fn test_orientation_validation_rejects_one_sided_periodic_neighbor() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(), + ) .unwrap(); { @@ -9782,13 +10417,27 @@ mod tests { #[test] fn test_boundary_facet_validation_rejects_unassigned_neighbor_slot() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); tds.assign_neighbors().unwrap(); let facet_to_simplices = tds.build_facet_to_simplices_map().unwrap(); @@ -9816,13 +10465,27 @@ mod tests { #[test] fn test_boundary_facet_validation_rejects_non_periodic_self_neighbor() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); tds.assign_neighbors().unwrap(); let facet_to_simplices = tds.build_facet_to_simplices_map().unwrap(); @@ -9850,18 +10513,38 @@ mod tests { #[test] fn test_orientation_validation_rejects_one_way_neighbor_pointer() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v_a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v_c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v_d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let simplex2_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v_b, v_a, v_d], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_b, v_a, v_d], None).unwrap(), + ) .unwrap(); tds.assign_neighbors().unwrap(); assert!(tds.validate_coherent_orientation().is_ok()); @@ -9900,18 +10583,38 @@ mod tests { #[test] fn test_local_orientation_validation_checks_neighbors_outside_scope() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([3.0, 3.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 3.0]).unwrap(), + ) + .unwrap(); let simplex1_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); tds.assign_neighbors().unwrap(); @@ -9923,14 +10626,28 @@ mod tests { #[test] fn test_local_orientation_validation_errors_on_missing_scope_simplex() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); assert_eq!(tds.remove_simplices_by_keys(&[simplex_key]), 1); @@ -9950,7 +10667,7 @@ mod tests { ($name:ident, $dim:literal) => { #[test] fn $name() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let mut vertex_keys = Vec::with_capacity($dim + 2); let mut seed = 1.0_f64; @@ -9964,7 +10681,12 @@ mod tests { seed += 1.0; } } - vertex_keys.push(tds.insert_vertex_with_mapping(vertex!(coords)).unwrap()); + vertex_keys.push( + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + ) + .unwrap(), + ); } // Construct two adjacent simplices that share a facet but induce the same shared-facet @@ -9976,10 +10698,10 @@ mod tests { vertex_keys.iter().take($dim).copied().collect(); simplex2_vertices.push(vertex_keys[$dim + 1]); - let simplex1: Simplex = - Simplex::new(simplex1_vertices, None).unwrap(); - let simplex2: Simplex = - Simplex::new(simplex2_vertices, None).unwrap(); + let simplex1: Simplex<(), $dim> = + Simplex::try_new_with_data(simplex1_vertices, None).unwrap(); + let simplex2: Simplex<(), $dim> = + Simplex::try_new_with_data(simplex2_vertices, None).unwrap(); tds.insert_simplex_with_mapping(simplex1).unwrap(); tds.insert_simplex_with_mapping(simplex2).unwrap(); @@ -10015,8 +10737,12 @@ mod tests { #[test] fn test_assign_incident_simplices_clears_incident_simplex_when_no_simplices() { - let mut tds: Tds = Tds::empty(); - let vkey = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let vkey = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); // Corrupt incident_simplex and ensure it gets cleared. tds.vertex_mut(vkey) @@ -10030,13 +10756,25 @@ mod tests { #[test] fn test_build_facet_to_simplices_map_errors_on_u8_facet_index_overflow() { - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); // NOTE: This scenario is not realistic for valid triangulations: @@ -10062,12 +10800,24 @@ mod tests { #[test] fn test_validate_vertex_and_simplex_mappings_detect_inconsistencies() { - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); // Start from a consistent state. @@ -10109,13 +10859,25 @@ mod tests { #[test] fn test_validate_simplex_vertex_keys_detects_missing_vertices() { - let mut tds: Tds = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let invalid_vkey = VertexKey::from(KeyData::from_ffi(u64::MAX)); @@ -10355,28 +11117,50 @@ mod tests { // Build a TDS with two triangles that have no neighbor wiring between them. // Since neither simplex's `neighbors` field is populated, BFS from either simplex // cannot reach the other → is_connected() must return false. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); // Component A - let a0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let a1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let a2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let a0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let a1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let a2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); // Component B (far away, no shared vertices) let b0 = tds - .insert_vertex_with_mapping(vertex!([10.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0]).unwrap(), + ) .unwrap(); let b1 = tds - .insert_vertex_with_mapping(vertex!([11.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 0.0]).unwrap(), + ) .unwrap(); let b2 = tds - .insert_vertex_with_mapping(vertex!([10.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 1.0]).unwrap(), + ) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![a0, a1, a2], None).unwrap()) - .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![b0, b1, b2], None).unwrap()) - .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a0, a1, a2], None).unwrap(), + ) + .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![b0, b1, b2], None).unwrap(), + ) + .unwrap(); // Two simplices with neighbors = None: BFS from the first simplex finds no edges // and can never visit the second. @@ -10390,6 +11174,108 @@ mod tests { // SERDE ROUND-TRIP TESTS // ========================================================================= + fn serialized_records<'a>( + json: &'a serde_json::Value, + field: &str, + ) -> Vec<&'a serde_json::Value> { + json.get(field) + .and_then(serde_json::Value::as_array) + .unwrap_or_else(|| panic!("serialized TDS should contain {field} records")) + .iter() + .inspect(|record| { + assert!( + record.get("value").is_none(), + "serialized TDS {field} records must not use slotmap value wrappers" + ); + }) + .collect() + } + + fn serialized_uuid_field(json: &serde_json::Value, field: &str) -> Uuid { + let uuid = json + .get(field) + .and_then(serde_json::Value::as_str) + .and_then(|value| Uuid::parse_str(value).ok()) + .unwrap_or_else(|| panic!("serialized record should contain {field} UUID")); + assert!(!uuid.is_nil(), "serialized {field} UUID should not be nil"); + uuid + } + + #[test] + fn test_tds_serialization_includes_stable_uuid_relationships() { + let vertices = [ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), + ]; + let dt = DelaunayTriangulation::new(&vertices).unwrap(); + let original = dt.tds().clone(); + let json = serde_json::to_value(&original).expect("serialize TDS to JSON value"); + + let vertex_uuids: Vec<_> = serialized_records(&json, "vertices") + .into_iter() + .map(|vertex| serialized_uuid_field(vertex, "uuid")) + .collect(); + assert_eq!(vertex_uuids.len(), original.number_of_vertices()); + for (_, vertex) in original.vertices() { + assert!(vertex_uuids.contains(&vertex.uuid())); + } + + let simplex_uuids: Vec<_> = serialized_records(&json, "simplices") + .into_iter() + .map(|simplex| { + assert!( + simplex.get("vertices").is_none(), + "serialized Simplex must not store slotmap VertexKey values" + ); + serialized_uuid_field(simplex, "uuid") + }) + .collect(); + assert_eq!(simplex_uuids.len(), original.number_of_simplices()); + for (_, simplex) in original.simplices() { + assert!(simplex_uuids.contains(&simplex.uuid())); + } + + let simplex_vertices = json + .get("simplex_vertices") + .and_then(serde_json::Value::as_object) + .expect("serialized TDS should contain simplex_vertices object"); + assert_eq!(simplex_vertices.len(), original.number_of_simplices()); + + for (simplex_uuid, serialized_vertex_uuids) in simplex_vertices { + let simplex_uuid = + Uuid::parse_str(simplex_uuid).expect("simplex_vertices keys should be UUIDs"); + assert!(simplex_uuids.contains(&simplex_uuid)); + let simplex_key = original + .simplex_key_from_uuid(&simplex_uuid) + .expect("serialized simplex UUID should resolve in original TDS"); + let expected_vertex_uuids = original + .simplex(simplex_key) + .expect("serialized simplex key should resolve") + .vertex_uuids(&original) + .expect("simplex vertex UUIDs should resolve"); + let serialized_vertex_uuids: Vec<_> = serialized_vertex_uuids + .as_array() + .expect("simplex_vertices values should be UUID arrays") + .iter() + .map(|vertex_uuid| { + let vertex_uuid = vertex_uuid + .as_str() + .and_then(|value| Uuid::parse_str(value).ok()) + .expect("simplex vertex reference should be a UUID string"); + assert!(vertex_uuids.contains(&vertex_uuid)); + vertex_uuid + }) + .collect(); + assert_eq!( + serialized_vertex_uuids.as_slice(), + expected_vertex_uuids.as_slice() + ); + } + } + #[test] fn test_serde_round_trip_preserves_tds_structure() { let verts = initial_simplex_vertices_3d(); @@ -10397,8 +11283,7 @@ mod tests { let original = dt.tds().clone(); let json = serde_json::to_string(&original).expect("serialize failed"); - let deserialized: Tds = - serde_json::from_str(&json).expect("deserialize failed"); + let deserialized: Tds<(), (), 3> = serde_json::from_str(&json).expect("deserialize failed"); assert_eq!( deserialized.number_of_vertices(), @@ -10417,18 +11302,18 @@ mod tests { fn test_serde_round_trip_multi_simplex_triangulation() { // 5 vertices in 3D → multiple tetrahedra let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let original = dt.tds().clone(); assert!(original.number_of_simplices() > 1); let json = serde_json::to_string(&original).unwrap(); - let deserialized: Tds = serde_json::from_str(&json).unwrap(); + let deserialized: Tds<(), (), 3> = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized, original); assert!(deserialized.is_valid().is_ok()); @@ -10439,40 +11324,325 @@ mod tests { #[test] fn test_serde_round_trip_2d() { let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); let original = dt.tds().clone(); let json = serde_json::to_string(&original).unwrap(); - let deserialized: Tds = serde_json::from_str(&json).unwrap(); + let deserialized: Tds<(), (), 2> = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized, original); assert!(deserialized.is_valid().is_ok()); } + #[test] + fn test_serde_round_trip_preserves_periodic_offsets() { + let (original, simplex_uuid, expected_offsets) = periodic_offset_tds_2d(); + let json = serde_json::to_value(&original).expect("serialize periodic-offset TDS"); + + let offset_records = json + .get("simplex_vertex_offsets") + .and_then(serde_json::Value::as_object) + .expect("serialized TDS should contain periodic offset map"); + let serialized_offsets = offset_records + .get(&simplex_uuid.to_string()) + .and_then(serde_json::Value::as_array) + .expect("serialized offset map should contain the simplex UUID"); + assert_eq!(serialized_offsets.len(), expected_offsets.len()); + + let deserialized: Tds<(), (), 2> = + serde_json::from_value(json).expect("deserialize periodic-offset TDS"); + let deserialized_simplex_key = deserialized + .simplex_key_from_uuid(&simplex_uuid) + .expect("simplex UUID should resolve after round-trip"); + let restored_offsets = deserialized + .simplex(deserialized_simplex_key) + .and_then(Simplex::periodic_vertex_offsets) + .expect("periodic offsets should survive TDS serde round-trip"); + + assert_eq!(restored_offsets, expected_offsets.as_slice()); + assert!(deserialized.is_valid().is_ok()); + } + + #[test] + fn test_deserialize_rejects_invalid_periodic_offset_mappings() { + let (original, simplex_uuid, _expected_offsets) = periodic_offset_tds_2d(); + let json = serde_json::to_value(&original).expect("serialize periodic-offset TDS"); + + let mut wrong_offset_count = json.clone(); + wrong_offset_count + .get_mut("simplex_vertex_offsets") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain periodic offset map") + .insert(simplex_uuid.to_string(), serde_json::json!([[0_i8, 0_i8]])); + let err = serde_json::from_value::>(wrong_offset_count) + .expect_err("wrong periodic offset count should be rejected"); + assert!( + err.to_string().contains("Periodic offset length mismatch"), + "unexpected error for wrong periodic offset count: {err}" + ); + + let mut wrong_offset_dimension = json.clone(); + wrong_offset_dimension + .get_mut("simplex_vertex_offsets") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain periodic offset map") + .insert( + simplex_uuid.to_string(), + serde_json::json!([[0_i8, 0_i8], [1_i8], [0_i8, 1_i8]]), + ); + let err = serde_json::from_value::>(wrong_offset_dimension) + .expect_err("wrong periodic offset dimension should be rejected"); + assert!( + err.to_string().contains("has dimension 1, expected 2"), + "unexpected error for wrong periodic offset dimension: {err}" + ); + + let mut unknown_simplex = json; + unknown_simplex + .get_mut("simplex_vertex_offsets") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain periodic offset map") + .insert( + Uuid::new_v4().to_string(), + serde_json::json!([[0_i8, 0_i8], [1_i8, 0_i8], [0_i8, 1_i8]]), + ); + let err = serde_json::from_value::>(unknown_simplex) + .expect_err("unknown periodic-offset simplex UUID should be rejected"); + assert!( + err.to_string().contains("unknown simplex"), + "unexpected error for unknown periodic-offset simplex UUID: {err}" + ); + } + + #[test] + fn test_deserialize_rejects_duplicate_vertex_uuids() { + let verts = initial_simplex_vertices_3d(); + let dt = DelaunayTriangulation::new(&verts).unwrap(); + let original = dt.tds().clone(); + let mut json = serde_json::to_value(&original).expect("serialize TDS to JSON value"); + + let vertex_records = json + .get_mut("vertices") + .and_then(serde_json::Value::as_array_mut) + .expect("serialized TDS should contain vertex records"); + assert!( + vertex_records + .iter() + .all(|record| record.get("value").is_none()), + "serialized vertices must not use slotmap value wrappers" + ); + let mut populated_vertices = vertex_records.iter_mut(); + let first_uuid = populated_vertices + .next() + .and_then(|vertex| vertex.get("uuid")) + .cloned() + .expect("first serialized vertex should contain uuid"); + populated_vertices + .next() + .and_then(|vertex| vertex.get_mut("uuid")) + .map(|uuid| *uuid = first_uuid) + .expect("second serialized vertex should contain uuid"); + + let err = serde_json::from_value::>(json) + .expect_err("duplicate serialized vertex UUIDs should be rejected"); + assert!( + err.to_string().contains("Duplicate vertex UUID"), + "unexpected error for duplicate vertex UUIDs: {err}" + ); + } + + #[test] + fn test_deserialize_rejects_extra_simplex_vertex_uuid_mapping() { + let verts = initial_simplex_vertices_3d(); + let dt = DelaunayTriangulation::new(&verts).unwrap(); + let original = dt.tds().clone(); + let mut json = serde_json::to_value(&original).expect("serialize TDS to JSON value"); + let (_, simplex) = original + .simplices() + .next() + .expect("single tetrahedron should have a simplex"); + let vertex_uuids = simplex + .vertex_uuids(&original) + .expect("simplex vertices should resolve"); + let unknown_simplex_uuid = Uuid::new_v4(); + + json.get_mut("simplex_vertices") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain simplex_vertices") + .insert( + unknown_simplex_uuid.to_string(), + serde_json::json!(vertex_uuids.to_vec()), + ); + + let err = serde_json::from_value::>(json) + .expect_err("extra simplex UUID mapping should be rejected"); + assert!( + err.to_string().contains("unknown simplex"), + "unexpected error for extra simplex UUID mapping: {err}" + ); + } + + #[test] + fn test_deserialize_rejects_invalid_simplex_vertex_uuid_mappings() { + let verts = initial_simplex_vertices_3d(); + let dt = DelaunayTriangulation::new(&verts).unwrap(); + let original = dt.tds().clone(); + let json = serde_json::to_value(&original).expect("serialize TDS to JSON value"); + let (_, simplex) = original + .simplices() + .next() + .expect("single tetrahedron should have a simplex"); + let simplex_uuid = simplex.uuid(); + let vertex_uuids = simplex + .vertex_uuids(&original) + .expect("simplex vertices should resolve"); + + let mut too_few_vertices = json.clone(); + too_few_vertices + .get_mut("simplex_vertices") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain simplex_vertices") + .insert( + simplex_uuid.to_string(), + serde_json::json!([vertex_uuids[0]]), + ); + let err = serde_json::from_value::>(too_few_vertices) + .expect_err("wrong simplex vertex count should be rejected"); + assert!( + err.to_string().contains("Insufficient vertices"), + "unexpected error for wrong vertex count: {err}" + ); + + let mut duplicate_vertex = json.clone(); + duplicate_vertex + .get_mut("simplex_vertices") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain simplex_vertices") + .insert( + simplex_uuid.to_string(), + serde_json::json!([ + vertex_uuids[0], + vertex_uuids[0], + vertex_uuids[0], + vertex_uuids[0] + ]), + ); + let err = serde_json::from_value::>(duplicate_vertex) + .expect_err("duplicate simplex vertex UUIDs should be rejected"); + assert!( + err.to_string().contains("Duplicate vertices"), + "unexpected error for duplicate vertex UUIDs: {err}" + ); + + let mut unknown_vertex = json; + let unknown_uuid = Uuid::new_v4(); + unknown_vertex + .get_mut("simplex_vertices") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain simplex_vertices") + .insert( + simplex_uuid.to_string(), + serde_json::json!([ + vertex_uuids[0], + vertex_uuids[1], + vertex_uuids[2], + unknown_uuid + ]), + ); + let err = serde_json::from_value::>(unknown_vertex) + .expect_err("unknown vertex UUID should be rejected"); + assert!( + err.to_string().contains("not found in vertices"), + "unexpected error for unknown vertex UUID: {err}" + ); + } + + #[test] + fn test_deserialize_rejects_duplicate_simplex_vertex_uuid_sets() { + let vertices = [ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), + ]; + let dt = DelaunayTriangulation::new(&vertices).unwrap(); + let original = dt.tds().clone(); + assert!(original.number_of_simplices() > 1); + let mut json = serde_json::to_value(&original).expect("serialize TDS to JSON value"); + + let simplex_uuids: Vec<_> = serialized_records(&json, "simplices") + .into_iter() + .map(|simplex| serialized_uuid_field(simplex, "uuid")) + .collect(); + let [first_simplex_uuid, second_simplex_uuid, ..] = simplex_uuids.as_slice() else { + panic!("test triangulation should serialize at least two simplices"); + }; + let simplex_vertices = json + .get_mut("simplex_vertices") + .and_then(serde_json::Value::as_object_mut) + .expect("serialized TDS should contain simplex_vertices"); + let duplicated_vertices = simplex_vertices + .get(&first_simplex_uuid.to_string()) + .cloned() + .expect("first simplex should have serialized vertex UUIDs"); + simplex_vertices.insert(second_simplex_uuid.to_string(), duplicated_vertices); + + let err = serde_json::from_value::>(json) + .expect_err("duplicate simplex vertex UUID sets should be rejected"); + let error_message = err.to_string(); + assert!( + error_message.contains("Duplicate simplices") + || error_message.contains("Facet with key") + || error_message.contains("2-manifold"), + "unexpected error for duplicate simplex vertex UUID sets: {err}" + ); + } + // ========================================================================= // COHERENT ORIENTATION NORMALIZATION & GENERATION COUNTER // ========================================================================= #[test] fn test_normalize_coherent_orientation_produces_coherent_result() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); // Two triangles sharing edge v1-v2, with deliberately inconsistent vertex order let c0 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(), + ) .unwrap(); // Assign neighbors: shared facet is edge v1-v2. @@ -10493,10 +11663,14 @@ mod tests { #[test] fn test_generation_counter_bumps_on_topology_modification() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); assert_eq!(tds.generation(), 0); - let _v = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); + let _v = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); assert!(tds.generation() > 0); let gen_before = tds.generation(); @@ -10510,7 +11684,7 @@ mod tests { $( #[test] fn []() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), $dim> = Tds::empty(); let cloned = tds.clone(); assert!( @@ -10522,9 +11696,9 @@ mod tests { #[test] fn []() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), $dim> = Tds::empty(); let _v = tds - .insert_vertex_with_mapping(vertex!([0.0_f64; $dim])) + .insert_vertex_with_mapping(crate::core::vertex::Vertex::<(), _>::try_new([0.0_f64; $dim]).unwrap()) .unwrap(); let snapshot = tds.clone_for_rollback(); let snapshot_generation = snapshot.generation(); @@ -10551,16 +11725,30 @@ mod tests { #[test] fn test_remove_duplicate_simplices_removes_exact_duplicates() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); // Insert the same simplex twice - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) - .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v2], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), ) .unwrap(); assert_eq!(tds.number_of_simplices(), 2); @@ -10588,31 +11776,49 @@ mod tests { #[test] fn test_remove_duplicate_simplices_rolls_back_when_rebuild_fails() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([0.5, -0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, -0.5]).unwrap(), + ) .unwrap(); // Three distinct triangles share edge v0-v1, so global neighbor // assignment will reject the complex after duplicate removal starts. - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) - .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v2], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), ) .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v3], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), ) .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v4], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(), ) .unwrap(); let before = tds.clone(); @@ -10635,13 +11841,27 @@ mod tests { #[test] fn test_validate_vertex_incidence_detects_dangling_incident_simplex() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let ck = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); tds.assign_incident_simplices().unwrap(); @@ -10654,15 +11874,29 @@ mod tests { #[test] fn test_validate_simplex_coordinate_uniqueness_rejects_duplicate_coords() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); // Two distinct vertex keys with identical coordinates - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + let err = tds.validate_simplex_coordinate_uniqueness().unwrap_err(); assert!( matches!(err, TdsError::DuplicateCoordinatesInSimplex { .. }), @@ -10672,20 +11906,44 @@ mod tests { #[test] fn test_validate_facet_sharing_rejects_triple_shared_facet() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap(); - let v4 = tds.insert_vertex_with_mapping(vertex!([0.3, 0.3])).unwrap(); - - // Three simplices sharing the v0-v1 edge (facet): - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), + ) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + let v4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap(), + ) .unwrap(); + + // Three simplices sharing the v0-v1 edge (facet): + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) + .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v4], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(), ) .unwrap(); @@ -10731,15 +11989,29 @@ mod tests { #[test] fn test_validate_no_duplicate_simplices_detects_dupes() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v2], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), ) .unwrap(); @@ -10758,14 +12030,14 @@ mod tests { #[test] fn test_tds_partial_eq_different_structures_not_equal() { let verts_a = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let verts_b = [ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([0.0, 2.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), ]; let dt_a: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&verts_a).unwrap(); @@ -10778,11 +12050,11 @@ mod tests { fn test_clear_all_neighbors_and_rebuild() { // Use 5 vertices so there are multiple simplices with actual neighbor pointers let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let mut tds = dt.tds().clone(); @@ -10821,11 +12093,11 @@ mod tests { #[test] fn test_find_simplices_containing_vertex_by_key() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tds = dt.tds(); @@ -10849,14 +12121,28 @@ mod tests { #[test] fn test_validation_report_accumulates_violations() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); // Create a simplex, then corrupt the UUID mapping - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) - .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); // Corrupt: add a stray UUID mapping pointing to a non-existent key tds.uuid_to_simplex_key .insert(Uuid::new_v4(), SimplexKey::from(KeyData::from_ffi(0xBAD))); @@ -10878,12 +12164,24 @@ mod tests { #[test] fn test_insert_simplex_with_mapping_registers_uuid_mapping() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); - let simplex = Simplex::new(vec![v0, v1, v2], None).unwrap(); + let simplex = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); let simplex_uuid = simplex.uuid(); let ck = tds.insert_simplex_with_mapping(simplex).unwrap(); @@ -10894,13 +12192,21 @@ mod tests { #[test] fn test_insert_simplex_with_mapping_rejects_missing_vertex() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); // Use a stale key that doesn't exist in the TDS. let stale = VertexKey::from(KeyData::from_ffi(0xDEAD)); - let simplex = Simplex::new(vec![v0, v1, stale], None).unwrap(); + let simplex = Simplex::try_new_with_data(vec![v0, v1, stale], None).unwrap(); let err = tds.insert_simplex_with_mapping(simplex).unwrap_err(); assert_matches!( err, @@ -10910,12 +12216,20 @@ mod tests { #[test] fn test_insert_simplex_with_mapping_trusted_vertices_rejects_missing_vertex() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); let stale = VertexKey::from(KeyData::from_ffi(0xDEAD)); - let simplex = Simplex::new(vec![v0, v1, stale], None).unwrap(); + let simplex = Simplex::try_new_with_data(vec![v0, v1, stale], None).unwrap(); let err = tds .insert_simplex_with_mapping_trusted_vertices(simplex) .unwrap_err(); @@ -10927,30 +12241,53 @@ mod tests { #[test] fn test_insert_simplex_with_mapping_rejects_duplicate_uuid() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); - let simplex_a = Simplex::new(vec![v0, v1, v2], None).unwrap(); + let simplex_a = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); let uuid_a = simplex_a.uuid(); tds.insert_simplex_with_mapping(simplex_a).unwrap(); // Create a second simplex with the same UUID. - let mut simplex_b = Simplex::new(vec![v0, v1, v2], None).unwrap(); - simplex_b.set_uuid(uuid_a).unwrap(); + let simplex_b = Simplex::try_new_with_uuid(vec![v0, v1, v2], uuid_a, None).unwrap(); let err = tds.insert_simplex_with_mapping(simplex_b).unwrap_err(); assert_matches!(err, TdsConstructionError::DuplicateUuid { .. }); } #[test] fn test_insert_simplex_rejects_candidate_periodic_offset_count_mismatch() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); - let mut candidate = Simplex::new(vec![v0, v1, v2], None).unwrap(); + let mut candidate = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); let candidate_uuid = candidate.uuid(); let generation_before = tds.generation(); candidate.periodic_vertex_offsets = Some(vec![[0_i8, 0_i8], [0_i8, 0_i8]].into()); @@ -10972,21 +12309,39 @@ mod tests { #[test] fn test_insert_simplex_propagates_existing_periodic_facet_key_error() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let existing = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); tds.simplex_mut(existing) .unwrap() .set_periodic_vertex_offsets(vec![[-128_i8, 0_i8], [127_i8, 0_i8], [0_i8, 0_i8]]) .unwrap(); - let candidate = Simplex::new(vec![v0, v1, v3], None).unwrap(); + let candidate = Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(); let candidate_uuid = candidate.uuid(); let generation_before = tds.generation(); @@ -11005,14 +12360,28 @@ mod tests { #[test] fn test_insert_simplex_with_mapping_rejects_duplicate_simplex_without_mutation() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) .unwrap(); - let candidate = Simplex::new(vec![v0, v1, v2], None).unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + let candidate = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); let candidate_uuid = candidate.uuid(); let generation_before = tds.generation(); @@ -11029,20 +12398,42 @@ mod tests { #[test] fn test_insert_simplex_with_mapping_rejects_third_incident_facet_without_mutation() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); - let v4 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + let v4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) .unwrap(); - let candidate = Simplex::new(vec![v0, v1, v4], None).unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) + .unwrap(); + let candidate = Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(); let candidate_uuid = candidate.uuid(); let generation_before = tds.generation(); @@ -11073,29 +12464,55 @@ mod tests { /// Verifies removed simplices are absent from subsequent insertion preflight scans. #[test] fn test_removed_simplices_do_not_block_future_simplex_insertions() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let v4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) .unwrap(); - let v4 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); let removed = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let second = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); assert_eq!(tds.remove_simplices_by_keys(&[removed]), 1); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) - .expect("removed duplicate simplex should not block reinsertion"); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .expect("removed duplicate simplex should not block reinsertion"); assert_eq!(tds.remove_simplices_by_keys(&[second]), 1); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v4], None).unwrap()) - .expect("removed incident simplex should not block later facet sharing"); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(), + ) + .expect("removed incident simplex should not block later facet sharing"); } // ========================================================================= @@ -11104,13 +12521,27 @@ mod tests { #[test] fn test_simplex_vertices_errors_on_missing_vertex_key() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let ck = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Corrupt: remove a vertex that the simplex references. @@ -11127,20 +12558,40 @@ mod tests { #[test] fn test_validate_vertex_incidence_detects_inconsistent_incident_simplex() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let _ck = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); tds.assign_incident_simplices().unwrap(); // Create a second simplex that does NOT contain v0, then point v0 at it. - let v3 = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + ) + .unwrap(); let ck2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(), + ) .unwrap(); tds.vertex_mut(v0).unwrap().set_incident_simplex(Some(ck2)); @@ -11150,13 +12601,27 @@ mod tests { #[test] fn test_validate_vertex_incidence_detects_dangling_simplex_key() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); tds.assign_incident_simplices().unwrap(); // Point v0 at a non-existent simplex key. @@ -11175,13 +12640,27 @@ mod tests { #[test] fn test_find_simplices_containing_vertex_fallback_when_no_incident_simplex() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); // Don't assign incident simplices — force fallback scan. let simplices = tds.find_simplices_containing_vertex_by_key(v0); assert_eq!(simplices.len(), 1); @@ -11189,19 +12668,37 @@ mod tests { #[test] fn test_find_simplices_containing_vertex_falls_back_on_unassigned_neighbor_slot() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([-1.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, 1.0]).unwrap(), + ) .unwrap(); let first_simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let second_simplex = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) .unwrap(); tds.assign_neighbors().unwrap(); tds.assign_incident_simplices().unwrap(); @@ -11228,11 +12725,11 @@ mod tests { #[test] fn test_remove_simplices_by_keys_batch_repairs_incidence_and_neighbors() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let mut tds = dt.tds().clone(); @@ -11269,13 +12766,27 @@ mod tests { #[test] fn test_assign_incident_simplices_errors_on_dangling_vertex_key() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let _ck = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Remove v2 from the vertex storage, leaving the simplex with a dangling reference. @@ -11304,11 +12815,11 @@ mod tests { #[test] fn test_normalize_coherent_orientation_multi_simplex() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let mut tds = dt.tds().clone(); @@ -11336,7 +12847,7 @@ mod tests { #[test] fn test_mark_topology_modified_bumps_generation() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); let gen_before = tds.generation(); tds.mark_topology_modified(); assert_eq!(tds.generation(), gen_before + 1); @@ -11348,16 +12859,30 @@ mod tests { #[test] fn test_remove_duplicate_simplices_removes_actual_duplicates() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); // Insert a duplicate simplex (same vertex set, different UUID). tds.insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v2], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), ) .unwrap(); assert_eq!(tds.number_of_simplices(), 2); @@ -11376,24 +12901,44 @@ mod tests { #[test] fn test_remove_simplices_by_keys_returns_zero_for_missing() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); let stale = SimplexKey::from(KeyData::from_ffi(0xDEAD)); assert_eq!(tds.remove_simplices_by_keys(&[stale]), 0); } #[test] fn test_remove_simplices_by_keys_repairs_local_topology() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let removed_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let surviving_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) .unwrap(); tds.construction_state = TriangulationConstructionState::Constructed; tds.assign_neighbors().unwrap(); @@ -11448,13 +12993,27 @@ mod tests { #[test] fn test_validate_neighbor_topology_rejects_wrong_length() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let ck = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Wrong length: 2 instead of D+1=3. @@ -11470,10 +13029,10 @@ mod tests { #[test] fn test_set_vertex_data_replaces_existing() { - let vertices: [Vertex; 3] = [ - vertex!([0.0, 0.0], 10i32), - vertex!([1.0, 0.0], 20), - vertex!([0.0, 1.0], 30), + let vertices: [Vertex; 3] = [ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 10i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 20).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 30).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() @@ -11490,9 +13049,9 @@ mod tests { fn test_set_vertex_data_on_no_data_vertex() { // Vertices without data have U = (), so set_vertex_data sets (). let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let mut tds = dt.tds().clone(); @@ -11506,7 +13065,7 @@ mod tests { #[test] fn test_set_vertex_data_invalid_key_returns_none() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds = Tds::empty(); let stale = VertexKey::from(KeyData::from_ffi(0xDEAD)); assert!(tds.set_vertex_data(stale, Some(1)).is_none()); } @@ -11514,9 +13073,9 @@ mod tests { #[test] fn test_set_simplex_data_on_empty_simplex() { let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .build::() @@ -11532,9 +13091,9 @@ mod tests { #[test] fn test_set_simplex_data_replaces_existing() { let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .build::() @@ -11550,17 +13109,17 @@ mod tests { #[test] fn test_set_simplex_data_invalid_key_returns_none() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), i32, 2> = Tds::empty(); let stale = SimplexKey::from(KeyData::from_ffi(0xDEAD)); assert!(tds.set_simplex_data(stale, Some(1)).is_none()); } #[test] fn test_set_vertex_data_preserves_triangulation_validity() { - let vertices: [Vertex; 3] = [ - vertex!([0.0, 0.0], 1i32), - vertex!([1.0, 0.0], 2), - vertex!([0.0, 1.0], 3), + let vertices: [Vertex; 3] = [ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 1i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 2).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 3).unwrap(), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() @@ -11585,10 +13144,10 @@ mod tests { #[test] fn test_set_simplex_data_preserves_triangulation_validity() { let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), - vertex!([1.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.5, 0.5]).unwrap(), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) .build::() @@ -11613,10 +13172,10 @@ mod tests { #[test] fn test_set_vertex_data_via_delaunay_wrapper() { - let vertices: [Vertex; 3] = [ - vertex!([0.0, 0.0], 10i32), - vertex!([1.0, 0.0], 20), - vertex!([0.0, 1.0], 30), + let vertices: [Vertex; 3] = [ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 10i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 20).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 30).unwrap(), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() @@ -11637,9 +13196,9 @@ mod tests { #[test] fn test_set_simplex_data_via_delaunay_wrapper() { let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) .build::() @@ -11659,17 +13218,18 @@ mod tests { #[test] fn test_set_data_via_dt_does_not_invalidate_locate_hint() { - let vertices: [Vertex; 3] = [ - vertex!([0.0, 0.0], 0i32), - vertex!([1.0, 0.0], 0), - vertex!([0.0, 1.0], 0), + let vertices: [Vertex; 3] = [ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 0i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 0).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 0).unwrap(), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() .unwrap(); // Insert a new vertex so the locate hint is populated. - let extra = vertex!([0.25, 0.25], 0i32); + let extra = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.25, 0.25], 0i32).unwrap(); dt.insert(extra).unwrap(); // Data mutation should NOT clear the insertion hint. @@ -11683,7 +13243,8 @@ mod tests { ); // A subsequent insert should still succeed (hint not invalidated). - let another = vertex!([0.75, 0.1], 0i32); + let another = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.75, 0.1], 0i32).unwrap(); assert!(dt.insert(another).is_ok()); assert!(dt.validate().is_ok()); } diff --git a/src/core/traits/boundary_analysis.rs b/src/core/traits/boundary_analysis.rs index bc7091d9..553847a7 100644 --- a/src/core/traits/boundary_analysis.rs +++ b/src/core/traits/boundary_analysis.rs @@ -29,10 +29,10 @@ use crate::core::{ /// # fn main() -> Result<(), ExampleError> { /// // Create a simple 3D triangulation (single tetrahedron) /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -47,7 +47,7 @@ use crate::core::{ /// # Ok(()) /// # } /// ``` -pub trait BoundaryAnalysis { +pub trait BoundaryAnalysis { /// Identifies all boundary facets in the triangulation. /// /// A boundary facet is a facet that belongs to only one simplex, meaning it lies on the @@ -56,7 +56,7 @@ pub trait BoundaryAnalysis { /// /// # Returns /// - /// A `Result, TdsError>` containing an iterator over boundary facets. + /// A `Result, TdsError>` containing an iterator over boundary facets. /// The iterator yields facets lazily without pre-allocating a vector, providing better performance. /// /// # Errors @@ -79,10 +79,10 @@ pub trait BoundaryAnalysis { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -94,7 +94,7 @@ pub trait BoundaryAnalysis { /// # Ok(()) /// # } /// ``` - fn boundary_facets(&self) -> Result, TdsError>; + fn boundary_facets(&self) -> Result, TdsError>; /// Checks if a specific facet is a boundary facet. /// @@ -132,10 +132,10 @@ pub trait BoundaryAnalysis { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -151,7 +151,7 @@ pub trait BoundaryAnalysis { /// # Ok(()) /// # } /// ``` - fn is_boundary_facet(&self, facet: &FacetView<'_, T, U, V, D>) -> Result; + fn is_boundary_facet(&self, facet: &FacetView<'_, U, V, D>) -> Result; /// Checks if a specific facet is a boundary facet using a precomputed facet map. /// @@ -193,10 +193,10 @@ pub trait BoundaryAnalysis { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -217,10 +217,10 @@ pub trait BoundaryAnalysis { /// # } /// ``` /// - /// [`build_facet_to_simplices_map`]: crate::core::tds::Tds::build_facet_to_simplices_map + /// [`build_facet_to_simplices_map`]: crate::prelude::tds::Tds::build_facet_to_simplices_map fn is_boundary_facet_with_map( &self, - facet: &FacetView<'_, T, U, V, D>, + facet: &FacetView<'_, U, V, D>, facet_to_simplices: &crate::core::collections::FacetToSimplicesMap, ) -> Result; @@ -254,10 +254,10 @@ pub trait BoundaryAnalysis { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, _, _, 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; diff --git a/src/core/traits/facet_cache.rs b/src/core/traits/facet_cache.rs index 145ee8fe..d893fcf0 100644 --- a/src/core/traits/facet_cache.rs +++ b/src/core/traits/facet_cache.rs @@ -9,7 +9,6 @@ use crate::core::{ collections::FacetToSimplicesMap, tds::{Tds, TdsError}, }; -use crate::geometry::traits::coordinate::CoordinateScalar; use arc_swap::ArcSwapOption; use std::sync::{ Arc, @@ -36,11 +35,9 @@ use std::sync::{ /// use delaunay::prelude::tds::FacetCacheProvider; /// use delaunay::prelude::tds::Tds; /// use delaunay::prelude::collections::FacetToSimplicesMap; -/// use delaunay::prelude::geometry::CoordinateScalar; /// use delaunay::prelude::DataType; /// use std::sync::Arc; /// use std::sync::atomic::{AtomicU64, Ordering}; -/// use serde::de::DeserializeOwned; /// use arc_swap::ArcSwapOption; /// /// struct MyAlgorithm { @@ -57,9 +54,8 @@ use std::sync::{ /// } /// } /// -/// impl FacetCacheProvider for MyAlgorithm +/// impl FacetCacheProvider for MyAlgorithm /// where -/// T: CoordinateScalar, /// U: DataType, /// V: DataType, /// { @@ -72,9 +68,8 @@ use std::sync::{ /// } /// } /// ``` -pub trait FacetCacheProvider +pub trait FacetCacheProvider where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -112,7 +107,7 @@ where /// (e.g., missing vertex keys) that prevents building a complete facet map. fn try_build_cache_with_rcu( &self, - tds: &Tds, + tds: &Tds, ) -> Result>, TdsError> { // We memoize the built cache outside the RCU closure to avoid recomputation // if RCU needs to retry due to concurrent updates. @@ -195,10 +190,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -218,7 +213,7 @@ where /// ``` fn try_get_or_build_facet_cache( &self, - tds: &Tds, + tds: &Tds, ) -> Result, TdsError> { let mut current_generation = tds.generation(); @@ -323,10 +318,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -355,7 +350,6 @@ mod tests { use crate::core::tds::Tds; use crate::geometry::kernel::AdaptiveKernel; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use std::sync::Arc; use std::sync::Barrier; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; @@ -377,7 +371,7 @@ mod tests { } } - impl FacetCacheProvider for TestCacheProvider { + impl FacetCacheProvider<(), (), 3> for TestCacheProvider { fn facet_cache(&self) -> &ArcSwapOption { &self.facet_to_simplices_cache } @@ -390,10 +384,10 @@ mod tests { /// Create a simple test triangulation for testing fn create_test_triangulation() -> DelaunayTriangulation, (), (), 3> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; DelaunayTriangulation::new(&vertices).expect("Failed to create test triangulation") } @@ -476,7 +470,7 @@ mod tests { // Modify triangulation by adding a new vertex - this will bump the generation // Use an interior vertex away from the circumcenter to avoid degenerate insertion cases - let new_vertex = vertex!([0.2, 0.2, 0.2]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(); dt.insert(new_vertex).expect("Failed to add vertex"); // Verify generation was incremented @@ -783,9 +777,9 @@ mod tests { // Perform multiple operations to increment generation // Each operation should bump the generation counter let operations = [ - vertex!([0.2, 0.2, 0.2]), - vertex!([0.3, 0.3, 0.3]), - vertex!([0.4, 0.4, 0.4]), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.4, 0.4, 0.4]).unwrap(), ]; for vertex in operations { @@ -840,7 +834,7 @@ mod tests { let provider = TestCacheProvider::new(); // Create an empty triangulation - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 3> = Tds::empty(); // Should handle empty triangulation gracefully let cache = provider.try_get_or_build_facet_cache(&tds).unwrap(); @@ -1020,7 +1014,7 @@ mod tests { // Modify triangulation by adding a new vertex - this will bump the generation // Use an interior vertex away from the circumcenter to avoid degenerate insertion cases - let new_vertex = vertex!([0.2, 0.2, 0.2]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(); dt.insert(new_vertex).expect("Failed to add vertex"); // Verify generation was incremented @@ -1156,9 +1150,9 @@ mod tests { // Modify triangulation multiple times rapidly with unique coordinates let test_vertices = [ // Interior point away from circumcenter to reduce degeneracy - vertex!([0.2, 0.2, 0.2]), - vertex!([0.3, 0.3, 0.1]), // Another interior point - vertex!([0.2, 0.1, 0.3]), // Third interior point + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3, 0.1]).unwrap(), // Another interior point + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.1, 0.3]).unwrap(), // Third interior point ]; for vertex in test_vertices { dt.insert(vertex).expect("Failed to add vertex"); @@ -1226,7 +1220,7 @@ mod tests { // Add vertex - size should change // Use an interior vertex away from circumcenter to avoid degenerate insertion cases - dt.insert(vertex!([0.2, 0.2, 0.2])) + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap()) .expect("Failed to add vertex"); let cache3 = provider.try_get_or_build_facet_cache(dt.tds()).unwrap(); // Size might be different after adding a vertex (more simplices = more facets) diff --git a/src/core/triangulation.rs b/src/core/triangulation.rs index d3ea3524..b7fc672b 100644 --- a/src/core/triangulation.rs +++ b/src/core/triangulation.rs @@ -7,7 +7,7 @@ //! This layer provides geometric operations while delegating topology to Tds. //! //! Validation policy, topology guarantees, and validation passes are implemented -//! in [`crate::core::validation`]. +//! in [`crate::prelude::validation`]. //! #![forbid(unsafe_code)] @@ -35,11 +35,11 @@ use crate::topology::traits::topological_space::GlobalTopology; /// assert_eq!(tri.number_of_vertices(), 0); /// ``` #[derive(Clone, Debug)] -pub struct Triangulation, U, V, const D: usize> { +pub struct Triangulation { /// The geometric kernel for predicates. pub(crate) kernel: K, /// The combinatorial triangulation data structure. - pub(crate) tds: Tds, + pub(crate) tds: Tds, /// Runtime metadata describing the global topological space represented by this triangulation. pub(crate) global_topology: GlobalTopology, pub(crate) validation_policy: ValidationPolicy, @@ -80,7 +80,11 @@ where #[cfg(test)] #[inline] - pub(crate) const fn new_with_tds(kernel: K, tds: Tds) -> Self { + #[expect( + clippy::missing_const_for_fn, + reason = "test-only constructor is not a pure math helper" + )] + pub(crate) fn new_with_tds(kernel: K, tds: Tds) -> Self { Self { kernel, tds, @@ -90,7 +94,7 @@ where } } - /// Sets the auxiliary data on a vertex, returning the previous value. + /// Sets the auxiliary data on a returning the previous value. /// /// Delegates to [`Tds::set_vertex_data`]. This is a safe O(1) operation /// that does not affect geometry, topology, or Delaunay invariants. @@ -104,7 +108,7 @@ where /// /// ``` /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, /// }; /// /// # #[derive(Debug, thiserror::Error)] @@ -115,10 +119,10 @@ where /// # MissingVertex, /// # } /// # fn main() -> Result<(), ExampleError> { - /// let vertices: [Vertex; 3] = [ - /// vertex!([0.0, 0.0], 10i32), - /// vertex!([1.0, 0.0], 20), - /// vertex!([0.0, 1.0], 30), + /// let vertices: [Vertex; 3] = [ + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 10i32).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 20).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 30).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let key = dt.vertices().next().ok_or(ExampleError::MissingVertex)?.0; @@ -152,7 +156,7 @@ where /// /// ``` /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// /// # #[derive(Debug, thiserror::Error)] @@ -164,9 +168,9 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::()?; /// let key = dt.simplices().next().ok_or(ExampleError::MissingSimplex)?.0; diff --git a/src/core/util/canonical_points.rs b/src/core/util/canonical_points.rs index 7df92152..da2c3c67 100644 --- a/src/core/util/canonical_points.rs +++ b/src/core/util/canonical_points.rs @@ -22,21 +22,67 @@ use crate::core::collections::{MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer}; use crate::core::simplex::Simplex; use crate::core::tds::{Tds, VertexKey}; -use crate::core::traits::DataType; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::CoordinateScalar; use slotmap::Key; +use thiserror::Error; // ============================================================================= // CANONICAL POINT-COLLECTION HELPERS // ============================================================================= +/// Errors that can occur while collecting canonical simplex predicate points. +#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)] +#[non_exhaustive] +pub(crate) enum CanonicalSimplexPointError { + /// A simplex did not contain exactly `D + 1` vertices for predicate input. + #[error("canonical simplex point collection expected {expected} vertices, found {found}")] + InvalidArity { + /// Expected simplex vertex count. + expected: usize, + /// Observed simplex vertex count. + found: usize, + }, + + /// A vertex key referenced by a simplex was not present in the TDS. + #[error("vertex {vertex_key:?} not found while collecting canonical simplex predicate points")] + MissingVertex { + /// Missing vertex key. + vertex_key: VertexKey, + }, +} + +/// Errors that can occur while collecting canonical facet predicate points. +#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)] +#[non_exhaustive] +pub(crate) enum CanonicalFacetPointError { + /// A facet did not contain exactly `D` vertices before appending the extra point. + #[error("canonical facet point collection expected {expected} vertices, found {found}")] + InvalidArity { + /// Expected facet vertex count. + expected: usize, + /// Observed facet vertex count. + found: usize, + }, + + /// A vertex key referenced by a facet was not present in the TDS. + #[error("vertex {vertex_key:?} not found while collecting canonical facet predicate points")] + MissingVertex { + /// Missing vertex key. + vertex_key: VertexKey, + }, +} + /// Collect simplex vertex points in canonical [`VertexKey`] order. /// /// Sorts the simplex's vertex keys by their stable identity /// (`vk.data().as_ffi()`), then resolves each to its [`Point`]. /// -/// Returns [`None`] if any vertex key cannot be resolved via the TDS. +/// # Errors +/// +/// Returns [`CanonicalSimplexPointError::InvalidArity`] if `simplex` does not +/// have exactly `D + 1` vertices. Returns +/// [`CanonicalSimplexPointError::MissingVertex`] if any vertex key cannot be +/// resolved via the TDS. /// /// # Arguments /// @@ -46,28 +92,33 @@ use slotmap::Key; /// # Examples /// /// ```rust,ignore -/// let points = sorted_simplex_points(tds, simplex) -/// .ok_or(SomeError::MissingVertex)?; +/// let points = sorted_simplex_points(tds, simplex)?; /// let sign = kernel.in_sphere(&points, &query_point)?; /// ``` -pub fn sorted_simplex_points( - tds: &Tds, - simplex: &Simplex, -) -> Option, MAX_PRACTICAL_DIMENSION_SIZE>> -where - T: CoordinateScalar, - U: DataType, - V: DataType, -{ +pub(crate) fn sorted_simplex_points( + tds: &Tds, + simplex: &Simplex, +) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, CanonicalSimplexPointError> { + let vertex_count = simplex.number_of_vertices(); + if vertex_count != D + 1 { + return Err(CanonicalSimplexPointError::InvalidArity { + expected: D + 1, + found: vertex_count, + }); + } + let mut keys: SmallBuffer = simplex.vertices().iter().copied().collect(); keys.sort_unstable_by_key(|vk| vk.data().as_ffi()); let mut points = SmallBuffer::with_capacity(keys.len()); for &vk in &keys { - points.push(*tds.vertex(vk)?.point()); + let vertex = tds + .vertex(vk) + .ok_or(CanonicalSimplexPointError::MissingVertex { vertex_key: vk })?; + points.push(*vertex.point()); } - Some(points) + Ok(points) } /// Collect facet vertex points in canonical [`VertexKey`] order, then append @@ -76,7 +127,12 @@ where /// Sorts `facet_keys` by their stable identity (`vk.data().as_ffi()`), /// resolves each to its [`Point`], and appends `extra` at the end. /// -/// Returns [`None`] if any vertex key cannot be resolved via the TDS. +/// # Errors +/// +/// Returns [`CanonicalFacetPointError::InvalidArity`] if `facet_keys` does not +/// contain exactly `D` vertices. Returns +/// [`CanonicalFacetPointError::MissingVertex`] if any vertex key cannot be +/// resolved via the TDS. /// /// # Arguments /// @@ -87,30 +143,34 @@ where /// # Examples /// /// ```rust,ignore -/// let points = sorted_facet_points_with_extra(tds, &facet_keys, opposite_point) -/// .ok_or(SomeError::MissingVertex)?; +/// let points = sorted_facet_points_with_extra(tds, &facet_keys, opposite_point)?; /// let orient = kernel.orientation(&points)?; /// ``` -pub fn sorted_facet_points_with_extra( - tds: &Tds, +pub(crate) fn sorted_facet_points_with_extra( + tds: &Tds, facet_keys: &[VertexKey], - extra: Point, -) -> Option, MAX_PRACTICAL_DIMENSION_SIZE>> -where - T: CoordinateScalar, - U: DataType, - V: DataType, -{ + extra: Point, +) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, CanonicalFacetPointError> { + if facet_keys.len() != D { + return Err(CanonicalFacetPointError::InvalidArity { + expected: D, + found: facet_keys.len(), + }); + } + let mut sorted_keys: SmallBuffer = facet_keys.iter().copied().collect(); sorted_keys.sort_unstable_by_key(|vk| vk.data().as_ffi()); let mut points = SmallBuffer::with_capacity(sorted_keys.len() + 1); for &vk in &sorted_keys { - points.push(*tds.vertex(vk)?.point()); + let vertex = tds + .vertex(vk) + .ok_or(CanonicalFacetPointError::MissingVertex { vertex_key: vk })?; + points.push(*vertex.point()); } points.push(extra); - Some(points) + Ok(points) } // ============================================================================= @@ -120,9 +180,9 @@ where #[cfg(test)] mod tests { use super::*; - use crate::core::vertex::VertexBuilder; + use crate::core::vertex::Vertex; use crate::geometry::kernel::{AdaptiveKernel, Kernel}; - use crate::geometry::traits::coordinate::Coordinate; + use slotmap::KeyData; // ========================================================================= // HELPER FUNCTIONS @@ -132,14 +192,11 @@ mod tests { /// vertex keys in insertion order. fn build_tds_with_points( coords: &[[f64; D]], - ) -> (Tds, Vec) { - let mut tds = Tds::::empty(); + ) -> (Tds<(), (), D>, Vec) { + let mut tds = Tds::<(), (), D>::empty(); let mut keys = Vec::with_capacity(coords.len()); for c in coords { - let v = VertexBuilder::<_, (), _>::default() - .point(Point::new(*c)) - .build() - .expect("vertex build should succeed"); + let v = Vertex::<(), D>::try_new(*c).expect("finite point coordinates"); let vk = tds .insert_vertex_with_mapping(v) .expect("insert should succeed"); @@ -158,7 +215,8 @@ mod tests { let (mut tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]); // Create a simplex with vertices in insertion order - let simplex = Simplex::new(keys.clone(), None::<()>).expect("simplex should be valid"); + let simplex = + Simplex::try_new_with_data(keys.clone(), None::<()>).expect("simplex should be valid"); let simplex_key = tds .insert_simplex_with_mapping(simplex) .expect("insert should succeed"); @@ -182,9 +240,9 @@ mod tests { let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]); // Create two simplices with vertices in different orders - let simplex_a = Simplex::new(vec![keys[0], keys[1], keys[2]], None::<()>) + let simplex_a = Simplex::try_new_with_data(vec![keys[0], keys[1], keys[2]], None::<()>) .expect("simplex should be valid"); - let simplex_b = Simplex::new(vec![keys[2], keys[0], keys[1]], None::<()>) + let simplex_b = Simplex::try_new_with_data(vec![keys[2], keys[0], keys[1]], None::<()>) .expect("simplex should be valid"); let points_a = sorted_simplex_points(&tds, &simplex_a).unwrap(); let points_b = sorted_simplex_points(&tds, &simplex_b).unwrap(); @@ -193,6 +251,42 @@ mod tests { assert_eq!(points_a.as_slice(), points_b.as_slice()); } + #[test] + fn test_sorted_simplex_points_rejects_wrong_arity() { + let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]); + let mut simplex = Simplex::try_new_with_data(vec![keys[0], keys[1], keys[2]], None::<()>) + .expect("simplex should be valid"); + + simplex.clear_vertex_keys(); + simplex.push_vertex_key(keys[0]); + simplex.push_vertex_key(keys[1]); + + let err = sorted_simplex_points(&tds, &simplex).unwrap_err(); + + assert!(matches!( + err, + CanonicalSimplexPointError::InvalidArity { + expected: 3, + found: 2, + } + )); + } + + #[test] + fn test_sorted_simplex_points_reports_missing_vertex_key() { + let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0]]); + let missing = VertexKey::from(KeyData::from_ffi(999_999)); + let simplex = Simplex::try_new_with_data(vec![keys[0], keys[1], missing], None::<()>) + .expect("simplex arity and uniqueness should be valid"); + + let err = sorted_simplex_points(&tds, &simplex).unwrap_err(); + + assert!(matches!( + err, + CanonicalSimplexPointError::MissingVertex { vertex_key } if vertex_key == missing + )); + } + // ========================================================================= // SORTED FACET POINTS WITH EXTRA TESTS // ========================================================================= @@ -202,7 +296,7 @@ mod tests { let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]); let facet_keys = &[keys[0], keys[1]]; - let extra = Point::new([0.5, 0.5]); + let extra = Point::from_validated_coords([0.5, 0.5]); let points = sorted_facet_points_with_extra(&tds, facet_keys, extra).expect("should resolve"); @@ -217,7 +311,7 @@ mod tests { fn test_sorted_facet_points_with_extra_permutation_invariant() { let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]); - let extra = Point::new([0.5, 0.5]); + let extra = Point::from_validated_coords([0.5, 0.5]); let points_a = sorted_facet_points_with_extra(&tds, &[keys[0], keys[1]], extra).unwrap(); let points_b = sorted_facet_points_with_extra(&tds, &[keys[1], keys[0]], extra).unwrap(); @@ -226,6 +320,36 @@ mod tests { assert_eq!(points_a.as_slice(), points_b.as_slice()); } + #[test] + fn test_sorted_facet_points_with_extra_rejects_wrong_arity() { + let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]); + let extra = Point::from_validated_coords([0.5, 0.5]); + + let err = sorted_facet_points_with_extra(&tds, &[keys[0]], extra).unwrap_err(); + + assert!(matches!( + err, + CanonicalFacetPointError::InvalidArity { + expected: 2, + found: 1, + } + )); + } + + #[test] + fn test_sorted_facet_points_with_extra_reports_missing_vertex_key() { + let (tds, keys) = build_tds_with_points(&[[0.0, 0.0]]); + let missing = VertexKey::from(KeyData::from_ffi(999_999)); + let extra = Point::from_validated_coords([0.5, 0.5]); + + let err = sorted_facet_points_with_extra(&tds, &[keys[0], missing], extra).unwrap_err(); + + assert!(matches!( + err, + CanonicalFacetPointError::MissingVertex { vertex_key } if vertex_key == missing + )); + } + // ========================================================================= // SOS PERMUTATION-INVARIANCE TESTS (KERNEL INTEGRATION) // ========================================================================= @@ -238,7 +362,7 @@ mod tests { // 3 points forming a right triangle + a cospherical test point. // The circumcircle of (0,0),(1,0),(0,1) passes through (1,1). let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]); - let test_point = Point::new([1.0, 1.0]); + let test_point = Point::from_validated_coords([1.0, 1.0]); let kernel = AdaptiveKernel::::new(); // All 6 permutations of the 3 vertices @@ -253,7 +377,7 @@ mod tests { let mut signs = Vec::new(); for perm in &permutations { - let simplex = Simplex::new( + let simplex = Simplex::try_new_with_data( vec![keys[perm[0]], keys[perm[1]], keys[perm[2]]], None::<()>, ) @@ -282,7 +406,7 @@ mod tests { [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], ]); - let test_point = Point::new([1.0, 1.0, 1.0]); + let test_point = Point::from_validated_coords([1.0, 1.0, 1.0]); let kernel = AdaptiveKernel::::new(); // All 24 permutations of 4 vertices @@ -296,7 +420,7 @@ mod tests { let mut signs = Vec::new(); for perm in &perms { - let simplex = Simplex::new( + let simplex = Simplex::try_new_with_data( vec![keys[perm[0]], keys[perm[1]], keys[perm[2]], keys[perm[3]]], None::<()>, ) diff --git a/src/core/util/deduplication.rs b/src/core/util/deduplication.rs index 1a878042..47aebaac 100644 --- a/src/core/util/deduplication.rs +++ b/src/core/util/deduplication.rs @@ -4,7 +4,7 @@ use crate::core::traits::data_type::DataType; use crate::core::vertex::Vertex; -use crate::geometry::traits::coordinate::CoordinateScalar; +use crate::geometry::traits::coordinate::OrderedEq; use thiserror::Error; /// Errors returned by fallible vertex deduplication helpers. @@ -45,32 +45,32 @@ pub enum DeduplicationError { /// /// ``` /// use delaunay::prelude::dedup_vertices_exact; -/// use delaunay::prelude::{Vertex, vertex}; +/// use delaunay::prelude::{Vertex}; /// -/// let v1: Vertex = vertex!([0.0, 0.0]); -/// let v2: Vertex = vertex!([0.0, 0.0]); // Duplicate -/// let v3: Vertex = vertex!([1.0, 1.0]); +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let v1: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?; +/// let v2: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?; // Duplicate +/// let v3: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0])?; /// /// let vertices = vec![v1, v2, v3]; /// let unique = dedup_vertices_exact(&vertices); /// assert_eq!(unique.len(), 2); // Only v1 and v3 +/// # Ok(()) +/// # } /// ``` #[must_use] -pub fn dedup_vertices_exact( - vertices: &[Vertex], -) -> Vec> +pub fn dedup_vertices_exact(vertices: &[Vertex]) -> Vec> where - T: CoordinateScalar, U: DataType, { - let mut unique: Vec> = Vec::with_capacity(vertices.len()); - - 'outer: for &v in vertices { - for u in &unique { - // Exact floating-point equality (NaN-aware, treats +0.0 == -0.0) - if coords_equal_exact(v.point().coords(), u.point().coords()) { - continue 'outer; // Skip exact duplicate - } + let mut unique: Vec> = Vec::with_capacity(vertices.len()); + + for &v in vertices { + if unique + .iter() + .any(|u| coords_equal_exact(v.point().coords(), u.point().coords())) + { + continue; } unique.push(v); @@ -109,25 +109,27 @@ where /// /// ``` /// use delaunay::prelude::dedup_vertices_epsilon; -/// use delaunay::prelude::{Vertex, vertex}; +/// use delaunay::prelude::{Vertex}; /// -/// let v1: Vertex = vertex!([0.0, 0.0]); -/// let v2: Vertex = vertex!([1e-11, 1e-11]); // Near duplicate -/// let v3: Vertex = vertex!([1.0, 1.0]); +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let v1: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?; +/// let v2: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([1e-11, 1e-11])?; // Near duplicate +/// let v3: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0])?; /// /// let vertices = vec![v1, v2, v3]; /// let unique = dedup_vertices_epsilon(&vertices, 1e-10); /// assert_eq!(unique.len(), 2); // v2 filtered as near-duplicate of v1 +/// # Ok(()) +/// # } /// ``` -pub fn dedup_vertices_epsilon( - vertices: &[Vertex], - epsilon: T, -) -> Vec> +pub fn dedup_vertices_epsilon( + vertices: &[Vertex], + epsilon: f64, +) -> Vec> where - T: CoordinateScalar, U: DataType, { - if !epsilon.is_finite_generic() || epsilon < T::zero() { + if !epsilon.is_finite() || epsilon < 0.0 { tracing::warn!( epsilon = ?epsilon, "dedup_vertices_epsilon received non-finite or negative epsilon; returning input unchanged" @@ -153,13 +155,12 @@ where /// /// ```rust /// use delaunay::prelude::{DeduplicationError, try_dedup_vertices_epsilon}; -/// use delaunay::prelude::construction::vertex; /// /// # fn main() -> Result<(), DeduplicationError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), /// ]; /// /// let deduped = try_dedup_vertices_epsilon(&vertices, 1e-9)?; @@ -170,41 +171,39 @@ where /// # Ok(()) /// # } /// ``` -pub fn try_dedup_vertices_epsilon( - vertices: &[Vertex], - epsilon: T, -) -> Result>, DeduplicationError> +pub fn try_dedup_vertices_epsilon( + vertices: &[Vertex], + epsilon: f64, +) -> Result>, DeduplicationError> where - T: CoordinateScalar, U: DataType, { - if !epsilon.is_finite_generic() { + if !epsilon.is_finite() { return Err(DeduplicationError::NonFiniteEpsilon); } - if epsilon < T::zero() { + if epsilon < 0.0 { return Err(DeduplicationError::NegativeEpsilon); } Ok(dedup_vertices_epsilon_nonnegative(vertices, epsilon)) } -fn dedup_vertices_epsilon_nonnegative( - vertices: &[Vertex], - epsilon: T, -) -> Vec> +fn dedup_vertices_epsilon_nonnegative( + vertices: &[Vertex], + epsilon: f64, +) -> Vec> where - T: CoordinateScalar, U: DataType, { - let mut unique: Vec> = Vec::with_capacity(vertices.len()); - - 'outer: for &v in vertices { - for u in &unique { - // Euclidean distance check - if coords_within_epsilon(v.point().coords(), u.point().coords(), epsilon) { - continue 'outer; // Skip near-duplicate - } + let mut unique: Vec> = Vec::with_capacity(vertices.len()); + + for &v in vertices { + if unique + .iter() + .any(|u| coords_within_epsilon(v.point().coords(), u.point().coords(), epsilon)) + { + continue; } unique.push(v); @@ -238,33 +237,35 @@ where /// /// ``` /// use delaunay::prelude::filter_vertices_excluding; -/// use delaunay::prelude::{Vertex, vertex}; +/// use delaunay::prelude::{Vertex}; /// -/// let v1: Vertex = vertex!([0.0, 0.0]); -/// let v2: Vertex = vertex!([1.0, 1.0]); +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let v1: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?; +/// let v2: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0])?; /// /// let reference = vec![v1]; // Exclude origin /// let vertices = vec![v1, v2]; /// /// let filtered = filter_vertices_excluding(&vertices, &reference); /// assert_eq!(filtered.len(), 1); // Only v2 remains +/// # Ok(()) +/// # } /// ``` -pub fn filter_vertices_excluding( - vertices: &[Vertex], - reference: &[Vertex], -) -> Vec> +pub fn filter_vertices_excluding( + vertices: &[Vertex], + reference: &[Vertex], +) -> Vec> where - T: CoordinateScalar, U: DataType, { let mut filtered = Vec::with_capacity(vertices.len()); - 'outer: for &v in vertices { - // Check against all reference vertices - for ref_v in reference { - if coords_equal_exact(v.point().coords(), ref_v.point().coords()) { - continue 'outer; // Skip matching vertex - } + for &v in vertices { + if reference + .iter() + .any(|ref_v| coords_equal_exact(v.point().coords(), ref_v.point().coords())) + { + continue; } filtered.push(v); @@ -278,11 +279,7 @@ where /// Uses `OrderedEq` which provides NaN-aware equality comparison. /// For f64, this ensures consistent comparison including special values. #[inline] -pub(crate) fn coords_equal_exact( - a: &[T; D], - b: &[T; D], -) -> bool { - // OrderedEq is already in scope via CoordinateScalar bound +pub(crate) fn coords_equal_exact(a: &[f64; D], b: &[f64; D]) -> bool { a.iter().zip(b.iter()).all(|(x, y)| x.ordered_eq(y)) } @@ -290,20 +287,19 @@ pub(crate) fn coords_equal_exact( /// /// Returns true if Euclidean distance is strictly less than epsilon (distance < epsilon). #[inline] -pub(crate) fn coords_within_epsilon( - a: &[T; D], - b: &[T; D], - epsilon: T, +pub(crate) fn coords_within_epsilon( + a: &[f64; D], + b: &[f64; D], + epsilon: f64, ) -> bool { - let dist_sq: T = a - .iter() - .zip(b.iter()) - .map(|(x, y)| (*x - *y) * (*x - *y)) - .fold(T::zero(), |acc, d| acc + d); + let dist_sq: f64 = a.iter().zip(b.iter()).fold(0.0, |acc, (x, y)| { + let diff = *x - *y; + diff.mul_add(diff, acc) + }); let epsilon_sq = epsilon * epsilon; #[cfg(debug_assertions)] - if dist_sq == epsilon_sq { + if dist_sq.to_bits() == epsilon_sq.to_bits() { tracing::debug!( "[dedup_vertices_epsilon] distance equals epsilon; keeping point (strict < epsilon)" ); @@ -317,22 +313,20 @@ mod tests { use super::*; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::Coordinate; - use crate::vertex; use approx::assert_relative_eq; #[test] fn test_dedup_vertices_exact_comprehensive() { // Sub-test: Basic deduplication - let v1: Vertex = Vertex::from_points(&[Point::new([0.0, 0.0])]) + let v1: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]) .into_iter() .next() .unwrap(); - let v2: Vertex = Vertex::from_points(&[Point::new([0.0, 0.0])]) + let v2: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]) .into_iter() .next() .unwrap(); - let v3: Vertex = Vertex::from_points(&[Point::new([1.0, 1.0])]) + let v3: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([1.0, 1.0])]) .into_iter() .next() .unwrap(); @@ -340,40 +334,24 @@ mod tests { let unique = dedup_vertices_exact(&vertices); assert_eq!(unique.len(), 2, "Should remove exact duplicate"); - // Sub-test: NaN handling - NaN should equal NaN - let v1_nan: Vertex = Vertex::from_points(&[Point::new([f64::NAN, f64::NAN])]) - .into_iter() - .next() - .unwrap(); - let v2_nan: Vertex = Vertex::from_points(&[Point::new([f64::NAN, f64::NAN])]) - .into_iter() - .next() - .unwrap(); - let v3_regular: Vertex = Vertex::from_points(&[Point::new([1.0, 1.0])]) - .into_iter() - .next() - .unwrap(); - let vertices_nan = vec![v1_nan, v2_nan, v3_regular]; - let unique_nan = dedup_vertices_exact(&vertices_nan); - assert_eq!( - unique_nan.len(), - 2, - "NaN should be considered equal to NaN for deduplication" - ); + assert!(Point::<2>::try_new([f64::NAN, f64::NAN]).is_err()); // Sub-test: Zero handling - +0.0 should equal -0.0 - let v1_pos_zero: Vertex = Vertex::from_points(&[Point::new([0.0, 0.0])]) - .into_iter() - .next() - .unwrap(); - let v2_neg_zero: Vertex = Vertex::from_points(&[Point::new([-0.0, -0.0])]) - .into_iter() - .next() - .unwrap(); - let v3_one: Vertex = Vertex::from_points(&[Point::new([1.0, 1.0])]) - .into_iter() - .next() - .unwrap(); + let v1_pos_zero: Vertex<(), 2> = + Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]) + .into_iter() + .next() + .unwrap(); + let v2_neg_zero: Vertex<(), 2> = + Vertex::from_points(&[Point::from_validated_coords([-0.0, -0.0])]) + .into_iter() + .next() + .unwrap(); + let v3_one: Vertex<(), 2> = + Vertex::from_points(&[Point::from_validated_coords([1.0, 1.0])]) + .into_iter() + .next() + .unwrap(); let vertices_zero = vec![v1_pos_zero, v2_neg_zero, v3_one]; let unique_zero = dedup_vertices_exact(&vertices_zero); assert_eq!( @@ -386,15 +364,16 @@ mod tests { #[test] fn test_dedup_vertices_epsilon_basic() { // Near-duplicates should be filtered - let v1: Vertex = Vertex::from_points(&[Point::new([0.0, 0.0])]) - .into_iter() - .next() - .unwrap(); - let v2: Vertex = Vertex::from_points(&[Point::new([1e-11, 1e-11])]) + let v1: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]) .into_iter() .next() .unwrap(); - let v3: Vertex = Vertex::from_points(&[Point::new([1.0, 1.0])]) + let v2: Vertex<(), 2> = + Vertex::from_points(&[Point::from_validated_coords([1e-11, 1e-11])]) + .into_iter() + .next() + .unwrap(); + let v3: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([1.0, 1.0])]) .into_iter() .next() .unwrap(); @@ -411,20 +390,21 @@ mod tests { #[test] fn test_dedup_vertices_epsilon_boundary() { // Test strict < epsilon semantics (distance = epsilon should NOT be filtered) - let v1: Vertex = Vertex::from_points(&[Point::new([0.0, 0.0])]) + let v1: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]) .into_iter() .next() .unwrap(); // Distance exactly epsilon (1e-10) in x direction - let v2: Vertex = Vertex::from_points(&[Point::new([1e-10, 0.0])]) + let v2: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([1e-10, 0.0])]) .into_iter() .next() .unwrap(); // Distance slightly less than epsilon - let v3: Vertex = Vertex::from_points(&[Point::new([0.99e-10, 0.0])]) - .into_iter() - .next() - .unwrap(); + let v3: Vertex<(), 2> = + Vertex::from_points(&[Point::from_validated_coords([0.99e-10, 0.0])]) + .into_iter() + .next() + .unwrap(); let vertices = vec![v1, v2, v3]; let unique = dedup_vertices_epsilon(&vertices, 1e-10); @@ -446,10 +426,10 @@ mod tests { #[test] fn test_dedup_vertices_epsilon_negative_epsilon_returns_input_unchanged() { - let vertices: Vec> = Vertex::from_points(&[ - Point::new([0.0, 0.0]), - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), + let vertices: Vec> = Vertex::from_points(&[ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), ]); let unique = dedup_vertices_epsilon(&vertices, -1.0); @@ -458,18 +438,19 @@ mod tests { assert_eq!( unique .iter() - .map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into) + .map(<&Vertex<_, _> as Into<[f64; 2]>>::into) .collect::>(), vertices .iter() - .map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into) + .map(<&Vertex<_, _> as Into<[f64; 2]>>::into) .collect::>() ); } #[test] fn test_try_dedup_vertices_epsilon_negative_epsilon_returns_error() { - let vertices: Vec> = Vertex::from_points(&[Point::new([0.0, 0.0])]); + let vertices: Vec> = + Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]); let err = try_dedup_vertices_epsilon(&vertices, -1.0).unwrap_err(); @@ -478,10 +459,10 @@ mod tests { #[test] fn test_dedup_vertices_epsilon_non_finite_epsilon_returns_input_unchanged() { - let vertices: Vec> = Vertex::from_points(&[ - Point::new([0.0, 0.0]), - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), + let vertices: Vec> = Vertex::from_points(&[ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), ]); for epsilon in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] { @@ -491,11 +472,11 @@ mod tests { assert_eq!( unique .iter() - .map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into) + .map(<&Vertex<_, _> as Into<[f64; 2]>>::into) .collect::>(), vertices .iter() - .map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into) + .map(<&Vertex<_, _> as Into<[f64; 2]>>::into) .collect::>() ); } @@ -503,7 +484,8 @@ mod tests { #[test] fn test_try_dedup_vertices_epsilon_non_finite_epsilon_returns_error() { - let vertices: Vec> = Vertex::from_points(&[Point::new([0.0, 0.0])]); + let vertices: Vec> = + Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]); for epsilon in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] { let err = try_dedup_vertices_epsilon(&vertices, epsilon).unwrap_err(); @@ -516,12 +498,12 @@ mod tests { fn test_dedup_vertices_epsilon_preserves_first_occurrence() { // Verify that first occurrence is kept, later duplicates removed let points = [ - Point::new([0.0, 0.0]), - Point::new([1e-11, 1e-11]), // Near-duplicate of first - Point::new([1.0, 1.0]), - Point::new([1.0 + 1e-11, 1.0 + 1e-11]), // Near-duplicate of third + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1e-11, 1e-11]), // Near-duplicate of first + Point::from_validated_coords([1.0, 1.0]), + Point::from_validated_coords([1.0 + 1e-11, 1.0 + 1e-11]), // Near-duplicate of third ]; - let vertices: Vec> = Vertex::from_points(&points); + let vertices: Vec> = Vertex::from_points(&points); let unique = dedup_vertices_epsilon(&vertices, 1e-10); assert_eq!(unique.len(), 2, "Should keep first of each cluster"); @@ -529,7 +511,7 @@ mod tests { // Verify first occurrences are kept let unique_coords: Vec<_> = unique .iter() - .map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into) + .map(<&Vertex<_, _> as Into<[f64; 2]>>::into) .collect(); assert_relative_eq!(unique_coords[0][0], 0.0, epsilon = 1e-12); assert_relative_eq!(unique_coords[0][1], 0.0, epsilon = 1e-12); @@ -540,15 +522,15 @@ mod tests { #[test] fn test_filter_vertices_excluding_comprehensive() { // Sub-test: Basic exclusion - let v1: Vertex = Vertex::from_points(&[Point::new([0.0, 0.0])]) + let v1: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]) .into_iter() .next() .unwrap(); - let v2: Vertex = Vertex::from_points(&[Point::new([1.0, 1.0])]) + let v2: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([1.0, 1.0])]) .into_iter() .next() .unwrap(); - let v3: Vertex = Vertex::from_points(&[Point::new([2.0, 2.0])]) + let v3: Vertex<(), 2> = Vertex::from_points(&[Point::from_validated_coords([2.0, 2.0])]) .into_iter() .next() .unwrap(); @@ -561,29 +543,19 @@ mod tests { "Should exclude vertex matching reference" ); - // Sub-test: NaN exclusion - NaN reference should match NaN vertices - let v_nan: Vertex = Vertex::from_points(&[Point::new([f64::NAN, f64::NAN])]) - .into_iter() - .next() - .unwrap(); - let reference_nan = vec![v_nan]; - let vertices_with_nan: Vec> = - Vertex::from_points(&[Point::new([f64::NAN, f64::NAN]), Point::new([1.0, 1.0])]); - let filtered_nan = filter_vertices_excluding(&vertices_with_nan, &reference_nan); - assert_eq!( - filtered_nan.len(), - 1, - "NaN reference should exclude NaN vertex" - ); + assert!(Point::<2>::try_new([f64::NAN, f64::NAN]).is_err()); // Sub-test: Zero exclusion - +0.0 reference should match -0.0 vertices - let v_pos_zero: Vertex = Vertex::from_points(&[Point::new([0.0, 0.0])]) - .into_iter() - .next() - .unwrap(); + let v_pos_zero: Vertex<(), 2> = + Vertex::from_points(&[Point::from_validated_coords([0.0, 0.0])]) + .into_iter() + .next() + .unwrap(); let reference_zero = vec![v_pos_zero]; - let vertices_with_neg_zero: Vec> = - Vertex::from_points(&[Point::new([-0.0, -0.0]), Point::new([1.0, 1.0])]); + let vertices_with_neg_zero: Vec> = Vertex::from_points(&[ + Point::from_validated_coords([-0.0, -0.0]), + Point::from_validated_coords([1.0, 1.0]), + ]); let filtered_zero = filter_vertices_excluding(&vertices_with_neg_zero, &reference_zero); assert_eq!( filtered_zero.len(), @@ -594,12 +566,12 @@ mod tests { // Sub-test: Multiple reference vertices // Multiple reference vertices let points = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 1.0]), - Point::new([2.0, 2.0]), - Point::new([3.0, 3.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 1.0]), + Point::from_validated_coords([2.0, 2.0]), + Point::from_validated_coords([3.0, 3.0]), ]; - let vertices: Vec> = Vertex::from_points(&points); + let vertices: Vec> = Vertex::from_points(&points); let reference = vec![vertices[0], vertices[2]]; // Exclude first and third let filtered = filter_vertices_excluding(&vertices, &reference); @@ -609,7 +581,7 @@ mod tests { // Verify remaining vertices are second and fourth let filtered_coords: Vec<_> = filtered .iter() - .map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into) + .map(<&Vertex<_, _> as Into<[f64; 2]>>::into) .collect(); assert_relative_eq!(filtered_coords[0][0], 1.0, epsilon = 1e-12); assert_relative_eq!(filtered_coords[0][1], 1.0, epsilon = 1e-12); @@ -619,9 +591,12 @@ mod tests { #[test] fn test_filter_vertices_excluding_empty_reference() { - let vertices: Vec> = - vec![vertex!([0.0]), vertex!([1.0]), vertex!([2.0])]; - let reference: Vec> = vec![]; + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0]).unwrap(), + ]; + let reference: Vec> = vec![]; let filtered = filter_vertices_excluding(&vertices, &reference); assert_eq!(filtered.len(), vertices.len()); } diff --git a/src/core/util/delaunay_validation.rs b/src/core/util/delaunay_validation.rs index 1989be16..1c0a2d7a 100644 --- a/src/core/util/delaunay_validation.rs +++ b/src/core/util/delaunay_validation.rs @@ -14,7 +14,7 @@ use crate::core::traits::data_type::DataType; use crate::geometry::point::Point; use crate::geometry::predicates::InSphere; use crate::geometry::robust_predicates::robust_insphere; -use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar}; +use crate::geometry::traits::coordinate::CoordinateConversionError; use smallvec::SmallVec; use thiserror::Error; @@ -60,7 +60,7 @@ pub enum DelaunayValidationError { }, /// Numeric predicate failure during Delaunay validation. #[error( - "Numeric predicate failure while validating Delaunay property for simplex {simplex_key:?}, vertex {vertex_key:?}: {source}" + "Numeric predicate failure while validating Delaunay property for simplex {simplex_key:?} against vertex {vertex_key:?}: {source}" )] NumericPredicateError { /// The key of the simplex whose circumsphere was being tested. @@ -95,9 +95,9 @@ pub enum DelaunayValidationError { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -188,13 +188,12 @@ pub struct DelaunayViolationDetail { /// Returns `Ok(None)` if the simplex satisfies the Delaunay property, /// `Ok(Some(simplex_key))` if it violates (has an external vertex inside its circumsphere), /// or `Err(...)` if validation fails due to structural or numeric issues. -fn validate_simplex_delaunay( - tds: &Tds, +fn validate_simplex_delaunay( + tds: &Tds, simplex_key: SimplexKey, - simplex_vertex_points: &mut SmallVec<[Point; 8]>, + simplex_vertex_points: &mut SmallVec<[Point; 8]>, ) -> Result, DelaunayValidationError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -205,13 +204,12 @@ where } /// Finds one external vertex that witnesses a simplex's Delaunay violation. -fn first_delaunay_violation_witness( - tds: &Tds, +fn first_delaunay_violation_witness( + tds: &Tds, simplex_key: SimplexKey, - simplex_vertex_points: &mut SmallVec<[Point; 8]>, + simplex_vertex_points: &mut SmallVec<[Point; 8]>, ) -> Result, DelaunayValidationError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -280,8 +278,7 @@ where let Some(a_neighbors) = simplex.neighbor_keys() else { break 'artifact false; }; - let mut b_points: SmallVec<[Point; 8]> = - SmallVec::with_capacity(D + 1); + let mut b_points: SmallVec<[Point; 8]> = SmallVec::with_capacity(D + 1); for (k, neighbor_opt) in a_neighbors.enumerate() { let Some(b_key) = neighbor_opt else { continue; @@ -360,16 +357,15 @@ where /// This performs the expensive geometric check but intentionally does **not** run /// `tds.is_valid()` up front. Callers that want cumulative validation should run /// lower-layer checks separately. -pub(crate) fn is_delaunay_property_only( - tds: &Tds, +pub(crate) fn is_delaunay_property_only( + tds: &Tds, ) -> Result<(), DelaunayValidationError> where - T: CoordinateScalar, U: DataType, V: DataType, { // Reusable buffer to minimize allocations - let mut simplex_vertex_points: SmallVec<[Point; 8]> = SmallVec::with_capacity(D + 1); + let mut simplex_vertex_points: SmallVec<[Point; 8]> = SmallVec::with_capacity(D + 1); // Check each simplex using the shared validation helper for simplex_key in tds.simplex_keys() { @@ -426,10 +422,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -441,17 +437,16 @@ where /// # Ok(()) /// # } /// ``` -pub fn find_delaunay_violations( - tds: &Tds, +pub fn find_delaunay_violations( + tds: &Tds, simplices_to_check: Option<&[SimplexKey]>, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { let mut violating_simplices = ViolationBuffer::new(); - let mut simplex_vertex_points: SmallVec<[Point; 8]> = SmallVec::with_capacity(D + 1); + let mut simplex_vertex_points: SmallVec<[Point; 8]> = SmallVec::with_capacity(D + 1); #[cfg(any(test, debug_assertions))] if let Some(keys) = simplices_to_check { @@ -541,10 +536,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -555,12 +550,11 @@ where /// ``` #[cfg(any(test, feature = "diagnostics"))] #[cfg_attr(docsrs, doc(cfg(feature = "diagnostics")))] -pub fn delaunay_violation_report( - tds: &Tds, +pub fn delaunay_violation_report( + tds: &Tds, simplices_to_check: Option<&[SimplexKey]>, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -582,12 +576,11 @@ where /// Builds the compact detail record for a violating simplex that still exists in the TDS. #[cfg(any(test, feature = "diagnostics"))] -fn build_violation_detail( - tds: &Tds, +fn build_violation_detail( + tds: &Tds, simplex_key: SimplexKey, ) -> Option where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -608,16 +601,15 @@ where /// Finds one external vertex that witnesses a simplex's Delaunay violation, if available. #[cfg(any(test, feature = "diagnostics"))] -fn first_offending_vertex( - tds: &Tds, +fn first_offending_vertex( + tds: &Tds, simplex_key: SimplexKey, ) -> Option where - T: CoordinateScalar, U: DataType, V: DataType, { - let mut simplex_vertex_points: SmallVec<[Point; 8]> = SmallVec::with_capacity(D + 1); + let mut simplex_vertex_points: SmallVec<[Point; 8]> = SmallVec::with_capacity(D + 1); first_delaunay_violation_witness(tds, simplex_key, &mut simplex_vertex_points) .ok() .flatten() @@ -642,7 +634,7 @@ where /// use delaunay::prelude::tds::Tds; /// use delaunay::prelude::diagnostics::debug_print_first_delaunay_violation; /// -/// let tds: Tds = Tds::empty(); +/// let tds: Tds<(), (), 3> = Tds::empty(); /// debug_print_first_delaunay_violation(&tds, None); /// ``` #[cfg(any(test, feature = "diagnostics"))] @@ -651,11 +643,10 @@ where clippy::too_many_lines, reason = "Debug-only helper with intentionally verbose logging" )] -pub fn debug_print_first_delaunay_violation( - tds: &Tds, +pub fn debug_print_first_delaunay_violation( + tds: &Tds, simplices_subset: Option<&[SimplexKey]>, ) where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -803,28 +794,22 @@ mod tests { use crate::core::algorithms::incremental_insertion::repair_neighbor_pointers; use crate::core::simplex::{NeighborSlot, Simplex}; use crate::core::triangulation::Triangulation; - use crate::core::util::make_uuid; - use crate::core::vertex::Vertex; use crate::geometry::kernel::FastKernel; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, InvalidCoordinateValue, - }; + use crate::geometry::traits::coordinate::{CoordinateConversionError, InvalidCoordinateValue}; use crate::triangulation::DelaunayTriangulation; use std::assert_matches; - use crate::vertex; - #[test] fn delaunay_validator_reports_no_violations_for_simple_tetrahedron() { init_tracing(); println!("Testing Delaunay validator and debug helper on a simple 3D tetrahedron"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -859,19 +844,35 @@ mod tests { }); } - fn build_non_delaunay_quad_2d() -> (Tds, SimplexKey, SimplexKey) { - let mut tds: Tds = Tds::empty(); + fn build_non_delaunay_quad_2d() -> (Tds<(), (), 2>, SimplexKey, SimplexKey) { + let mut tds: Tds<(), (), 2> = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([0.8, 0.8])).unwrap(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.8]).unwrap(), + ) + .unwrap(); let simplex_1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let simplex_2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, c, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, c, d], None).unwrap()) .unwrap(); tds.assign_incident_simplices().unwrap(); @@ -883,18 +884,34 @@ mod tests { #[test] fn delaunay_validator_reports_violation_for_non_delaunay_quad_2d() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([0.8, 0.8])).unwrap(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.8]).unwrap(), + ) + .unwrap(); let simplex_1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let simplex_2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, c, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, c, d], None).unwrap()) .unwrap(); tds.assign_incident_simplices().unwrap(); @@ -909,18 +926,34 @@ mod tests { #[test] fn find_delaunay_violations_subset_skips_missing_simplices() { init_tracing(); - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let d = tds.insert_vertex_with_mapping(vertex!([0.8, 0.8])).unwrap(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let d = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.8]).unwrap(), + ) + .unwrap(); let simplex_1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); let _simplex_2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, c, d], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, c, d], None).unwrap()) .unwrap(); tds.assign_incident_simplices().unwrap(); @@ -933,7 +966,7 @@ mod tests { #[test] fn delaunay_validation_handles_empty_tds() { init_tracing(); - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); assert!(is_delaunay_property_only(&tds).is_ok()); let violations = find_delaunay_violations(&tds, None).unwrap(); @@ -959,31 +992,34 @@ mod tests { } #[test] - fn delaunay_property_only_handles_non_finite_vertex_without_error() { + fn delaunay_property_only_rejects_non_finite_vertex_at_point_boundary() { init_tracing(); - let mut tds: Tds = Tds::empty(); - - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - tds.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) .unwrap(); - let invalid_uuid = make_uuid(); - let invalid_vk = tds - .insert_vertex_with_mapping(Vertex::new_with_uuid( - Point::new([f64::NAN, 0.0]), - invalid_uuid, - None, - )) + tds.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); - tds.remove_vertex(invalid_vk).unwrap(); + assert!(Point::<2>::try_new([f64::NAN, 0.0]).is_err()); assert!( is_delaunay_property_only(&tds).is_ok(), - "delaunay_property_only_handles_non_finite_vertex_without_error should skip non-finite vertices before validation" + "delaunay_property_only should remain valid when non-finite vertices are rejected before insertion" ); } @@ -1021,10 +1057,10 @@ mod tests { fn delaunay_violation_report_summarizes_valid_tds() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1069,13 +1105,25 @@ mod tests { #[test] fn delaunay_violation_detail_preserves_neighbor_slot_state() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let a = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let b = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let c = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap()) .unwrap(); tds.assign_neighbors().unwrap(); @@ -1119,10 +1167,10 @@ mod tests { fn delaunay_property_only_reports_triangulation_state_on_missing_vertex() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut tds = @@ -1154,9 +1202,9 @@ mod tests { fn is_delaunay_property_only_reports_invalid_simplex() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut tds = diff --git a/src/core/util/facet_keys.rs b/src/core/util/facet_keys.rs index 3f59e3bc..d8991591 100644 --- a/src/core/util/facet_keys.rs +++ b/src/core/util/facet_keys.rs @@ -44,10 +44,10 @@ use thiserror::Error; /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -71,7 +71,7 @@ use thiserror::Error; /// /// # See Also /// -/// - [`crate::core::facet::facet_key_from_vertices`] - Low-level function that computes the hash from keys +/// - [`crate::prelude::tds::facet_key_from_vertices`] - Low-level function that computes the hash from keys pub fn checked_facet_key_from_vertex_keys( facet_vertex_keys: &[VertexKey], ) -> Result { @@ -126,9 +126,9 @@ pub(crate) enum PeriodicFacetKeyDerivationError { /// Computes a translation-invariant periodic facet key from lifted simplex vertices. /// /// `lifted_vertices` must represent one full lifted simplex as `(vertex_key, lattice_offset)` pairs. -/// The facet opposite `facet_index` is selected, vertex keys are sorted for permutation -/// invariance, and offsets are normalized against the first facet vertex so equivalent -/// periodic facets hash identically. +/// The facet opposite `facet_index` is selected first. Keys are sorted for +/// permutation invariance, and offsets are normalized against the first facet +/// vertex so equivalent periodic facets hash identically. pub(crate) fn periodic_facet_key_from_lifted_vertices( lifted_vertices: &[(VertexKey, [i8; D])], facet_index: usize, @@ -219,7 +219,7 @@ pub(crate) fn periodic_facet_key_from_lifted_vertices( /// use delaunay::prelude::tds::{FacetError, Tds, verify_facet_index_consistency}; /// /// fn validate_neighbor_consistency( -/// tds: &Tds, +/// tds: &Tds<(), (), 3>, /// ) -> Result { /// // Get two neighboring simplex keys /// let simplex_keys: Vec<_> = tds.simplex_keys().take(2).collect(); @@ -248,8 +248,8 @@ pub(crate) fn periodic_facet_key_from_lifted_vertices( /// /// - Time Complexity: O(D²) where D is the dimension (iterates over facets and vertices) /// - Space Complexity: O(D) for temporary vertex buffers -pub fn verify_facet_index_consistency( - tds: &Tds, +pub fn verify_facet_index_consistency( + tds: &Tds, simplex1_key: SimplexKey, simplex2_key: SimplexKey, facet_idx: usize, @@ -365,7 +365,6 @@ mod tests { use crate::core::simplex::Simplex; use crate::core::util::measure_with_result; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use std::thread; use std::time::Instant; @@ -467,10 +466,10 @@ mod tests { // Create a triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tds = &dt.as_triangulation().tds; @@ -607,10 +606,10 @@ mod tests { fn test_verify_facet_index_consistency_true_false_and_error_cases() { // True case: comparing a simplex to itself. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tds = &dt.as_triangulation().tds; @@ -628,31 +627,47 @@ mod tests { assert_matches!(err_large, FacetError::InvalidFacetIndexOverflow { .. }); // False case: two disjoint triangles in the same TDS share no facet keys. - let mut tds2: Tds = Tds::empty(); + let mut tds2: Tds<(), (), 2> = Tds::empty(); let v_a = tds2 - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); let v_b = tds2 - .insert_vertex_with_mapping(vertex!([1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) .unwrap(); let v_c = tds2 - .insert_vertex_with_mapping(vertex!([0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) .unwrap(); let v_d = tds2 - .insert_vertex_with_mapping(vertex!([10.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0]).unwrap(), + ) .unwrap(); let v_e = tds2 - .insert_vertex_with_mapping(vertex!([11.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 10.0]).unwrap(), + ) .unwrap(); let v_f = tds2 - .insert_vertex_with_mapping(vertex!([10.0, 11.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 11.0]).unwrap(), + ) .unwrap(); let c1 = tds2 - .insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(), + ) .unwrap(); let c2 = tds2 - .insert_simplex_with_mapping(Simplex::new(vec![v_d, v_e, v_f], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v_d, v_e, v_f], None).unwrap(), + ) .unwrap(); assert!(!verify_facet_index_consistency(&tds2, c1, c2, 0).unwrap()); diff --git a/src/core/util/facet_utils.rs b/src/core/util/facet_utils.rs index a0b742c6..815e34cd 100644 --- a/src/core/util/facet_utils.rs +++ b/src/core/util/facet_utils.rs @@ -6,7 +6,6 @@ use crate::core::collections::VertexUuidBuffer; use crate::core::facet::{FacetError, FacetView}; use crate::core::traits::data_type::DataType; use crate::core::vertex::Vertex; -use crate::geometry::traits::coordinate::CoordinateScalar; /// Determines if two facet views are adjacent by comparing their vertices. /// @@ -36,11 +35,11 @@ use crate::geometry::traits::coordinate::CoordinateScalar; /// use delaunay::prelude::tds::Tds; /// /// // This is a conceptual example - in practice you would get these from a real TDS -/// fn example(tds: &Tds) -> Result { +/// fn example(tds: &Tds<(), (), 3>) -> Result { /// let simplex_keys: Vec<_> = tds.simplex_keys().take(2).collect(); /// if simplex_keys.len() >= 2 { -/// let facet1 = FacetView::new(tds, simplex_keys[0], 0)?; -/// let facet2 = FacetView::new(tds, simplex_keys[1], 0)?; +/// let facet1 = FacetView::try_new(tds, simplex_keys[0], 0)?; +/// let facet2 = FacetView::try_new(tds, simplex_keys[1], 0)?; /// /// let adjacent = facet_views_are_adjacent(&facet1, &facet2)?; /// match adjacent { @@ -53,12 +52,11 @@ use crate::geometry::traits::coordinate::CoordinateScalar; /// } /// } /// ``` -pub fn facet_views_are_adjacent( - facet1: &FacetView<'_, T, U, V, D>, - facet2: &FacetView<'_, T, U, V, D>, +pub fn facet_views_are_adjacent( + facet1: &FacetView<'_, U, V, D>, + facet2: &FacetView<'_, U, V, D>, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -70,11 +68,10 @@ where /// Canonicalizes facet vertex UUIDs so facet comparison stays allocation-light /// while remaining independent of local vertex order. -fn sorted_facet_vertex_uuids( - facet: &FacetView<'_, T, U, V, D>, +fn sorted_facet_vertex_uuids( + facet: &FacetView<'_, U, V, D>, ) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -119,10 +116,10 @@ where /// } /// /// fn extract_vertices_example( -/// tds: &Tds, +/// tds: &Tds<(), (), 3>, /// ) -> Result<(), ExampleError> { /// let simplex_key = tds.simplex_keys().next().ok_or(ExampleError::MissingSimplex)?; -/// let facet_view = FacetView::new(tds, simplex_key, 0)?; +/// let facet_view = FacetView::try_new(tds, simplex_key, 0)?; /// /// // Extract owned vertices /// let vertices = facet_view_to_vertices(&facet_view)?; @@ -136,11 +133,10 @@ where /// - Time Complexity: O(D) where D is the dimension (number of vertices in facet) /// - Space Complexity: O(D) for the returned vector /// - Uses `Copy` semantics so this is as efficient as possible for owned vertices -pub fn facet_view_to_vertices( - facet_view: &FacetView<'_, T, U, V, D>, -) -> Result>, FacetError> +pub fn facet_view_to_vertices( + facet_view: &FacetView<'_, U, V, D>, +) -> Result>, FacetError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -158,12 +154,11 @@ where /// * `k` - The size of each combination. /// #[cfg(test)] -fn generate_combinations( - vertices: &[Vertex], +fn generate_combinations( + vertices: &[Vertex], k: usize, -) -> Vec>> +) -> Vec>> where - T: CoordinateScalar, U: DataType, { let mut combinations = Vec::new(); @@ -216,17 +211,16 @@ mod tests { use crate::core::collections::FastHashSet; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use std::time::Instant; #[test] fn test_generate_combinations_comprehensive() { // Test basic functionality with 4 vertices - let vertices: Vec> = vec![ - vertex!([0.0]), - vertex!([1.0]), - vertex!([2.0]), - vertex!([3.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([3.0]).unwrap(), ]; // Combinations of 2 from 4 - should be C(4,2) = 6 @@ -285,18 +279,21 @@ mod tests { ); // Test with different size - 3 vertices, choose 2 - let small_vertices: Vec> = - vec![vertex!([1.0]), vertex!([2.0]), vertex!([3.0])]; + let small_vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([3.0]).unwrap(), + ]; let combinations_small = generate_combinations(&small_vertices, 2); assert_eq!(combinations_small.len(), 3, "C(3,2) should equal 3"); // Test larger case - 5 vertices, choose 3 to exercise inner loops - let large_vertices: Vec> = vec![ - vertex!([1.0]), - vertex!([2.0]), - vertex!([3.0]), - vertex!([4.0]), - vertex!([5.0]), + let large_vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([3.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([4.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([5.0]).unwrap(), ]; let combinations_large = generate_combinations(&large_vertices, 3); assert_eq!(combinations_large.len(), 10, "C(5,3) should equal 10"); @@ -320,7 +317,7 @@ mod tests { ); // Test empty input edge cases - let empty_vertices: Vec> = vec![]; + let empty_vertices: Vec> = vec![]; let combinations_empty_k1 = generate_combinations(&empty_vertices, 1); assert!( combinations_empty_k1.is_empty(), @@ -346,13 +343,13 @@ mod tests { // Create two tetrahedra that share 3 vertices (forming a shared triangular face) let shared_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), // v0 - vertex!([1.0, 0.0, 0.0]), // v1 - vertex!([0.5, 1.0, 0.0]), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // v0 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0, 0.0]).unwrap(), // v2 ]; - let vertex_a = vertex!([0.5, 0.5, 1.0]); // Above the shared triangle - let vertex_b = vertex!([0.5, 0.5, -1.0]); // Below the shared triangle + let vertex_a = crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 1.0]).unwrap(); // Above the shared triangle + let vertex_b = crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, -1.0]).unwrap(); // Below the shared triangle // Tetrahedron 1: shared triangle + vertex_a let mut vertices1 = shared_vertices.clone(); @@ -377,8 +374,8 @@ mod tests { for facet_idx1 in 0..4 { for facet_idx2 in 0..4 { - let fv1 = FacetView::new(tds1, simplex1_key, facet_idx1).unwrap(); - let fv2 = FacetView::new(tds2, simplex2_key, facet_idx2).unwrap(); + let fv1 = FacetView::try_new(tds1, simplex1_key, facet_idx1).unwrap(); + let fv2 = FacetView::try_new(tds2, simplex2_key, facet_idx2).unwrap(); if facet_views_are_adjacent(&fv1, &fv2).unwrap() { found_adjacent = true; facet_view1_adj = Some(fv1); @@ -403,8 +400,8 @@ mod tests { let mut found_non_adjacent = false; for facet_idx1 in 0..4 { for facet_idx2 in 0..4 { - let fv1 = FacetView::new(tds1, simplex1_key, facet_idx1).unwrap(); - let fv2 = FacetView::new(tds2, simplex2_key, facet_idx2).unwrap(); + let fv1 = FacetView::try_new(tds1, simplex1_key, facet_idx1).unwrap(); + let fv2 = FacetView::try_new(tds2, simplex2_key, facet_idx2).unwrap(); if !facet_views_are_adjacent(&fv1, &fv2).unwrap() { found_non_adjacent = true; break; @@ -438,12 +435,12 @@ mod tests { // Create two 2D triangles that share an edge (2 vertices) let shared_edge = vec![ - vertex!([0.0, 0.0]), // v0 - vertex!([1.0, 0.0]), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), // v0 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), // v1 ]; - let vertex_c = vertex!([0.5, 1.0]); // Above the shared edge - let vertex_d = vertex!([0.5, -1.0]); // Below the shared edge + let vertex_c = crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(); // Above the shared edge + let vertex_d = crate::core::vertex::Vertex::<(), _>::try_new([0.5, -1.0]).unwrap(); // Below the shared edge // Triangle 1: shared edge + vertex_c let mut vertices1 = shared_edge.clone(); @@ -466,8 +463,8 @@ mod tests { let mut found_adjacent = false; for facet_idx1 in 0..3 { for facet_idx2 in 0..3 { - let facet_view1 = FacetView::new(tds1, simplex1_key, facet_idx1).unwrap(); - let facet_view2 = FacetView::new(tds2, simplex2_key, facet_idx2).unwrap(); + let facet_view1 = FacetView::try_new(tds1, simplex1_key, facet_idx1).unwrap(); + let facet_view2 = FacetView::try_new(tds2, simplex2_key, facet_idx2).unwrap(); if facet_views_are_adjacent(&facet_view1, &facet_view2).unwrap() { found_adjacent = true; break; @@ -493,9 +490,9 @@ mod tests { // In 1D, simplices are edges and facets are vertices (0D) // Two edges sharing a vertex have adjacent facets - let shared_vertex = vertex!([0.0]); - let vertex_left = vertex!([-1.0]); - let vertex_right = vertex!([1.0]); + let shared_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(); + let vertex_left = crate::core::vertex::Vertex::<(), _>::try_new([-1.0]).unwrap(); + let vertex_right = crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(); // Edge 1: shared_vertex to vertex_left let vertices1 = vec![shared_vertex, vertex_left]; @@ -511,14 +508,14 @@ mod tests { let simplex2_key = tds2.simplex_keys().next().unwrap(); // In 1D, the facets are the individual vertices (0D) - // Both edges contain the shared vertex, so find which facet pairs are adjacent + // Both edges contain the shared so find which facet pairs are adjacent let mut found_adjacent = false; let mut found_non_adjacent = false; for facet_idx1 in 0..2 { for facet_idx2 in 0..2 { - let fv1 = FacetView::new(tds1, simplex1_key, facet_idx1).unwrap(); - let fv2 = FacetView::new(tds2, simplex2_key, facet_idx2).unwrap(); + let fv1 = FacetView::try_new(tds1, simplex1_key, facet_idx1).unwrap(); + let fv2 = FacetView::try_new(tds2, simplex2_key, facet_idx2).unwrap(); if facet_views_are_adjacent(&fv1, &fv2).unwrap() { found_adjacent = true; } else { @@ -548,10 +545,10 @@ mod tests { // Test with minimal triangulation (single tetrahedron) let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -559,10 +556,10 @@ mod tests { let simplex_key = tds.simplex_keys().next().unwrap(); // All facets of the same tetrahedron should be different from each other - let facet0 = FacetView::new(tds, simplex_key, 0).unwrap(); - let facet1 = FacetView::new(tds, simplex_key, 1).unwrap(); - let facet2 = FacetView::new(tds, simplex_key, 2).unwrap(); - let facet3 = FacetView::new(tds, simplex_key, 3).unwrap(); + let facet0 = FacetView::try_new(tds, simplex_key, 0).unwrap(); + let facet1 = FacetView::try_new(tds, simplex_key, 1).unwrap(); + let facet2 = FacetView::try_new(tds, simplex_key, 2).unwrap(); + let facet3 = FacetView::try_new(tds, simplex_key, 3).unwrap(); // Each facet should be adjacent to itself assert!(facet_views_are_adjacent(&facet0, &facet0).unwrap()); @@ -588,18 +585,18 @@ mod tests { // Create a moderately complex case to test performance let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), - vertex!([1.0, 1.0, 2.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 2.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tds = &dt.as_triangulation().tds; let simplex_key = tds.simplex_keys().next().unwrap(); - let facet1 = FacetView::new(tds, simplex_key, 0).unwrap(); - let facet2 = FacetView::new(tds, simplex_key, 1).unwrap(); + let facet1 = FacetView::try_new(tds, simplex_key, 0).unwrap(); + let facet2 = FacetView::try_new(tds, simplex_key, 1).unwrap(); // Run the adjacency check many times to measure performance let start = Instant::now(); @@ -627,17 +624,17 @@ mod tests { // Create vertices with different coordinates to ensure different UUIDs let vertices1 = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let vertices2 = vec![ - vertex!([10.0, 10.0, 10.0]), - vertex!([11.0, 10.0, 10.0]), - vertex!([10.0, 11.0, 10.0]), - vertex!([10.0, 10.0, 11.0]), + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0, 10.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 10.0, 10.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 11.0, 10.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0, 11.0]).unwrap(), ]; let dt1 = DelaunayTriangulation::new(&vertices1).unwrap(); @@ -648,8 +645,8 @@ mod tests { let simplex1_key = tds1.simplex_keys().next().unwrap(); let simplex2_key = tds2.simplex_keys().next().unwrap(); - let facet1 = FacetView::new(tds1, simplex1_key, 0).unwrap(); - let facet2 = FacetView::new(tds2, simplex2_key, 0).unwrap(); + let facet1 = FacetView::try_new(tds1, simplex1_key, 0).unwrap(); + let facet2 = FacetView::try_new(tds2, simplex2_key, 0).unwrap(); // Facets from completely different geometries should not be adjacent assert!( @@ -666,10 +663,10 @@ mod tests { // Create identical geometry in separate TDS instances let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt1 = DelaunayTriangulation::new(&vertices).unwrap(); @@ -680,8 +677,8 @@ mod tests { let simplex1_key = tds1.simplex_keys().next().unwrap(); let simplex2_key = tds2.simplex_keys().next().unwrap(); - let facet1 = FacetView::new(tds1, simplex1_key, 0).unwrap(); - let facet2 = FacetView::new(tds2, simplex2_key, 0).unwrap(); + let facet1 = FacetView::try_new(tds1, simplex1_key, 0).unwrap(); + let facet2 = FacetView::try_new(tds2, simplex2_key, 0).unwrap(); // Check if the UUID generation is deterministic based on coordinates let facet1_vertex_uuids: FastHashSet<_> = facet1 @@ -727,14 +724,16 @@ mod tests { // Create two 4D simplices (5-vertices each) that share a 3D facet (4 vertices) let shared_tetrahedron = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), // v0 - vertex!([1.0, 0.0, 0.0, 0.0]), // v1 - vertex!([0.0, 1.0, 0.0, 0.0]), // v2 - vertex!([0.0, 0.0, 1.0, 0.0]), // v3 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), // v0 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), // v3 ]; - let vertex_e = vertex!([0.25, 0.25, 0.25, 1.0]); // Above in 4th dimension - let vertex_f = vertex!([0.25, 0.25, 0.25, -1.0]); // Below in 4th dimension + let vertex_e = + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25, 1.0]).unwrap(); // Above in 4th dimension + let vertex_f = + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25, -1.0]).unwrap(); // Below in 4th dimension // 4D Simplex 1: shared tetrahedron + vertex_e let mut vertices1 = shared_tetrahedron.clone(); @@ -758,8 +757,8 @@ mod tests { for facet_idx1 in 0..5 { for facet_idx2 in 0..5 { - let fv1 = FacetView::new(tds1, simplex1_key, facet_idx1).unwrap(); - let fv2 = FacetView::new(tds2, simplex2_key, facet_idx2).unwrap(); + let fv1 = FacetView::try_new(tds1, simplex1_key, facet_idx1).unwrap(); + let fv2 = FacetView::try_new(tds2, simplex2_key, facet_idx2).unwrap(); if facet_views_are_adjacent(&fv1, &fv2).unwrap() { found_adjacent = true; } else { @@ -787,15 +786,17 @@ mod tests { // Create two 5D simplices (6-vertices each) that share a 4D facet (5 vertices) let shared_4d_simplex = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), // v0 - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), // v1 - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), // v2 - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), // v3 - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), // v4 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), // v0 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), // v3 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), // v4 ]; - let vertex_g = vertex!([0.2, 0.2, 0.2, 0.2, 1.0]); // Above in 5th dimension - let vertex_h = vertex!([0.2, 0.2, 0.2, 0.2, -1.0]); // Below in 5th dimension + let vertex_g = + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, 1.0]).unwrap(); // Above in 5th dimension + let vertex_h = + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, -1.0]).unwrap(); // Below in 5th dimension // 5D Simplex 1: shared 4D simplex + vertex_g let mut vertices1 = shared_4d_simplex.clone(); @@ -819,8 +820,8 @@ mod tests { for facet_idx1 in 0..6 { for facet_idx2 in 0..6 { - let fv1 = FacetView::new(tds1, simplex1_key, facet_idx1).unwrap(); - let fv2 = FacetView::new(tds2, simplex2_key, facet_idx2).unwrap(); + let fv1 = FacetView::try_new(tds1, simplex1_key, facet_idx1).unwrap(); + let fv2 = FacetView::try_new(tds2, simplex2_key, facet_idx2).unwrap(); if facet_views_are_adjacent(&fv1, &fv2).unwrap() { found_adjacent = true; } else { diff --git a/src/core/util/hilbert.rs b/src/core/util/hilbert.rs index f680f9fd..071379bd 100644 --- a/src/core/util/hilbert.rs +++ b/src/core/util/hilbert.rs @@ -9,12 +9,11 @@ #![forbid(unsafe_code)] -use crate::geometry::{ - coordinate_range::{CoordinateRange, CoordinateRangeError, CoordinateRangeOrdering}, - traits::coordinate::CoordinateScalar, +use crate::geometry::coordinate_range::{ + CoordinateRange, CoordinateRangeError, CoordinateRangeOrdering, }; use core::fmt; -use num_traits::cast; +use num_traits::ToPrimitive; use std::num::NonZeroU32; /// Maximum supported Hilbert bit depth per coordinate accepted by [`HilbertBitDepth`]. @@ -51,17 +50,6 @@ pub enum HilbertError { dimension: usize, }, - /// The quantization grid maximum could not be represented as the coordinate scalar. - #[error( - "Hilbert quantization grid maximum {max_grid_value} for {bits} bits cannot be represented by the coordinate scalar" - )] - QuantizationScaleConversionFailed { - /// The requested number of Hilbert bits per coordinate. - bits: u32, - /// The computed grid maximum, `2^bits - 1`. - max_grid_value: u32, - }, - /// A Hilbert quantization bound was non-finite. #[error( "Hilbert quantization bounds must be finite: lower bound finite = {lower_bound_finite}, upper bound finite = {upper_bound_finite}" @@ -490,9 +478,7 @@ impl HilbertQuantizedVec { /// /// Returns [`HilbertError::IndexOverflow`] if `D * bits > 128` (index would not fit in `u128`). /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX`. -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. Returns -/// [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], +/// Returns [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], /// [`HilbertError::NonFiniteNormalizedCoordinate`], or /// [`HilbertError::QuantizedCoordinateConversionFailed`] if coordinate /// quantization fails. @@ -519,15 +505,12 @@ impl HilbertQuantizedVec { /// # Ok(()) /// # } /// ``` -pub fn hilbert_quantize_batch_in_range( +pub fn hilbert_quantize_batch_in_range( items: &[Item], - bounds: CoordinateRange, + bounds: CoordinateRange, bits: HilbertBitDepth, - mut coords_of: impl FnMut(&Item) -> [T; D], -) -> Result, HilbertError> -where - T: CoordinateScalar, -{ + mut coords_of: impl FnMut(&Item) -> [f64; D], +) -> Result, HilbertError> { let index_mode = HilbertIndexMode::try_new(bits)?; if D == 0 { @@ -537,7 +520,7 @@ where }); } - let (max_val_u32, max_val_t) = quantization_scale::(bits)?; + let (max_val_u32, max_val_t) = quantization_scale(bits); let quantized = items .iter() @@ -553,20 +536,10 @@ where }) } -/// Converts a validated bit depth into a scalar grid maximum so Hilbert -/// ordering cannot silently collapse when a coordinate type cannot represent it. -fn quantization_scale( - bits: HilbertBitDepth, -) -> Result<(u32, T), HilbertError> { - let bits_value = bits.get(); +/// Converts a validated bit depth into the exact f64 grid maximum. +fn quantization_scale(bits: HilbertBitDepth) -> (u32, f64) { let max_grid_value = max_quantized_coordinate(bits); - let Some(max_grid_value_scalar): Option = cast(max_grid_value) else { - return Err(HilbertError::QuantizationScaleConversionFailed { - bits: bits_value, - max_grid_value, - }); - }; - Ok((max_grid_value, max_grid_value_scalar)) + (max_grid_value, f64::from(max_grid_value)) } /// Returns the largest coordinate accepted by Hilbert APIs for the selected grid. @@ -633,11 +606,9 @@ fn validate_index_width(bits: HilbertBitDepth) -> Result<(), Hil Ok(()) } -fn parse_hilbert_bounds( - bounds: (T, T), -) -> Result, HilbertError> { - let lower_bound_finite = bounds.0.is_finite_generic(); - let upper_bound_finite = bounds.1.is_finite_generic(); +fn parse_hilbert_bounds(bounds: (f64, f64)) -> Result, HilbertError> { + let lower_bound_finite = bounds.0.is_finite(); + let upper_bound_finite = bounds.1.is_finite(); CoordinateRange::try_from(bounds).map_err(|error| match error { CoordinateRangeError::NonFiniteBound { .. } => HilbertError::NonFiniteBounds { @@ -657,9 +628,6 @@ fn parse_hilbert_bounds( /// /// # Errors /// -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. -/// /// Returns [`HilbertError::NonFiniteBounds`], /// [`HilbertError::NonIncreasingBounds`], /// [`HilbertError::NonFiniteBoundsExtent`], @@ -680,9 +648,9 @@ fn parse_hilbert_bounds( /// assert!(q[0] <= 3 && q[1] <= 3); /// # Ok::<(), HilbertError>(()) /// ``` -pub fn try_hilbert_quantize( - coords: &[T; D], - bounds: (T, T), +pub fn try_hilbert_quantize( + coords: &[f64; D], + bounds: (f64, f64), bits: HilbertBitDepth, ) -> Result<[u32; D], HilbertError> { let bounds = parse_hilbert_bounds(bounds)?; @@ -691,7 +659,7 @@ pub fn try_hilbert_quantize( return Ok([0_u32; D]); } - let (max_val_u32, max_val_t) = quantization_scale::(bits)?; + let (max_val_u32, max_val_t) = quantization_scale(bits); quantize_with_scale(coords, bounds, bits, max_val_u32, max_val_t) } @@ -700,9 +668,7 @@ pub fn try_hilbert_quantize( /// /// # Errors /// -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the -/// quantization grid maximum cannot be represented by the coordinate scalar -/// type. Returns [`HilbertError::NonFiniteBoundsExtent`] if the validated bounds +/// Returns [`HilbertError::NonFiniteBoundsExtent`] if the validated bounds /// produce a non-finite extent. Returns [`HilbertError::NonFiniteCoordinate`] or /// [`HilbertError::NonFiniteNormalizedCoordinate`] if a coordinate or its /// normalized value is non-finite. Returns @@ -726,47 +692,47 @@ pub fn try_hilbert_quantize( /// # Ok(()) /// # } /// ``` -pub fn hilbert_quantize_in_range( - coords: &[T; D], - bounds: CoordinateRange, +pub fn hilbert_quantize_in_range( + coords: &[f64; D], + bounds: CoordinateRange, bits: HilbertBitDepth, ) -> Result<[u32; D], HilbertError> { if D == 0 { return Ok([0_u32; D]); } - let (max_val_u32, max_val_t) = quantization_scale::(bits)?; + let (max_val_u32, max_val_t) = quantization_scale(bits); quantize_with_scale(coords, bounds, bits, max_val_u32, max_val_t) } /// Quantizes coordinates with a precomputed scalar grid maximum so hot callers /// can validate conversion once before sorting or batch index generation. #[inline] -fn quantize_with_scale( - coords: &[T; D], - bounds: CoordinateRange, +fn quantize_with_scale( + coords: &[f64; D], + bounds: CoordinateRange, bits: HilbertBitDepth, max_val_u32: u32, - max_val_t: T, + max_val_t: f64, ) -> Result<[u32; D], HilbertError> { let min = bounds.min(); let max = bounds.max(); let extent = max - min; - if !extent.is_finite_generic() { + if !extent.is_finite() { return Err(HilbertError::NonFiniteBoundsExtent {}); } let mut quantized = [0_u32; D]; for (i, &coord) in coords.iter().enumerate() { - if !coord.is_finite_generic() { + if !coord.is_finite() { return Err(HilbertError::NonFiniteCoordinate { coordinate_index: i, }); } let t = (coord - min) / extent; - let normalized = if t.is_finite_generic() { - t.max(T::zero()).min(T::one()) + let normalized = if t.is_finite() { + t.clamp(0.0, 1.0) } else { return Err(HilbertError::NonFiniteNormalizedCoordinate { coordinate_index: i, @@ -859,9 +825,6 @@ fn apply_order( /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX` /// (extremely unlikely in practice). /// -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. -/// /// Returns [`HilbertError::NonFiniteBounds`], /// [`HilbertError::NonIncreasingBounds`], /// [`HilbertError::NonFiniteBoundsExtent`], @@ -881,9 +844,9 @@ fn apply_order( /// assert_eq!(idx, 0); /// # Ok::<(), HilbertError>(()) /// ``` -pub fn try_hilbert_index( - coords: &[T; D], - bounds: (T, T), +pub fn try_hilbert_index( + coords: &[f64; D], + bounds: (f64, f64), bits: HilbertBitDepth, ) -> Result { let bounds = parse_hilbert_bounds(bounds)?; @@ -896,9 +859,7 @@ pub fn try_hilbert_index( /// /// Returns [`HilbertError::IndexOverflow`] if `D * bits > 128` (index would not fit in `u128`). /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX`. -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. Returns -/// [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], +/// Returns [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], /// [`HilbertError::NonFiniteNormalizedCoordinate`], or /// [`HilbertError::QuantizedCoordinateConversionFailed`] if coordinate /// quantization fails. @@ -918,9 +879,9 @@ pub fn try_hilbert_index( /// # Ok(()) /// # } /// ``` -pub fn hilbert_index_in_range( - coords: &[T; D], - bounds: CoordinateRange, +pub fn hilbert_index_in_range( + coords: &[f64; D], + bounds: CoordinateRange, bits: HilbertBitDepth, ) -> Result { let index_mode = HilbertIndexMode::try_new(bits)?; @@ -1020,9 +981,6 @@ fn index_from_quantized(coords: &[u32; D], params: HilbertIndexP /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX` /// (extremely unlikely in practice). /// -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. -/// /// Returns [`HilbertError::NonFiniteBounds`], /// [`HilbertError::NonIncreasingBounds`], /// [`HilbertError::NonFiniteBoundsExtent`], @@ -1048,11 +1006,11 @@ fn index_from_quantized(coords: &[u32; D], params: HilbertIndexP /// assert_eq!(points[0], [0.1, 0.1]); /// # Ok::<(), HilbertError>(()) /// ``` -pub fn try_hilbert_sort_by_stable( +pub fn try_hilbert_sort_by_stable( items: &mut [Item], - bounds: (T, T), + bounds: (f64, f64), bits: HilbertBitDepth, - coords_of: impl FnMut(&Item) -> [T; D], + coords_of: impl FnMut(&Item) -> [f64; D], ) -> Result<(), HilbertError> { let bounds = parse_hilbert_bounds(bounds)?; hilbert_sort_by_stable_in_range(items, bounds, bits, coords_of) @@ -1067,9 +1025,7 @@ pub fn try_hilbert_sort_by_stable( /// /// Returns [`HilbertError::IndexOverflow`] if `D * bits > 128` (index would not fit in `u128`). /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX`. -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. Returns -/// [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], +/// Returns [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], /// [`HilbertError::NonFiniteNormalizedCoordinate`], or /// [`HilbertError::QuantizedCoordinateConversionFailed`] if coordinate /// quantization fails. @@ -1097,18 +1053,18 @@ pub fn try_hilbert_sort_by_stable( /// # Ok(()) /// # } /// ``` -pub fn hilbert_sort_by_stable_in_range( +pub fn hilbert_sort_by_stable_in_range( items: &mut [Item], - bounds: CoordinateRange, + bounds: CoordinateRange, bits: HilbertBitDepth, - mut coords_of: impl FnMut(&Item) -> [T; D], + mut coords_of: impl FnMut(&Item) -> [f64; D], ) -> Result<(), HilbertError> { let index_mode = HilbertIndexMode::try_new(bits)?; let HilbertIndexMode::Positive(index_params) = index_mode else { return Ok(()); }; - let (max_val_u32, max_val_t) = quantization_scale::(bits)?; + let (max_val_u32, max_val_t) = quantization_scale(bits); let mut keyed: Vec<((u128, [u32; D]), usize)> = items .iter() @@ -1143,9 +1099,6 @@ pub fn hilbert_sort_by_stable_in_range(()) /// ``` -pub fn try_hilbert_sort_by_unstable( +pub fn try_hilbert_sort_by_unstable( items: &mut [Item], - bounds: (T, T), + bounds: (f64, f64), bits: HilbertBitDepth, - coords_of: impl FnMut(&Item) -> [T; D], + coords_of: impl FnMut(&Item) -> [f64; D], ) -> Result<(), HilbertError> { let bounds = parse_hilbert_bounds(bounds)?; hilbert_sort_by_unstable_in_range(items, bounds, bits, coords_of) @@ -1190,9 +1143,7 @@ pub fn try_hilbert_sort_by_unstable( /// /// Returns [`HilbertError::IndexOverflow`] if `D * bits > 128` (index would not fit in `u128`). /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX`. -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. Returns -/// [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], +/// Returns [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], /// [`HilbertError::NonFiniteNormalizedCoordinate`], or /// [`HilbertError::QuantizedCoordinateConversionFailed`] if coordinate /// quantization fails. @@ -1220,18 +1171,18 @@ pub fn try_hilbert_sort_by_unstable( /// # Ok(()) /// # } /// ``` -pub fn hilbert_sort_by_unstable_in_range( +pub fn hilbert_sort_by_unstable_in_range( items: &mut [Item], - bounds: CoordinateRange, + bounds: CoordinateRange, bits: HilbertBitDepth, - mut coords_of: impl FnMut(&Item) -> [T; D], + mut coords_of: impl FnMut(&Item) -> [f64; D], ) -> Result<(), HilbertError> { let index_mode = HilbertIndexMode::try_new(bits)?; let HilbertIndexMode::Positive(index_params) = index_mode else { return Ok(()); }; - let (max_val_u32, max_val_t) = quantization_scale::(bits)?; + let (max_val_u32, max_val_t) = quantization_scale(bits); let mut keyed: Vec<((u128, [u32; D]), usize)> = items .iter() @@ -1425,9 +1376,6 @@ fn indices_for_mode( /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX` /// (extremely unlikely in practice). /// -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. -/// /// Returns [`HilbertError::NonFiniteBounds`], /// [`HilbertError::NonIncreasingBounds`], /// [`HilbertError::NonFiniteBoundsExtent`], @@ -1448,9 +1396,9 @@ fn indices_for_mode( /// assert_eq!(order.len(), coords.len()); /// # Ok::<(), HilbertError>(()) /// ``` -pub fn try_hilbert_sorted_indices( - coords: &[[T; D]], - bounds: (T, T), +pub fn try_hilbert_sorted_indices( + coords: &[[f64; D]], + bounds: (f64, f64), bits: HilbertBitDepth, ) -> Result, HilbertError> { let bounds = parse_hilbert_bounds(bounds)?; @@ -1466,9 +1414,7 @@ pub fn try_hilbert_sorted_indices( /// /// Returns [`HilbertError::IndexOverflow`] if `D * bits > 128` (index would not fit in `u128`). /// Returns [`HilbertError::DimensionTooLarge`] if the dimension `D` exceeds `u32::MAX`. -/// Returns [`HilbertError::QuantizationScaleConversionFailed`] if the quantization -/// grid maximum cannot be represented by the coordinate scalar type. Returns -/// [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], +/// Returns [`HilbertError::NonFiniteBoundsExtent`], [`HilbertError::NonFiniteCoordinate`], /// [`HilbertError::NonFiniteNormalizedCoordinate`], or /// [`HilbertError::QuantizedCoordinateConversionFailed`] if coordinate /// quantization fails. @@ -1491,9 +1437,9 @@ pub fn try_hilbert_sorted_indices( /// # Ok(()) /// # } /// ``` -pub fn hilbert_sorted_indices_in_range( - coords: &[[T; D]], - bounds: CoordinateRange, +pub fn hilbert_sorted_indices_in_range( + coords: &[[f64; D]], + bounds: CoordinateRange, bits: HilbertBitDepth, ) -> Result, HilbertError> { let index_mode = HilbertIndexMode::try_new(bits)?; @@ -1501,7 +1447,7 @@ pub fn hilbert_sorted_indices_in_range( return Ok((0..coords.len()).collect()); }; - let (max_val_u32, max_val_t) = quantization_scale::(bits)?; + let (max_val_u32, max_val_t) = quantization_scale(bits); let mut keyed: Vec<((u128, [u32; D]), usize)> = coords .iter() @@ -1523,7 +1469,6 @@ mod tests { use std::assert_matches; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::Coordinate; fn bit_depth(value: u32) -> HilbertBitDepth { HilbertBitDepth::try_new(value).expect("test bit depth must be valid") @@ -2240,7 +2185,7 @@ mod tests { #[test] fn test_point_coords_work_with_hilbert() { - let p: Point = Point::new([0.25, 0.75]); + let p: Point<2> = Point::from_validated_coords([0.25, 0.75]); let idx = try_hilbert_index(p.coords(), (0.0, 1.0), bit_depth(16)).unwrap(); assert!(idx > 0); } diff --git a/src/core/util/jaccard.rs b/src/core/util/jaccard.rs index 98fc7daa..27569f6b 100644 --- a/src/core/util/jaccard.rs +++ b/src/core/util/jaccard.rs @@ -10,7 +10,6 @@ use crate::core::triangulation::Triangulation; use crate::geometry::algorithms::convex_hull::ConvexHull; use crate::geometry::kernel::Kernel; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::CoordinateScalar; use std::collections::HashSet; use std::fmt::Debug; use std::hash::{BuildHasher, Hash}; @@ -198,7 +197,7 @@ where /// /// # Returns /// -/// A `HashSet` containing all unique vertex coordinates as `Point` +/// A `HashSet` containing all unique vertex coordinates as `Point` /// /// # Examples /// @@ -208,10 +207,10 @@ where /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -222,11 +221,8 @@ where /// # } /// ``` #[must_use] -pub fn extract_vertex_coordinate_set( - tds: &Tds, -) -> HashSet> +pub fn extract_vertex_coordinate_set(tds: &Tds) -> HashSet> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -275,10 +271,10 @@ const fn canonical_edge(u: u128, v: u128) -> (u128, u128) { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -289,11 +285,10 @@ const fn canonical_edge(u: u128, v: u128) -> (u128, u128) { /// # Ok(()) /// # } /// ``` -pub fn extract_edge_set( - tds: &Tds, +pub fn extract_edge_set( + tds: &Tds, ) -> Result, FacetError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -357,10 +352,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -371,11 +366,10 @@ where /// # Ok(()) /// # } /// ``` -pub fn extract_facet_identifier_set( - tds: &Tds, +pub fn extract_facet_identifier_set( + tds: &Tds, ) -> Result, FacetError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -433,10 +427,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices: Vec<_> = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -448,7 +442,7 @@ where /// # } /// ``` pub fn extract_hull_facet_set( - hull: &ConvexHull, + hull: &ConvexHull, tri: &Triangulation, ) -> Result, FacetError> where @@ -462,7 +456,7 @@ where for facet_handle in hull.facets() { // Create FacetView using simplex_key() and facet_index() methods from FacetHandle let facet_view = - FacetView::new(tds, facet_handle.simplex_key(), facet_handle.facet_index())?; + FacetView::try_new(tds, facet_handle.simplex_key(), facet_handle.facet_index())?; // Use the existing FacetView::key() method let facet_id = facet_view.key()?; facet_ids.insert(facet_id); @@ -648,9 +642,7 @@ mod tests { use std::assert_matches; use crate::core::tds::{Tds, VertexKey}; - use crate::geometry::traits::coordinate::Coordinate; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use approx::assert_relative_eq; use slotmap::KeyData; @@ -706,10 +698,10 @@ mod tests { fn test_set_extraction_utilities_comprehensive() { // Create a simple tetrahedron for all extraction tests let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tds = &dt.as_triangulation().tds; @@ -717,10 +709,10 @@ mod tests { // Sub-test: Vertex coordinate extraction let coord_set = extract_vertex_coordinate_set(tds); assert_eq!(coord_set.len(), 4, "Should have 4 unique coordinates"); - assert!(coord_set.contains(&Point::new([0.0, 0.0, 0.0]))); - assert!(coord_set.contains(&Point::new([1.0, 0.0, 0.0]))); - assert!(coord_set.contains(&Point::new([0.0, 1.0, 0.0]))); - assert!(coord_set.contains(&Point::new([0.0, 0.0, 1.0]))); + assert!(coord_set.contains(&Point::from_validated_coords([0.0, 0.0, 0.0]))); + assert!(coord_set.contains(&Point::from_validated_coords([1.0, 0.0, 0.0]))); + assert!(coord_set.contains(&Point::from_validated_coords([0.0, 1.0, 0.0]))); + assert!(coord_set.contains(&Point::from_validated_coords([0.0, 0.0, 1.0]))); // Sub-test: Edge extraction - tetrahedron has 6 edges (binomial(4,2)) let edge_set = extract_edge_set(tds).unwrap(); @@ -741,10 +733,10 @@ mod tests { #[test] fn test_extract_edge_set_errors_on_missing_vertex_key() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -766,10 +758,10 @@ mod tests { #[test] fn test_extract_facet_identifier_set_errors_on_boundary_facet_retrieval_failure() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::new(&vertices).unwrap(); @@ -805,10 +797,10 @@ mod tests { #[test] fn test_extract_hull_facet_set_consistent_with_boundary_facets() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tri = dt.as_triangulation(); @@ -822,7 +814,7 @@ mod tests { #[test] fn test_extract_edge_set_empty_tds_is_empty() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 3> = Tds::empty(); let edges = extract_edge_set(&tds).unwrap(); assert!(edges.is_empty()); } diff --git a/src/core/validation.rs b/src/core/validation.rs index b200b403..9c876a53 100644 --- a/src/core/validation.rs +++ b/src/core/validation.rs @@ -4,10 +4,10 @@ //! triangulation-level validation pipeline: //! //! - **Level 1** element validity remains implemented next to the element types: -//! [`Vertex::is_valid`](crate::core::vertex::Vertex::is_valid) and -//! [`Simplex::is_valid`](crate::core::simplex::Simplex::is_valid). +//! [`Vertex::is_valid`](crate::prelude::Vertex::is_valid) and +//! [`Simplex::is_valid`](crate::prelude::tds::Simplex::is_valid). //! - **Level 2** structural validation remains implemented by -//! [`Tds`](crate::core::tds::Tds). +//! [`Tds`](crate::prelude::tds::Tds). //! - **Level 3** topological validation is orchestrated here for //! [`Triangulation`](crate::Triangulation). //! @@ -21,14 +21,14 @@ //! //! ## Level 1: Element Validity //! -//! - **Methods**: [`Simplex::is_valid()`](crate::core::simplex::Simplex::is_valid), -//! [`Vertex::is_valid()`](crate::core::vertex::Vertex::is_valid) +//! - **Methods**: [`Simplex::is_valid()`](crate::prelude::tds::Simplex::is_valid), +//! [`Vertex::is_valid()`](crate::prelude::Vertex::is_valid) //! - **Checks**: Basic data integrity (coordinate validity, UUID presence, proper initialization) //! - **Cost**: O(1) per element //! //! ## Level 2: TDS Structural Validity //! -//! - **Method**: [`Tds::is_valid()`](crate::core::tds::Tds::is_valid) +//! - **Method**: [`Tds::is_valid()`](crate::prelude::tds::Tds::is_valid) //! - **Checks**: //! - UUID ↔ Key mapping consistency //! - No duplicate simplices (same vertex sets) @@ -36,12 +36,12 @@ //! - Neighbor consistency (mutual relationships) //! - **Cost**: O(N×D²) where N = simplices, D = dimension //! -//! Use [`Tds::validate()`](crate::core::tds::Tds::validate) for cumulative +//! Use [`Tds::validate()`](crate::prelude::tds::Tds::validate) for cumulative //! Levels 1–2 (element + structural) validation. //! //! ## Level 3: Manifold Topology //! -//! - **Method**: [`Triangulation::is_valid()`](crate::core::triangulation::Triangulation::is_valid) +//! - **Method**: [`Triangulation::is_valid()`](crate::prelude::triangulation::Triangulation::is_valid) //! - **Checks**: //! - **Codimension-1 manifoldness**: exactly 1 boundary simplex or 2 interior simplices per facet //! - **Codimension-2 boundary manifoldness**: the boundary is closed ("no boundary of boundary") @@ -50,7 +50,7 @@ //! - Euler characteristic (χ = V - E + F - C matches expected topology) //! - **Cost**: O(N×D²) dominated by simplex counting //! -//! Use [`Triangulation::validate()`](crate::core::triangulation::Triangulation::validate) +//! Use [`Triangulation::validate()`](crate::prelude::triangulation::Triangulation::validate) //! for cumulative Levels 1–3. //! //! ## Level 4: Delaunay Property @@ -64,10 +64,10 @@ //! //! ## Topology guarantees //! -//! [`TopologyGuarantee`](crate::core::validation::TopologyGuarantee) selects +//! [`TopologyGuarantee`](crate::prelude::validation::TopologyGuarantee) selects //! which **manifoldness** invariants are checked by Level 3 topology validation. //! Whether those checks run automatically after insertion is controlled by -//! [`ValidationPolicy`](crate::core::validation::ValidationPolicy). +//! [`ValidationPolicy`](crate::prelude::validation::ValidationPolicy). //! //! Level 3 validation always checks: //! - Codimension-1 facet degree (pseudomanifold condition: 1 boundary or 2 interior simplices per facet) @@ -77,7 +77,7 @@ //! - Euler characteristic //! //! With -//! [`TopologyGuarantee::PLManifold`](crate::core::validation::TopologyGuarantee::PLManifold), +//! [`TopologyGuarantee::PLManifold`](crate::prelude::validation::TopologyGuarantee::PLManifold), //! Level 3 validation additionally checks the canonical **vertex-link** PL-manifoldness condition via //! [`crate::topology::manifold::validate_vertex_links`]. //! @@ -146,10 +146,10 @@ pub(crate) fn insertion_error_to_invariant_error( /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -399,7 +399,7 @@ pub enum ValidationPolicy { impl ValidationPolicy { /// Returns `true` if a global validation pass should be run given the observed - /// [`crate::core::operations::SuspicionFlags`]. + /// [`crate::prelude::operations::SuspicionFlags`]. #[inline] #[must_use] pub const fn should_validate(&self, suspicion: SuspicionFlags) -> bool { @@ -442,10 +442,10 @@ impl Default for ValidationPolicy { /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -655,7 +655,7 @@ pub(crate) enum InsertionValidationWork { impl Triangulation where - K: Kernel, + K: Kernel, { /// Returns the topology guarantee used for Level 3 topology validation. #[inline] @@ -912,11 +912,11 @@ where /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices_4d = [ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 4> = /// DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; @@ -1043,10 +1043,10 @@ where /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1093,7 +1093,7 @@ where /// Performs cumulative validation for Levels 1–3. /// /// This validates: - /// - **Level 1–2** via [`Tds::validate`](crate::core::tds::Tds::validate) + /// - **Level 1–2** via [`Tds::validate`](crate::prelude::tds::Tds::validate) /// - **Level 3** via [`Triangulation::is_valid`](Self::is_valid) /// - **Completion-time PL-manifold check** via [`Triangulation::validate_at_completion`](Self::validate_at_completion) /// @@ -1111,11 +1111,11 @@ where /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices_4d = [ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 4> = /// DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; @@ -1219,7 +1219,7 @@ where /// Validates that the triangulation's simplex neighbor graph is a single connected component. /// - /// Delegates to [`Tds::is_connected`](crate::core::tds::Tds::is_connected), an O(N·D) BFS + /// Delegates to [`Tds::is_connected`](crate::prelude::tds::Tds::is_connected), an O(N·D) BFS /// over neighbor pointers. pub(crate) fn validate_global_connectedness(&self) -> Result<(), TriangulationValidationError> { if !self.tds.is_connected() { @@ -1560,30 +1560,32 @@ mod tests { use crate::core::simplex::Simplex; use crate::core::tds::{GeometricError, NeighborValidationError, Tds}; use crate::core::vertex::Vertex; - use crate::core::vertex::VertexBuilder; use crate::geometry::coordinate_range::CoordinateRange; use crate::geometry::kernel::FastKernel; + use crate::geometry::point::Point; use crate::geometry::util::generate_random_points_in_range_seeded; use crate::repair::DelaunayRepairPolicy; use crate::triangulation::DelaunayTriangulation; use crate::validation::DelaunayTriangulationValidationError; - use crate::vertex; use slotmap::KeyData; use std::assert_matches; fn insert_test_vertex_with_coords( - tds: &mut Tds, + tds: &mut Tds<(), (), D>, entries: &[(usize, f64)], ) -> VertexKey { let mut coords = [0.0_f64; D]; for &(axis, value) in entries { coords[axis] = value; } - tds.insert_vertex_with_mapping(vertex!(coords)).unwrap() + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + ) + .unwrap() } - fn build_invalid_vertex_link_tds() -> (Tds, VertexKey) { - let mut tds: Tds = Tds::empty(); + fn build_invalid_vertex_link_tds() -> (Tds<(), (), D>, VertexKey) { + let mut tds: Tds<(), (), D> = Tds::empty(); let shared = insert_test_vertex_with_coords(&mut tds, &[]); if D == 2 { @@ -1603,7 +1605,9 @@ mod tests { vec![shared, second_c, second_a], ] { let _ = tds - .insert_simplex_with_mapping(Simplex::new(simplex_vertices, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(simplex_vertices, None).unwrap(), + ) .unwrap(); } @@ -1615,7 +1619,12 @@ mod tests { for axis in 0..D { let mut coords = [0.0_f64; D]; coords[axis] = 1.0; - first_simplex_vertices.push(tds.insert_vertex_with_mapping(vertex!(coords)).unwrap()); + first_simplex_vertices.push( + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + ) + .unwrap(), + ); } let mut second_simplex_vertices = vec![shared]; @@ -1623,14 +1632,23 @@ mod tests { let mut coords = [0.0_f64; D]; coords[0] = 10.0; coords[axis] += 1.0; - second_simplex_vertices.push(tds.insert_vertex_with_mapping(vertex!(coords)).unwrap()); + second_simplex_vertices.push( + tds.insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap(), + ) + .unwrap(), + ); } let _ = tds - .insert_simplex_with_mapping(Simplex::new(first_simplex_vertices, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(first_simplex_vertices, None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(second_simplex_vertices, None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(second_simplex_vertices, None).unwrap(), + ) .unwrap(); tds.assign_incident_simplices().unwrap(); @@ -1638,72 +1656,116 @@ mod tests { (tds, shared) } - fn build_disconnected_two_triangles_tds_2d() -> Tds { - let mut tds: Tds = Tds::empty(); + fn build_disconnected_two_triangles_tds_2d() -> Tds<(), (), 2> { + let mut tds: Tds<(), (), 2> = Tds::empty(); - let a0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let a1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let a2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let a0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let a1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let a2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let b0 = tds - .insert_vertex_with_mapping(vertex!([10.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0]).unwrap(), + ) .unwrap(); let b1 = tds - .insert_vertex_with_mapping(vertex!([11.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 0.0]).unwrap(), + ) .unwrap(); let b2 = tds - .insert_vertex_with_mapping(vertex!([10.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 1.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![a0, a1, a2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![a0, a1, a2], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![b0, b1, b2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![b0, b1, b2], None).unwrap(), + ) .unwrap(); tds } - fn build_three_triangles_sharing_edge_tds_2d() -> Tds { - let mut tds: Tds = Tds::empty(); + fn build_three_triangles_sharing_edge_tds_2d() -> Tds<(), (), 2> { + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0]).unwrap(), + ) + .unwrap(); + let v4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + ) .unwrap(); - let v4 = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); let _ = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v4], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(), ) .unwrap(); tds } - fn unit_simplex_vertices() -> Vec> { + fn unit_simplex_vertices() -> Vec> { let mut vertices = Vec::with_capacity(D + 1); - vertices.push(vertex!([0.0_f64; D])); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new([0.0_f64; D]).unwrap()); for axis in 0..D { let mut coords = [0.0_f64; D]; coords[axis] = 1.0; - vertices.push(vertex!(coords)); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap()); } vertices } - fn unit_simplex_interior_vertex() -> Vertex { - vertex!([0.125_f64; D]) + fn unit_simplex_interior_vertex() -> Vertex<(), D> { + crate::core::vertex::Vertex::<(), _>::try_new([0.125_f64; D]).unwrap() } fn build_single_tet() -> ( @@ -1711,21 +1773,31 @@ mod tests { [VertexKey; 4], SimplexKey, ) { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let ck = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); for vk in [v0, v1, v2, v3] { tds.vertex_mut(vk).unwrap().set_incident_simplex(Some(ck)); @@ -2090,7 +2162,9 @@ mod tests { tri.set_validation_policy(ValidationPolicy::Always); let _ = tri .tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) .unwrap(); assert_eq!(tri.number_of_simplices(), 0); @@ -2247,7 +2321,9 @@ mod tests { let (mut tri, _, _) = build_single_tet(); let iso = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 0.5, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), + ) .unwrap(); match tri.is_valid() { @@ -2293,7 +2369,9 @@ mod tests { let (mut tri, _, _) = build_single_tet(); let _ = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 0.5, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), + ) .unwrap(); match tri.validate() { @@ -2309,11 +2387,11 @@ mod tests { #[test] fn validation_report_ok_for_valid_triangulation() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2325,7 +2403,9 @@ mod tests { let (mut tri, _, _) = build_single_tet(); let _ = tri .tds - .insert_vertex_with_mapping(vertex!([0.5, 0.5, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), + ) .unwrap(); let report = tri.validation_report().unwrap_err(); @@ -2357,8 +2437,8 @@ mod tests { pastey::paste! { #[test] fn []() { - let vertices: Vec> = vec![ - $(vertex!($simplex_coords)),+ + let vertices: Vec> = vec![ + $(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+ ]; let expected_vertices = vertices.len(); @@ -2418,47 +2498,85 @@ mod tests { #[test] fn is_valid_pl_manifold_mode_rejects_wedge_at_vertex_in_2d() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([10.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0]).unwrap(), + ) .unwrap(); let v5 = tds - .insert_vertex_with_mapping(vertex!([11.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 10.0]).unwrap(), + ) .unwrap(); let v6 = tds - .insert_vertex_with_mapping(vertex!([10.0, 11.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 11.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v4, v5], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v4, v5], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v4, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v4, v6], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v5, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v5, v6], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v4, v5, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v4, v5, v6], None).unwrap(), + ) .unwrap(); repair_neighbor_pointers(&mut tds).unwrap(); @@ -2495,20 +2613,24 @@ mod tests { const N: usize = 3; const M: usize = 3; - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let mut v: [[VertexKey; M]; N] = [[VertexKey::from(KeyData::from_ffi(0)); M]; N]; for (i, row) in v.iter_mut().enumerate() { for (j, slot) in row.iter_mut().enumerate() { let i_f = >::from(u32::try_from(i).unwrap()); let j_f = >::from(u32::try_from(j).unwrap()); *slot = tds - .insert_vertex_with_mapping(vertex!([i_f, j_f, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([i_f, j_f, 0.0]).unwrap(), + ) .unwrap(); } } let apex = tds - .insert_vertex_with_mapping(vertex!([0.5, 0.5, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 1.0]).unwrap(), + ) .unwrap(); for i in 0..N { @@ -2523,7 +2645,8 @@ mod tests { for tri in [[v00, v10, v01], [v10, v11, v01]] { let _ = tds .insert_simplex_with_mapping( - Simplex::new(vec![tri[0], tri[1], tri[2], apex], None).unwrap(), + Simplex::try_new_with_data(vec![tri[0], tri[1], tri[2], apex], None) + .unwrap(), ) .unwrap(); } @@ -2560,35 +2683,55 @@ mod tests { #[test] fn is_valid_disconnected_detected_before_non_manifold_boundary_ridge() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let shared_edge_v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let shared_edge_v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let tet1_v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let tet1_v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let tet2_v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0, 0.0]).unwrap(), + ) .unwrap(); let tet2_v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, -1.0]).unwrap(), + ) .unwrap(); let _ = tds .insert_simplex_with_mapping( - Simplex::new(vec![shared_edge_v0, shared_edge_v1, tet1_v2, tet1_v3], None).unwrap(), + Simplex::try_new_with_data( + vec![shared_edge_v0, shared_edge_v1, tet1_v2, tet1_v3], + None, + ) + .unwrap(), ) .unwrap(); let _ = tds .insert_simplex_with_mapping( - Simplex::new(vec![shared_edge_v0, shared_edge_v1, tet2_v2, tet2_v3], None).unwrap(), + Simplex::try_new_with_data( + vec![shared_edge_v0, shared_edge_v1, tet2_v2, tet2_v3], + None, + ) + .unwrap(), ) .unwrap(); @@ -2605,10 +2748,10 @@ mod tests { #[test] fn validate_includes_tds_validation() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new(&vertices).unwrap(); let tri = dt.as_triangulation(); @@ -2621,7 +2764,7 @@ mod tests { fn is_valid_rejects_bootstrap_phase_with_isolated_vertex() { let mut tri: Triangulation, (), (), 3> = Triangulation::new_empty(FastKernel::new()); - let vertex = vertex!([0.0, 0.0, 0.0]); + let vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); let expected_uuid = vertex.uuid(); let expected_vk = tri.tds.insert_vertex_with_mapping(vertex).unwrap(); @@ -2640,10 +2783,10 @@ mod tests { #[test] fn is_valid_rejects_isolated_vertex_even_when_simplices_exist() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = @@ -2654,7 +2797,9 @@ mod tests { let _isolated_vk = tri .tds - .insert_vertex_with_mapping(vertex!([10.0, 10.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0, 10.0]).unwrap(), + ) .unwrap(); assert_matches!( @@ -2667,29 +2812,53 @@ mod tests { #[test] fn is_valid_rejects_disconnected_even_when_euler_matches() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 1> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([2.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0]).unwrap(), + ) + .unwrap(); let e0 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v0, v1], None).unwrap()) .unwrap(); let e1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v1, v2], None).unwrap()) .unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([10.0])).unwrap(); - let v4 = tds.insert_vertex_with_mapping(vertex!([11.0])).unwrap(); - let v5 = tds.insert_vertex_with_mapping(vertex!([12.0])).unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0]).unwrap(), + ) + .unwrap(); + let v4 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0]).unwrap(), + ) + .unwrap(); + let v5 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([12.0]).unwrap(), + ) + .unwrap(); let c0 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v3, v4], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v3, v4], None).unwrap()) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v4, v5], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v4, v5], None).unwrap()) .unwrap(); let c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v5, v3], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v5, v3], None).unwrap()) .unwrap(); for (simplex_key, neighbor_keys) in [ @@ -2728,10 +2897,10 @@ mod tests { #[test] fn tds_is_valid_rejects_boundary_facet_has_neighbor() { let vertices_simplex_1 = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut tds = @@ -2740,20 +2909,30 @@ mod tests { let first_simplex_key = tds.simplex_keys().next().unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([10.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v5 = tds - .insert_vertex_with_mapping(vertex!([11.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v6 = tds - .insert_vertex_with_mapping(vertex!([10.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v7 = tds - .insert_vertex_with_mapping(vertex!([10.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let second_simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v4, v5, v6, v7], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v4, v5, v6, v7], None).unwrap(), + ) .unwrap(); let first_simplex = tds.simplex_mut(first_simplex_key).unwrap(); @@ -2772,29 +2951,43 @@ mod tests { #[test] fn tds_is_valid_rejects_interior_facet_neighbor_mismatch() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v4], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v4], None).unwrap(), + ) .unwrap(); assert_matches!( @@ -2807,36 +3000,52 @@ mod tests { #[test] fn is_valid_non_manifold_facet_multiplicity() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), + ) .unwrap(); let v5 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 3.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 3.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v4], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v4], None).unwrap(), + ) .unwrap(); let _ = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v2, v5], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v2, v5], None).unwrap(), ) .unwrap(); @@ -2856,10 +3065,10 @@ mod tests { #[test] fn validation_report_returns_mapping_failures_only() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices).unwrap(); @@ -2882,17 +3091,21 @@ mod tests { #[test] fn validation_report_includes_vertex_and_simplex_validity() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices).unwrap(); let mut tri = Triangulation::, (), (), 3>::new_with_tds(FastKernel::new(), tds); - let invalid_vertex: Vertex = Vertex::empty(); + let invalid_vertex: Vertex<(), 3> = Vertex::new_with_uuid( + Point::from_validated_coords([0.25, 0.25, 0.25]), + Uuid::nil(), + None, + ); let _ = tri.tds.insert_vertex_with_mapping(invalid_vertex).unwrap(); let simplex_key = tri.tds.simplex_keys().next().unwrap(); @@ -3046,7 +3259,9 @@ mod tests { let _ = tri .tds - .insert_vertex_with_mapping(vertex!([5.0, 5.0, 5.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([5.0, 5.0, 5.0]).unwrap(), + ) .unwrap(); let simplex = tri.tds.simplex_mut(ck).unwrap(); @@ -3081,10 +3296,10 @@ mod tests { #[test] fn validate_after_insertion_ok_for_valid_simplex() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -3120,7 +3335,7 @@ mod tests { #[test] fn pl_manifold_insertion_keeps_valid_topology_with_explicit_only_validation() { let bounds = CoordinateRange::try_new(-100.0_f64, 100.0).unwrap(); - let points = generate_random_points_in_range_seeded::(25, bounds, 123); + let points = generate_random_points_in_range_seeded::<3>(25, bounds, 123); let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); @@ -3130,7 +3345,7 @@ mod tests { dt.set_delaunay_repair_policy(DelaunayRepairPolicy::Never); for (i, point) in points.into_iter().enumerate() { - let vertex = VertexBuilder::default().point(point).build().unwrap(); + let vertex = Vertex::from_validated_point(point); let (outcome, stats) = dt .insert_best_effort_with_statistics(vertex) .unwrap_or_else(|err| panic!("Non-retryable insertion error at i={i}: {err:?}")); @@ -3150,9 +3365,9 @@ mod tests { #[test] fn required_topology_validation_records_telemetry() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; for guarantee in [ @@ -3169,7 +3384,7 @@ mod tests { let hint = tri.simplices().next().map(|(simplex_key, _)| simplex_key); let detail = tri .insert_with_statistics_seeded_indexed_detailed( - vertex!([0.25, 0.25]), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25]).unwrap(), None, hint, 0, diff --git a/src/core/vertex.rs b/src/core/vertex.rs index b142121c..370f3f38 100644 --- a/src/core/vertex.rs +++ b/src/core/vertex.rs @@ -6,40 +6,37 @@ //! //! # Key Features //! -//! - **Geometry when you need it**: the `Vertex` type does not require `T: CoordinateScalar` -//! at the type level, but geometric operations, validation, and serialization are only -//! available when `T: CoordinateScalar` (`f64` in this crate) +//! - **f64 coordinate storage**: vertices store `Point` coordinates, matching +//! the crate's supported caller-visible coordinate scalar //! - **Unique Identification**: Each vertex has a UUID for consistent identification -//! - **Optional Data Storage**: [`Vertex`] and [`VertexBuilder`] support arbitrary -//! user data `U`; serialization adds [`DataSerialize`] / [`DataDeserialize`] -//! bounds when needed +//! - **Optional Data Storage**: [`Vertex`] supports arbitrary user data `U`; +//! serialization adds [`DataSerialize`] / [`DataDeserialize`] bounds when needed //! - **Incident Simplex Tracking**: Maintains references to containing simplices //! - **Serialization Support**: Serde support for persistence (`incident_simplex` is reconstructed by TDS) -//! - **Builder Pattern**: Convenient vertex construction using `VertexBuilder` //! //! # Examples //! //! ```rust //! use delaunay::prelude::Vertex; -//! use delaunay::vertex; //! //! // Create a simple vertex -//! let vertex: Vertex = vertex!([1.0, 2.0, 3.0]); +//! let vertex = Vertex::<(), 3>::try_new([1.0, 2.0, 3.0]).expect("finite vertex coordinates"); //! //! // Create vertex with data -//! let vertex_with_data: Vertex = vertex!([1.0, 2.0], 42); +//! let vertex_with_data = Vertex::::try_new_with_data([1.0, 2.0], 42).expect("finite vertex coordinates"); +//! # Ok::<(), delaunay::prelude::geometry::CoordinateConversionError>(()) //! ``` #![forbid(unsafe_code)] use super::{ - tds::SimplexKey, + tds::{EntityKind, SimplexKey, TdsConstructionError}, traits::{DataDeserialize, DataSerialize}, util::{UuidValidationError, make_uuid, validate_uuid}, }; use crate::geometry::{ point::Point, - traits::coordinate::{Coordinate, CoordinateScalar, CoordinateValidationError}, + traits::coordinate::{Coordinate, CoordinateConversionError, CoordinateValidationError}, }; use serde::{ Deserialize, Serialize, @@ -92,174 +89,6 @@ pub enum VertexValidationError { }, } -/// Errors that can occur when building a [`Vertex`] via [`VertexBuilder`]. -/// -/// # Examples -/// -/// ```rust -/// use delaunay::prelude::VertexBuilderError; -/// -/// let err = VertexBuilderError::MissingPoint; -/// assert_eq!(err.to_string(), "Missing required field: `point`"); -/// ``` -#[derive(Clone, Debug, Error, PartialEq, Eq)] -#[non_exhaustive] -pub enum VertexBuilderError { - /// The required `point` field was not set before calling [`VertexBuilder::build`]. - #[error("Missing required field: `point`")] - MissingPoint, -} - -// ============================================================================= -// VERTEX BUILDER -// ============================================================================= - -/// A builder for constructing [`Vertex`] instances. -/// -/// The builder pattern allows setting optional fields incrementally before -/// constructing the final vertex. The `point` field is required; all other -/// fields have sensible defaults. -/// -/// # Generic Parameters -/// -/// * `T` - The coordinate scalar type (`f64` for supported geometric operations) -/// * `U` - User data type. [`VertexBuilder`] accepts arbitrary metadata; serialization -/// requires [`DataSerialize`] / [`DataDeserialize`] when serializing or deserializing -/// a [`Vertex`]. -/// * `D` - The spatial dimension (compile-time constant) -/// -/// # Examples -/// -/// ```rust -/// use delaunay::prelude::{Vertex, VertexBuilder, VertexBuilderError}; -/// use delaunay::prelude::geometry::Point; -/// use delaunay::prelude::geometry::Coordinate; -/// -/// # fn main() -> Result<(), VertexBuilderError> { -/// // Build a vertex with just a point -/// let v: Vertex = VertexBuilder::default() -/// .point(Point::new([1.0, 2.0, 3.0])) -/// .build()?; -/// -/// // Build a vertex with data -/// let v: Vertex = VertexBuilder::default() -/// .point(Point::new([0.0, 1.0])) -/// .data(42) -/// .build()?; -/// # Ok(()) -/// # } -/// ``` -pub struct VertexBuilder { - point: Option>, - data: Option, -} - -impl Default for VertexBuilder { - fn default() -> Self { - Self { - point: None, - data: None, - } - } -} - -impl VertexBuilder { - /// Sets the point coordinates for the vertex. - /// - /// This is a required field — [`build`](Self::build) will return an error - /// if the point has not been set. - #[must_use] - pub fn point(mut self, point: Point) -> Self { - self.point = Some(point); - self - } - - /// Sets optional user data for the vertex. - /// - /// The value is converted via [`Into`], allowing ergonomic use with - /// compatible types. - #[must_use] - pub fn data(mut self, data: impl Into) -> Self { - self.data = Some(data.into()); - self - } - - /// Consumes the builder and returns a [`Vertex`]. - /// - /// # Errors - /// - /// Returns [`VertexBuilderError::MissingPoint`] if [`point`](Self::point) - /// was not called. - pub fn build(self) -> Result, VertexBuilderError> { - let point = self.point.ok_or(VertexBuilderError::MissingPoint)?; - Ok(Vertex { - point, - uuid: make_uuid(), - incident_simplex: None, - data: self.data, - }) - } -} - -// ============================================================================= -// CONVENIENCE MACROS AND HELPERS -// ============================================================================= - -/// Convenience macro for creating vertices with less boilerplate. -/// -/// This macro simplifies vertex creation by using the `VertexBuilder` pattern internally -/// and automatically unwrapping the result for convenience. It takes coordinate arrays -/// and optional data, returning a `Vertex` directly. -/// -/// # Returns -/// -/// Returns `Vertex` where: -/// - `T` is the coordinate scalar type (`f64` for supported geometric operations) -/// - `U` is the user data type (use `()` for no data) -/// - `D` is the spatial dimension -/// -/// # Panics -/// -/// Panics if the `VertexBuilder` fails to construct a valid vertex, which should -/// not happen under normal circumstances with valid input data. -/// -/// # Usage -/// -/// ```rust -/// use delaunay::vertex; -/// use delaunay::prelude::Vertex; -/// -/// // Create a vertex without data -/// let v1: Vertex = vertex!([1.0, 2.0, 3.0]); -/// -/// // Create a vertex with data -/// let v2: Vertex = vertex!([0.0, 1.0], 42); -/// ``` -#[macro_export] -macro_rules! vertex { - // Pattern 1: Just coordinates - no data (defaults to ()) - ($coords:expr) => { - $crate::tds::VertexBuilder::<_, (), _>::default() - .point($crate::geometry::point::Point::try_from($coords) - .expect("Failed to convert coordinates to Point: invalid or out-of-range values")) - .build() - .expect("Failed to build vertex: invalid coordinates or builder configuration") - }; - - // Pattern 2: Coordinates with data - ($coords:expr, $data:expr) => { - $crate::tds::VertexBuilder::default() - .point($crate::geometry::point::Point::try_from($coords) - .expect("Failed to convert coordinates to Point: invalid or out-of-range values")) - .data($data) - .build() - .expect("Failed to build vertex with data: invalid coordinates, data, or builder configuration") - }; -} - -// Re-export the macro at the crate level for convenience -pub use crate::vertex; - // ============================================================================= // VERTEX STRUCT DEFINITION // ============================================================================= @@ -270,41 +99,41 @@ pub use crate::vertex; /// /// # Generic Parameters /// -/// * `T` - The scalar coordinate type (`f64` in this crate) /// * `U` - User data type stored with the vertex (use `()` for no data) /// * `D` - The spatial dimension (compile-time constant) /// /// # Properties /// -/// - **`point`**: A `Point` representing the geometric coordinates of the vertex +/// - **`point`**: A `Point` representing the geometric coordinates of the vertex /// - **`uuid`**: A universally unique identifier for the vertex (auto-generated) /// - **`incident_simplex`**: Optional reference to a containing simplex (managed by TDS) /// - **`data`**: Optional user-defined data associated with the vertex. Read via [`data()`](Self::data), -/// mutate via [`Tds::set_vertex_data`](crate::core::tds::Tds::set_vertex_data) +/// mutate via [`Tds::set_vertex_data`](crate::prelude::tds::Tds::set_vertex_data) /// /// # Constraints /// -/// - `T` must implement `CoordinateScalar` for geometric operations, validation, and serialization; -/// the only currently supported caller-visible coordinate scalar is `f64` -/// (the struct itself does not require it, enabling purely combinatorial use) -/// - `U` has no bound for standalone [`Vertex`] and [`VertexBuilder`] construction -/// or access. Serialization uses [`DataSerialize`] / [`DataDeserialize`]; TDS and -/// triangulation algorithms add [`DataType`](crate::core::traits::DataType) only -/// where they need copy/debug/serde metadata behavior. +/// - `U` has no bound for standalone [`Vertex`] construction or access. +/// Serialization uses [`DataSerialize`] / [`DataDeserialize`]; TDS and +/// triangulation algorithms add [`DataType`](crate::prelude::DataType) +/// only where they need copy/debug/serde metadata behavior. /// /// # Usage /// -/// Vertices are typically created using the builder pattern for convenience: +/// Vertices are typically created from raw coordinates with [`Vertex::try_new`] +/// or [`Vertex::try_new_with_data`]. /// /// ```rust /// use delaunay::prelude::Vertex; -/// use delaunay::vertex; /// -/// let vertex: Vertex = vertex!([1.0, 2.0, 3.0], 42); +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let vertex = Vertex::::try_new_with_data([1.0, 2.0, 3.0], 42).expect("finite vertex coordinates"); +/// assert_eq!(vertex.data(), Some(&42)); +/// # Ok(()) +/// # } /// ``` -pub struct Vertex { +pub struct Vertex { /// The coordinates of the vertex as a D-dimensional Point. - point: Point, + point: Point, /// A universally unique identifier for the vertex. uuid: Uuid, /// The `SimplexKey` of the simplex that the vertex is incident to. @@ -317,7 +146,7 @@ pub struct Vertex { pub(crate) data: Option, } -impl Vertex { +impl Vertex { /// Returns the UUID of the vertex. #[inline] pub const fn uuid(&self) -> Uuid { @@ -340,15 +169,17 @@ impl Vertex { /// /// ``` /// use delaunay::prelude::Vertex; - /// use delaunay::vertex; /// use delaunay::prelude::geometry::Coordinate; /// - /// let vertex: Vertex = vertex!([1.0, 2.0, 3.0]); + /// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { + /// let vertex: Vertex<(), 3> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 3.0])?; /// let retrieved_point = vertex.point(); /// assert_eq!(retrieved_point.coords(), &[1.0, 2.0, 3.0]); + /// # Ok(()) + /// # } /// ``` #[inline] - pub const fn point(&self) -> &Point { + pub const fn point(&self) -> &Point { &self.point } @@ -362,11 +193,18 @@ impl Vertex { /// ```rust /// use delaunay::prelude::*; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Coordinates(#[from] delaunay::prelude::geometry::CoordinateConversionError), + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0])?, + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0])?, /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((_, vertex)) = dt.vertices().next() else { @@ -388,14 +226,16 @@ impl Vertex { /// # Examples /// /// ``` - /// use delaunay::vertex; /// use delaunay::prelude::Vertex; /// - /// let v: Vertex = vertex!([1.0, 2.0], 42); + /// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { + /// let v: Vertex = delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 2.0], 42)?; /// assert_eq!(v.data(), Some(&42)); /// - /// let v_no_data: Vertex = vertex!([1.0, 2.0]); + /// let v_no_data: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0])?; /// assert_eq!(v_no_data.data(), None); + /// # Ok(()) + /// # } /// ``` #[inline] #[must_use] @@ -419,9 +259,8 @@ impl Vertex { /// This implementation handles serialization of all vertex fields. The `incident_simplex` /// field is skipped as it's a runtime-only reference that gets reconstructed during /// deserialization. -impl Serialize for Vertex +impl Serialize for Vertex where - T: CoordinateScalar, U: DataSerialize, { fn serialize(&self, serializer: S) -> Result @@ -447,45 +286,41 @@ where /// /// This custom implementation ensures proper handling of all vertex fields /// during deserialization, including validation of required fields. -impl<'de, T, U, const D: usize> Deserialize<'de> for Vertex +impl<'de, U, const D: usize> Deserialize<'de> for Vertex where - T: CoordinateScalar, U: DataDeserialize, { fn deserialize(deserializer: De) -> Result where De: serde::Deserializer<'de>, { - struct VertexVisitor + struct VertexVisitor where - T: CoordinateScalar, U: DataDeserialize, { - _phantom: PhantomData<(T, U)>, + _phantom: PhantomData, } - impl<'de, T, U, const D: usize> Visitor<'de> for VertexVisitor + impl<'de, U, const D: usize> Visitor<'de> for VertexVisitor where - T: CoordinateScalar, U: DataDeserialize, { - type Value = Vertex; + type Value = Vertex; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a Vertex struct") } - fn visit_map(self, mut map: V) -> Result, V::Error> + fn visit_map(self, mut map: V) -> Result, V::Error> where V: MapAccess<'de>, { - let mut point: Option> = None; + let mut point: Option> = None; let mut uuid = None; - let mut incident_simplex = None; let mut data = None; - while let Some(key) = map.next_key()? { - match key { + while let Some(key) = map.next_key::()? { + match key.as_str() { "point" => { if point.is_some() { return Err(de::Error::duplicate_field("point")); @@ -499,12 +334,9 @@ where uuid = Some(map.next_value()?); } "incident_simplex" => { - if incident_simplex.is_some() { - return Err(de::Error::duplicate_field("incident_simplex")); - } - // TDS reconstructs incident_simplex mappings via assign_incident_simplices(). - let _ = map.next_value::()?; - incident_simplex = Some(None); + return Err(de::Error::custom( + "incident_simplex is a storage-local slotmap key and must not be deserialized; deserialize Tds so incident mappings can be reconstructed", + )); } "data" => { if data.is_some() { @@ -522,24 +354,18 @@ where let uuid: Uuid = uuid.ok_or_else(|| de::Error::missing_field("uuid"))?; validate_uuid(&uuid) .map_err(|e| de::Error::custom(format!("invalid uuid: {e}")))?; - let incident_simplex = incident_simplex.unwrap_or(None); let data = data.unwrap_or(None); - // Validate point before constructing - point.validate().map_err(|e| { - de::Error::custom(format!("Invalid point during deserialization: {e}")) - })?; - Ok(Vertex { point, uuid, - incident_simplex, + incident_simplex: None, data, }) } } - const FIELDS: &[&str] = &["point", "uuid", "incident_simplex", "data"]; + const FIELDS: &[&str] = &["point", "uuid", "data"]; deserializer.deserialize_struct( "Vertex", FIELDS, @@ -554,106 +380,101 @@ where // VERTEX IMPLEMENTATION - CORE METHODS // ============================================================================= -impl Vertex { - /// Creates an empty vertex at the origin with nil UUID and default data. - /// - /// This method creates a vertex with coordinates all set to `T::default()` (typically zero), - /// a nil UUID, and default data. This is useful for creating placeholder vertices or - /// for testing purposes. - /// - /// Note: A vertex created with `empty()` will fail validation due to the nil UUID. - /// Use the `vertex!` macro or `new()` constructor for creating valid vertices. - /// - /// # Examples - /// - /// ``` - /// use delaunay::prelude::Vertex; - /// use delaunay::prelude::geometry::Coordinate; - /// use approx::assert_relative_eq; - /// - /// let empty_vertex: Vertex = Vertex::empty(); - /// assert_relative_eq!( - /// empty_vertex.point().coords().as_slice(), - /// [0.0, 0.0, 0.0].as_slice(), - /// epsilon = 1e-9 - /// ); - /// assert!(empty_vertex.uuid().is_nil()); - /// assert!(empty_vertex.data().is_none()); - /// ``` +impl Vertex { + /// Creates a vertex from already-validated point coordinates. + #[inline] #[must_use] - pub fn empty() -> Self - where - T: CoordinateScalar + Default, - { + pub(crate) fn from_validated_point(point: Point) -> Self { Self { - point: Point::default(), - uuid: Uuid::nil(), + point, + uuid: make_uuid(), incident_simplex: None, data: None, } } - /// Creates a vertex directly from a point with a fresh UUID and no user data. + /// Creates a vertex with user data from already-validated point coordinates. + #[inline] + #[must_use] + pub(crate) fn from_validated_point_with_data(point: Point, data: impl Into) -> Self { + Self { + point, + uuid: make_uuid(), + incident_simplex: None, + data: Some(data.into()), + } + } + + /// Tries to create a vertex from raw coordinate values with a fresh UUID. /// - /// This constructor is infallible because it stores the supplied [`Point`] - /// without performing additional validation. Callers that require finite or - /// otherwise validated coordinates must enforce that before constructing - /// the vertex. + /// # Errors + /// + /// Returns [`CoordinateConversionError`] when any coordinate cannot be + /// converted exactly to finite `f64`. /// /// # Examples /// /// ```rust - /// use delaunay::prelude::geometry::{Coordinate, Point}; - /// use delaunay::prelude::Vertex; + /// use delaunay::prelude::construction::Vertex; + /// use delaunay::prelude::geometry::CoordinateConversionError; /// - /// let point = Point::new([1.0, 2.0]); - /// let vertex = Vertex::::from_point(point); + /// let vertex = Vertex::<(), 2>::try_new([0.0, 1.0])?; + /// assert_eq!(vertex.point().coords(), &[0.0, 1.0]); + /// # Ok::<(), CoordinateConversionError>(()) + /// ``` + /// + /// ```rust + /// use delaunay::prelude::construction::Vertex; + /// use delaunay::prelude::geometry::CoordinateConversionError; /// - /// assert_eq!(vertex.point().coords(), &[1.0, 2.0]); - /// assert!(vertex.data().is_none()); + /// let err = Vertex::<(), 2>::try_new([f64::INFINITY, 1.0]).unwrap_err(); + /// std::assert_matches!(err, CoordinateConversionError::NonFiniteValue { coordinate_index: 0, .. }); /// ``` #[inline] - #[must_use] - pub fn from_point(point: Point) -> Self { - Self { - point, - uuid: make_uuid(), - incident_simplex: None, - data: None, - } + pub fn try_new(coords: [T; D]) -> Result + where + T: num_traits::cast::NumCast + Copy + fmt::Debug + PartialEq, + { + Point::try_from(coords).map(Self::from_validated_point) } - /// Creates a vertex directly from a point and user data with a fresh UUID. + /// Tries to create a vertex with user data from raw coordinate values. /// - /// This constructor is infallible because it stores the supplied [`Point`] - /// and user data without performing additional validation. Callers that - /// require finite or otherwise validated coordinates must enforce that - /// before constructing the vertex. + /// # Errors + /// + /// Returns [`CoordinateConversionError`] when any coordinate cannot be + /// converted exactly to finite `f64`. /// /// # Examples /// /// ```rust - /// use delaunay::prelude::geometry::{Coordinate, Point}; - /// use delaunay::prelude::Vertex; + /// use delaunay::prelude::construction::Vertex; + /// use delaunay::prelude::geometry::CoordinateConversionError; + /// + /// let vertex = Vertex::<_, 2>::try_new_with_data([0.0, 1.0], "boundary")?; + /// assert_eq!(vertex.data(), Some(&"boundary")); + /// # Ok::<(), CoordinateConversionError>(()) + /// ``` /// - /// let point = Point::new([1.0, 2.0]); - /// let vertex = Vertex::::from_point_with_data(point, 7); + /// ```rust + /// use delaunay::prelude::construction::Vertex; + /// use delaunay::prelude::geometry::CoordinateConversionError; /// - /// assert_eq!(vertex.data(), Some(&7)); + /// let err = Vertex::<&str, 2>::try_new_with_data([f64::NAN, 1.0], "bad").unwrap_err(); + /// std::assert_matches!(err, CoordinateConversionError::NonFiniteValue { coordinate_index: 0, .. }); /// ``` #[inline] - #[must_use] - pub fn from_point_with_data(point: Point, data: impl Into) -> Self { - Self { - point, - uuid: make_uuid(), - incident_simplex: None, - data: Some(data.into()), - } + pub fn try_new_with_data( + coords: [T; D], + data: impl Into, + ) -> Result + where + T: num_traits::cast::NumCast + Copy + fmt::Debug + PartialEq, + { + Point::try_from(coords).map(|point| Self::from_validated_point_with_data(point, data)) } - /// The function `from_points` takes a slice of points and returns a - /// vector of vertices. + /// Creates vertices with fresh UUIDs from validated points. /// /// # Arguments /// @@ -661,10 +482,8 @@ impl Vertex { /// /// # Returns /// - /// The function `from_points` returns a `Vec>`, where `T` - /// is the type of the coordinates of the [Vertex], `U` is the type of the - /// optional data associated with the [Vertex], and `D` is the - /// dimensionality of the [Vertex]. + /// Returns one vertex per point. The new vertices have no user data and no + /// incident simplex pointer until they are inserted into a TDS. /// /// # Example /// @@ -672,22 +491,25 @@ impl Vertex { /// use delaunay::prelude::Vertex; /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; - /// let points = [Point::new([1.0, 2.0, 3.0])]; - /// let vertices: Vec> = Vertex::from_points(&points); + /// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { + /// let points = [Point::try_from([1.0, 2.0, 3.0])?]; + /// let vertices: Vec> = Vertex::from_points(&points); /// assert_eq!(vertices.len(), 1); /// assert_eq!(vertices[0].point().coords(), &[1.0, 2.0, 3.0]); + /// # Ok(()) + /// # } /// ``` #[inline] #[must_use] - pub fn from_points(points: &[Point]) -> Vec - where - T: Copy, - { - points.iter().copied().map(Self::from_point).collect() + pub fn from_points(points: &[Point]) -> Vec { + points + .iter() + .copied() + .map(Self::from_validated_point) + .collect() } - /// The function `into_hashmap` converts a collection of vertices into a - /// [`HashMap`], using the vertices [Uuid] as the key. + /// Converts vertices into a [`HashMap`] keyed by stable vertex [`Uuid`]. /// /// # Arguments /// @@ -695,60 +517,56 @@ impl Vertex { /// /// # Returns /// - /// The function `into_hashmap` returns a [`HashMap`] with the key type - /// [Uuid] and the value type [Vertex], i.e. `std::collections::HashMap>`. + /// Returns a [`HashMap`] whose keys are the vertices' stable UUIDs. + /// + /// # Errors + /// + /// Returns [`TdsConstructionError::DuplicateUuid`] if the input contains + /// duplicate vertex UUIDs. /// /// # Example /// /// ``` /// use std::collections::HashMap; /// use delaunay::prelude::Vertex; - /// use delaunay::vertex; /// - /// let v1: Vertex = vertex!([1.0, 2.0]); - /// let v2: Vertex = vertex!([3.0, 4.0]); + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), + /// # #[error(transparent)] + /// # Tds(#[from] delaunay::prelude::tds::TdsConstructionError), + /// # } + /// # fn main() -> Result<(), ExampleError> { + /// let v1: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0])?; + /// let v2: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([3.0, 4.0])?; /// - /// let map: HashMap<_, _> = Vertex::into_hashmap([v1, v2]); + /// let map: HashMap<_, _> = Vertex::try_into_hashmap([v1, v2])?; /// assert_eq!(map.len(), 2); /// assert!(map.values().all(|v| v.dim() == 2)); + /// # Ok(()) + /// # } /// ``` #[inline] - #[must_use] - pub fn into_hashmap(vertices: I) -> HashMap + pub fn try_into_hashmap(vertices: I) -> Result, TdsConstructionError> where I: IntoIterator, { - vertices.into_iter().map(|v| (v.uuid(), v)).collect() - } - - /// Sets the vertex UUID with validation. - /// - /// This is a test-only utility for creating vertices with specific UUIDs - /// to test error handling (e.g., duplicate UUID detection). - /// - /// # Arguments - /// - /// * `uuid` - The new UUID to set for this vertex - /// - /// # Returns - /// - /// Returns `Ok(())` if the UUID is valid and was set successfully, - /// otherwise returns a `VertexValidationError::InvalidUuid` if the UUID - /// is nil or has an invalid version. - /// - /// # Errors - /// - /// Returns `VertexValidationError::InvalidUuid` if the UUID is nil or invalid. - #[cfg(test)] - pub(crate) fn set_uuid(&mut self, uuid: Uuid) -> Result<(), VertexValidationError> { - // Validate the UUID before setting it - validate_uuid(&uuid)?; - - self.uuid = uuid; - Ok(()) + let iter = vertices.into_iter(); + let mut map = HashMap::with_capacity(iter.size_hint().0); + for vertex in iter { + let uuid = vertex.uuid(); + if map.insert(uuid, vertex).is_some() { + return Err(TdsConstructionError::DuplicateUuid { + entity: EntityKind::Vertex, + uuid, + }); + } + } + Ok(map) } - /// The function `is_valid` checks if a [Vertex] is valid. + /// Checks whether this vertex satisfies its standalone invariants. /// /// # Returns /// @@ -766,24 +584,21 @@ impl Vertex { /// # Example /// /// ``` - /// use delaunay::prelude::{Vertex, VertexValidationError}; - /// use delaunay::vertex; + /// use delaunay::prelude::{Point, Vertex, VertexValidationError}; /// use uuid::Uuid; /// - /// let vertex: Vertex = vertex!([1.0, 2.0, 3.0]); + /// # fn main() -> Result<(), VertexValidationError> { + /// let vertex: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).expect("finite vertex coordinates"); /// assert!(vertex.is_valid().is_ok()); /// - /// // Test with empty vertex (which has nil UUID) to show validation - /// let default_vertex: Vertex = Vertex::empty(); - /// match default_vertex.is_valid() { + /// match Vertex::<(), 3>::try_new_with_uuid(Point::default(), Uuid::nil(), None) { /// Err(VertexValidationError::InvalidUuid { .. }) => (), // Expected - nil UUID /// other => panic!("Expected InvalidUuid error, got: {:?}", other), /// } + /// # Ok(()) + /// # } /// ``` - pub fn is_valid(&self) -> Result<(), VertexValidationError> - where - T: CoordinateScalar, - { + pub fn is_valid(&self) -> Result<(), VertexValidationError> { // Check if the point is valid using the Coordinate trait validation self.point .validate() @@ -800,21 +615,50 @@ impl Vertex { // User data validation (if U: DataType requires it) could be added here. } - /// Create a vertex for testing with a specific UUID. + /// Creates a vertex with a caller-provided UUID after validating it. /// - /// # ⚠️ WARNING: Internal Test Helper - Do Not Use + /// This constructor is intended for serialization round-trips and other + /// boundary code that must preserve an existing stable vertex identity. + /// Ordinary construction should prefer [`Self::try_new`] or + /// [`Self::try_new_with_data`], which generate a fresh UUID automatically. /// - /// This function is **only for internal testing** and bypasses: - /// - UUID auto-generation - /// - UUID validation - /// - All safety checks + /// # Errors + /// + /// Returns [`VertexValidationError::InvalidUuid`] if `uuid` is nil or + /// otherwise fails UUID validation. + /// + /// # Example + /// + /// ```rust + /// use delaunay::prelude::{Point, Vertex}; + /// use uuid::Uuid; + /// + /// # fn main() -> Result<(), delaunay::prelude::VertexValidationError> { + /// let point = Point::default(); + /// let uuid = Uuid::from_u128(0x67e5504410b1426f9247bb680e5fe0c8); + /// let vertex = Vertex::<(), 3>::try_new_with_uuid(point, uuid, None)?; /// - /// Using this function in production code will likely lead to: - /// - Duplicate UUID collisions - /// - Invalid triangulation state - /// - Data corruption + /// assert_eq!(vertex.uuid(), uuid); + /// # Ok(()) + /// # } + /// ``` + pub fn try_new_with_uuid( + point: Point, + uuid: Uuid, + data: Option, + ) -> Result { + validate_uuid(&uuid)?; + + Ok(Self::new_with_uuid(point, uuid, data)) + } + + /// Creates a vertex with a UUID already known to be valid. /// - /// **Always use** the `vertex!` macro or builder pattern in production code. + /// This crate-internal constructor preserves stable vertex identity while + /// transforming already-valid vertices or rebuilding topology that has + /// validated UUIDs at the boundary. Public callers with raw UUIDs must use + /// [`Self::try_new_with_uuid`] so the UUID proof is established before the + /// value is stored. /// /// # Arguments /// @@ -824,9 +668,8 @@ impl Vertex { /// /// # Returns /// - /// A new `Vertex` with the specified UUID and data (unchecked). - #[doc(hidden)] - pub const fn new_with_uuid(point: Point, uuid: Uuid, data: Option) -> Self { + /// A new `Vertex` with the specified UUID and data. + pub(crate) const fn new_with_uuid(point: Point, uuid: Uuid, data: Option) -> Self { Self { point, uuid, @@ -839,10 +682,7 @@ impl Vertex { // ============================================================================= // STANDARD TRAIT IMPLEMENTATIONS // ============================================================================= -impl PartialEq for Vertex -where - T: CoordinateScalar, -{ +impl PartialEq for Vertex { /// Equality of vertices is based on ordered equality of coordinates using the Coordinate trait. #[inline] fn eq(&self, other: &Self) -> bool { @@ -853,13 +693,8 @@ where } } -impl PartialOrd for Vertex -where - T: CoordinateScalar, -{ - /// Order of vertices is based on lexicographic order of coordinates using Point's `partial_cmp`. - /// This ensures consistent ordering with special floating-point values (NaN, infinity) - /// through `OrderedFloat` semantics. +impl PartialOrd for Vertex { + /// Order of vertices is based on lexicographic order of their validated finite coordinates. #[inline] fn partial_cmp(&self, other: &Self) -> Option { self.point.partial_cmp(&other.point) @@ -867,53 +702,40 @@ where } /// Enable implicit conversion from Vertex to coordinate array -/// This allows using `Into` to convert from `Vertex` to `[T; D]` -impl From> for [T; D] { +/// This allows using `Into` to convert from `Vertex` to `[f64; D]` +impl From> for [f64; D] { #[inline] - fn from(vertex: Vertex) -> [T; D] { + fn from(vertex: Vertex) -> [f64; D] { vertex.point.into() } } /// Enable implicit conversion from Vertex reference to coordinate array -/// This allows `&vertex` to be implicitly converted to `[T; D]` for coordinate access -impl From<&Vertex> for [T; D] -where - T: Copy, -{ +/// This allows `&vertex` to be implicitly converted to `[f64; D]` for coordinate access +impl From<&Vertex> for [f64; D] { #[inline] - fn from(vertex: &Vertex) -> [T; D] { + fn from(vertex: &Vertex) -> [f64; D] { vertex.point().into() } } /// Enable implicit conversion from Vertex reference to Point -/// This allows `&vertex` to be implicitly converted to `Point` -impl From<&Vertex> for Point -where - T: Clone, -{ +/// This allows `&vertex` to be implicitly converted to `Point` +impl From<&Vertex> for Point { #[inline] - fn from(vertex: &Vertex) -> Self { - vertex.point().clone() + fn from(vertex: &Vertex) -> Self { + *vertex.point() } } // ============================================================================= // HASHING AND EQUALITY IMPLEMENTATIONS // ============================================================================= -impl Eq for Vertex -where - T: CoordinateScalar, -{ +impl Eq for Vertex { // Generic Eq implementation for Vertex based on point equality } -impl Hash for Vertex -where - T: CoordinateScalar, - Point: Hash, -{ +impl Hash for Vertex { /// Hash implementation for Vertex using only coordinates for consistency with `PartialEq`. /// /// This ensures that vertices with the same coordinates have the same hash, @@ -998,29 +820,26 @@ mod tests { // ============================================================================= /// Simplified helper function to test basic vertex properties - fn assert_vertex_properties( - vertex: &Vertex, - expected_coords: [T; D], - ) where - T: CoordinateScalar, + fn assert_vertex_properties(vertex: &Vertex, expected_coords: [f64; D]) + where U: DataType, { - assert_eq!(vertex.point().coords(), &expected_coords); + assert_abs_diff_eq!( + vertex.point().coords().as_slice(), + expected_coords.as_slice() + ); assert_eq!(vertex.dim(), D); assert!(!vertex.uuid().is_nil()); assert!(vertex.incident_simplex.is_none()); } // ============================================================================= - // VERTEX BUILDER TESTS + // VERTEX CONSTRUCTOR TESTS // ============================================================================= #[test] - fn test_vertex_builder_with_point() { - let v: Vertex = VertexBuilder::default() - .point(Point::new([1.0, 2.0, 3.0])) - .build() - .unwrap(); + fn test_vertex_try_new() { + let v: Vertex<(), 3> = Vertex::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates"); assert_relative_eq!( v.point().coords().as_slice(), [1.0, 2.0, 3.0].as_slice(), @@ -1032,12 +851,9 @@ mod tests { } #[test] - fn test_vertex_builder_with_data() { - let v: Vertex = VertexBuilder::default() - .point(Point::new([0.0, 1.0])) - .data(42) - .build() - .unwrap(); + fn test_vertex_try_new_with_data() { + let v: Vertex = + Vertex::try_new_with_data([0.0, 1.0], 42).expect("finite point coordinates"); assert_relative_eq!( v.point().coords().as_slice(), [0.0, 1.0].as_slice(), @@ -1047,39 +863,39 @@ mod tests { } #[test] - fn test_vertex_builder_accepts_non_data_type_metadata() { - let v: Vertex = VertexBuilder::default() - .point(Point::new([0.0, 1.0])) - .data(String::from("standalone-label")) - .build() - .unwrap(); + fn test_vertex_try_new_with_data_accepts_non_data_type_metadata() { + let v: Vertex = + Vertex::try_new_with_data([0.0, 1.0], String::from("standalone-label")) + .expect("finite point coordinates"); assert_eq!(v.data().map(String::as_str), Some("standalone-label")); } #[test] - fn test_vertex_builder_missing_point() { - let result = VertexBuilder::::default().build(); - assert_eq!(result, Err(VertexBuilderError::MissingPoint)); + fn test_vertex_try_new_rejects_invalid_coordinates() { + let result = Vertex::<(), 3>::try_new([1.0, f64::NAN, 3.0]); + assert!(result.is_err()); } #[test] fn test_vertex_data_accessor() { - let v_with: Vertex = vertex!([1.0, 2.0], 42); + let v_with: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0], 42).unwrap(); assert_eq!(v_with.data(), Some(&42)); - let v_without: Vertex = vertex!([1.0, 2.0]); + let v_without: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); assert_eq!(v_without.data(), None); } // ============================================================================= - // CONVENIENCE MACRO AND HELPER TESTS + // SMART CONSTRUCTOR TESTS // ============================================================================= #[test] - fn test_vertex_macro() { - // Test new macro syntax without data - no None required! - let v1: Vertex = vertex!([1.0, 2.0, 3.0]); + fn test_vertex_try_new_constructor_variants() { + let v1: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); assert_relative_eq!( v1.point().coords().as_slice(), [1.0, 2.0, 3.0].as_slice(), @@ -1089,8 +905,8 @@ mod tests { assert!(!v1.uuid().is_nil()); assert!(v1.data.is_none()); - // Test new macro syntax with data - no Some() required! - let v2: Vertex = vertex!([0.0, 1.0], 99); + let v2: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 99).unwrap(); assert_relative_eq!( v2.point().coords().as_slice(), [0.0, 1.0].as_slice(), @@ -1100,8 +916,9 @@ mod tests { assert!(!v2.uuid().is_nil()); assert_eq!(v2.data.unwrap(), 99); - // Test macro with different data type (using Copy type) - let v3: Vertex = vertex!([1.0, 2.0, 3.0, 4.0], 42u32); + let v3: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 42u32) + .unwrap(); assert_relative_eq!( v3.point().coords().as_slice(), [1.0f64, 2.0f64, 3.0f64, 4.0f64].as_slice(), @@ -1135,8 +952,6 @@ mod tests { let serialized = serde_json::to_string(&original).unwrap(); let deserialized: PointType = serde_json::from_str(&serialized).unwrap(); assert_eq!(original, deserialized); - - println!("✓ PointType u8::from conversion works correctly"); } // ============================================================================= @@ -1145,20 +960,10 @@ mod tests { #[test] fn test_vertex_basic_operations() { - // Test Vertex::empty() - let empty_vertex: Vertex = Vertex::empty(); - assert_relative_eq!( - empty_vertex.point().coords().as_slice(), - [0.0, 0.0, 0.0].as_slice(), - epsilon = 1e-9 - ); - assert_eq!(empty_vertex.dim(), 3); - assert!(empty_vertex.uuid().is_nil()); - assert!(empty_vertex.incident_simplex.is_none()); - assert!(empty_vertex.data.is_none()); - // Test vertex copying - let vertex: Vertex = vertex!([1.0, 2.0, 3.0, 4.0], 4u8); + let vertex: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 4u8) + .unwrap(); let vertex_copy = vertex; assert_eq!(vertex, vertex_copy); assert_relative_eq!( @@ -1169,11 +974,11 @@ mod tests { // Test Vertex::from_points() with multiple points let points = vec![ - Point::new([1.0, 2.0, 3.0]), - Point::new([4.0, 5.0, 6.0]), - Point::new([7.0, 8.0, 9.0]), + Point::from_validated_coords([1.0, 2.0, 3.0]), + Point::from_validated_coords([4.0, 5.0, 6.0]), + Point::from_validated_coords([7.0, 8.0, 9.0]), ]; - let mut vertices: Vec> = Vertex::from_points(&points); + let mut vertices: Vec> = Vertex::from_points(&points); assert_eq!(vertices.len(), 3); assert_relative_eq!( @@ -1196,13 +1001,13 @@ mod tests { assert_eq!(vertices[2].dim(), 3); // Test Vertex::from_points() with empty slice - let empty_points: Vec> = Vec::new(); - let empty_vertices: Vec> = Vertex::from_points(&empty_points); + let empty_points: Vec> = Vec::new(); + let empty_vertices: Vec> = Vertex::from_points(&empty_points); assert!(empty_vertices.is_empty()); // Test Vertex::from_points() with single point - let single_point = vec![Point::new([1.0, 2.0, 3.0])]; - let single_vertices: Vec> = Vertex::from_points(&single_point); + let single_point = vec![Point::from_validated_coords([1.0, 2.0, 3.0])]; + let single_vertices: Vec> = Vertex::from_points(&single_point); assert_eq!(single_vertices.len(), 1); assert_relative_eq!( single_vertices[0].point().coords().as_slice(), @@ -1212,9 +1017,9 @@ mod tests { assert_eq!(single_vertices[0].dim(), 3); assert!(!single_vertices[0].uuid().is_nil()); - // Test Vertex::into_hashmap() with multiple vertices - let hashmap = Vertex::into_hashmap(vertices.iter().copied()); - let mut values: Vec> = hashmap.into_values().collect(); + // Test Vertex::try_into_hashmap() with multiple vertices + let hashmap = Vertex::try_into_hashmap(vertices.iter().copied()).unwrap(); + let mut values: Vec> = hashmap.into_values().collect(); assert_eq!(values.len(), 3); @@ -1223,14 +1028,15 @@ mod tests { assert_eq!(values, vertices); - // Test Vertex::into_hashmap() with empty input - let empty_hashmap = Vertex::::into_hashmap([]); + // Test Vertex::try_into_hashmap() with empty input + let empty_hashmap = Vertex::<(), 3>::try_into_hashmap([]).unwrap(); assert!(empty_hashmap.is_empty()); - // Test Vertex::into_hashmap() with single vertex - let single_vertex: Vertex = vertex!([1.0, 2.0, 3.0]); + // Test Vertex::try_into_hashmap() with single vertex + let single_vertex: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let uuid = single_vertex.uuid(); - let single_hashmap = Vertex::into_hashmap([single_vertex]); + let single_hashmap = Vertex::try_into_hashmap([single_vertex]).unwrap(); assert_eq!(single_hashmap.len(), 1); assert!(single_hashmap.contains_key(&uuid)); @@ -1244,22 +1050,62 @@ mod tests { [1.0, 2.0, 3.0].as_slice(), epsilon = 1e-9 ); + } - println!("{empty_vertex:?}"); - println!("{vertices:?}"); - println!("values = {values:?}"); + #[test] + fn test_try_into_hashmap_rejects_duplicate_uuid() { + let uuid = make_uuid(); + let first: Vertex<(), 2> = + Vertex::new_with_uuid(Point::from_validated_coords([0.0, 0.0]), uuid, None); + let second: Vertex<(), 2> = + Vertex::new_with_uuid(Point::from_validated_coords([1.0, 0.0]), uuid, None); + + assert_matches!( + Vertex::try_into_hashmap([first, second]), + Err(TdsConstructionError::DuplicateUuid { + entity: EntityKind::Vertex, + uuid: duplicate_uuid, + }) if duplicate_uuid == uuid + ); + } + + #[test] + fn test_try_new_with_uuid_rejects_nil_uuid() { + let point = Point::from_validated_coords([1.0, 2.0, 3.0]); + + let result = Vertex::<(), 3>::try_new_with_uuid(point, uuid::Uuid::nil(), None); + + assert_eq!( + result.unwrap_err(), + VertexValidationError::InvalidUuid { + source: UuidValidationError::NilUuid, + } + ); + } + + #[test] + fn test_try_new_with_uuid_preserves_valid_uuid() { + let point = Point::from_validated_coords([1.0, 2.0, 3.0]); + let uuid = make_uuid(); + + let vertex = Vertex::::try_new_with_uuid(point, uuid, Some(7)).unwrap(); + + assert_eq!(vertex.uuid(), uuid); + assert_eq!(vertex.point(), &point); + assert_eq!(vertex.data(), Some(&7)); } #[test] fn test_vertex_serialization_roundtrip() { // Test basic serialization/deserialization roundtrip - let vertex: Vertex = vertex!([1.0, 2.0, 3.0]); + let vertex: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let serialized = serde_json::to_string(&vertex).unwrap(); assert!(serialized.contains("point")); assert!(serialized.contains("[1.0,2.0,3.0]")); - let deserialized: Vertex = serde_json::from_str(&serialized).unwrap(); + let deserialized: Vertex<(), 3> = serde_json::from_str(&serialized).unwrap(); // Check that deserialized vertex has same point coordinates using approx equality assert_relative_eq!( @@ -1273,12 +1119,13 @@ mod tests { assert_eq!(deserialized.uuid(), vertex.uuid()); // Test serialization with data - let vertex_with_data: Vertex = vertex!([1.0, 2.0, 3.0], 42); + let vertex_with_data: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0], 42).unwrap(); let serialized_with_data = serde_json::to_string(&vertex_with_data).unwrap(); assert!(serialized_with_data.contains("\"data\":")); assert!(serialized_with_data.contains("42")); - let deserialized_with_data: Vertex = + let deserialized_with_data: Vertex = serde_json::from_str(&serialized_with_data).unwrap(); assert_eq!(deserialized_with_data.data, Some(42)); assert_relative_eq!( @@ -1288,33 +1135,42 @@ mod tests { ); // Test serialization with None data (should omit data field) - let vertex_no_data: Vertex = vertex!([1.0, 2.0, 3.0]); + let vertex_no_data: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let serialized_no_data = serde_json::to_string(&vertex_no_data).unwrap(); assert!(!serialized_no_data.contains("\"data\":")); - let deserialized_no_data: Vertex = + let deserialized_no_data: Vertex<(), 3> = serde_json::from_str(&serialized_no_data).unwrap(); assert_eq!(deserialized_no_data.data, None); + let json_with_incident_simplex = r#"{"point":[1.0,2.0,3.0],"uuid":"550e8400-e29b-41d4-a716-446655440000","incident_simplex":{"idx":1,"version":1}}"#; + let incident_simplex_error = + serde_json::from_str::>(json_with_incident_simplex).unwrap_err(); + assert!( + incident_simplex_error + .to_string() + .contains("incident_simplex is a storage-local slotmap key") + ); + // Test backward compatibility: explicit "data": null should still work let json_with_null = r#"{"point":[1.0,2.0,3.0],"uuid":"550e8400-e29b-41d4-a716-446655440000","data":null}"#; - let vertex_null_data: Vertex = serde_json::from_str(json_with_null).unwrap(); + let vertex_null_data: Vertex<(), 3> = serde_json::from_str(json_with_null).unwrap(); assert_eq!(vertex_null_data.data, None); // Test with different data types - let vertex_char: Vertex = vertex!([1.0, 2.0, 3.0, 4.0], 'A'); + let vertex_char: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 'A') + .unwrap(); let serialized_char = serde_json::to_string(&vertex_char).unwrap(); - let deserialized_char: Vertex = - serde_json::from_str(&serialized_char).unwrap(); + let deserialized_char: Vertex = serde_json::from_str(&serialized_char).unwrap(); assert_eq!(deserialized_char.data, Some('A')); assert_relative_eq!( deserialized_char.point().coords().as_slice(), [1.0, 2.0, 3.0, 4.0].as_slice(), epsilon = f64::EPSILON ); - - println!("Serialized: {serialized:?}"); } // ============================================================================= @@ -1330,9 +1186,12 @@ mod tests { #[test] fn test_vertex_equality_and_hashing() { // Test basic equality behavior - let v1: Vertex = vertex!([1.0, 2.0, 3.0]); - let v2: Vertex = vertex!([1.0, 2.0, 3.0]); - let v3: Vertex = vertex!([1.0, 2.0, 4.0]); + let v1: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); + let v2: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); + let v3: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 4.0]).unwrap(); // Same coordinates should be equal assert_eq!(v1, v2); @@ -1350,8 +1209,10 @@ mod tests { assert!(v1.eq(&v1)); // Test that equality ignores UUID, incident_simplex, and data - let v4: Vertex = vertex!([1.0, 2.0], 42); - let v5: Vertex = vertex!([1.0, 2.0], 99); // Different data + let v4: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0], 42).unwrap(); + let v5: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0], 99).unwrap(); // Different data // Different UUIDs and data but same coordinates assert_ne!(v4.uuid(), v5.uuid()); @@ -1361,8 +1222,8 @@ mod tests { assert_eq!(v4, v5); // Test with None data - let v6: Vertex = vertex!([1.0, 2.0]); - let v7: Vertex = vertex!([1.0, 2.0]); + let v6: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); + let v7: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); assert_eq!(v6, v7); // Test hash consistency for same vertex @@ -1416,8 +1277,10 @@ mod tests { ]; for (coords1, coords2) in test_cases { - let v_a: Vertex = vertex!(coords1); - let v_b: Vertex = vertex!(coords2); + let v_a: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new(coords1).unwrap(); + let v_b: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new(coords2).unwrap(); // Verify equality assert_eq!(v_a, v_b); @@ -1433,14 +1296,34 @@ mod tests { } } + #[test] + fn test_vertex_signed_zero_eq_hash_and_order_are_consistent() { + let positive_zero: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -0.0]).unwrap(); + let negative_zero: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([-0.0, 0.0]).unwrap(); + + assert_eq!(positive_zero, negative_zero); + assert_eq!( + positive_zero.partial_cmp(&negative_zero), + Some(Ordering::Equal) + ); + + let mut positive_hash = DefaultHasher::new(); + let mut negative_hash = DefaultHasher::new(); + positive_zero.hash(&mut positive_hash); + negative_zero.hash(&mut negative_hash); + assert_eq!(positive_hash.finish(), negative_hash.finish()); + } + #[test] fn test_vertex_collections() { // Test vertices in collections to verify Hash/Eq contract in practice - let mut set: FastHashSet> = FastHashSet::default(); + let mut set: FastHashSet> = FastHashSet::default(); - let v1: Vertex = vertex!([1.0, 2.0]); - let v2: Vertex = vertex!([3.0, 4.0]); - let v3: Vertex = vertex!([1.0, 2.0]); // Same coordinates as v1 + let v1: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); + let v2: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([3.0, 4.0]).unwrap(); + let v3: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); // Same coordinates as v1 // Insert vertices assert!(set.insert(v1)); // First insert should succeed @@ -1455,14 +1338,14 @@ mod tests { assert!(set.contains(&v3)); // v3 is "found" because it equals v1 // Verify we can look up by coordinates - let v4: Vertex = vertex!([1.0, 2.0]); + let v4: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); assert!(set.contains(&v4)); // Should find it even with different UUID // Test vertices as FastHashMap keys - let mut map: FastHashMap, i32> = FastHashMap::default(); + let mut map: FastHashMap, i32> = FastHashMap::default(); - let v5: Vertex = vertex!([1.0, 2.0]); - let v6: Vertex = vertex!([3.0, 4.0]); + let v5: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); + let v6: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([3.0, 4.0]).unwrap(); map.insert(v5, 10); map.insert(v6, 20); @@ -1473,7 +1356,7 @@ mod tests { assert_eq!(map.len(), 2); // Test lookup with equivalent vertex (same coordinates, different UUID) - let v7: Vertex = vertex!([1.0, 2.0]); + let v7: Vertex<(), 2> = crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); assert_eq!(map.get(&v7), Some(&10)); // Should find v5's value // Test overwrite with equivalent vertex @@ -1483,14 +1366,16 @@ mod tests { assert_eq!(map.get(&v5), Some(&30)); // v5 now maps to new value // Test that vertices with different data types but same coordinates work in collections - let v8: Vertex = vertex!([1.0, 2.0], 999u16); - let v9: Vertex = vertex!([3.0, 4.0], -42i32); + let v8: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0], 999u16).unwrap(); + let v9: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([3.0, 4.0], -42i32).unwrap(); - let mut map1: FastHashMap, &str> = FastHashMap::default(); + let mut map1: FastHashMap, &str> = FastHashMap::default(); map1.insert(v8, "first"); assert_eq!(map1.len(), 1); - let mut map2: FastHashMap, bool> = FastHashMap::default(); + let mut map2: FastHashMap, bool> = FastHashMap::default(); map2.insert(v9, true); assert_eq!(map2.len(), 1); } @@ -1521,18 +1406,18 @@ mod tests { ),+ $(,)?) => { $( #[test] - fn $test_name() { - // Test basic vertex creation - let vertex: Vertex = vertex!([$($coord),+]); + fn $test_name() { + // Test basic vertex creation + let vertex: Vertex<(), $dim> = crate::core::vertex::Vertex::<(), _>::try_new([$($coord),+]).unwrap(); assert_vertex_properties(&vertex, [$($coord),+]); - assert!(vertex.data.is_none()); - } + assert!(vertex.data.is_none()); + } pastey::paste! { #[test] fn [<$test_name _with_data>]() { // Test vertex with data - let vertex: Vertex = vertex!([$($coord),+], 42); + let vertex: Vertex = crate::core::vertex::Vertex::<_, _>::try_new_with_data([$($coord),+], 42).unwrap(); assert_vertex_properties(&vertex, [$($coord),+]); assert_eq!(vertex.data, Some(42)); } @@ -1540,26 +1425,26 @@ mod tests { #[test] fn [<$test_name _serialization_roundtrip>]() { // Test serialization with Some data - let vertex_with_data: Vertex = vertex!([$($coord),+], 99); + let vertex_with_data: Vertex = crate::core::vertex::Vertex::<_, _>::try_new_with_data([$($coord),+], 99).unwrap(); let serialized = serde_json::to_string(&vertex_with_data).unwrap(); assert!(serialized.contains("\"data\":")); - let deserialized: Vertex = serde_json::from_str(&serialized).unwrap(); + let deserialized: Vertex = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.data, Some(99)); assert_vertex_properties(&deserialized, [$($coord),+]); // Test serialization with None data - let vertex_no_data: Vertex = vertex!([$($coord),+]); + let vertex_no_data: Vertex<(), $dim> = crate::core::vertex::Vertex::<(), _>::try_new([$($coord),+]).unwrap(); let serialized = serde_json::to_string(&vertex_no_data).unwrap(); assert!(!serialized.contains("\"data\":")); - let deserialized: Vertex = serde_json::from_str(&serialized).unwrap(); + let deserialized: Vertex<(), $dim> = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.data, None); } #[test] fn [<$test_name _uuid_uniqueness>]() { // Test UUID uniqueness for same coordinates - let v1: Vertex = vertex!([$($coord),+]); - let v2: Vertex = vertex!([$($coord),+]); + let v1: Vertex<(), $dim> = crate::core::vertex::Vertex::<(), _>::try_new([$($coord),+]).unwrap(); + let v2: Vertex<(), $dim> = crate::core::vertex::Vertex::<(), _>::try_new([$($coord),+]).unwrap(); assert_ne!(v1.uuid(), v2.uuid()); assert!(!v1.uuid().is_nil()); assert!(!v2.uuid().is_nil()); @@ -1580,7 +1465,7 @@ mod tests { // Keep 1D test separate as it's less common #[test] fn vertex_1d() { - let vertex: Vertex = vertex!([42.0]); + let vertex: Vertex<(), 1> = crate::core::vertex::Vertex::<(), _>::try_new([42.0]).unwrap(); assert_vertex_properties(&vertex, [42.0]); assert!(vertex.data.is_none()); } @@ -1592,12 +1477,14 @@ mod tests { #[test] fn test_vertex_data_types_and_ordering() { // Test vertex with tuple data - let vertex_tuple: Vertex = vertex!([1.0, 2.0], (42, 84)); + let vertex_tuple: Vertex<(i32, i32), 2> = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0], (42, 84)).unwrap(); assert_vertex_properties(&vertex_tuple, [1.0, 2.0]); assert_eq!(vertex_tuple.data.unwrap(), (42, 84)); // Test debug formatting - let vertex_debug: Vertex = vertex!([1.0, 2.0, 3.0], 42); + let vertex_debug: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0], 42).unwrap(); let debug_str = format!("{vertex_debug:?}"); assert!(debug_str.contains("Vertex")); @@ -1608,8 +1495,10 @@ mod tests { assert!(debug_str.contains("3.0")); // Test ordering edge cases - let vertex1: Vertex = vertex!([1.0, 2.0]); - let vertex2: Vertex = vertex!([1.0, 2.0]); + let vertex1: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); + let vertex2: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(); // Test that equal points result in equal ordering assert!(vertex1.partial_cmp(&vertex2) != Some(Ordering::Less)); @@ -1635,7 +1524,8 @@ mod tests { #[test] fn test_vertex_coordinate_values() { // Test negative coordinates - let vertex_neg: Vertex = vertex!([-1.0, -2.0, -3.0]); + let vertex_neg: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, -2.0, -3.0]).unwrap(); assert_relative_eq!( vertex_neg.point().coords().as_slice(), [-1.0, -2.0, -3.0].as_slice(), @@ -1644,12 +1534,15 @@ mod tests { assert_eq!(vertex_neg.dim(), 3); // Test zero coordinates - let vertex_zero: Vertex = vertex!([0.0, 0.0, 0.0]); - let origin_vertex: Vertex = vertex!([0.0, 0.0, 0.0]); + let vertex_zero: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); + let origin_vertex: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); assert_eq!(vertex_zero.point(), origin_vertex.point()); // Test large coordinates - let vertex_large: Vertex = vertex!([1e6, 2e6, 3e6]); + let vertex_large: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1e6, 2e6, 3e6]).unwrap(); assert_relative_eq!( vertex_large.point().coords().as_slice(), [1_000_000.0, 2_000_000.0, 3_000_000.0].as_slice(), @@ -1658,7 +1551,8 @@ mod tests { assert_eq!(vertex_large.dim(), 3); // Test small coordinates - let vertex_small: Vertex = vertex!([1e-6, 2e-6, 3e-6]); + let vertex_small: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1e-6, 2e-6, 3e-6]).unwrap(); assert_relative_eq!( vertex_small.point().coords().as_slice(), [0.000_001, 0.000_002, 0.000_003].as_slice(), @@ -1667,7 +1561,8 @@ mod tests { assert_eq!(vertex_small.dim(), 3); // Test mixed positive/negative coordinates - let vertex_mixed: Vertex = vertex!([1.0, -2.0, 3.0, -4.0]); + let vertex_mixed: Vertex<(), 4> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, -2.0, 3.0, -4.0]).unwrap(); assert_relative_eq!( vertex_mixed.point().coords().as_slice(), [1.0, -2.0, 3.0, -4.0].as_slice(), @@ -1679,8 +1574,10 @@ mod tests { #[test] fn test_vertex_properties() { // Test UUID uniqueness - let vertex1: Vertex = vertex!([1.0, 2.0, 3.0]); - let vertex2: Vertex = vertex!([1.0, 2.0, 3.0]); + let vertex1: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); + let vertex2: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); // Same points but different UUIDs assert_ne!(vertex1.uuid(), vertex2.uuid()); @@ -1691,7 +1588,8 @@ mod tests { #[test] fn test_vertex_type_conversions() { // Test implicit conversion from owned vertex to coordinates - let vertex_coords: Vertex = vertex!([1.0, 2.0, 3.0]); + let vertex_coords: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let coords_owned: [f64; 3] = vertex_coords.into(); assert_relative_eq!( coords_owned.as_slice(), @@ -1700,7 +1598,8 @@ mod tests { ); // Test implicit conversion from vertex reference to coordinates - let vertex_ref_coords: Vertex = vertex!([4.0, 5.0, 6.0]); + let vertex_ref_coords: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 5.0, 6.0]).unwrap(); let coords_ref: [f64; 3] = (&vertex_ref_coords).into(); assert_relative_eq!( coords_ref.as_slice(), @@ -1716,8 +1615,9 @@ mod tests { ); // Test implicit conversion from vertex reference to Point - let vertex_point: Vertex = vertex!([1.0, 2.0, 3.0]); - let point_from_vertex: Point = (&vertex_point).into(); + let vertex_point: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); + let point_from_vertex: Point<3> = (&vertex_point).into(); assert_relative_eq!( point_from_vertex.coords().as_slice(), [1.0, 2.0, 3.0].as_slice(), @@ -1735,8 +1635,9 @@ mod tests { ); // Test with different dimensions - let vertex_2d: Vertex = vertex!([10.5, -5.3]); - let point_2d: Point = (&vertex_2d).into(); + let vertex_2d: Vertex<(), 2> = + crate::core::vertex::Vertex::<(), _>::try_new([10.5, -5.3]).unwrap(); + let point_2d: Point<2> = (&vertex_2d).into(); assert_relative_eq!( point_2d.coords().as_slice(), [10.5, -5.3].as_slice(), @@ -1752,97 +1653,44 @@ mod tests { #[test] fn test_vertex_validation() { // Test valid vertices with f64 coordinates and various dimensions - let valid_f64: Vertex = vertex!([1.0, 2.0, 3.0]); + let valid_f64: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); assert!(valid_f64.is_valid().is_ok()); - let valid_negative: Vertex = vertex!([-1.0, -2.0, -3.0]); + let valid_negative: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, -2.0, -3.0]).unwrap(); assert!(valid_negative.is_valid().is_ok()); - let valid_zero: Vertex = vertex!([0.0, 0.0, 0.0]); + let valid_zero: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); assert!(valid_zero.is_valid().is_ok()); // Test different dimensions - let valid_1d: Vertex = vertex!([42.0]); + let valid_1d: Vertex<(), 1> = + crate::core::vertex::Vertex::<(), _>::try_new([42.0]).unwrap(); assert!(valid_1d.is_valid().is_ok()); - let valid_5d: Vertex = vertex!([1.0, 2.0, 3.0, 4.0, 5.0]); + let valid_5d: Vertex<(), 5> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0, 4.0, 5.0]).unwrap(); assert!(valid_5d.is_valid().is_ok()); - // Test invalid vertices with NaN coordinates - let invalid_nan_f64: Vertex = Vertex { - point: Point::new([1.0, f64::NAN, 3.0]), - uuid: make_uuid(), - incident_simplex: None, - data: None, - }; - assert!(invalid_nan_f64.is_valid().is_err()); - - let invalid_all_nan: Vertex = Vertex { - point: Point::new([f64::NAN, f64::NAN, f64::NAN]), - uuid: make_uuid(), - incident_simplex: None, - data: None, - }; - assert!(invalid_all_nan.is_valid().is_err()); - - let invalid_1d_nan: Vertex = Vertex { - point: Point::new([f64::NAN]), - uuid: make_uuid(), - incident_simplex: None, - data: None, - }; - assert!(invalid_1d_nan.is_valid().is_err()); - - let invalid_5d_nan: Vertex = Vertex { - point: Point::new([1.0, 2.0, f64::NAN, 4.0, 5.0]), - uuid: make_uuid(), - incident_simplex: None, - data: None, - }; - assert!(invalid_5d_nan.is_valid().is_err()); - - // Test invalid vertices with infinity coordinates - let invalid_pos_inf: Vertex = Vertex { - point: Point::new([1.0, f64::INFINITY, 3.0]), - uuid: make_uuid(), - incident_simplex: None, - data: None, - }; - assert!(invalid_pos_inf.is_valid().is_err()); - - let invalid_neg_inf: Vertex = Vertex { - point: Point::new([1.0, f64::NEG_INFINITY, 3.0]), - uuid: make_uuid(), - incident_simplex: None, - data: None, - }; - assert!(invalid_neg_inf.is_valid().is_err()); - - let invalid_mixed: Vertex = Vertex { - point: Point::new([f64::NAN, f64::INFINITY, 1.0]), - uuid: make_uuid(), - incident_simplex: None, - data: None, - }; - assert!(invalid_mixed.is_valid().is_err()); + assert!(Point::<3>::try_new([1.0, f64::NAN, 3.0]).is_err()); + assert!(Point::<3>::try_new([f64::NAN, f64::NAN, f64::NAN]).is_err()); + assert!(Point::<1>::try_new([f64::NAN]).is_err()); + assert!(Point::<5>::try_new([1.0, 2.0, f64::NAN, 4.0, 5.0]).is_err()); + assert!(Point::<3>::try_new([1.0, f64::INFINITY, 3.0]).is_err()); + assert!(Point::<3>::try_new([1.0, f64::NEG_INFINITY, 3.0]).is_err()); + assert!(Point::<3>::try_new([f64::NAN, f64::INFINITY, 1.0]).is_err()); // Test UUID validation - let valid_vertex: Vertex = vertex!([1.0, 2.0, 3.0]); + let valid_vertex: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); assert!(valid_vertex.is_valid().is_ok()); assert!(!valid_vertex.uuid().is_nil()); - // Test that empty vertex (which has nil UUID) is invalid - let default_vertex: Vertex = Vertex::empty(); - match default_vertex.is_valid() { - Err(VertexValidationError::InvalidUuid { source: _ }) => (), // Expected - other => panic!("Expected InvalidUuid error, got: {other:?}"), - } - assert!(default_vertex.uuid().is_nil()); - assert!(default_vertex.point().validate().is_ok()); - // Create a vertex with valid point but manually set nil UUID to test UUID validation - let invalid_uuid_vertex: Vertex = Vertex { - point: Point::new([1.0, 2.0, 3.0]), + let invalid_uuid_vertex: Vertex<(), 3> = Vertex { + point: Point::from_validated_coords([1.0, 2.0, 3.0]), uuid: uuid::Uuid::nil(), incident_simplex: None, data: None, @@ -1854,19 +1702,7 @@ mod tests { assert!(invalid_uuid_vertex.point().validate().is_ok()); assert!(invalid_uuid_vertex.uuid().is_nil()); // UUID is nil - // Test vertex with both invalid point and nil UUID (should return point error first) - let invalid_both: Vertex = Vertex { - point: Point::new([f64::NAN, 2.0, 3.0]), - uuid: uuid::Uuid::nil(), - incident_simplex: None, - data: None, - }; - match invalid_both.is_valid() { - Err(VertexValidationError::InvalidPoint { .. }) => (), // Expected - point checked first - other => panic!("Expected InvalidPoint error, got: {other:?}"), - } - assert!(invalid_both.point().validate().is_err()); - assert!(invalid_both.uuid().is_nil()); // UUID is nil + assert!(Point::<3>::try_new([f64::NAN, 2.0, 3.0]).is_err()); } // ============================================================================= @@ -1875,7 +1711,7 @@ mod tests { #[test] fn vertex_string_data_usage_examples() { - // String metadata now works with `Vertex`, `VertexBuilder`, and the `vertex!` macro. + // String metadata works with `Vertex` constructors. // ===================================================================== // PRACTICAL ALTERNATIVE: Use numeric IDs with external lookup @@ -1889,11 +1725,11 @@ mod tests { label_lookup.insert(3, "boundary_point".to_string()); // Use numeric IDs in vertices - this works perfectly and is efficient - let vertices_with_ids: Vec> = vec![ - vertex!([0.5, 0.5], 0u32), // center - vertex!([1.0, 1.0], 1u32), // corner - vertex!([0.5, 1.0], 2u32), // edge_midpoint - vertex!([0.0, 0.5], 3u32), // boundary_point + let vertices_with_ids: Vec> = vec![ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.5, 0.5], 0u32).unwrap(), // center + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 1.0], 1u32).unwrap(), // corner + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.5, 1.0], 2u32).unwrap(), // edge_midpoint + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.5], 3u32).unwrap(), // boundary_point ]; // Verify we can retrieve the labels @@ -1915,8 +1751,7 @@ mod tests { assert_eq!(vertices_with_ids[1].data.unwrap(), 1u32); // Test hashing and equality (works because u32 implements all required traits) - let vertex_set: FastHashSet> = - vertices_with_ids.iter().copied().collect(); + let vertex_set: FastHashSet> = vertices_with_ids.iter().copied().collect(); assert_eq!(vertex_set.len(), 4); // ===================================================================== @@ -1924,10 +1759,10 @@ mod tests { // ===================================================================== // Alternative 1: Use character codes - let vertices_with_chars: Vec> = vec![ - vertex!([0.0, 0.0], 'A'), - vertex!([1.0, 0.0], 'B'), - vertex!([0.0, 1.0], 'C'), + let vertices_with_chars: Vec> = vec![ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 'A').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 'B').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 'C').unwrap(), ]; for (i, v) in vertices_with_chars.iter().enumerate() { @@ -1938,10 +1773,13 @@ mod tests { // Alternative 2: Use small integer codes with enum mapping - let vertices_with_enums: Vec> = vec![ - vertex!([0.0, 0.0], PointType::Origin), - vertex!([1.0, 0.0], PointType::Corner), - vertex!([0.5, 0.5], PointType::Interior), + let vertices_with_enums: Vec> = vec![ + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], PointType::Origin) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0], PointType::Corner) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.5, 0.5], PointType::Interior) + .unwrap(), ]; assert_eq!(vertices_with_enums[0].data.unwrap(), PointType::Origin); @@ -1952,7 +1790,7 @@ mod tests { // SUMMARY OF STRING DATA LIMITATIONS // ===================================================================== - // 1. Owned String metadata is supported with Vertex, VertexBuilder, and vertex!, + // 1. Owned String metadata is supported with Vertex constructors, // but it is not Copy and cannot be used in Copy-only contexts. // 2. &str has complex lifetime issues that make it impractical // 3. &'static str could work but only for compile-time constants @@ -1966,15 +1804,17 @@ mod tests { #[test] fn vertex_hash_with_copy_data() { // Test hashing with Copy data - let vertex1: Vertex = vertex!([1.0, 2.0], 999u16); + let vertex1: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0], 999u16).unwrap(); - let vertex2: Vertex = vertex!([3.0, 4.0], 42); + let vertex2: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([3.0, 4.0], 42).unwrap(); // Test that vertices with Copy data can be used as HashMap keys - let mut map: FastHashMap, i32> = FastHashMap::default(); + let mut map: FastHashMap, i32> = FastHashMap::default(); map.insert(vertex1, 100); - let mut map2: FastHashMap, u8> = FastHashMap::default(); + let mut map2: FastHashMap, u8> = FastHashMap::default(); map2.insert(vertex2, 255u8); assert_eq!(map.len(), 1); @@ -1993,7 +1833,7 @@ mod tests { "uuid": "550e8400-e29b-41d4-a716-446655440000" }"#; - let result: Result, _> = serde_json::from_str(json_minimal); + let result: Result, _> = serde_json::from_str(json_minimal); assert!(result.is_ok()); let vertex = result.unwrap(); @@ -2010,7 +1850,7 @@ mod tests { assert!(vertex.data.is_none()); // Test deserialization with all fields including SimplexKey - let point = Point::new([1.5, 2.5, 3.5]); + let point = Point::from_validated_coords([1.5, 2.5, 3.5]); let uuid_str = "550e8400-e29b-41d4-a716-446655440000"; let uuid = uuid::Uuid::parse_str(uuid_str).unwrap(); let simplex_key = SimplexKey::from(KeyData::from_ffi(42u64)); @@ -2036,12 +1876,11 @@ mod tests { let json_with_unknown = r#"{ "point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", - "incident_simplex": null, "data": null, "unknown_field": "this should be ignored" }"#; - let result: Result, _> = serde_json::from_str(json_with_unknown); + let result: Result, _> = serde_json::from_str(json_with_unknown); assert!(result.is_ok()); let vertex = result.unwrap(); assert_relative_eq!( @@ -2055,49 +1894,51 @@ mod tests { ( r#"{"point": [1.0, 2.0, 3.0], "point": [4.0, 5.0, 6.0], "uuid": "550e8400-e29b-41d4-a716-446655440000"}"#, "duplicate point", + "duplicate field `point`", ), ( r#"{"point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", "uuid": "550e8400-e29b-41d4-a716-446655440001"}"#, "duplicate uuid", + "duplicate field `uuid`", ), ( - r#"{"point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", "incident_simplex": null, "incident_simplex": "550e8400-e29b-41d4-a716-446655440001"}"#, - "duplicate incident_simplex", + r#"{"point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", "incident_simplex": null}"#, + "forbidden incident_simplex", + "storage-local slotmap key", ), ( r#"{"point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", "data": null, "data": null}"#, "duplicate data", + "duplicate field `data`", ), ( r#"{"uuid": "550e8400-e29b-41d4-a716-446655440000"}"#, "missing point", + "missing field `point`", + ), + ( + r#"{"point": [1.0, 2.0, 3.0]}"#, + "missing uuid", + "missing field `uuid`", ), - (r#"{"point": [1.0, 2.0, 3.0]}"#, "missing uuid"), ]; - for (json, description) in test_cases { - let result: Result, _> = serde_json::from_str(json); + for (json, description, expected_fragment) in test_cases { + let result: Result, _> = serde_json::from_str(json); assert!( result.is_err(), "Expected error for {description}, but got success" ); let error_message = result.unwrap_err().to_string(); - // Check that error message contains relevant keywords - let has_relevant_error = error_message.contains("duplicate") - || error_message.contains("missing") - || error_message.contains("point") - || error_message.contains("uuid") - || error_message.contains("data") - || error_message.contains("incident_simplex"); assert!( - has_relevant_error, - "Error message for {description} doesn't contain expected keywords: {error_message}" + error_message.contains(expected_fragment), + "Error message for {description} should contain {expected_fragment:?}: {error_message}" ); } // Test invalid JSON structure let invalid_json = r#"["not", "a", "vertex", "object"]"#; - let result: Result, _> = serde_json::from_str(invalid_json); + let result: Result, _> = serde_json::from_str(invalid_json); assert!(result.is_err()); let error_message = result.unwrap_err().to_string(); assert!( @@ -2107,7 +1948,7 @@ mod tests { // Test validation error for nil UUID let vertex_with_nil_uuid = Vertex { - point: Point::new([1.0, 2.0, 3.0]), + point: Point::from_validated_coords([1.0, 2.0, 3.0]), uuid: uuid::Uuid::nil(), incident_simplex: None, data: None::<()>, @@ -2175,72 +2016,6 @@ mod tests { assert_ne!(error1, error3); } - // ============================================================================= - // SET_UUID METHOD TESTS - // ============================================================================= - - #[test] - fn test_set_uuid_valid() { - let mut vertex: Vertex = vertex!([1.0, 2.0, 3.0]); - let original_uuid = vertex.uuid(); - let new_uuid = make_uuid(); - - // Test setting a valid UUID - let result = vertex.set_uuid(new_uuid); - assert!(result.is_ok()); - assert_eq!(vertex.uuid(), new_uuid); - assert_ne!(vertex.uuid(), original_uuid); - } - - #[test] - fn test_set_uuid_nil_uuid() { - let mut vertex: Vertex = vertex!([1.0, 2.0, 3.0]); - let original_uuid = vertex.uuid(); - - // Test setting a nil UUID (should fail) - let result = vertex.set_uuid(uuid::Uuid::nil()); - assert!(result.is_err()); - - // Verify the UUID wasn't changed - assert_eq!(vertex.uuid(), original_uuid); - - // Verify the error type - match result.unwrap_err() { - VertexValidationError::InvalidUuid { - source: UuidValidationError::NilUuid, - } => (), // Expected - other => panic!("Expected InvalidUuid with NilUuid source, got: {other:?}"), - } - } - - #[test] - fn test_set_uuid_invalid_version() { - let mut vertex: Vertex = vertex!([1.0, 2.0, 3.0]); - let original_uuid = vertex.uuid(); - - // Create a UUID with an invalid version (version 0) - // Note: This is tricky since the uuid crate doesn't easily allow creating invalid UUIDs - // We'll use the fact that version 3 UUIDs exist but our validation might reject them - // depending on implementation - let uuid_bytes = [0u8; 16]; // This creates a nil-like UUID - let invalid_uuid = uuid::Uuid::from_bytes(uuid_bytes); - - // Test setting an invalid UUID - let result = vertex.set_uuid(invalid_uuid); - assert!(result.is_err()); - - // Verify the UUID wasn't changed - assert_eq!(vertex.uuid(), original_uuid); - - // Verify it's a UUID validation error - match result.unwrap_err() { - VertexValidationError::InvalidUuid { source: _ } => (), // Expected some UUID error - other @ VertexValidationError::InvalidPoint { .. } => { - panic!("Expected InvalidUuid error, got: {other:?}") - } - } - } - // ============================================================================= // SERIALIZATION ROUNDTRIP TESTS // ============================================================================= @@ -2248,13 +2023,15 @@ mod tests { #[test] fn test_serialization_deserialization_roundtrip() { // Test that serialization -> deserialization preserves all data - let original_vertex: Vertex = vertex!([1.0, 2.0, 3.0, 4.0], 'A'); + let original_vertex: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 'A') + .unwrap(); // Serialize let serialized = serde_json::to_string(&original_vertex).unwrap(); // Deserialize - let deserialized_vertex: Vertex = serde_json::from_str(&serialized).unwrap(); + let deserialized_vertex: Vertex = serde_json::from_str(&serialized).unwrap(); // Verify all fields match assert_relative_eq!( @@ -2273,7 +2050,8 @@ mod tests { #[test] fn test_serialization_with_some_data_includes_field() { // Test that when data is Some, the JSON includes the data field - let vertex: Vertex = vertex!([1.0, 2.0, 3.0], 42); + let vertex: Vertex = + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0], 42).unwrap(); let serialized = serde_json::to_string(&vertex).unwrap(); // Verify JSON contains "data" field @@ -2284,7 +2062,7 @@ mod tests { assert!(serialized.contains("42"), "JSON should include data value"); // Roundtrip test - let deserialized: Vertex = serde_json::from_str(&serialized).unwrap(); + let deserialized: Vertex = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.data, Some(42)); assert_relative_eq!( vertex.point().coords().as_slice(), @@ -2296,7 +2074,8 @@ mod tests { #[test] fn test_serialization_with_none_data_omits_field() { // Test that when data is None, the JSON omits the data field entirely - let vertex: Vertex = vertex!([1.0, 2.0, 3.0]); + let vertex: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap(); let serialized = serde_json::to_string(&vertex).unwrap(); // Verify JSON does NOT contain "data" field (optimization) @@ -2306,7 +2085,7 @@ mod tests { ); // Roundtrip test - missing data field should deserialize as None - let deserialized: Vertex = serde_json::from_str(&serialized).unwrap(); + let deserialized: Vertex<(), 3> = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.data, None); assert_relative_eq!( vertex.point().coords().as_slice(), @@ -2320,7 +2099,7 @@ mod tests { // Test backward compatibility: explicit "data": null should still work let json_with_null = r#"{"point":[1.0,2.0,3.0],"uuid":"550e8400-e29b-41d4-a716-446655440000","data":null}"#; - let vertex: Vertex = serde_json::from_str(json_with_null).unwrap(); + let vertex: Vertex<(), 3> = serde_json::from_str(json_with_null).unwrap(); assert_eq!(vertex.data, None); assert_relative_eq!( diff --git a/src/delaunay/builder.rs b/src/delaunay/builder.rs index 2a9deb35..68792bab 100644 --- a/src/delaunay/builder.rs +++ b/src/delaunay/builder.rs @@ -10,13 +10,13 @@ //! |---|---| //! | Simple Euclidean, default options | [`DelaunayTriangulationBuilder::new`] | //! | Custom `ConstructionOptions` or `TopologyGuarantee` | [`DelaunayTriangulationBuilder`] | -//! | Periodic toroidal quotient (χ = 0) | [`DelaunayTriangulationBuilder`] with [`.toroidal()`](DelaunayTriangulationBuilder::toroidal) | -//! | Canonicalized toroidal points | [`DelaunayTriangulationBuilder`] with [`.canonicalized_toroidal()`](DelaunayTriangulationBuilder::canonicalized_toroidal) | +//! | Periodic toroidal quotient (χ = 0) | [`DelaunayTriangulationBuilder`] with [`.try_toroidal()`](DelaunayTriangulationBuilder::try_toroidal) | +//! | Canonicalized toroidal points | [`DelaunayTriangulationBuilder`] with [`.try_canonicalized_toroidal()`](DelaunayTriangulationBuilder::try_canonicalized_toroidal) | //! | Custom kernel (`RobustKernel`, etc.) | [`DelaunayTriangulationBuilder::build_with_kernel`] | //! //! # Canonicalized vs periodic toroidal construction //! -//! **Periodic image-point (`.toroidal()`, issue #210):** Periodic construction using +//! **Periodic image-point (`.try_toroidal()`, issue #210):** Periodic construction using //! the 3^D image-point method — generating copies of each point shifted by ±L in each //! dimension, building the full Euclidean DT on the expanded set, normalizing lifted //! simplices, searching a closed quotient candidate subset, and rebuilding quotient @@ -26,7 +26,7 @@ //! for routine validation. See `REFERENCES.md`, "Periodic and Toroidal //! Triangulations", first entry. //! -//! **Canonicalized (`.canonicalized_toroidal()`):** The builder canonicalizes all input +//! **Canonicalized (`.try_canonicalized_toroidal()`):** The builder canonicalizes all input //! vertices into the fundamental domain `[0, L_i)` before passing them to the standard //! Euclidean constructor. The resulting triangulation is a valid Euclidean Delaunay //! triangulation of the canonicalized point set; it does **not** identify opposite @@ -37,13 +37,13 @@ //! ## Standard Euclidean construction //! //! ```rust -//! use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; +//! use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; //! //! # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0]), -//! vertex!([1.0, 0.0]), -//! vertex!([0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! //! let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -57,19 +57,20 @@ //! ## Toroidal construction (canonicalization only) //! //! ```rust -//! use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; +//! use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; //! //! # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { //! // Vertices that fall outside [0, 1)² are wrapped before triangulation. //! let vertices = vec![ -//! vertex!([0.2, 0.3]), -//! vertex!([1.8, 0.1]), // x wraps to 0.8 -//! vertex!([0.5, 0.7]), -//! vertex!([-0.4, 0.9]), // x wraps to 0.6 +//! delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.3]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.8, 0.1]).expect("finite vertex coordinates"), // x wraps to 0.8 +//! delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.7]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([-0.4, 0.9]).expect("finite vertex coordinates"), // x wraps to 0.6 //! ]; //! //! let dt = DelaunayTriangulationBuilder::new(&vertices) -//! .canonicalized_toroidal([1.0, 1.0]) +//! .try_canonicalized_toroidal([1.0, 1.0]) +//! .expect("unit toroidal domain is valid") //! .build::<()>()?; //! //! assert_eq!(dt.number_of_vertices(), 4); @@ -84,22 +85,23 @@ //! //! ```rust,no_run //! use delaunay::prelude::geometry::RobustKernel; -//! use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; +//! use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; //! //! # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.1, 0.2]), -//! vertex!([0.4, 0.7]), -//! vertex!([0.7, 0.3]), -//! vertex!([0.2, 0.9]), -//! vertex!([0.8, 0.6]), -//! vertex!([0.5, 0.1]), -//! vertex!([0.3, 0.5]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.2]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.4, 0.7]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.7, 0.3]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.9]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.6]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.1]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.3, 0.5]).expect("finite vertex coordinates"), //! ]; //! //! let kernel = RobustKernel::new(); //! let dt = DelaunayTriangulationBuilder::new(&vertices) -//! .toroidal([1.0, 1.0]) +//! .try_toroidal([1.0, 1.0]) +//! .expect("unit toroidal domain is valid") //! .build_with_kernel::<_, ()>(&kernel)?; //! //! assert_eq!(dt.number_of_vertices(), 7); @@ -112,8 +114,8 @@ #![forbid(unsafe_code)] use crate::construction::{ - ConstructionOptions, DelaunayTriangulationConstructionError, InitialSimplexStrategy, - RetryPolicy, + ConstructionOptions, DelaunayConstructionFailure, DelaunayTriangulationConstructionError, + InitialSimplexStrategy, RetryPolicy, }; use crate::core::algorithms::incremental_insertion::{ DelaunayRepairErrorKind, InsertionError, InsertionErrorSourceKind, @@ -136,14 +138,11 @@ use crate::core::validation::TopologyGuarantee; use crate::core::vertex::Vertex; use crate::geometry::kernel::{AdaptiveKernel, Kernel}; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{Coordinate, CoordinateScalar}; use crate::geometry::util::circumcenter; use crate::repair::DelaunayRepairPolicy; -use crate::topology::traits::global_topology_model::{ - GlobalTopologyModel, GlobalTopologyModelError, -}; +use crate::topology::traits::global_topology_model::GlobalTopologyModel; use crate::topology::traits::topological_space::{ - GlobalTopology, TopologyKind, ToroidalConstructionMode, ToroidalDomainError, + GlobalTopology, TopologyKind, ToroidalConstructionMode, ToroidalDomain, ToroidalDomainError, }; use crate::triangulation::DelaunayTriangulation; use crate::validation::DelaunayTriangulationValidationError; @@ -468,6 +467,8 @@ pub enum ExplicitInsertionErrorKind { MaxSimplicesRemovedExceeded, /// Spatial index construction failed. SpatialIndexConstruction, + /// Perturbation retry produced invalid coordinates. + PerturbedCoordinateInvalid, } /// Compact summary of an [`InsertionError`] used by explicit construction. @@ -538,6 +539,9 @@ impl From for ExplicitInsertionError { InsertionError::SpatialIndexConstruction { .. } => { ExplicitInsertionErrorKind::SpatialIndexConstruction } + InsertionError::PerturbedCoordinateInvalid { .. } => { + ExplicitInsertionErrorKind::PerturbedCoordinateInvalid + } }; let source_kind = match &source { InsertionError::DelaunayValidationFailed { source } => { @@ -772,10 +776,15 @@ impl From for ExplicitDelaunayValidationEr /// ```rust /// use delaunay::prelude::construction::{ /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, -/// ExplicitConstructionError, vertex, +/// ExplicitConstructionError, /// }; /// -/// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let vertices = vec![ +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])?, +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0])?, +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0])?, +/// ]; /// let simplices = vec![vec![0, 1]]; // Wrong arity for 2D (needs 3 vertices) /// /// std::assert_matches!( @@ -784,6 +793,8 @@ impl From for ExplicitDelaunayValidationEr /// ExplicitConstructionError::InvalidSimplexArity { simplex_index: 0, actual: 2, expected: 3 }, /// )) /// ); +/// # Ok(()) +/// # } /// ``` #[derive(Clone, Debug, Error, PartialEq)] #[non_exhaustive] @@ -922,7 +933,6 @@ pub enum ExplicitConstructionError { /// # Type Parameters /// /// - `'v` — Lifetime of the borrowed vertex slice. -/// - `T` — Coordinate scalar type (inferred from the vertex slice). /// - `U` — Vertex data type (inferred from the vertex slice). /// - `D` — Spatial dimension (inferred from the vertex slice). /// @@ -934,15 +944,15 @@ pub enum ExplicitConstructionError { /// /// ```rust /// use delaunay::prelude::construction::{ -/// ConstructionOptions, DelaunayTriangulationBuilder, TopologyGuarantee, vertex, +/// ConstructionOptions, DelaunayTriangulationBuilder, TopologyGuarantee, /// }; /// /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -954,18 +964,15 @@ pub enum ExplicitConstructionError { /// # Ok(()) /// # } /// ``` -pub struct DelaunayTriangulationBuilder<'v, T, U, const D: usize> { - vertices: &'v [Vertex], - /// Optional toroidal topology for the construction. +pub struct DelaunayTriangulationBuilder<'v, U, const D: usize> { + vertices: &'v [Vertex], + /// Topology mode for construction. /// - /// When set, all input vertices are canonicalized into the fundamental domain - /// `[0, L_i)` before the selected toroidal construction mode runs. - topology: Option<[f64; D]>, + /// Toroidal modes carry a validated domain and determine whether construction + /// uses canonicalization-only or periodic image points. + topology: BuilderTopology, topology_guarantee: TopologyGuarantee, construction_options: ConstructionOptions, - /// When `true` (set by [`.toroidal()`](Self::toroidal)), the - /// periodic image-point algorithm is used instead of the canonicalization-only path. - use_image_point_method: bool, /// Optional explicit simplex specifications for direct combinatorial construction. /// /// When set, the builder constructs a triangulation from the given vertices and @@ -981,72 +988,65 @@ pub struct DelaunayTriangulationBuilder<'v, T, U, const D: usize> { global_topology: GlobalTopology, } +enum BuilderTopology { + Euclidean, + Canonicalized(ToroidalDomain), + PeriodicImagePoint(ToroidalDomain), +} + // ============================================================================= -// SPECIALIZED IMPL — f64 coordinates, any vertex data U +// BUILDER IMPL — f64 coordinate storage, any vertex data U // -// Pins T=f64 so callers using the default AdaptiveKernel never need explicit -// type annotations. U is inferred from the vertex slice. +// U is inferred from the vertex slice. // -// let vertices = vec![vertex!([0.0, 0.0]), ...]; +// let vertices = vec![crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0])?, ...]; // let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>(); // -// let typed: [Vertex; 3] = [vertex!([0.0, 0.0], 1), ...]; +// let typed: [Vertex; 3] = [crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 1)?, ...]; // let dt = DelaunayTriangulationBuilder::new(&typed).build::<()>(); // // ============================================================================= -impl<'v, U, const D: usize> DelaunayTriangulationBuilder<'v, f64, U, D> { +impl<'v, U, const D: usize> DelaunayTriangulationBuilder<'v, U, D> { /// Creates a builder for `f64` vertices with any user data type `U`. /// /// `U` is inferred from the vertex slice — no explicit type annotations needed /// for either `U = ()` (the common case) or typed vertex data. /// - /// For non-`f64` scalar types, use - /// [`from_vertices`](Self::from_vertices) in the generic impl. - /// /// # Examples /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, /// }; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// // No vertex data (U = () inferred) - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let _dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// // Typed vertex data (U = i32 inferred) - /// let typed: [Vertex; 3] = [ - /// vertex!([0.0, 0.0], 1i32), - /// vertex!([1.0, 0.0], 2), - /// vertex!([0.0, 1.0], 3), + /// let typed: [Vertex; 3] = [ + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 1i32).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 2).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 3).expect("finite vertex coordinates"), /// ]; /// let _dt = DelaunayTriangulationBuilder::new(&typed).build::<()>()?; /// # Ok(()) /// # } /// ``` #[must_use] - pub fn new(vertices: &'v [Vertex]) -> Self { + pub fn new(vertices: &'v [Vertex]) -> Self { Self { vertices, - topology: None, + topology: BuilderTopology::Euclidean, topology_guarantee: TopologyGuarantee::DEFAULT, construction_options: ConstructionOptions::default(), - use_image_point_method: false, explicit_simplices: None, global_topology: GlobalTopology::DEFAULT, } } - /// `f64` convenience wrapper for - /// [`from_vertices_and_simplices_generic`](Self::from_vertices_and_simplices_generic). - /// - /// Pins `T = f64` so that callers using the default `AdaptiveKernel` never - /// need explicit type annotations. For non-`f64` scalars, use the generic - /// [`from_vertices_and_simplices_generic`](Self::from_vertices_and_simplices_generic) - /// on the `T: CoordinateScalar` impl block. - /// /// This is not an unchecked topology wrapper. The deferred /// [`build`](Self::build) or [`build_with_kernel`](Self::build_with_kernel) /// call accepts only Euclidean explicit connectivity and validates the @@ -1060,15 +1060,15 @@ impl<'v, U, const D: usize> DelaunayTriangulationBuilder<'v, f64, U, D> { /// ```rust /// use delaunay::prelude::construction::{ /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, - /// ExplicitConstructionError, vertex, + /// ExplicitConstructionError, /// }; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([1.0, 1.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let simplices = vec![vec![0, 1, 2], vec![0, 2, 3]]; /// @@ -1090,22 +1090,15 @@ impl<'v, U, const D: usize> DelaunayTriangulationBuilder<'v, f64, U, D> { /// ``` #[must_use] pub fn from_vertices_and_simplices( - vertices: &'v [Vertex], + vertices: &'v [Vertex], simplices: &'v [Vec], ) -> Self { Self::from_vertices_and_simplices_generic(vertices, simplices) } -} - -// ============================================================================= -// GENERIC IMPL — any scalar T, any vertex data U -// ============================================================================= - -impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// Creates a builder from explicit vertex and simplex specifications. /// /// This constructs a triangulation from the given connectivity without - /// Delaunay point insertion. Works with any scalar type `T`. + /// Delaunay point insertion. /// /// The explicit connectivity is still validated when /// [`build`](Self::build) or [`build_with_kernel`](Self::build_with_kernel) @@ -1115,29 +1108,25 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// path for the public `DelaunayTriangulation` wrapper; quotient meshes need /// Level 4 handling before they can be accepted. /// - /// For `f64` coordinates, prefer the convenience wrapper on the - /// `f64`-specialised impl which avoids explicit type annotations. - /// /// # Examples /// /// ```rust - /// use delaunay::prelude::geometry::{Coordinate, Point}; /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, Vertex, VertexBuilder, + /// DelaunayTriangulationBuilder, Vertex, /// }; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { /// # #[error(transparent)] - /// # Vertex(#[from] delaunay::prelude::construction::VertexBuilderError), - /// # #[error(transparent)] /// # Construction(#[from] delaunay::prelude::construction::DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { - /// let vertices: Vec> = vec![ - /// VertexBuilder::default().point(Point::new([0.0, 0.0])).build()?, - /// VertexBuilder::default().point(Point::new([1.0, 0.0])).build()?, - /// VertexBuilder::default().point(Point::new([0.0, 1.0])).build()?, + /// let vertices: Vec> = vec![ + /// Vertex::try_new([0.0, 0.0])?, + /// Vertex::try_new([1.0, 0.0])?, + /// Vertex::try_new([0.0, 1.0])?, /// ]; /// let simplices = vec![vec![0, 1, 2]]; /// @@ -1151,18 +1140,12 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// ``` #[must_use] pub fn from_vertices_and_simplices_generic( - vertices: &'v [Vertex], + vertices: &'v [Vertex], simplices: &'v [Vec], ) -> Self { - Self { - vertices, - topology: None, - topology_guarantee: TopologyGuarantee::DEFAULT, - construction_options: ConstructionOptions::default(), - use_image_point_method: false, - explicit_simplices: Some(simplices), - global_topology: GlobalTopology::DEFAULT, - } + let mut builder = Self::new(vertices); + builder.explicit_simplices = Some(simplices); + builder } /// Creates a builder from a vertex slice. @@ -1174,23 +1157,22 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// # Examples /// /// ```rust - /// use delaunay::prelude::geometry::{Coordinate, Point}; /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, Vertex, VertexBuilder, + /// DelaunayTriangulationBuilder, Vertex, /// }; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { /// # #[error(transparent)] - /// # Vertex(#[from] delaunay::prelude::construction::VertexBuilderError), - /// # #[error(transparent)] /// # Construction(#[from] delaunay::prelude::construction::DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # } /// # fn main() -> Result<(), ExampleError> { - /// let vertices: Vec> = vec![ - /// VertexBuilder::default().point(Point::new([0.0, 0.0])).build()?, - /// VertexBuilder::default().point(Point::new([1.0, 0.0])).build()?, - /// VertexBuilder::default().point(Point::new([0.0, 1.0])).build()?, + /// let vertices: Vec> = vec![ + /// Vertex::try_new([0.0, 0.0])?, + /// Vertex::try_new([1.0, 0.0])?, + /// Vertex::try_new([0.0, 1.0])?, /// ]; /// /// let dt = DelaunayTriangulationBuilder::from_vertices(&vertices) @@ -1201,16 +1183,8 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// # } /// ``` #[must_use] - pub fn from_vertices(vertices: &'v [Vertex]) -> Self { - Self { - vertices, - topology: None, - topology_guarantee: TopologyGuarantee::DEFAULT, - construction_options: ConstructionOptions::default(), - use_image_point_method: false, - explicit_simplices: None, - global_topology: GlobalTopology::DEFAULT, - } + pub fn from_vertices(vertices: &'v [Vertex]) -> Self { + Self::new(vertices) } /// Enables periodic toroidal topology via the image-point method. @@ -1218,7 +1192,7 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// This is the correctness-first toroidal constructor: it builds a periodic /// quotient with rewired neighbor pointers and Euler characteristic χ = 0 for /// the validated 2D and compact 3D cases. Use - /// [`.canonicalized_toroidal()`](Self::canonicalized_toroidal) only when you + /// [`.try_canonicalized_toroidal()`](Self::try_canonicalized_toroidal) only when you /// explicitly want the cheaper wrapping-only Euclidean construction. /// /// # Arguments @@ -1230,23 +1204,24 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// ```rust,no_run /// use delaunay::prelude::geometry::RobustKernel; /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, vertex, + /// DelaunayTriangulationBuilder, /// }; /// /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.1, 0.2]), - /// vertex!([0.4, 0.7]), - /// vertex!([0.7, 0.3]), - /// vertex!([0.2, 0.9]), - /// vertex!([0.8, 0.6]), - /// vertex!([0.5, 0.1]), - /// vertex!([0.3, 0.5]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.2]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.4, 0.7]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.7, 0.3]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.9]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.6]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.1]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.3, 0.5]).expect("finite vertex coordinates"), /// ]; /// /// let kernel = RobustKernel::new(); /// let dt = DelaunayTriangulationBuilder::new(&vertices) - /// .toroidal([1.0, 1.0]) + /// .try_toroidal([1.0, 1.0]) + /// .expect("unit toroidal domain is valid") /// .build_with_kernel::<_, ()>(&kernel)?; /// /// assert_eq!(dt.number_of_vertices(), 7); @@ -1254,10 +1229,53 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// # Ok(()) /// # } /// ``` + /// + /// # Errors + /// + /// Returns [`ToroidalDomainError`] when any period is non-finite, zero, or + /// negative. + pub fn try_toroidal(mut self, domain: [f64; D]) -> Result { + self.topology = BuilderTopology::PeriodicImagePoint(ToroidalDomain::try_new(domain)?); + Ok(self) + } + + /// Enables periodic toroidal topology from an already-validated domain. + /// + /// This infallible setter is for callers that already hold a + /// [`ToroidalDomain`]. Use [`Self::try_toroidal`] at raw numeric boundaries + /// when the periods still need validation. + /// + /// # Examples + /// + /// ```rust + /// use delaunay::prelude::construction::DelaunayTriangulationBuilder; + /// use delaunay::prelude::geometry::RobustKernel; + /// use delaunay::prelude::topology::spaces::ToroidalDomain; + /// + /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { + /// let vertices = vec![ + /// delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.2]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.4, 0.7]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.7, 0.3]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.9]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.6]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.1]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.3, 0.5]).expect("finite vertex coordinates"), + /// ]; + /// + /// let domain = ToroidalDomain::<2>::unit(); + /// let kernel = RobustKernel::new(); + /// let dt = DelaunayTriangulationBuilder::new(&vertices) + /// .toroidal(domain) + /// .build_with_kernel::<_, ()>(&kernel)?; + /// + /// assert_eq!(dt.number_of_vertices(), 7); + /// # Ok(()) + /// # } + /// ``` #[must_use] - pub const fn toroidal(mut self, domain: [f64; D]) -> Self { - self.topology = Some(domain); - self.use_image_point_method = true; + pub const fn toroidal(mut self, domain: ToroidalDomain) -> Self { + self.topology = BuilderTopology::PeriodicImagePoint(domain); self } @@ -1266,7 +1284,7 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// Input vertices are canonicalized into `[0, L_i)` per dimension before the /// triangulation is built. The resulting triangulation is a valid Euclidean /// Delaunay triangulation of the wrapped point set; boundary facets are - /// **not** rewired. Use [`.toroidal()`](Self::toroidal) for the true + /// **not** rewired. Use [`.try_toroidal()`](Self::try_toroidal) for the true /// periodic quotient path. /// /// # Arguments @@ -1277,19 +1295,62 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, vertex, + /// DelaunayTriangulationBuilder, /// }; /// /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.2, 0.3]), - /// vertex!([0.8, 0.1]), - /// vertex!([0.5, 0.7]), - /// vertex!([0.1, 0.9]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.3]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.1]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.7]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.9]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices) - /// .canonicalized_toroidal([1.0, 1.0]) + /// .try_canonicalized_toroidal([1.0, 1.0]) + /// .expect("unit toroidal domain is valid") + /// .build::<()>()?; + /// + /// assert_eq!(dt.number_of_vertices(), 4); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns [`ToroidalDomainError`] when any period is non-finite, zero, or + /// negative. + pub fn try_canonicalized_toroidal( + mut self, + domain: [f64; D], + ) -> Result { + self.topology = BuilderTopology::Canonicalized(ToroidalDomain::try_new(domain)?); + Ok(self) + } + + /// Enables canonicalized toroidal topology from an already-validated domain. + /// + /// This infallible setter is for callers that already hold a + /// [`ToroidalDomain`]. Use [`Self::try_canonicalized_toroidal`] at raw + /// numeric boundaries when the periods still need validation. + /// + /// # Examples + /// + /// ```rust + /// use delaunay::prelude::construction::DelaunayTriangulationBuilder; + /// use delaunay::prelude::topology::spaces::ToroidalDomain; + /// + /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { + /// let vertices = vec![ + /// delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.3]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.1]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.7]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.9]).expect("finite vertex coordinates"), + /// ]; + /// + /// let domain = ToroidalDomain::<2>::unit(); + /// let dt = DelaunayTriangulationBuilder::new(&vertices) + /// .canonicalized_toroidal(domain) /// .build::<()>()?; /// /// assert_eq!(dt.number_of_vertices(), 4); @@ -1297,9 +1358,8 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// # } /// ``` #[must_use] - pub const fn canonicalized_toroidal(mut self, domain: [f64; D]) -> Self { - self.topology = Some(domain); - self.use_image_point_method = false; + pub const fn canonicalized_toroidal(mut self, domain: ToroidalDomain) -> Self { + self.topology = BuilderTopology::Canonicalized(domain); self } @@ -1314,14 +1374,14 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, TopologyGuarantee, vertex, + /// DelaunayTriangulationBuilder, TopologyGuarantee, /// }; /// /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -1349,8 +1409,8 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// canonicalization or image-point construction. Explicit non-Euclidean /// connectivity is rejected until Level 4 validation supports quotient /// topology. For construction-time toroidal processing, use - /// [`.toroidal()`](Self::toroidal) or - /// [`.canonicalized_toroidal()`](Self::canonicalized_toroidal) instead. + /// [`.try_toroidal()`](Self::try_toroidal) or + /// [`.try_canonicalized_toroidal()`](Self::try_canonicalized_toroidal) instead. /// /// Defaults to [`GlobalTopology::Euclidean`]. /// @@ -1358,13 +1418,13 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, GlobalTopology, ToroidalConstructionMode, vertex, + /// DelaunayTriangulationBuilder, GlobalTopology, ToroidalConstructionMode, /// }; /// /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let simplices = vec![vec![0, 1, 2]]; /// @@ -1393,14 +1453,14 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { /// /// ```rust /// use delaunay::prelude::construction::{ - /// ConstructionOptions, DelaunayTriangulationBuilder, InsertionOrderStrategy, vertex, + /// ConstructionOptions, DelaunayTriangulationBuilder, InsertionOrderStrategy, /// }; /// /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let opts = ConstructionOptions::default() @@ -1421,9 +1481,8 @@ impl<'v, T, U, const D: usize> DelaunayTriangulationBuilder<'v, T, U, D> { } } -impl DelaunayTriangulationBuilder<'_, T, U, D> +impl DelaunayTriangulationBuilder<'_, U, D> where - T: CoordinateScalar, U: DataType, { // ------------------------------------------------------------------------- @@ -1446,54 +1505,26 @@ where /// /// # Errors /// - /// Maps [`GlobalTopologyModelError`] to [`DelaunayTriangulationConstructionError`]: - /// - [`GlobalTopologyModelError::InvalidToroidalPeriod`] → detailed message with axis and period. - /// - Other errors → generic configuration error message. + /// Preserves + /// [`GlobalTopologyModelError`](crate::topology::traits::GlobalTopologyModelError) + /// as + /// [`DelaunayConstructionFailure::TopologyModelConfiguration`]. /// /// # Usage /// /// Called internally by [`build_with_kernel`](Self::build_with_kernel) before - /// canonicalization in both `.toroidal()` and `.canonicalized_toroidal()` paths. + /// canonicalization in both toroidal construction paths. fn validate_topology_model(model: &M) -> Result<(), DelaunayTriangulationConstructionError> where M: GlobalTopologyModel, { - model.validate_configuration().map_err(|error| match error { - GlobalTopologyModelError::InvalidToroidalPeriod { axis, period } => { - TriangulationConstructionError::GeometricDegeneracy { - message: format!( - "Invalid toroidal domain at axis {axis}: period {period:?}; expected finite value > 0", - ), - } - .into() - } - other => TriangulationConstructionError::GeometricDegeneracy { - message: format!("Invalid topology model configuration: {other}"), - } - .into(), + model.validate_configuration().map_err(|source| { + DelaunayTriangulationConstructionError::Triangulation( + DelaunayConstructionFailure::TopologyModelConfiguration { source }, + ) }) } - /// Maps raw toroidal-domain parse failures into the builder's public construction error. - /// - /// This keeps `.toroidal([...])` and `.canonicalized_toroidal([...])` ergonomic while - /// preserving the public contract that invalid periods fail during `build()` with the - /// same geometric-degeneracy message shape used by topology-model validation. - fn invalid_toroidal_domain( - error: ToroidalDomainError, - ) -> DelaunayTriangulationConstructionError { - match error { - ToroidalDomainError::InvalidPeriod { axis, period } => { - TriangulationConstructionError::GeometricDegeneracy { - message: format!( - "Invalid toroidal domain at axis {axis}: period {period:?}; expected finite value > 0", - ), - } - .into() - } - } - } - /// Derives a periodic facet key from a lifted simplex and maps derivation /// failures into construction-level geometric-degeneracy errors. /// @@ -1528,7 +1559,7 @@ where /// Canonicalizes vertices using a topology behavior model. /// - /// For each input vertex, calls [`GlobalTopologyModel::canonicalize_point_in_place`] to wrap + /// For each input calls [`GlobalTopologyModel::canonicalize_point_in_place`] to wrap /// coordinates into the model's fundamental domain (e.g., [0, L) for toroidal topologies). /// Preserves vertex UUIDs and data while transforming coordinates. /// @@ -1546,21 +1577,20 @@ where /// /// # Errors /// - /// Returns [`DelaunayTriangulationConstructionError`] if canonicalization fails for any vertex: - /// - Non-finite coordinates (NaN, infinity). - /// - Invalid toroidal periods. - /// - Scalar conversion failures. - /// - /// Error messages include the failing vertex index and original coordinates for debugging. + /// Returns [`DelaunayTriangulationConstructionError`] if canonicalization + /// fails for any vertex. Topology-model failures are reported as + /// [`DelaunayConstructionFailure::VertexCanonicalization`]; invalid + /// canonicalized coordinates are reported as + /// [`DelaunayConstructionFailure::CanonicalizedPointValidation`]. /// /// # Usage /// /// Called internally by [`build_with_kernel`](Self::build_with_kernel) before delegating /// to the underlying triangulation construction. fn canonicalize_vertices( - vertices: &[Vertex], + vertices: &[Vertex], model: &M, - ) -> Result>, DelaunayTriangulationConstructionError> + ) -> Result>, DelaunayTriangulationConstructionError> where M: GlobalTopologyModel, { @@ -1570,14 +1600,23 @@ where let mut canonicalized_coords = *v.point().coords(); model .canonicalize_point_in_place(&mut canonicalized_coords) - .map_err(|error| TriangulationConstructionError::GeometricDegeneracy { - message: format!( - "Failed to canonicalize vertex {idx}: original coords {:?}; reason: {error}", - v.point().coords(), - ), + .map_err(|source| { + DelaunayTriangulationConstructionError::Triangulation( + DelaunayConstructionFailure::VertexCanonicalization { + vertex_index: idx, + source, + }, + ) })?; - let new_point = Point::new(canonicalized_coords); + let new_point = Point::try_new(canonicalized_coords).map_err(|error| { + DelaunayTriangulationConstructionError::Triangulation( + DelaunayConstructionFailure::CanonicalizedPointValidation { + vertex_index: idx, + source: error, + }, + ) + })?; let new_vertex = Vertex::new_with_uuid(new_point, v.uuid(), v.data); out.push(new_vertex); @@ -1590,7 +1629,7 @@ where // Build methods // ------------------------------------------------------------------------- - /// Builds the triangulation using [`AdaptiveKernel`](crate::geometry::kernel::AdaptiveKernel). + /// Builds the triangulation using [`AdaptiveKernel`](crate::geometry::kernel::AdaptiveKernel). /// /// This is the most common build path. Simplex data type `V` is inferred or /// specified at the call site; it is independent of the vertex data type `U`. @@ -1606,15 +1645,15 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, vertex, + /// DelaunayTriangulationBuilder, /// }; /// /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -1628,7 +1667,7 @@ where pub fn build( self, ) -> Result< - DelaunayTriangulation, U, V, D>, + DelaunayTriangulation, U, V, D>, DelaunayTriangulationConstructionError, > where @@ -1660,15 +1699,15 @@ where /// ```rust /// use delaunay::prelude::geometry::RobustKernel; /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, vertex, + /// DelaunayTriangulationBuilder, /// }; /// /// # fn main() -> Result<(), delaunay::prelude::construction::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let kernel = RobustKernel::new(); @@ -1684,12 +1723,12 @@ where kernel: &K, ) -> Result, DelaunayTriangulationConstructionError> where - K: Kernel, + K: Kernel, V: DataType, { // Explicit-simplices path: bypass Delaunay insertion entirely. if let Some(simplices) = self.explicit_simplices { - if self.topology.is_some() { + if !matches!(self.topology, BuilderTopology::Euclidean) { return Err(ExplicitConstructionError::IncompatibleTopology.into()); } if self.construction_options != ConstructionOptions::default() { @@ -1704,8 +1743,8 @@ where ); } - match (self.topology, self.use_image_point_method) { - (None, _) => { + match self.topology { + BuilderTopology::Euclidean => { // Euclidean path: delegate directly. let mut dt = DelaunayTriangulation::with_topology_guarantee_and_options( kernel, @@ -1716,10 +1755,11 @@ where dt.set_global_topology(self.global_topology); Ok(dt) } - (Some(space), false) => { - let topology = - GlobalTopology::try_toroidal(space, ToroidalConstructionMode::Canonicalized) - .map_err(Self::invalid_toroidal_domain)?; + BuilderTopology::Canonicalized(domain) => { + let topology = GlobalTopology::Toroidal { + domain, + mode: ToroidalConstructionMode::Canonicalized, + }; let topology_model = topology.model(); Self::validate_topology_model(&topology_model)?; // Canonicalized toroidal construction: canonicalize then delegate. @@ -1733,12 +1773,11 @@ where dt.set_global_topology(topology); Ok(dt) } - (Some(space), true) => { - let topology = GlobalTopology::try_toroidal( - space, - ToroidalConstructionMode::PeriodicImagePoint, - ) - .map_err(Self::invalid_toroidal_domain)?; + BuilderTopology::PeriodicImagePoint(domain) => { + let topology = GlobalTopology::Toroidal { + domain, + mode: ToroidalConstructionMode::PeriodicImagePoint, + }; let topology_model = topology.model(); Self::validate_topology_model(&topology_model)?; if !topology_model.supports_periodic_facet_signatures() { @@ -1820,13 +1859,13 @@ where /// 12. Validate the Euclidean Level 4 Delaunay property. fn build_explicit( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], simplices: &[Vec], topology_guarantee: TopologyGuarantee, global_topology: GlobalTopology, ) -> Result, DelaunayTriangulationConstructionError> where - K: Kernel, + K: Kernel, V: DataType, { Self::validate_explicit_simplex_specs(vertices.len(), simplices)?; @@ -1835,7 +1874,7 @@ where let vertex_count = vertices.len(); // --- Build TDS --- - let mut tds: Tds = Tds::empty(); + let mut tds: Tds = Tds::empty(); // Insert all vertices and build index → VertexKey map. let mut index_to_key = Vec::with_capacity(vertex_count); @@ -1852,7 +1891,7 @@ where for (simplex_idx, simplex_spec) in simplices.iter().enumerate() { let vertex_keys: Vec = simplex_spec.iter().map(|&vi| index_to_key[vi]).collect(); - let simplex = Simplex::new(vertex_keys, None).map_err(|e| { + let simplex = Simplex::try_new(vertex_keys).map_err(|e| { ExplicitConstructionError::SimplexCreation { simplex_index: simplex_idx, source: e, @@ -2004,7 +2043,7 @@ where dt: &DelaunayTriangulation, ) -> Result<(), DelaunayTriangulationConstructionError> where - K: Kernel, + K: Kernel, V: DataType, { dt.is_valid().map_err(|source| { @@ -2033,7 +2072,7 @@ where /// /// **Algorithm** (see module-level doc for periodic image-point details): /// 1. Validate: at least `2*D + 1` canonical vertices required. - /// 2. Build 3^D-1 image copies of each vertex, shifted by `{-L_i, 0, +L_i}` per axis. + /// 2. Build 3^D-1 image copies of each shifted by `{-L_i, 0, +L_i}` per axis. /// Every copy of canonical vertex `v_i` (including the zero-offset canonical copy) /// receives the **same** tiny deterministic per-vertex perturbation `δ_i`. /// 3. Build a full Euclidean DT on the expanded set (n * 3^D points). @@ -2058,13 +2097,13 @@ where )] fn build_periodic( kernel: &K, - canonical_vertices: &[Vertex], + canonical_vertices: &[Vertex], topology_model: &M, topology_guarantee: TopologyGuarantee, construction_options: ConstructionOptions, ) -> Result, DelaunayTriangulationConstructionError> where - K: Kernel, + K: Kernel, V: DataType, M: GlobalTopologyModel, { @@ -2164,7 +2203,7 @@ where let mut image_uuid_to_canonical_with_offset: FastHashMap = FastHashMap::default(); - let mut expanded: Vec> = Vec::with_capacity(n.saturating_mul(three_pow_d)); + let mut expanded: Vec> = Vec::with_capacity(n.saturating_mul(three_pow_d)); for k in 0..three_pow_d { // Per-axis integer offsets {-1, 0, +1}. let mut offset = [0i8; D]; @@ -2177,7 +2216,7 @@ where let is_canonical = k == zero_offset_idx; for (canon_idx, v) in canonical_vertices.iter().enumerate() { - let mut new_coords = [T::zero(); D]; + let mut new_coords = [0.0; D]; for i in 0..D { let shift_f64 = >::from(offset[i]) * domain[i]; let jitter_f64 = if is_canonical { @@ -2189,21 +2228,21 @@ where / TWO_POW_52_F64) * domain[i] }; - let coord_f64 = canonical_f64[canon_idx][i] + shift_f64 + jitter_f64; - new_coords[i] = - ::from(coord_f64).ok_or_else(|| { - TriangulationConstructionError::GeometricDegeneracy { - message: format!("Overflow on axis {i}: image coord {coord_f64}"), - } - })?; + new_coords[i] = canonical_f64[canon_idx][i] + shift_f64 + jitter_f64; } - let new_point = Point::new(new_coords); + let new_point = Point::try_new(new_coords).map_err(|error| { + TriangulationConstructionError::GeometricDegeneracy { + message: format!( + "Periodic image coordinates for vertex {canon_idx} image {k} violated point invariants: {error}", + ), + } + })?; if is_canonical { image_uuid_to_canonical_with_offset.insert(v.uuid(), (v.uuid(), [0_i8; D])); let canonical_v = Vertex::new_with_uuid(new_point, v.uuid(), v.data); expanded.push(canonical_v); } else { - let image_v: Vertex = Vertex::from_point(new_point); + let image_v: Vertex = Vertex::from_validated_point(new_point); image_uuid_to_canonical_with_offset.insert(image_v.uuid(), (v.uuid(), offset)); expanded.push(image_v); } @@ -2412,7 +2451,7 @@ where }; let simplex_circumcenter_in_fundamental_domain = |simplex_key: SimplexKey| -> Option { let simplex = tds_ref.simplex(simplex_key)?; - let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(D + 1); for vk in simplex.vertices() { let vertex = tds_ref.vertex(*vk)?; @@ -2837,7 +2876,7 @@ where }; let canonical_vertex_keys: Vec = lifted_vertices.iter().map(|(ck, _)| *ck).collect(); - let mut simplex = Simplex::new(canonical_vertex_keys, None).map_err(|e| { + let mut simplex = Simplex::try_new(canonical_vertex_keys).map_err(|e| { TriangulationConstructionError::GeometricDegeneracy { message: format!("Failed to create quotient periodic simplex: {e}"), } @@ -3039,18 +3078,17 @@ mod tests { }; use crate::core::util::uuid::UuidValidationError; use crate::core::validation::TriangulationValidationError; - use crate::core::vertex::VertexBuilder; use crate::core::vertex::VertexValidationError; use crate::geometry::kernel::RobustKernel; use crate::geometry::traits::coordinate::{CoordinateConversionValue, CoordinateValues}; use crate::repair::DelaunayRepairOperation; + use crate::topology::traits::GlobalTopologyModelError; use crate::topology::traits::global_topology_model::{ - EuclideanModel, GlobalTopologyModel, GlobalTopologyModelError, ToroidalModel, + EuclideanModel, GlobalTopologyModel, ToroidalModel, }; use crate::topology::traits::topological_space::{ - GlobalTopology, TopologyKind, ToroidalConstructionMode, + GlobalTopology, TopologyKind, ToroidalConstructionMode, ToroidalDomainError, }; - use crate::vertex; use approx::assert_relative_eq; use slotmap::{Key, KeyData}; use std::assert_matches; @@ -3063,28 +3101,14 @@ mod tests { } fn assert_invalid_toroidal_domain_error( - err: DelaunayTriangulationConstructionError, + err: ToroidalDomainError, expected_axis: usize, - expected_period_fragment: &str, + expected_period: f64, ) { - let DelaunayTriangulationConstructionError::Triangulation( - DelaunayConstructionFailure::GeometricDegeneracy { message }, - ) = err - else { - panic!("expected GeometricDegeneracy mapping, got: {err:?}"); - }; - - assert!( - message.contains("Invalid toroidal domain"), - "Error message should mention invalid toroidal domain: {message}" - ); - assert!( - message.contains(&format!("axis {expected_axis}")), - "Error message should mention axis {expected_axis}: {message}" - ); - assert!( - message.contains(expected_period_fragment), - "Error message should mention period {expected_period_fragment}: {message}" + assert_matches!( + err, + ToroidalDomainError::InvalidPeriod { axis, period } + if axis == expected_axis && period.to_bits() == expected_period.to_bits() ); } @@ -3107,24 +3131,18 @@ mod tests { }) } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - _coords: &mut [T; 2], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + _coords: &mut [f64; 2], + ) -> Result<(), GlobalTopologyModelError> { Ok(()) } - fn lift_for_orientation( + fn lift_for_orientation( &self, - coords: [T; 2], + coords: [f64; 2], periodic_offset: Option<[i8; 2]>, - ) -> Result<[T; 2], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; 2], GlobalTopologyModelError> { if periodic_offset.is_some() { return Err(GlobalTopologyModelError::PeriodicOffsetsUnsupported { kind: TopologyKind::Euclidean, @@ -3564,27 +3582,21 @@ mod tests { Ok(()) } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - _coords: &mut [T; 2], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + _coords: &mut [f64; 2], + ) -> Result<(), GlobalTopologyModelError> { Err(GlobalTopologyModelError::NonFiniteCoordinate { axis: 0, value: f64::NAN, }) } - fn lift_for_orientation( + fn lift_for_orientation( &self, - coords: [T; 2], + coords: [f64; 2], periodic_offset: Option<[i8; 2]>, - ) -> Result<[T; 2], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; 2], GlobalTopologyModelError> { if periodic_offset.is_some() { return Err(GlobalTopologyModelError::PeriodicOffsetsUnsupported { kind: TopologyKind::Euclidean, @@ -3610,24 +3622,18 @@ mod tests { Ok(()) } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - _coords: &mut [T; 2], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + _coords: &mut [f64; 2], + ) -> Result<(), GlobalTopologyModelError> { Ok(()) } - fn lift_for_orientation( + fn lift_for_orientation( &self, - coords: [T; 2], + coords: [f64; 2], _periodic_offset: Option<[i8; 2]>, - ) -> Result<[T; 2], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; 2], GlobalTopologyModelError> { Ok(coords) } @@ -3647,9 +3653,9 @@ mod tests { #[test] fn test_builder_euclidean_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() @@ -3662,10 +3668,10 @@ mod tests { #[test] fn test_builder_euclidean_3d() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() @@ -3677,9 +3683,9 @@ mod tests { #[test] fn test_builder_topology_guarantee_propagated() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .topology_guarantee(TopologyGuarantee::Pseudomanifold) @@ -3691,9 +3697,9 @@ mod tests { #[test] fn test_builder_custom_options_propagated() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let opts = ConstructionOptions::default().with_insertion_order(InsertionOrderStrategy::Input); @@ -3713,13 +3719,14 @@ mod tests { #[test] fn test_builder_canonicalized_toroidal_canonicalizes_out_of_domain_vertices() { let vertices = vec![ - vertex!([0.2, 0.3]), // in domain - vertex!([1.8, 0.1]), // x → 0.8 - vertex!([0.5, 0.7]), // in domain - vertex!([-0.4, 0.9]), // x → 0.6 + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.3]).unwrap(), // in domain + crate::core::vertex::Vertex::<(), _>::try_new([1.8, 0.1]).unwrap(), // x → 0.8 + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), // in domain + crate::core::vertex::Vertex::<(), _>::try_new([-0.4, 0.9]).unwrap(), // x → 0.6 ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .unwrap(); @@ -3736,12 +3743,13 @@ mod tests { #[test] fn test_builder_canonicalized_toroidal_in_domain_vertices_unchanged() { let vertices = vec![ - vertex!([0.1, 0.2]), - vertex!([0.8, 0.3]), - vertex!([0.4, 0.9]), + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.4, 0.9]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .unwrap(); @@ -3755,13 +3763,14 @@ mod tests { #[test] fn test_builder_canonicalized_toroidal_build_succeeds_2d() { let vertices = vec![ - vertex!([0.2, 0.3]), - vertex!([0.8, 0.1]), - vertex!([0.5, 0.7]), - vertex!([0.1, 0.9]), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.1]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.9]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .unwrap(); assert_eq!(dt.number_of_vertices(), 4); @@ -3779,13 +3788,14 @@ mod tests { #[test] fn test_builder_canonicalized_toroidal_build_out_of_domain_input_2d() { let vertices = vec![ - vertex!([2.2, 3.3]), // → (0.2, 0.3) - vertex!([-0.2, 1.1]), // → (0.8, 0.1) - vertex!([1.5, 0.7]), // → (0.5, 0.7) - vertex!([-0.9, 2.9]), // → (0.1, 0.9) + crate::core::vertex::Vertex::<(), _>::try_new([2.2, 3.3]).unwrap(), // → (0.2, 0.3) + crate::core::vertex::Vertex::<(), _>::try_new([-0.2, 1.1]).unwrap(), // → (0.8, 0.1) + crate::core::vertex::Vertex::<(), _>::try_new([1.5, 0.7]).unwrap(), // → (0.5, 0.7) + crate::core::vertex::Vertex::<(), _>::try_new([-0.9, 2.9]).unwrap(), // → (0.1, 0.9) ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .unwrap(); assert_eq!(dt.number_of_vertices(), 4); @@ -3793,78 +3803,59 @@ mod tests { assert!(dt.as_triangulation().validate().is_ok()); } - /// A non-finite (NaN) coordinate must cause the canonicalized toroidal build to return an error. - /// We create the vertex via `VertexBuilder` + `Point::new` to bypass `try_from` - /// validation, which would otherwise panic. #[test] fn test_builder_canonicalized_toroidal_non_finite_coordinate_is_error() { - let vertices = vec![ - VertexBuilder::::default() - .point(Point::new([0.2_f64, 0.3])) - .build() - .unwrap(), - VertexBuilder::::default() - .point(Point::new([f64::NAN, 0.1])) - .build() - .unwrap(), - VertexBuilder::::default() - .point(Point::new([0.5_f64, 0.7])) - .build() - .unwrap(), - ]; - let result = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) - .build::<()>(); - assert!(result.is_err()); + assert!(Point::<2>::try_new([f64::NAN, 0.1]).is_err()); } #[test] fn test_builder_canonicalized_toroidal_invalid_domain_is_error() { let vertices = vec![ - vertex!([0.2, 0.3]), - vertex!([0.8, 0.1]), - vertex!([0.5, 0.7]), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.1]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), ]; - let result = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([0.0, 1.0]) - .build::<()>(); - let err = result.expect_err("zero period should be rejected"); - assert_invalid_toroidal_domain_error(err, 0, "0.0"); + let Err(err) = + DelaunayTriangulationBuilder::new(&vertices).try_canonicalized_toroidal([0.0, 1.0]) + else { + panic!("zero period should be rejected"); + }; + assert_invalid_toroidal_domain_error(err, 0, 0.0); } #[test] fn test_builder_toroidal_invalid_domain_is_error() { let vertices = vec![ - vertex!([0.1, 0.2]), - vertex!([0.4, 0.7]), - vertex!([0.7, 0.3]), - vertex!([0.2, 0.9]), - vertex!([0.8, 0.6]), - vertex!([0.5, 0.1]), - vertex!([0.3, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.4, 0.7]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.7, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.9]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.6]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.1]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.5]).unwrap(), ]; - let result = DelaunayTriangulationBuilder::new(&vertices) - .toroidal([1.0, 0.0]) - .build::<()>(); - let err = result.expect_err("zero period should be rejected"); - assert_invalid_toroidal_domain_error(err, 1, "0.0"); + let Err(err) = DelaunayTriangulationBuilder::new(&vertices).try_toroidal([1.0, 0.0]) else { + panic!("zero period should be rejected"); + }; + assert_invalid_toroidal_domain_error(err, 1, 0.0); } #[test] fn test_builder_toroidal_2d_smoke() { let vertices = vec![ - vertex!([0.1_f64, 0.2]), - vertex!([0.4, 0.7]), - vertex!([0.7, 0.3]), - vertex!([0.2, 0.9]), - vertex!([0.8, 0.6]), - vertex!([0.5, 0.1]), - vertex!([0.3, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.1_f64, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.4, 0.7]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.7, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.9]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.6]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.1]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.5]).unwrap(), ]; let n = vertices.len(); let kernel = RobustKernel::new(); let dt = DelaunayTriangulationBuilder::new(&vertices) - .toroidal([1.0, 1.0]) + .try_toroidal([1.0, 1.0]) + .unwrap() .build_with_kernel::<_, ()>(&kernel) .unwrap(); assert_eq!(dt.number_of_vertices(), n); @@ -3881,15 +3872,16 @@ mod tests { #[test] fn test_builder_canonicalized_toroidal_idempotent_on_canonical_input() { let vertices = vec![ - vertex!([0.1, 0.2]), - vertex!([0.8, 0.3]), - vertex!([0.4, 0.9]), + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.8, 0.3]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.4, 0.9]).unwrap(), ]; let dt_euclidean = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() .unwrap(); let dt_toroidal = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .unwrap(); assert_eq!( @@ -3910,25 +3902,14 @@ mod tests { /// Verify that the data is preserved after canonicalized toroidal wrapping. #[test] fn test_builder_from_vertices_preserves_vertex_data() { - let vertices: Vec> = vec![ - VertexBuilder::default() - .point(Point::new([0.2_f64, 0.3])) - .data(1_i32) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([1.8_f64, 0.1])) // x → 0.8 - .data(2_i32) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([0.5_f64, 0.7])) - .data(3_i32) - .build() - .unwrap(), + let vertices: Vec> = vec![ + Vertex::try_new_with_data([0.2_f64, 0.3], 1_i32).unwrap(), + Vertex::try_new_with_data([1.8_f64, 0.1], 2_i32).unwrap(), // x → 0.8 + Vertex::try_new_with_data([0.5_f64, 0.7], 3_i32).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .unwrap(); @@ -3950,10 +3931,10 @@ mod tests { #[test] fn test_builder_with_robust_kernel() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let kernel = RobustKernel::::new(); let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -3970,7 +3951,7 @@ mod tests { #[test] fn test_validate_topology_model_accepts_valid_toroidal() { let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); - let result = DelaunayTriangulationBuilder::::validate_topology_model(&model); + let result = DelaunayTriangulationBuilder::<(), 2>::validate_topology_model(&model); assert!(result.is_ok()); } @@ -3982,11 +3963,9 @@ mod tests { (VertexKey::null(), [0_i8, 1_i8]), ]; let expected = periodic_facet_key_from_lifted_vertices::<2>(&lifted_ordered, 1).unwrap(); - let derived = DelaunayTriangulationBuilder::::derive_periodic_facet_key( - &lifted_ordered, - 1, - ) - .unwrap(); + let derived = + DelaunayTriangulationBuilder::<(), 2>::derive_periodic_facet_key(&lifted_ordered, 1) + .unwrap(); assert_eq!(derived, expected); } @@ -3997,11 +3976,9 @@ mod tests { (VertexKey::null(), [0_i8, 0_i8]), (VertexKey::null(), [127_i8, 0_i8]), ]; - let err = DelaunayTriangulationBuilder::::derive_periodic_facet_key( - &lifted_ordered, - 1, - ) - .unwrap_err(); + let err = + DelaunayTriangulationBuilder::<(), 2>::derive_periodic_facet_key(&lifted_ordered, 1) + .unwrap_err(); let DelaunayTriangulationConstructionError::Triangulation( DelaunayConstructionFailure::GeometricDegeneracy { message }, ) = err @@ -4019,72 +3996,42 @@ mod tests { ); } - #[test] - fn test_invalid_toroidal_domain_maps_zero_period() { - let source = - ToroidalModel::<2>::try_new([0.0, 3.0], ToroidalConstructionMode::Canonicalized) - .unwrap_err(); - let err = DelaunayTriangulationBuilder::::invalid_toroidal_domain(source); - assert_invalid_toroidal_domain_error(err, 0, "0.0"); - } - - #[test] - fn test_invalid_toroidal_domain_maps_negative_period() { - let source = - ToroidalModel::<3>::try_new([2.0, -1.0, 3.0], ToroidalConstructionMode::Canonicalized) - .unwrap_err(); - let err = DelaunayTriangulationBuilder::::invalid_toroidal_domain(source); - assert_invalid_toroidal_domain_error(err, 1, "-1.0"); - } - - #[test] - fn test_invalid_toroidal_domain_maps_infinite_period() { - let source = ToroidalModel::<2>::try_new( - [f64::INFINITY, 3.0], - ToroidalConstructionMode::Canonicalized, - ) - .unwrap_err(); - let err = DelaunayTriangulationBuilder::::invalid_toroidal_domain(source); - assert_invalid_toroidal_domain_error(err, 0, "inf"); - } - - #[test] - fn test_invalid_toroidal_domain_maps_nan_period() { - let source = - ToroidalModel::<2>::try_new([f64::NAN, 3.0], ToroidalConstructionMode::Canonicalized) - .unwrap_err(); - let err = DelaunayTriangulationBuilder::::invalid_toroidal_domain(source); - assert_invalid_toroidal_domain_error(err, 0, "NaN"); - } - #[test] fn test_validate_topology_model_accepts_euclidean() { let model = EuclideanModel; - let result = DelaunayTriangulationBuilder::::validate_topology_model(&model); + let result = DelaunayTriangulationBuilder::<(), 2>::validate_topology_model(&model); assert!(result.is_ok()); } #[test] fn test_validate_topology_model_maps_non_period_errors() { - let result = DelaunayTriangulationBuilder::::validate_topology_model( - &ValidationFailureModel, - ); + let result = + DelaunayTriangulationBuilder::<(), 2>::validate_topology_model(&ValidationFailureModel); let err = result.expect_err("non-period validation failure should be mapped"); - let err_str = err.to_string(); - assert!(err_str.contains("Invalid topology model configuration")); + assert_matches!( + err, + DelaunayTriangulationConstructionError::Triangulation( + DelaunayConstructionFailure::TopologyModelConfiguration { + source: GlobalTopologyModelError::NonFiniteCoordinate { + axis: 0, + value, + }, + }, + ) if value.is_nan() + ); } #[test] fn test_canonicalize_vertices_preserves_uuids() { let vertices = vec![ - vertex!([2.5, 3.7]), - vertex!([1.8, -0.5]), - vertex!([0.5, 0.7]), + crate::core::vertex::Vertex::<(), _>::try_new([2.5, 3.7]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.8, -0.5]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), ]; let original_uuids: Vec<_> = vertices.iter().map(Vertex::uuid).collect(); let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); let canonical = - DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model) + DelaunayTriangulationBuilder::<(), 2>::canonicalize_vertices(&vertices, &model) .unwrap(); assert_eq!(canonical.len(), vertices.len()); @@ -4094,26 +4041,14 @@ mod tests { #[test] fn test_canonicalize_vertices_preserves_data() { - let vertices: Vec> = vec![ - VertexBuilder::default() - .point(Point::new([2.5_f64, 3.7])) - .data(10_i32) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([1.8_f64, -0.5])) - .data(20_i32) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([0.5_f64, 0.7])) - .data(30_i32) - .build() - .unwrap(), + let vertices: Vec> = vec![ + Vertex::try_new_with_data([2.5_f64, 3.7], 10_i32).unwrap(), + Vertex::try_new_with_data([1.8_f64, -0.5], 20_i32).unwrap(), + Vertex::try_new_with_data([0.5_f64, 0.7], 30_i32).unwrap(), ]; let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); let canonical = - DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model) + DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model) .unwrap(); assert_eq!(canonical.len(), vertices.len()); @@ -4125,13 +4060,13 @@ mod tests { #[test] fn test_canonicalize_vertices_transforms_coordinates() { let vertices = vec![ - vertex!([2.5, 3.7]), // → (0.5, 0.7) - vertex!([1.8, -0.5]), // → (1.8, 2.5) - vertex!([0.3, 0.2]), // → (0.3, 0.2) + crate::core::vertex::Vertex::<(), _>::try_new([2.5, 3.7]).unwrap(), // → (0.5, 0.7) + crate::core::vertex::Vertex::<(), _>::try_new([1.8, -0.5]).unwrap(), // → (1.8, 2.5) + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.2]).unwrap(), // → (0.3, 0.2) ]; let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); let canonical = - DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model) + DelaunayTriangulationBuilder::<(), 2>::canonicalize_vertices(&vertices, &model) .unwrap(); assert_eq!(canonical.len(), 3); @@ -4145,28 +4080,40 @@ mod tests { #[test] fn test_canonicalize_vertices_includes_vertex_context_on_error() { - let vertices = vec![vertex!([0.25_f64, 0.75_f64]), vertex!([0.9_f64, 0.1_f64])]; - let result = DelaunayTriangulationBuilder::::canonicalize_vertices( + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.25_f64, 0.75_f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.9_f64, 0.1_f64]).unwrap(), + ]; + let result = DelaunayTriangulationBuilder::<(), 2>::canonicalize_vertices( &vertices, &CanonicalizationFailureModel, ); let err = result.expect_err("canonicalization failure should be reported"); - let err_str = err.to_string(); - assert!(err_str.contains("Failed to canonicalize vertex 0")); - assert!(err_str.contains("reason")); + assert_matches!( + err, + DelaunayTriangulationConstructionError::Triangulation( + DelaunayConstructionFailure::VertexCanonicalization { + vertex_index: 0, + source: GlobalTopologyModelError::NonFiniteCoordinate { + axis: 0, + value, + }, + }, + ) if value.is_nan() + ); } #[test] fn test_build_periodic_requires_periodic_domain() { let kernel = AdaptiveKernel::new(); let canonical_vertices = vec![ - vertex!([0.1_f64, 0.1_f64]), - vertex!([0.9_f64, 0.2_f64]), - vertex!([0.2_f64, 0.8_f64]), - vertex!([0.7_f64, 0.9_f64]), - vertex!([0.5_f64, 0.4_f64]), + crate::core::vertex::Vertex::<(), _>::try_new([0.1_f64, 0.1_f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.9_f64, 0.2_f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2_f64, 0.8_f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.7_f64, 0.9_f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5_f64, 0.4_f64]).unwrap(), ]; - let result = DelaunayTriangulationBuilder::::build_periodic::<_, (), _>( + let result = DelaunayTriangulationBuilder::<(), 2>::build_periodic::<_, (), _>( &kernel, &canonical_vertices, &MissingPeriodicDomainModel, @@ -4182,13 +4129,13 @@ mod tests { #[test] fn test_canonicalize_vertices_euclidean_identity() { let vertices = vec![ - vertex!([1.5, 2.5]), - vertex!([3.7, 4.2]), - vertex!([-1.0, -2.0]), + crate::core::vertex::Vertex::<(), _>::try_new([1.5, 2.5]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([3.7, 4.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([-1.0, -2.0]).unwrap(), ]; let model = EuclideanModel; let canonical = - DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model) + DelaunayTriangulationBuilder::<(), 2>::canonicalize_vertices(&vertices, &model) .unwrap(); assert_eq!(canonical.len(), vertices.len()); @@ -4200,79 +4147,16 @@ mod tests { #[test] fn test_canonicalize_vertices_propagates_nan_error() { - let vertices = vec![ - VertexBuilder::default() - .point(Point::new([0.5_f64, 0.5])) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([f64::NAN, 0.5])) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([0.3_f64, 0.2])) - .build() - .unwrap(), - ]; - let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); - let result = - DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model); - - assert!(result.is_err()); - let err_str = format!("{}", result.unwrap_err()); - assert!( - err_str.contains("Failed to canonicalize vertex"), - "Error should mention canonicalization failure: {err_str}" - ); - assert!( - err_str.contains("vertex 1"), - "Error should mention vertex index: {err_str}" - ); + assert!(Point::<2>::try_new([f64::NAN, 0.5]).is_err()); } #[test] fn test_canonicalize_vertices_propagates_infinity_error() { - let vertices = vec![ - VertexBuilder::default() - .point(Point::new([0.5_f64, 0.5])) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([0.3_f64, 0.2])) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([f64::INFINITY, 0.5])) - .build() - .unwrap(), - ]; - let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); - let result = - DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model); - - assert!(result.is_err()); - let err_str = format!("{}", result.unwrap_err()); - assert!(err_str.contains("Failed to canonicalize vertex")); - assert!(err_str.contains("vertex 2")); + assert!(Point::<2>::try_new([f64::INFINITY, 0.5]).is_err()); } #[test] fn test_canonicalize_vertices_includes_original_coords_in_error() { - let vertices = vec![ - VertexBuilder::default() - .point(Point::new([f64::NAN, 1.5_f64])) - .build() - .unwrap(), - ]; - let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); - let result = - DelaunayTriangulationBuilder::::canonicalize_vertices(&vertices, &model); - - assert!(result.is_err()); - let err_str = format!("{}", result.unwrap_err()); - assert!( - err_str.contains("original coords"), - "Error should mention original coords: {err_str}" - ); + assert!(Point::<2>::try_new([f64::NAN, 1.5]).is_err()); } } diff --git a/src/delaunay/construction.rs b/src/delaunay/construction.rs index 3fb0f44f..773860d5 100644 --- a/src/delaunay/construction.rs +++ b/src/delaunay/construction.rs @@ -8,9 +8,9 @@ //! importing flip editing or validation-only APIs. //! //! Most examples should import these items through -//! [`delaunay::prelude::construction`](crate::prelude::construction), which -//! bundles the builder, construction options, construction errors, and the -//! [`vertex!`](crate::vertex) macro. +//! [`crate::prelude::construction`](crate::prelude::construction), which +//! bundles the builder, construction options, construction errors, and +//! [`Vertex`](crate::prelude::Vertex). //! //! # Examples //! @@ -18,14 +18,14 @@ //! use delaunay::prelude::construction::{ //! ConstructionOptions, DelaunayTriangulationBuilder, //! DelaunayTriangulationConstructionError, InitialSimplexStrategy, -//! InsertionOrderStrategy, vertex, +//! InsertionOrderStrategy, Vertex, //! }; //! //! # fn main() -> Result<(), DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0]), -//! vertex!([1.0, 0.0]), -//! vertex!([0.0, 1.0]), +//! Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +//! Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +//! Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! //! let options = ConstructionOptions::default() @@ -70,8 +70,8 @@ use crate::core::tds::{TdsConstructionError, TdsError}; use crate::core::traits::data_type::DataType; use crate::core::triangulation::Triangulation; use crate::core::util::{ - HilbertBitDepth, coords_equal_exact, coords_within_epsilon, hilbert_quantize_batch_in_range, - stable_hash_u64_slice, + DeduplicationError, HilbertBitDepth, coords_equal_exact, coords_within_epsilon, + hilbert_quantize_batch_in_range, stable_hash_u64_slice, }; use crate::core::validation::{TopologyGuarantee, TriangulationValidationError, ValidationPolicy}; use crate::core::vertex::Vertex; @@ -79,19 +79,17 @@ use crate::diagnostics::{BatchLocalRepairTrigger, ConstructionTelemetry, LocalRe use crate::geometry::coordinate_range::CoordinateRange; use crate::geometry::kernel::{AdaptiveKernel, Kernel}; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{Coordinate, CoordinateScalar, CoordinateValues}; -use crate::geometry::util::{ - RandomPointGenerationError, safe_coords_to_f64, safe_usize_to_scalar, simplex_volume, -}; +use crate::geometry::traits::coordinate::{CoordinateValidationError, CoordinateValues}; +use crate::geometry::util::{RandomPointGenerationError, safe_usize_to_scalar, simplex_volume}; use crate::locality::{ accumulate_live_simplex_seeds, clear_simplex_seed_set, retain_live_simplex_seeds, }; use crate::repair::DelaunayRepairPolicy; -use crate::topology::traits::topological_space::GlobalTopology; +use crate::topology::traits::{GlobalTopology, GlobalTopologyModelError}; use crate::triangulation::DelaunayTriangulation; use crate::validation::DelaunayTriangulationValidationError; use core::{cmp::Ordering, fmt}; -use num_traits::{NumCast, ToPrimitive, Zero}; +use num_traits::ToPrimitive; use rand::SeedableRng; use rand::rngs::StdRng; use rand::seq::SliceRandom; @@ -286,6 +284,34 @@ pub enum DelaunayConstructionFailure { message: String, }, + /// Topology model configuration was invalid before construction. + #[error("topology model configuration failed during construction: {source}")] + TopologyModelConfiguration { + /// Underlying topology model configuration error. + #[source] + source: GlobalTopologyModelError, + }, + + /// A topology model failed while canonicalizing an input vertex. + #[error("failed to canonicalize vertex {vertex_index} during construction: {source}")] + VertexCanonicalization { + /// Zero-based input vertex index. + vertex_index: usize, + /// Underlying topology model error. + #[source] + source: GlobalTopologyModelError, + }, + + /// A canonicalized coordinate tuple failed point validation. + #[error("canonicalized vertex {vertex_index} failed point validation: {source}")] + CanonicalizedPointValidation { + /// Zero-based input vertex index. + vertex_index: usize, + /// Underlying coordinate validation error. + #[source] + source: CoordinateValidationError, + }, + /// Periodic quotient construction is not release-validated for this dimension. #[error( "periodic image-point construction is release-validated only up to {max_validated_dimension}D; {dimension}D scalable quotient construction is tracked by issue #{tracking_issue}" @@ -528,6 +554,85 @@ pub enum InsertionOrderStrategy { Hilbert, } +/// Non-negative finite Euclidean tolerance for epsilon deduplication. +/// +/// This is the proof-carrying value stored by [`DedupPolicy::Epsilon`]. +/// Construct it with [`Self::try_new`] at raw numeric boundaries; after +/// construction, [`Self::get`] is infallible. +/// +/// # Examples +/// +/// ```rust +/// use delaunay::prelude::construction::{DedupPolicy, DedupTolerance}; +/// +/// # fn main() -> Result<(), delaunay::prelude::construction::DeduplicationError> { +/// let tolerance = DedupTolerance::try_new(1.0e-9)?; +/// let policy = DedupPolicy::epsilon(tolerance); +/// +/// assert_eq!(tolerance.get(), 1.0e-9); +/// std::assert_matches!(policy, DedupPolicy::Epsilon { .. }); +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +#[must_use] +pub struct DedupTolerance(f64); + +impl DedupTolerance { + /// Parses a raw `f64` into a non-negative finite deduplication tolerance. + /// + /// # Errors + /// + /// Returns [`DeduplicationError::NonFiniteEpsilon`] for NaN or infinite + /// values, and [`DeduplicationError::NegativeEpsilon`] for negative values. + /// + /// # Examples + /// + /// ```rust + /// use delaunay::prelude::construction::{DeduplicationError, DedupTolerance}; + /// + /// let err = DedupTolerance::try_new(-1.0).unwrap_err(); + /// assert_eq!(err, DeduplicationError::NegativeEpsilon); + /// ``` + pub fn try_new(tolerance: f64) -> Result { + if !tolerance.is_finite() { + return Err(DeduplicationError::NonFiniteEpsilon); + } + + if tolerance < 0.0 { + return Err(DeduplicationError::NegativeEpsilon); + } + + Ok(Self(if tolerance == 0.0 { 0.0 } else { tolerance })) + } + + /// Returns the validated non-negative finite tolerance. + /// + /// # Examples + /// + /// ```rust + /// use delaunay::prelude::construction::DedupTolerance; + /// + /// # fn main() -> Result<(), delaunay::prelude::construction::DeduplicationError> { + /// let tolerance = DedupTolerance::try_new(0.25)?; + /// assert_eq!(tolerance.get(), 0.25); + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub const fn get(self) -> f64 { + self.0 + } +} + +impl TryFrom for DedupTolerance { + type Error = DeduplicationError; + + fn try_from(tolerance: f64) -> Result { + Self::try_new(tolerance) + } +} + /// Policy controlling optional preprocessing to remove duplicate vertices. #[derive(Debug, Clone, Copy, PartialEq, Default)] #[non_exhaustive] @@ -544,10 +649,61 @@ pub enum DedupPolicy { /// Remove near-duplicates within the given Euclidean tolerance. Epsilon { /// Non-negative Euclidean tolerance. - tolerance: f64, + tolerance: DedupTolerance, }, } +impl DedupPolicy { + /// Parses a raw `f64` tolerance into an epsilon deduplication policy. + /// + /// # Errors + /// + /// Returns [`DeduplicationError`] when `tolerance` is negative, NaN, or + /// infinite. + /// + /// # Examples + /// + /// ```rust + /// use delaunay::prelude::construction::{DedupPolicy, DeduplicationError}; + /// + /// # fn main() -> Result<(), DeduplicationError> { + /// let policy = DedupPolicy::try_epsilon(1.0e-8)?; + /// std::assert_matches!(policy, DedupPolicy::Epsilon { .. }); + /// + /// assert_eq!( + /// DedupPolicy::try_epsilon(f64::NAN), + /// Err(DeduplicationError::NonFiniteEpsilon) + /// ); + /// # Ok(()) + /// # } + /// ``` + pub fn try_epsilon(tolerance: f64) -> Result { + Ok(Self::Epsilon { + tolerance: DedupTolerance::try_new(tolerance)?, + }) + } + + /// Builds an epsilon deduplication policy from an already-validated tolerance. + /// + /// # Examples + /// + /// ```rust + /// use delaunay::prelude::construction::{DedupPolicy, DedupTolerance}; + /// + /// # fn main() -> Result<(), delaunay::prelude::construction::DeduplicationError> { + /// let tolerance = DedupTolerance::try_new(0.0)?; + /// let policy = DedupPolicy::epsilon(tolerance); + /// + /// std::assert_matches!(policy, DedupPolicy::Epsilon { tolerance: t } if t.get() == 0.0); + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub const fn epsilon(tolerance: DedupTolerance) -> Self { + Self::Epsilon { tolerance } + } +} + /// Strategy controlling how the initial D+1 simplex vertices are selected. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[non_exhaustive] @@ -999,18 +1155,18 @@ pub(crate) fn batch_local_repair_trigger( // BATCH CONSTRUCTION ORDERING HELPERS (INTERNAL) // ============================================================================= -type VertexBuffer = Vec>; +type VertexBuffer = Vec>; /// Preprocessed vertex ordering state used by batch construction to preserve /// deterministic insertion order, retry fallback order, and deduplication-grid /// reuse across public construction entry points. -pub(crate) struct PreprocessVertices { - primary: Option>, - fallback: Option>, - grid_cell_size: Option, +pub(crate) struct PreprocessVertices { + primary: Option>, + fallback: Option>, + grid_cell_size: Option, } -impl fmt::Debug for PreprocessVertices { +impl fmt::Debug for PreprocessVertices { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PreprocessVertices") .field("primary_len", &self.primary.as_ref().map(Vec::len)) @@ -1020,62 +1176,42 @@ impl fmt::Debug for PreprocessVertices { } } -impl PreprocessVertices { +impl PreprocessVertices { /// Borrows the preprocessed vertex order when one exists, avoiding a clone /// for policies that leave the input unchanged. - pub(crate) fn primary_slice<'a>( - &'a self, - input: &'a [Vertex], - ) -> &'a [Vertex] { + pub(crate) fn primary_slice<'a>(&'a self, input: &'a [Vertex]) -> &'a [Vertex] { self.primary.as_deref().unwrap_or(input) } /// Exposes the original order as a retry fallback for balanced-simplex /// preprocessing. - pub(crate) fn fallback_slice(&self) -> Option<&[Vertex]> { + pub(crate) fn fallback_slice(&self) -> Option<&[Vertex]> { self.fallback.as_deref() } /// Carries the dedup grid size forward so incremental insertion can reuse a /// compatible spatial index. - pub(crate) const fn grid_cell_size(&self) -> Option - where - T: Copy, - { + pub(crate) const fn grid_cell_size(&self) -> Option { self.grid_cell_size } } -pub(crate) type PreprocessVerticesResult = - Result, DelaunayTriangulationConstructionError>; +pub(crate) type PreprocessVerticesResult = + Result, DelaunayTriangulationConstructionError>; /// Hashes coordinates as a deterministic tiebreaker for partial vertex ordering. -fn vertex_coordinate_hash(vertex: &Vertex) -> u64 -where - T: CoordinateScalar, -{ +fn vertex_coordinate_hash(vertex: &Vertex) -> u64 { let mut hasher = FastHasher::default(); vertex.hash(&mut hasher); hasher.finish() } -fn total_cmp_for_coordinate(left: &T, right: &T) -> Ordering -where - T: CoordinateScalar, -{ - left.ordered_partial_cmp(right) - .expect("CoordinateScalar::ordered_partial_cmp must define a total order") -} - -fn compare_vertices_by_coordinates( - left: &Vertex, - right: &Vertex, -) -> Ordering -where - T: CoordinateScalar, -{ +fn compare_vertices_by_coordinates( + left: &Vertex, + right: &Vertex, +) -> Ordering { for (left_coord, right_coord) in left.point().coords().iter().zip(right.point().coords()) { - let ordering = total_cmp_for_coordinate(left_coord, right_coord); + let ordering = left_coord.total_cmp(right_coord); if ordering != Ordering::Equal { return ordering; } @@ -1085,13 +1221,10 @@ where /// Produces a stable construction order when Hilbert ordering is unavailable or /// unsuitable. -fn order_vertices_lexicographic( - vertices: Vec>, -) -> Vec> -where - T: CoordinateScalar, -{ - let mut keyed: Vec<(Vertex, u64, usize)> = vertices +fn order_vertices_lexicographic( + vertices: Vec>, +) -> Vec> { + let mut keyed: Vec<(Vertex, u64, usize)> = vertices .into_iter() .enumerate() .map(|(input_index, vertex)| { @@ -1114,14 +1247,11 @@ const BATCH_DEDUP_MAX_DIMENSION: usize = 5; /// Centralizes insertion-order dispatch so preprocessing applies dedup and /// ordering in a consistent sequence. -fn order_vertices_by_strategy( - vertices: Vec>, +fn order_vertices_by_strategy( + vertices: Vec>, insertion_order: InsertionOrderStrategy, dedup_quantized: bool, -) -> Vec> -where - T: CoordinateScalar, -{ +) -> Vec> { match insertion_order { InsertionOrderStrategy::Input => vertices, InsertionOrderStrategy::Hilbert => order_vertices_hilbert(vertices, dedup_quantized), @@ -1130,8 +1260,8 @@ where /// Provides a scalar-aware tolerance for dedup paths that need a nonzero grid /// size even under exact duplicate policy. -pub(crate) fn default_duplicate_tolerance() -> T { - ::from(1e-10_f64).unwrap_or_else(T::default_tolerance) +pub(crate) const fn default_duplicate_tolerance() -> f64 { + 1e-10_f64 } /// Builds a hash-grid index after construction has validated its tolerance. @@ -1140,12 +1270,9 @@ pub(crate) fn default_duplicate_tolerance() -> T { /// construction inputs and the internal duplicate index: a failure here means /// an upstream construction invariant was broken after validation, so it is /// surfaced as a typed spatial-index construction failure. -fn hash_grid_from_validated_cell_size( - cell_size: T, -) -> Result, DelaunayTriangulationConstructionError> -where - T: CoordinateScalar, -{ +fn hash_grid_from_validated_cell_size( + cell_size: f64, +) -> Result, DelaunayTriangulationConstructionError> { HashGridIndex::try_new(cell_size).map_err(|error| { DelaunayTriangulationConstructionError::from( TriangulationConstructionError::SpatialIndexConstruction { @@ -1157,13 +1284,10 @@ where /// Verifies the hash grid can represent every input coordinate before choosing /// the O(n) duplicate path. -fn hash_grid_usable_for_vertices( - grid: &HashGridIndex, - vertices: &[Vertex], -) -> bool -where - T: CoordinateScalar, -{ +fn hash_grid_usable_for_vertices( + grid: &HashGridIndex, + vertices: &[Vertex], +) -> bool { if !grid.is_usable() { return false; } @@ -1174,14 +1298,11 @@ where /// Keeps exact dedup deterministic when the hash grid cannot safely key the /// input coordinates. -fn dedup_vertices_exact_sorted( - vertices: Vec>, -) -> Vec> -where - T: CoordinateScalar, -{ +fn dedup_vertices_exact_sorted( + vertices: Vec>, +) -> Vec> { let ordered = order_vertices_lexicographic(vertices); - let mut unique: Vec> = Vec::with_capacity(ordered.len()); + let mut unique: Vec> = Vec::with_capacity(ordered.len()); for v in ordered { if let Some(last) = unique.last() @@ -1199,18 +1320,15 @@ where /// Uses the spatial grid for exact duplicate removal while falling back to the /// sorted path if coordinate keying is unavailable. -fn dedup_vertices_exact_hash_grid( - vertices: Vec>, - grid: &mut HashGridIndex, -) -> Vec> -where - T: CoordinateScalar, -{ +fn dedup_vertices_exact_hash_grid( + vertices: Vec>, + grid: &mut HashGridIndex, +) -> Vec> { if !hash_grid_usable_for_vertices(grid, &vertices) { return dedup_vertices_exact_sorted(vertices); } grid.clear(); - let mut unique: Vec> = Vec::with_capacity(vertices.len()); + let mut unique: Vec> = Vec::with_capacity(vertices.len()); for v in vertices { let coords = *v.point().coords(); @@ -1243,13 +1361,10 @@ struct QuantizedKey([i64; D]); /// Quantizes coordinates for epsilon buckets only when finite values can be /// represented without losing the bucket invariant. -fn quantize_coords( - coords: &[T; D], - inv_cell: f64, -) -> Option<[i64; D]> { +fn quantize_coords(coords: &[f64; D], inv_cell: f64) -> Option<[i64; D]> { let mut key = [0_i64; D]; for (axis, coord) in coords.iter().enumerate() { - let c = coord.to_f64()?; + let c = *coord; if !c.is_finite() { return None; } @@ -1293,14 +1408,11 @@ where /// Provides the correctness fallback for epsilon dedup when bucket or grid /// assumptions fail. -fn dedup_vertices_epsilon_n2( - vertices: Vec>, - epsilon: T, -) -> Vec> -where - T: CoordinateScalar, -{ - let mut unique: Vec> = Vec::with_capacity(vertices.len()); +fn dedup_vertices_epsilon_n2( + vertices: Vec>, + epsilon: f64, +) -> Vec> { + let mut unique: Vec> = Vec::with_capacity(vertices.len()); for v in vertices { let mut duplicate = false; for u in &unique { @@ -1319,20 +1431,15 @@ where /// Uses bounded quantized buckets for epsilon dedup in practical dimensions /// while preserving an exact fallback for unsupported cases. -fn dedup_vertices_epsilon_quantized( - vertices: Vec>, - epsilon: T, -) -> Vec> -where - T: CoordinateScalar, -{ +fn dedup_vertices_epsilon_quantized( + vertices: Vec>, + epsilon: f64, +) -> Vec> { if D > BATCH_DEDUP_MAX_DIMENSION { return dedup_vertices_epsilon_n2(vertices, epsilon); } - let Some(eps_f64) = epsilon.to_f64() else { - return dedup_vertices_epsilon_n2(vertices, epsilon); - }; + let eps_f64 = epsilon; if !eps_f64.is_finite() || eps_f64 <= 0.0 { return dedup_vertices_epsilon_n2(vertices, epsilon); } @@ -1344,7 +1451,7 @@ where QuantizedKey, SmallBuffer, > = SecureHashMap::default(); - let mut unique: Vec> = Vec::with_capacity(vertices.len()); + let mut unique: Vec> = Vec::with_capacity(vertices.len()); let mut iter = vertices.into_iter(); while let Some(v) = iter.next() { let coords = v.point().coords(); @@ -1390,19 +1497,16 @@ where /// Prefers the reusable hash grid for epsilon dedup when its coordinate model is /// valid for every input vertex. -fn dedup_vertices_epsilon_hash_grid( - vertices: Vec>, - epsilon: T, - grid: &mut HashGridIndex, -) -> Vec> -where - T: CoordinateScalar, -{ +fn dedup_vertices_epsilon_hash_grid( + vertices: Vec>, + epsilon: f64, + grid: &mut HashGridIndex, +) -> Vec> { if !hash_grid_usable_for_vertices(grid, &vertices) { return dedup_vertices_epsilon_quantized(vertices, epsilon); } grid.clear(); - let mut unique: Vec> = Vec::with_capacity(vertices.len()); + let mut unique: Vec> = Vec::with_capacity(vertices.len()); let epsilon_sq = epsilon * epsilon; for v in vertices { @@ -1412,10 +1516,10 @@ where let used_index = grid.for_each_candidate_vertex_key(&coords, |idx| { candidate_count = candidate_count.saturating_add(1); let existing_coords = unique[idx].point().coords(); - let mut dist_sq = T::zero(); + let mut dist_sq = 0.0; for i in 0..D { let diff = coords[i] - existing_coords[i]; - dist_sq += diff * diff; + dist_sq = diff.mul_add(diff, dist_sq); } if dist_sq < epsilon_sq { duplicate = true; @@ -1438,13 +1542,10 @@ where /// Converts candidate simplex vertices to f64 coordinates for deterministic /// preprocessing heuristics without hiding non-finite inputs. -fn vertices_coords_f64(vertices: &[Vertex]) -> Option> -where - T: CoordinateScalar, -{ +fn vertices_coords_f64(vertices: &[Vertex]) -> Option> { let mut coords_f64: Vec<[f64; D]> = Vec::with_capacity(vertices.len()); for v in vertices { - let coords = safe_coords_to_f64(v.point().coords()).ok()?; + let coords = *v.point().coords(); if coords.iter().any(|coord| !coord.is_finite()) { return None; } @@ -1634,12 +1735,9 @@ fn initial_simplex_candidate_pool_indices(coords_f64: &[[f64; D] /// Chooses a well-spread initial simplex to reduce early degeneracy in /// incremental construction. -fn select_balanced_simplex_indices( - vertices: &[Vertex], -) -> Option> -where - T: CoordinateScalar, -{ +fn select_balanced_simplex_indices( + vertices: &[Vertex], +) -> Option> { if vertices.len() < D + 1 { return None; } @@ -1741,10 +1839,10 @@ fn simplex_volume_for_indices( return None; } - let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(simplex_indices.len()); for &idx in simplex_indices { - points.push(Point::new(coords_f64[idx])); + points.push(Point::from_validated_coords(coords_f64[idx])); } simplex_volume(&points) .ok() @@ -1753,12 +1851,9 @@ fn simplex_volume_for_indices( /// Chooses the largest-volume nondegenerate real simplex from a bounded /// extreme-vertex candidate pool. -fn select_max_volume_simplex_indices( - vertices: &[Vertex], -) -> Option> -where - T: CoordinateScalar, -{ +fn select_max_volume_simplex_indices( + vertices: &[Vertex], +) -> Option> { if vertices.len() < D + 1 { return None; } @@ -1796,12 +1891,11 @@ where /// Places the selected simplex first while preserving every remaining input /// vertex exactly once. -fn reorder_vertices_for_simplex( - vertices: &[Vertex], +fn reorder_vertices_for_simplex( + vertices: &[Vertex], simplex_indices: &[usize], -) -> Option>> +) -> Option>> where - T: Copy, U: Copy, { if simplex_indices.len() != D + 1 { @@ -1850,30 +1944,24 @@ fn hilbert_bits_per_coord() -> Option { /// Returns `None` if any coordinate cannot be represented as `f64`, allowing /// callers to omit diagnostic coordinates instead of hiding conversion failure /// behind `NaN` or infinity. -pub(crate) fn vertex_coords_f64(vertex: &Vertex) -> Option> -where - T: CoordinateScalar, -{ +pub(crate) fn vertex_coords_f64(vertex: &Vertex) -> Option> { vertex .point() .coords() .iter() - .map(|coord| coord.to_f64().filter(|value| value.is_finite())) + .map(|coord| coord.is_finite().then_some(*coord)) .collect() } -/// Sort key for Hilbert ordering: `(Hilbert index, quantized coords, vertex, input index)`. -type HilbertSortKey = (u128, [u32; D], Vertex, usize); +/// Sort key for Hilbert ordering: `(Hilbert index, quantized coords, input index)`. +type HilbertSortKey = (u128, [u32; D], Vertex, usize); /// Orders vertices along a Hilbert curve to improve insertion locality while /// retaining deterministic lexicographic fallbacks. -fn order_vertices_hilbert( - vertices: Vec>, +fn order_vertices_hilbert( + vertices: Vec>, dedup_quantized: bool, -) -> Vec> -where - T: CoordinateScalar, -{ +) -> Vec> { if vertices.is_empty() || D == 0 { return vertices; } @@ -1889,9 +1977,7 @@ where for v in &vertices { for &coord in v.point().coords() { - let Some(c) = coord.to_f64() else { - return order_vertices_lexicographic(vertices); - }; + let c = coord; if !c.is_finite() { return order_vertices_lexicographic(vertices); } @@ -1908,11 +1994,7 @@ where }; } - let (Some(min_t), Some(max_t)) = (NumCast::from(min), NumCast::from(max)) else { - return order_vertices_lexicographic(vertices); - }; - - let Ok(bounds) = CoordinateRange::try_new(min_t, max_t) else { + let Ok(bounds) = CoordinateRange::try_new(min, max) else { return order_vertices_lexicographic(vertices); }; @@ -1934,7 +2016,7 @@ where } // Pair indices with vertices, quantized coords, and input indices. - let mut keyed: Vec> = vertices + let mut keyed: Vec> = vertices .into_iter() .zip(quantized) .zip(indices) @@ -2001,14 +2083,14 @@ impl DelaunayTriangulation, (), (), D> { /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -2016,9 +2098,7 @@ impl DelaunayTriangulation, (), (), D> { /// # Ok(()) /// # } /// ``` - pub fn new( - vertices: &[Vertex], - ) -> Result { + pub fn new(vertices: &[Vertex<(), D>]) -> Result { Self::with_kernel(&AdaptiveKernel::::new(), vertices) } @@ -2039,14 +2119,14 @@ impl DelaunayTriangulation, (), (), D> { /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -2059,7 +2139,7 @@ impl DelaunayTriangulation, (), (), D> { reason = "Public API intentionally returns by-value construction statistics for compatibility" )] pub fn new_with_construction_statistics( - vertices: &[Vertex], + vertices: &[Vertex<(), D>], ) -> Result<(Self, ConstructionStatistics), DelaunayTriangulationConstructionErrorWithStatistics> { let kernel = AdaptiveKernel::::new(); @@ -2089,16 +2169,16 @@ impl DelaunayTriangulation, (), (), D> { /// /// ```rust /// use delaunay::prelude::construction::{ - /// ConstructionOptions, DelaunayTriangulationBuilder, RetryPolicy, vertex, + /// ConstructionOptions, DelaunayTriangulationBuilder, RetryPolicy, /// }; /// use std::num::NonZeroUsize; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let Some(attempts) = NonZeroUsize::new(2) else { @@ -2120,7 +2200,7 @@ impl DelaunayTriangulation, (), (), D> { reason = "Public API intentionally returns by-value construction statistics for compatibility" )] pub fn new_with_options_and_construction_statistics( - vertices: &[Vertex], + vertices: &[Vertex<(), D>], options: ConstructionOptions, ) -> Result<(Self, ConstructionStatistics), DelaunayTriangulationConstructionErrorWithStatistics> { @@ -2143,15 +2223,14 @@ impl DelaunayTriangulation, (), (), D> { /// ```rust /// use delaunay::prelude::construction::{ /// ConstructionOptions, DedupPolicy, DelaunayTriangulationBuilder, InsertionOrderStrategy, - /// vertex, /// }; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let options = ConstructionOptions::default() /// .with_insertion_order(InsertionOrderStrategy::Hilbert) @@ -2165,7 +2244,7 @@ impl DelaunayTriangulation, (), (), D> { /// # } /// ``` pub fn new_with_options( - vertices: &[Vertex], + vertices: &[Vertex<(), D>], options: ConstructionOptions, ) -> Result { let kernel = AdaptiveKernel::::new(); @@ -2193,11 +2272,11 @@ impl DelaunayTriangulation, (), (), D> { /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, TopologyGuarantee, vertex, + /// DelaunayTriangulationBuilder, TopologyGuarantee, /// }; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let dt = DelaunayTriangulationBuilder::new(&vertices) /// .topology_guarantee(TopologyGuarantee::PLManifold) /// .build::<()>()?; @@ -2206,7 +2285,7 @@ impl DelaunayTriangulation, (), (), D> { /// # } /// ``` pub fn new_with_topology_guarantee( - vertices: &[Vertex], + vertices: &[Vertex<(), D>], topology_guarantee: TopologyGuarantee, ) -> Result { let kernel = AdaptiveKernel::::new(); @@ -2252,41 +2331,37 @@ impl DelaunayTriangulation, (), (), D> { /// Creates a fluent builder for default `f64` Delaunay triangulations. /// - /// For non-`f64` coordinates, vertex data, or custom kernels, construct + /// For vertex data or custom kernels, construct /// [`DelaunayTriangulationBuilder::new`] directly. /// /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.number_of_vertices(), 3); /// # Ok(()) /// # } /// ``` #[must_use] - pub fn builder( - vertices: &[Vertex], - ) -> DelaunayTriangulationBuilder<'_, f64, (), D> { + pub fn builder(vertices: &[Vertex<(), D>]) -> DelaunayTriangulationBuilder<'_, (), D> { DelaunayTriangulationBuilder::new(vertices) } } // ============================================================================= -// CONSTRUCTION (Requires Numeric Scalar Bounds) +// CONSTRUCTION (Requires f64 Coordinate Storage) // ============================================================================= // // Batch and incremental constructors, preprocessing, Hilbert ordering, spatial -// hashing, and deduplication — the kernel already guarantees `CoordinateScalar`; -// these paths add `NumCast`. +// hashing, and deduplication operate on f64-backed vertices. impl DelaunayTriangulation where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -2302,11 +2377,11 @@ where )] pub(crate) fn build_with_shuffled_retries( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, attempts: NonZeroUsize, base_seed: Option, - grid_cell_size: Option, + grid_cell_size: Option, batch_repair_policy: DelaunayRepairPolicy, use_global_repair_fallback: bool, ) -> Result { @@ -2488,11 +2563,11 @@ where )] pub(crate) fn build_with_shuffled_retries_with_construction_statistics( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, attempts: NonZeroUsize, base_seed: Option, - grid_cell_size: Option, + grid_cell_size: Option, batch_repair_policy: DelaunayRepairPolicy, use_global_repair_fallback: bool, ) -> Result<(Self, ConstructionStatistics), DelaunayTriangulationConstructionErrorWithStatistics> @@ -2724,9 +2799,9 @@ where /// final validation path as the statistics variant. pub(crate) fn build_with_kernel_inner( kernel: K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, - grid_cell_size: Option, + grid_cell_size: Option, batch_repair_policy: DelaunayRepairPolicy, use_global_repair_fallback: bool, ) -> Result { @@ -2768,9 +2843,9 @@ where )] pub(crate) fn build_with_kernel_inner_with_construction_statistics( kernel: K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, - grid_cell_size: Option, + grid_cell_size: Option, batch_repair_policy: DelaunayRepairPolicy, use_global_repair_fallback: bool, ) -> Result<(Self, ConstructionStatistics), DelaunayTriangulationConstructionErrorWithStatistics> @@ -2826,11 +2901,11 @@ where )] fn build_with_kernel_inner_seeded_with_construction_statistics( kernel: K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, perturbation_seed: u64, run_final_repair: bool, - grid_cell_size: Option, + grid_cell_size: Option, batch_repair_policy: DelaunayRepairPolicy, use_global_repair_fallback: bool, ) -> Result<(Self, ConstructionStatistics), DelaunayTriangulationConstructionErrorWithStatistics> @@ -2971,11 +3046,11 @@ where )] fn build_with_kernel_inner_seeded( kernel: K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, perturbation_seed: u64, run_final_repair: bool, - grid_cell_size: Option, + grid_cell_size: Option, batch_repair_policy: DelaunayRepairPolicy, use_global_repair_fallback: bool, ) -> Result { @@ -3232,15 +3307,15 @@ where )] fn insert_remaining_vertices_seeded( &mut self, - vertices: &[Vertex], + vertices: &[Vertex], perturbation_seed: u64, - grid_cell_size: Option, + grid_cell_size: Option, batch_repair_policy: DelaunayRepairPolicy, construction_stats: Option<&mut ConstructionStatistics>, pending_repair_seeds: &mut Vec, soft_fail_seeds: &mut Vec, ) -> Result<(), DelaunayTriangulationConstructionError> { - let mut grid_index: Option> = match grid_cell_size { + let mut grid_index: Option> = match grid_cell_size { Some(cell_size) => Some(hash_grid_from_validated_cell_size(cell_size)?), None => None, }; @@ -3854,8 +3929,7 @@ where impl DelaunayTriangulation where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -3876,7 +3950,7 @@ where /// ``` #[must_use] pub fn with_empty_kernel(kernel: K) -> Self { - let duplicate_tolerance = default_duplicate_tolerance::(); + let duplicate_tolerance = default_duplicate_tolerance(); Self { tri: Triangulation::new_empty(kernel), @@ -3905,7 +3979,7 @@ where kernel: K, topology_guarantee: TopologyGuarantee, ) -> Self { - let duplicate_tolerance = default_duplicate_tolerance::(); + let duplicate_tolerance = default_duplicate_tolerance(); let mut tri = Triangulation::new_empty(kernel); tri.topology_guarantee = topology_guarantee; @@ -3931,15 +4005,15 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// use delaunay::prelude::geometry::RobustKernel; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let kernel = RobustKernel::::new(); /// let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -3950,7 +4024,7 @@ where /// ``` pub fn with_kernel( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], ) -> Result { Self::with_topology_guarantee(kernel, vertices, TopologyGuarantee::DEFAULT) } @@ -3971,16 +4045,16 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, TopologyGuarantee, vertex, + /// DelaunayTriangulationBuilder, TopologyGuarantee, /// }; /// use delaunay::prelude::geometry::RobustKernel; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let kernel = RobustKernel::::new(); /// let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -3992,7 +4066,7 @@ where /// ``` pub fn with_topology_guarantee( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, ) -> Result { Self::with_topology_guarantee_and_options( @@ -4018,16 +4092,16 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// ConstructionOptions, DelaunayTriangulationBuilder, TopologyGuarantee, vertex, + /// ConstructionOptions, DelaunayTriangulationBuilder, TopologyGuarantee, /// }; /// use delaunay::prelude::geometry::RobustKernel; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let kernel = RobustKernel::::new(); /// let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -4040,7 +4114,7 @@ where /// ``` pub fn with_topology_guarantee_and_options( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, options: ConstructionOptions, ) -> Result { @@ -4060,10 +4134,10 @@ where initial_simplex, )?; let grid_cell_size = preprocessed.grid_cell_size(); - let primary_vertices: &[Vertex] = preprocessed.primary_slice(vertices); + let primary_vertices: &[Vertex] = preprocessed.primary_slice(vertices); let fallback_vertices = preprocessed.fallback_slice(); - let build_with_vertices = |vertices: &[Vertex]| { + let build_with_vertices = |vertices: &[Vertex]| { match retry_policy { RetryPolicy::Disabled => {} RetryPolicy::Shuffled { @@ -4141,16 +4215,16 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// ConstructionOptions, DelaunayTriangulation, TopologyGuarantee, vertex, + /// ConstructionOptions, DelaunayTriangulation, TopologyGuarantee, /// }; /// use delaunay::prelude::geometry::RobustKernel; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionErrorWithStatistics> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let kernel = RobustKernel::::new(); /// let (dt, stats) = @@ -4174,7 +4248,7 @@ where )] pub fn with_options_and_statistics( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], topology_guarantee: TopologyGuarantee, options: ConstructionOptions, ) -> Result<(Self, ConstructionStatistics), DelaunayTriangulationConstructionErrorWithStatistics> @@ -4211,10 +4285,10 @@ where }; let preprocessing_nanos = duration_nanos_saturating(preprocessing_started.elapsed()); let grid_cell_size = preprocessed.grid_cell_size(); - let primary_vertices: &[Vertex] = preprocessed.primary_slice(vertices); + let primary_vertices: &[Vertex] = preprocessed.primary_slice(vertices); let fallback_vertices = preprocessed.fallback_slice(); - let build_with_vertices = |vertices: &[Vertex]| { + let build_with_vertices = |vertices: &[Vertex]| { match retry_policy { RetryPolicy::Disabled => {} RetryPolicy::Shuffled { @@ -4308,55 +4382,30 @@ where /// Applies deduplication, insertion ordering, and initial-simplex selection /// before any topology is created. - #[expect( - clippy::too_many_lines, - reason = "preprocessing keeps dedup, ordering, and initial-simplex selection in one boundary" - )] pub(crate) fn preprocess_vertices_for_construction( - vertices: &[Vertex], + vertices: &[Vertex], dedup_policy: DedupPolicy, insertion_order: InsertionOrderStrategy, initial_simplex: InitialSimplexStrategy, - ) -> PreprocessVerticesResult { - let default_tolerance = default_duplicate_tolerance::(); - - let mut epsilon: Option = None; - if let DedupPolicy::Epsilon { tolerance } = dedup_policy { - if !tolerance.is_finite() || tolerance < 0.0 { - return Err(TriangulationConstructionError::GeometricDegeneracy { - message: format!( - "Invalid DedupPolicy::Epsilon tolerance {tolerance:?} (must be finite and non-negative)" - ), - } - .into()); - } + ) -> PreprocessVerticesResult { + let default_tolerance = default_duplicate_tolerance(); - let Some(epsilon_value) = ::from(tolerance) else { - return Err(TriangulationConstructionError::GeometricDegeneracy { - message: format!( - "Failed to convert DedupPolicy::Epsilon tolerance {tolerance:?} into scalar type" - ), - } - .into()); - }; - epsilon = Some(epsilon_value); - } + let epsilon = if let DedupPolicy::Epsilon { tolerance } = dedup_policy { + Some(tolerance.get()) + } else { + None + }; let grid_cell_size_value = if let (DedupPolicy::Epsilon { .. }, Some(eps)) = (dedup_policy, epsilon) { - if eps > K::Scalar::zero() { - eps - } else { - default_tolerance - } + if eps > 0.0 { eps } else { default_tolerance } } else { default_tolerance }; - let mut grid = - hash_grid_from_validated_cell_size::(grid_cell_size_value)?; + let mut grid = hash_grid_from_validated_cell_size::(grid_cell_size_value)?; // Deduplicate first to reduce work for ordering strategies. - let mut owned_vertices: Option>> = match dedup_policy { + let mut owned_vertices: Option>> = match dedup_policy { DedupPolicy::Off => None, DedupPolicy::Exact => { let vertices = vertices.to_vec(); @@ -4572,7 +4621,8 @@ where | InsertionError::NonManifoldTopology { .. } | InsertionError::HullExtension { .. } | InsertionError::DelaunayValidationFailed { .. } - | InsertionError::DuplicateCoordinates { .. }) => { + | InsertionError::DuplicateCoordinates { .. } + | InsertionError::PerturbedCoordinateInvalid { .. }) => { TriangulationConstructionError::GeometricDegeneracy { message: format!( "Failed to canonicalize orientation after post-construction repair: {error}" @@ -4647,17 +4697,22 @@ where InsertionError::SpatialIndexConstruction { reason } => { TriangulationConstructionError::SpatialIndexConstruction { reason } } + InsertionError::PerturbedCoordinateInvalid { source } => { + TriangulationConstructionError::GeometricDegeneracy { + message: format!("Perturbation retry produced invalid coordinates: {source}"), + } + } } } /// Avoids retry work when construction has no incremental phase to reorder. - pub(crate) const fn should_retry_construction(vertices: &[Vertex]) -> bool { + pub(crate) const fn should_retry_construction(vertices: &[Vertex]) -> bool { D >= 2 && vertices.len() > D + 1 } /// Derives an input-order-independent seed so shuffled retries are /// reproducible for the same vertex set. - pub(crate) fn construction_shuffle_seed(vertices: &[Vertex]) -> u64 { + pub(crate) fn construction_shuffle_seed(vertices: &[Vertex]) -> u64 { let mut vertex_hashes = Vec::with_capacity(vertices.len()); for vertex in vertices { let mut hasher = FastHasher::default(); @@ -4669,7 +4724,7 @@ where } /// Keeps construction retry shuffling deterministic for diagnostics and tests. - pub(crate) fn shuffle_vertices(vertices: &mut [Vertex], seed: u64) { + pub(crate) fn shuffle_vertices(vertices: &mut [Vertex], seed: u64) { let mut rng = StdRng::seed_from_u64(seed); vertices.shuffle(&mut rng); } @@ -4751,10 +4806,10 @@ pub(crate) fn log_bulk_progress_if_due( let chunk_elapsed = state.last_progress.elapsed(); let chunk_processed = sample.bulk_processed.saturating_sub(state.last_processed); - let overall_rate = safe_usize_to_scalar::(sample.bulk_processed) + let overall_rate = safe_usize_to_scalar(sample.bulk_processed) .ok() .map(|processed| processed / elapsed.as_secs_f64().max(1e-9)); - let chunk_rate = safe_usize_to_scalar::(chunk_processed) + let chunk_rate = safe_usize_to_scalar(chunk_processed) .ok() .map(|processed| processed / chunk_elapsed.as_secs_f64().max(1e-9)); @@ -4867,7 +4922,6 @@ mod tests { use crate::core::validation::{ TopologyGuarantee, TriangulationValidationError, ValidationPolicy, }; - use crate::core::vertex::VertexBuilder; use crate::diagnostics::BatchLocalRepairTrigger; use crate::geometry::coordinate_range::{CoordinateRangeError, CoordinateRangeOrdering}; use crate::geometry::kernel::{AdaptiveKernel, FastKernel, RobustKernel}; @@ -4879,7 +4933,6 @@ mod tests { use crate::repair::DelaunayRepairPolicy; use crate::topology::characteristics::euler::TopologyClassification; use crate::validation::DelaunayTriangulationValidationError; - use crate::vertex; use slotmap::KeyData; use std::assert_matches; use std::num::NonZeroUsize; @@ -4957,11 +5010,11 @@ mod tests { #[test] fn test_finalize_bulk_construction_validates_pseudomanifold_topology() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([0.0, 2.0]), - vertex!([2.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulation::new_with_topology_guarantee( &vertices, @@ -5001,10 +5054,10 @@ mod tests { #[test] fn []() { init_tracing(); - let mut vertices: Vec> = vec![ - $(vertex!($simplex_coords)),+ + let mut vertices: Vec> = vec![ + $(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+ ]; - vertices.push(vertex!($interior_point)); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new($interior_point).unwrap()); let expected_vertices = vertices.len(); let dt: DelaunayTriangulation<_, (), (), $dim> = @@ -5022,7 +5075,7 @@ mod tests { assert_eq!(dt.number_of_vertices(), 0); assert_eq!(dt.number_of_simplices(), 0); - let vertices = vec![$(vertex!($simplex_coords)),+]; + let vertices = vec![$(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+]; assert_eq!(vertices.len(), $dim + 1); for (i, vertex) in vertices.iter().take($dim).enumerate() { @@ -5042,14 +5095,14 @@ mod tests { init_tracing(); let mut dt: DelaunayTriangulation<_, (), (), $dim> = DelaunayTriangulation::empty(); - let initial_vertices = vec![$(vertex!($simplex_coords)),+]; + let initial_vertices = vec![$(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+]; for vertex in &initial_vertices { dt.insert(*vertex).unwrap(); } assert_eq!(dt.number_of_simplices(), 1); - dt.insert(vertex!($interior_point)).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new($interior_point).unwrap()).unwrap(); assert_eq!(dt.number_of_vertices(), $dim + 2); assert!(dt.number_of_simplices() > 1); assert!(dt.is_valid().is_ok()); @@ -5058,7 +5111,7 @@ mod tests { #[test] fn []() { init_tracing(); - let vertices = vec![$(vertex!($simplex_coords)),+]; + let vertices = vec![$(crate::core::vertex::Vertex::<(), _>::try_new($simplex_coords).unwrap()),+]; let mut dt_bootstrap: DelaunayTriangulation<_, (), (), $dim> = DelaunayTriangulation::empty(); @@ -5123,9 +5176,9 @@ mod tests { fn test_with_kernel_fast_kernel() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation, (), (), 2> = @@ -5139,9 +5192,9 @@ mod tests { fn test_with_kernel_robust_kernel() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation, (), (), 2> = @@ -5154,7 +5207,10 @@ mod tests { #[test] fn test_with_kernel_insufficient_vertices_2d() { init_tracing(); - let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ]; let result: Result, (), (), 2>, _> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices); @@ -5171,9 +5227,9 @@ mod tests { fn test_with_kernel_insufficient_vertices_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), ]; let result: Result, (), (), 3>, _> = @@ -5191,14 +5247,16 @@ mod tests { fn test_with_kernel_aborts_on_duplicate_uuid_in_insertion_loop() { init_tracing(); let mut vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([0.0, 2.0]), - vertex!([0.25, 0.25]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), ]; let dup_uuid = vertices[0].uuid(); - vertices[3].set_uuid(dup_uuid).unwrap(); + vertices.push( + Vertex::try_new_with_uuid(Point::from_validated_coords([0.25, 0.25]), dup_uuid, None) + .unwrap(), + ); let result: Result, (), (), 2>, _> = DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices); @@ -5217,11 +5275,11 @@ mod tests { fn test_batch_3d_construction_with_extra_vertex_triggers_incremental_repair() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.3, 0.3, 0.3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3, 0.3]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -5233,11 +5291,11 @@ mod tests { fn test_batch_3d_construction_statistics_with_extra_vertex_triggers_incremental_repair() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.3, 0.3, 0.3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3, 0.3]).unwrap(), ]; let (dt, stats) = DelaunayTriangulation::<_, (), (), 3>::new_with_construction_statistics(&vertices) @@ -5251,13 +5309,13 @@ mod tests { fn test_batch_4d_forced_nonconvergent_local_repair_canonicalizes_without_stats() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), - vertex!([0.35, 0.25, 0.15, 0.3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.35, 0.25, 0.15, 0.3]).unwrap(), ]; let _guard = ForceRepairNonconvergentGuard::enable(); @@ -5276,13 +5334,13 @@ mod tests { fn test_batch_4d_forced_nonconvergent_local_repair_canonicalizes_with_stats() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), - vertex!([0.35, 0.25, 0.15, 0.3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.35, 0.25, 0.15, 0.3]).unwrap(), ]; let _guard = ForceRepairNonconvergentGuard::enable(); @@ -5307,13 +5365,13 @@ mod tests { fn test_batch_4d_every_n_repair_cadence_runs_with_pending_seeds() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), - vertex!([0.35, 0.25, 0.15, 0.3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.35, 0.25, 0.15, 0.3]).unwrap(), ]; test_hooks::reset_batch_local_repair_calls(); @@ -5571,7 +5629,8 @@ mod tests { #[test] fn test_vertex_coords_f64_converts_f64_vertex_coords() { init_tracing(); - let vertex: Vertex = vertex!([1.25, -2.5, 3.75]); + let vertex: Vertex<(), 3> = + crate::core::vertex::Vertex::<(), _>::try_new([1.25, -2.5, 3.75]).unwrap(); assert_eq!(vertex_coords_f64(&vertex), Some(vec![1.25, -2.5, 3.75])); } @@ -5579,20 +5638,11 @@ mod tests { #[test] fn test_vertex_coords_f64_rejects_non_finite_coords() { init_tracing(); - let nan_vertex: Vertex = VertexBuilder::default() - .point(Point::new([1.0, f64::NAN, 3.0])) - .build() - .unwrap(); - let infinite_vertex: Vertex = VertexBuilder::default() - .point(Point::new([1.0, f64::INFINITY, 3.0])) - .build() - .unwrap(); - - assert_eq!(vertex_coords_f64(&nan_vertex), None); - assert_eq!(vertex_coords_f64(&infinite_vertex), None); + assert!(Point::<3>::try_new([1.0, f64::NAN, 3.0]).is_err()); + assert!(Point::<3>::try_new([1.0, f64::INFINITY, 3.0]).is_err()); } - fn coord_sequence_2d(vertices: &[Vertex]) -> Vec<[f64; 2]> { + fn coord_sequence_2d(vertices: &[Vertex<(), 2>]) -> Vec<[f64; 2]> { vertices.iter().map(|v| *v.point().coords()).collect() } @@ -5600,9 +5650,9 @@ mod tests { fn order_vertices_input_preserves_order() { init_tracing(); let vertices = vec![ - vertex!([2.0, 0.0]), - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), ]; let expected = coord_sequence_2d(&vertices); @@ -5614,11 +5664,11 @@ mod tests { #[test] fn preprocess_hilbert_with_dedup_off_preserves_duplicate_vertices() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 0.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), ]; let preprocess = TestDelaunay::<2>::preprocess_vertices_for_construction( @@ -5636,10 +5686,10 @@ mod tests { fn dedup_exact_sorted_without_grid() { init_tracing(); let vertices = vec![ - vertex!([1.0, 0.0]), - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let unique = dedup_vertices_exact_sorted(vertices); @@ -5654,21 +5704,21 @@ mod tests { fn dedup_exact_grid_fallback() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), ]; - let mut grid = HashGridIndex::::try_new(1.0e-10).unwrap(); + let mut grid = HashGridIndex::<2, usize>::try_new(1.0e-10).unwrap(); let unique = dedup_vertices_exact_hash_grid(vertices, &mut grid); assert_eq!(coord_sequence_2d(&unique), vec![[0.0, 0.0], [1.0, 0.0]]); let vertices_6d = vec![ - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), ]; - let mut unusable_grid = HashGridIndex::::try_new(1.0e-10).unwrap(); + let mut unusable_grid = HashGridIndex::<6, usize>::try_new(1.0e-10).unwrap(); let fallback_unique = dedup_vertices_exact_hash_grid(vertices_6d, &mut unusable_grid); @@ -5679,29 +5729,27 @@ mod tests { fn epsilon_dedup_quantized_paths() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([0.09, 0.0]), - vertex!([0.25, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.09, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.0]).unwrap(), ]; let unique = dedup_vertices_epsilon_quantized(vertices, 0.1); assert_eq!(coord_sequence_2d(&unique), vec![[0.0, 0.0], [0.25, 0.0]]); - let zero_epsilon_vertices = vec![vertex!([0.0, 0.0]), vertex!([0.0, 0.0])]; + let zero_epsilon_vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ]; let zero_epsilon_unique = dedup_vertices_epsilon_quantized(zero_epsilon_vertices, 0.0); assert_eq!(zero_epsilon_unique.len(), 2); - let nonfinite_vertices = vec![ - vertex!([0.0, 0.0]), - Vertex::new_with_uuid(Point::new([f64::NAN, 0.0]), Uuid::new_v4(), None), - ]; - let nonfinite_unique = dedup_vertices_epsilon_quantized(nonfinite_vertices, 0.1); - assert_eq!(nonfinite_unique.len(), 2); + assert!(Point::<2>::try_new([f64::NAN, 0.0]).is_err()); let vertices_6d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.01, 0.0, 0.0, 0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.01, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), ]; let fallback_unique = dedup_vertices_epsilon_quantized(vertices_6d, 0.1); assert_eq!(fallback_unique.len(), 1); @@ -5711,18 +5759,21 @@ mod tests { fn dedup_epsilon_grid_fallback() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([0.05, 0.0]), - vertex!([0.25, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.05, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.0]).unwrap(), ]; - let mut grid = HashGridIndex::::try_new(0.1).unwrap(); + let mut grid = HashGridIndex::<2, usize>::try_new(0.1).unwrap(); let unique = dedup_vertices_epsilon_hash_grid(vertices, 0.1, &mut grid); assert_eq!(coord_sequence_2d(&unique), vec![[0.0, 0.0], [0.25, 0.0]]); - let fallback_vertices = vec![vertex!([0.0, 0.0]), vertex!([0.05, 0.0])]; - let mut unusable_grid = HashGridIndex::::try_new(0.1).unwrap(); + let fallback_vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.05, 0.0]).unwrap(), + ]; + let mut unusable_grid = HashGridIndex::<2, usize>::try_new(0.1).unwrap(); unusable_grid.remove_vertex(&0, &[f64::NAN, 0.0]); let fallback_unique = @@ -5735,9 +5786,9 @@ mod tests { fn preprocess_falls_back_when_grid_unusable() { init_tracing(); let exact_vertices = vec![ - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), ]; let exact = TestDelaunay::<6>::preprocess_vertices_for_construction( @@ -5752,14 +5803,14 @@ mod tests { assert!(exact.grid_cell_size().is_none()); let epsilon_vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.01, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.5, 0.0, 0.0, 0.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.01, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), ]; let epsilon = TestDelaunay::<6>::preprocess_vertices_for_construction( &epsilon_vertices, - DedupPolicy::Epsilon { tolerance: 0.1 }, + DedupPolicy::try_epsilon(0.1).unwrap(), InsertionOrderStrategy::Input, InitialSimplexStrategy::First, ) @@ -5773,14 +5824,14 @@ mod tests { fn preprocess_zero_epsilon_keeps_base() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), ]; let preprocess = TestDelaunay::<3>::preprocess_vertices_for_construction( &vertices, - DedupPolicy::Epsilon { tolerance: 0.0 }, + DedupPolicy::try_epsilon(0.0).unwrap(), InsertionOrderStrategy::Input, InitialSimplexStrategy::Balanced, ) @@ -5794,7 +5845,7 @@ mod tests { #[test] fn hash_grid_from_validated_cell_size_rejects_invalid_internal_tolerance() { init_tracing(); - let error = hash_grid_from_validated_cell_size::(0.0) + let error = hash_grid_from_validated_cell_size::<3, usize>(0.0) .expect_err("invalid internal hash-grid tolerance should fail"); assert_matches!( @@ -5826,37 +5877,18 @@ mod tests { } #[test] - fn hilbert_fallback_for_nonfinite_coords() { + fn hilbert_rejects_nonfinite_coords_at_point_boundary() { init_tracing(); - let vertices = vec![ - vertex!([1.0, 0.0]), - Vertex::new_with_uuid(Point::new([f64::NAN, 0.0]), Uuid::new_v4(), None), - vertex!([0.0, 0.0]), - ]; - - let ordered = order_vertices_hilbert(vertices, true); - - assert_eq!(ordered.len(), 3); - assert!( - ordered.iter().any(|v| v.point().coords()[0].is_nan()), - "fallback ordering should preserve the non-finite vertex" - ); - assert!( - ordered - .iter() - .any(|v| coords_equal_exact(v.point().coords(), &[0.0, 0.0])) - ); - assert!( - ordered - .iter() - .any(|v| coords_equal_exact(v.point().coords(), &[1.0, 0.0])) - ); + assert!(Point::<2>::try_new([f64::NAN, 0.0]).is_err()); } #[test] fn hilbert_fallback_for_unsupported_dim() { init_tracing(); - let vertices = vec![vertex!([1.0; 17]), vertex!([0.0; 17])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([1.0; 17]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0; 17]).unwrap(), + ]; let ordered = order_vertices_hilbert(vertices, true); @@ -5867,10 +5899,10 @@ mod tests { #[test] fn test_select_balanced_simplex_indices_insufficient_vertices() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), ]; let result = select_balanced_simplex_indices(&vertices); @@ -5878,17 +5910,9 @@ mod tests { } #[test] - fn test_select_balanced_simplex_indices_rejects_non_finite_coords() { + fn test_select_balanced_simplex_indices_non_finite_coords_rejected_at_point_boundary() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - Vertex::new_with_uuid(Point::new([f64::NAN, 0.0, 0.0]), Uuid::new_v4(), None), - ]; - - let result = select_balanced_simplex_indices(&vertices); - assert!(result.is_none()); + assert!(Point::<3>::try_new([f64::NAN, 0.0, 0.0]).is_err()); } macro_rules! max_volume_axis_simplex_test { @@ -5896,7 +5920,7 @@ mod tests { #[test] fn $test_name() { init_tracing(); - let vertices: Vec> = vec![$(vertex!($coords)),+]; + let vertices: Vec> = vec![$(crate::core::vertex::Vertex::<(), _>::try_new($coords).unwrap()),+]; let result = select_max_volume_simplex_indices(&vertices) .expect("max-volume simplex selection failed"); @@ -5981,11 +6005,11 @@ mod tests { #[test] fn test_select_max_volume_simplex_indices_rejects_degenerate_pool() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([3.0, 0.0, 0.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 0.0, 0.0]).unwrap(), ]; let result = select_max_volume_simplex_indices(&vertices); @@ -5995,12 +6019,12 @@ mod tests { #[test] fn test_reorder_vertices_for_simplex_valid_and_invalid() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([2.0, 2.0, 2.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).unwrap(), ]; let indices = [2_usize, 0, 3, 1]; @@ -6029,12 +6053,12 @@ mod tests { #[test] fn test_preprocess_vertices_for_construction_balanced_sets_fallback() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([2.0, 2.0, 2.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).unwrap(), ]; let preprocess = DelaunayTriangulation::, (), (), 3>::preprocess_vertices_for_construction( @@ -6054,14 +6078,14 @@ mod tests { #[test] fn test_preprocess_vertices_for_construction_max_volume_sets_largest_simplex_first() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([10.0, 0.0, 0.0]), - vertex!([0.0, 10.0, 0.0]), - vertex!([0.0, 0.0, 10.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 10.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 10.0]).unwrap(), ]; let preprocess = DelaunayTriangulation::< @@ -6099,85 +6123,68 @@ mod tests { } #[test] - fn test_preprocess_vertices_rejects_invalid_epsilon_tolerance() { + fn dedup_tolerance_rejects_invalid_raw_values() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - ]; - let result = DelaunayTriangulation::, (), (), 3>::preprocess_vertices_for_construction( - &vertices, - DedupPolicy::Epsilon { tolerance: -1.0 }, - InsertionOrderStrategy::Input, - InitialSimplexStrategy::First, + assert_eq!( + DedupTolerance::try_new(-1.0), + Err(DeduplicationError::NegativeEpsilon) ); - - assert_matches!( - result, - Err(DelaunayTriangulationConstructionError::Triangulation( - DelaunayConstructionFailure::GeometricDegeneracy { .. } - )) + assert_eq!( + DedupTolerance::try_new(f64::NAN), + Err(DeduplicationError::NonFiniteEpsilon) + ); + assert_eq!( + DedupTolerance::try_new(f64::INFINITY), + Err(DeduplicationError::NonFiniteEpsilon) ); + + let zero = DedupTolerance::try_new(-0.0).expect("signed zero is non-negative"); + assert_eq!(zero.get().to_bits(), 0.0_f64.to_bits()); } #[test] - fn stats_preprocess_error_defaults() { + fn dedup_policy_try_epsilon_parses_raw_tolerance() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - ]; - let options = ConstructionOptions::default().with_dedup_policy(DedupPolicy::Epsilon { - tolerance: f64::NAN, - }); - let error = - DelaunayTriangulation::, (), (), 3>::with_options_and_statistics( - &AdaptiveKernel::new(), - &vertices, - TopologyGuarantee::PLManifold, - options, - ) - .expect_err("NaN epsilon should fail during preprocessing"); + let policy = DedupPolicy::try_epsilon(1.0e-10).expect("finite non-negative tolerance"); + assert_eq!( + policy, + DedupPolicy::epsilon(DedupTolerance::try_new(1.0e-10).unwrap()) + ); - assert_eq!(error.statistics.inserted, 0); - assert_eq!(error.statistics.total_skipped(), 0); - assert_eq!(error.statistics.total_attempts, 0); - assert!(error.statistics.skip_samples.is_empty()); - assert_matches!( - error.error, - DelaunayTriangulationConstructionError::Triangulation(_) + assert_eq!( + DedupPolicy::try_epsilon(f64::NEG_INFINITY), + Err(DeduplicationError::NonFiniteEpsilon) ); } fn vertices_from_coords_permutation_3d( coords: &[[f64; 3]], permutation: &[usize], - ) -> Vec> { - permutation.iter().map(|&i| vertex!(coords[i])).collect() + ) -> Vec> { + permutation + .iter() + .map(|&i| crate::core::vertex::Vertex::<(), _>::try_new(coords[i]).unwrap()) + .collect() } #[test] fn test_bulk_construction_skips_near_duplicate_coordinates_3d() { init_tracing(); // Test that epsilon-based deduplication removes near-duplicates - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), // Near-duplicate within tolerance 1e-10 - vertex!([0.25 + 5e-11, 0.25, 0.25]), + crate::core::vertex::Vertex::<(), _>::try_new([0.25 + 5e-11, 0.25, 0.25]).unwrap(), ]; let opts = ConstructionOptions::default() - .with_dedup_policy(DedupPolicy::Epsilon { tolerance: 1e-10 }) + .with_dedup_policy(DedupPolicy::try_epsilon(1e-10).unwrap()) .with_retry_policy(RetryPolicy::Disabled); let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new_with_options(&vertices, opts).unwrap(); @@ -6186,7 +6193,7 @@ mod tests { assert!(dt.validate().is_ok()); } - fn coord_sequence_3d(vertices: &[Vertex]) -> Vec<[f64; 3]> { + fn coord_sequence_3d(vertices: &[Vertex<(), 3>]) -> Vec<[f64; 3]> { vertices.iter().map(Into::into).collect() } @@ -6236,35 +6243,35 @@ mod tests { // ========================================================================= /// Build D+1 standard simplex vertices: origin + D unit vectors. - fn simplex_vertices() -> Vec> { + fn simplex_vertices() -> Vec> { let mut verts = Vec::with_capacity(D + 1); - verts.push(vertex!([0.0; D])); + verts.push(crate::core::vertex::Vertex::<(), _>::try_new([0.0; D]).unwrap()); for i in 0..D { let mut coords = [0.0; D]; coords[i] = 1.0; - verts.push(vertex!(coords)); + verts.push(crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap()); } verts } /// Build simplex vertices plus exact duplicates of the first two. - fn simplex_with_duplicates() -> (Vec>, usize) { + fn simplex_with_duplicates() -> (Vec>, usize) { let mut verts = simplex_vertices::(); let distinct = verts.len(); // Duplicate the origin and first unit vector - verts.push(vertex!([0.0; D])); + verts.push(crate::core::vertex::Vertex::<(), _>::try_new([0.0; D]).unwrap()); let mut unit = [0.0; D]; unit[0] = 1.0; - verts.push(vertex!(unit)); + verts.push(crate::core::vertex::Vertex::<(), _>::try_new(unit).unwrap()); (verts, distinct) } /// Build simplex vertices plus an interior point (all distinct). - fn simplex_with_interior() -> Vec> { + fn simplex_with_interior() -> Vec> { let mut verts = simplex_vertices::(); - let dimension = safe_usize_to_scalar::(D).expect("test dimensions fit in f64"); + let dimension = safe_usize_to_scalar(D).expect("test dimensions fit in f64"); let interior = [0.1_f64 / dimension; D]; - verts.push(vertex!(interior)); + verts.push(crate::core::vertex::Vertex::<(), _>::try_new(interior).unwrap()); verts } @@ -6311,10 +6318,10 @@ mod tests { #[test] fn []() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.5; $dim]), - vertex!([0.5; $dim]), - vertex!([0.5; $dim]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.5; $dim]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5; $dim]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5; $dim]).unwrap(), ]; let result = order_vertices_hilbert(vertices, true); assert_eq!( @@ -6339,14 +6346,15 @@ mod tests { #[test] fn test_hilbert_dedup_empty_input() { - let vertices: Vec> = vec![]; + let vertices: Vec> = vec![]; let result = order_vertices_hilbert(vertices, true); assert!(result.is_empty(), "empty input must produce empty output"); } #[test] fn test_hilbert_dedup_single_vertex() { - let vertices: Vec> = vec![vertex!([1.0, 2.0, 3.0])]; + let vertices: Vec> = + vec![crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap()]; let result = order_vertices_hilbert(vertices, true); assert_eq!(result.len(), 1, "single vertex must be preserved"); } @@ -6354,11 +6362,11 @@ mod tests { #[test] fn test_hilbert_dedup_already_unique() { // Distinct vertices — dedup should be a no-op. - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let n = vertices.len(); let result = order_vertices_hilbert(vertices, true); @@ -6368,12 +6376,12 @@ mod tests { #[test] fn test_new_with_options_hilbert_smoke_3d() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), ]; let opts = ConstructionOptions::default() @@ -6390,12 +6398,12 @@ mod tests { #[test] fn test_new_with_options_shuffled_retry_policy_smoke_3d() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), ]; let opts = ConstructionOptions::default() @@ -6415,11 +6423,11 @@ mod tests { #[test] fn test_new_with_options_smoke_3d() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let opts = ConstructionOptions::default().with_retry_policy(RetryPolicy::Disabled); @@ -6434,11 +6442,11 @@ mod tests { #[test] fn test_new_with_construction_statistics_counts_initial_simplex_3d() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let (dt, stats) = @@ -6455,12 +6463,12 @@ mod tests { #[test] fn test_new_with_options_and_construction_statistics_skips_duplicate_3d() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 0.0, 0.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), ]; let duplicate_uuid = vertices[4].uuid(); @@ -6493,10 +6501,10 @@ mod tests { #[test] fn test_new_with_topology_guarantee_sets_pl() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -6568,11 +6576,14 @@ mod tests { let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::empty(); assert_eq!(dt.number_of_vertices(), 0); - dt.insert(vertex!([0.0, 0.0])).unwrap(); - dt.insert(vertex!([1.0, 0.0])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap()) + .unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_simplices(), 0); - dt.insert(vertex!([0.0, 1.0])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_simplices(), 1); } diff --git a/src/delaunay/delaunayize.rs b/src/delaunay/delaunayize.rs index 64b9958b..bebafba7 100644 --- a/src/delaunay/delaunayize.rs +++ b/src/delaunay/delaunayize.rs @@ -31,10 +31,10 @@ //! # } //! # fn main() -> Result<(), ExampleError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -69,7 +69,6 @@ use crate::core::tds::{SimplexKey, Tds}; use crate::core::traits::data_type::DataType; use crate::core::vertex::Vertex; use crate::geometry::kernel::{ExactPredicates, Kernel}; -use crate::geometry::traits::coordinate::CoordinateScalar; use crate::repair::DelaunayRepairHeuristicConfig; use crate::triangulation::DelaunayTriangulation; use thiserror::Error; @@ -197,10 +196,10 @@ impl Default for DelaunayizeConfig { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -212,14 +211,14 @@ impl Default for DelaunayizeConfig { /// ``` #[derive(Debug, Clone)] #[non_exhaustive] -pub struct DelaunayizeOutcome { +pub struct DelaunayizeOutcome { /// Statistics from the PL-manifold topology repair pass. /// /// If topology repair fails but fallback rebuild succeeds, these remain the /// failed/default repair stats for the repair attempt. Use /// [`used_fallback_rebuild`](Self::used_fallback_rebuild) to distinguish /// successful rebuild recovery from direct topology repair success. - pub topology_repair: PlManifoldRepairStats, + pub topology_repair: PlManifoldRepairStats, /// Statistics from the flip-based Delaunay repair pass. pub delaunay_repair: DelaunayRepairStats, /// Whether the fallback vertex-set rebuild was used. @@ -377,16 +376,14 @@ enum SimplexDataMatch { } type SimplexDataByVertexUuids = FastHashMap>; -type FallbackRebuildState = - (Vec>, SimplexDataByVertexUuids); +type FallbackRebuildState = (Vec>, SimplexDataByVertexUuids); /// Captures the fallback rebuild inputs from the current TDS, including typed /// failure if any simplex cannot resolve its vertex UUID identity. -fn snapshot_rebuild_state( - tds: &Tds, -) -> Result, SimplexValidationError> +fn snapshot_rebuild_state( + tds: &Tds, +) -> Result, SimplexValidationError> where - T: CoordinateScalar, U: DataType, V: DataType, { @@ -400,8 +397,8 @@ where /// Hashes simplex payloads by sorted vertex UUIDs so fallback rebuilds can /// recover payloads for simplices whose vertex set survives unchanged. -fn collect_simplex_data( - tds: &Tds, +fn collect_simplex_data( + tds: &Tds, ) -> Result, SimplexValidationError> where U: DataType, @@ -424,9 +421,9 @@ where /// Builds the order-independent simplex identity used to match original and /// rebuilt simplices across fallback reconstruction. -fn simplex_vertex_uuids( - tds: &Tds, - simplex: &Simplex, +fn simplex_vertex_uuids( + tds: &Tds, + simplex: &Simplex, ) -> Result where U: DataType, @@ -446,7 +443,7 @@ fn restore_simplex_data( original_simplex_data: &SimplexDataByVertexUuids, ) -> Result<(), SimplexValidationError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -531,11 +528,11 @@ fn delaunay_rebuild_error( /// simplex payloads whose vertex UUID signatures survive the rebuild unchanged. fn rebuild_preserving_data( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], original_simplex_data: &SimplexDataByVertexUuids, ) -> Result, FallbackRebuildError> where - K: ExactPredicates, + K: ExactPredicates, U: DataType, V: DataType, { @@ -550,7 +547,7 @@ fn run_configured_delaunay_repair( config: DelaunayizeConfig, ) -> Result where - K: ExactPredicates, + K: ExactPredicates, U: DataType, V: DataType, { @@ -620,10 +617,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -639,9 +636,9 @@ where pub fn delaunayize_by_flips( dt: &mut DelaunayTriangulation, config: DelaunayizeConfig, -) -> Result, DelaunayizeError> +) -> Result, DelaunayizeError> where - K: ExactPredicates, + K: ExactPredicates, U: DataType, V: DataType, { @@ -758,9 +755,7 @@ mod tests { use super::*; use crate::geometry::kernel::AdaptiveKernel; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::Coordinate; use crate::tds::VertexKey; - use crate::vertex; use crate::{DelaunayTriangulationBuilder, TriangulationConstructionError}; use slotmap::KeyData; use std::assert_matches; @@ -783,13 +778,13 @@ mod tests { } /// Builds the canonical D-simplex vertex set used by fallback rebuild tests. - fn unit_simplex_vertices() -> Vec> { + fn unit_simplex_vertices() -> Vec> { let mut points = Vec::with_capacity(D + 1); - points.push(Point::new([0.0; D])); + points.push(Point::from_validated_coords([0.0; D])); for axis in 0..D { let mut coords = [0.0; D]; coords[axis] = 1.0; - points.push(Point::new(coords)); + points.push(Point::from_validated_coords(coords)); } Vertex::from_points(&points) } @@ -807,7 +802,7 @@ mod tests { dt.tri .tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(duplicate_vertices.clone(), None).unwrap(), + Simplex::try_new_with_data(duplicate_vertices.clone(), None).unwrap(), ) .unwrap(); } @@ -816,7 +811,7 @@ mod tests { /// Forces topology repair to fail on duplicate simplices, then checks fallback rebuild. fn assert_topology_repair_fallback_rebuilds_duplicate_simplex() where - AdaptiveKernel: ExactPredicates, + AdaptiveKernel: ExactPredicates, { init_tracing(); let vertices = unit_simplex_vertices::(); @@ -927,10 +922,10 @@ mod tests { fn test_already_delaunay_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -945,10 +940,10 @@ mod tests { fn test_already_delaunay_2d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -966,11 +961,11 @@ mod tests { fn test_outcome_populated_on_success() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -987,17 +982,17 @@ mod tests { #[test] fn test_simplex_vertex_uuids_missing_vertex() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), i32, 2> = Tds::empty(); let vertex_keys: Vec<_> = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ] .iter() .map(|vertex| tds.insert_vertex_with_mapping(*vertex).unwrap()) .collect(); let missing = vertex_keys[0]; - let simplex = Simplex::new(vertex_keys, Some(7)).unwrap(); + let simplex = Simplex::try_new_with_data(vertex_keys, Some(7)).unwrap(); tds.remove_isolated_vertex(missing); let err = simplex_vertex_uuids(&tds, &simplex).unwrap_err(); @@ -1218,10 +1213,10 @@ mod tests { fn test_fallback_enabled_on_valid_triangulation() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1238,7 +1233,10 @@ mod tests { #[test] fn delaunay_repair_fallback_rebuilds_after_unsupported_dimension() { init_tracing(); - let vertices = [vertex!([0.0]), vertex!([1.0])]; + let vertices = [ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let mut dt: DelaunayTriangulation<_, (), (), 1> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1265,10 +1263,10 @@ mod tests { fn delaunay_repair_fallback_rebuilds_supported_2d_after_repair_failure() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1313,9 +1311,9 @@ mod tests { fn test_rebuild_restores_simplex_data() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt = DelaunayTriangulationBuilder::new(&vertices) .build::() @@ -1343,18 +1341,18 @@ mod tests { fn test_rebuild_drops_ambiguous_data() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), i32, 2> = Tds::empty(); let vertex_keys: Vec<_> = vertices .iter() .map(|vertex| tds.insert_vertex_with_mapping(*vertex).unwrap()) .collect(); - let duplicate_a = Simplex::new(vertex_keys.clone(), Some(42)).unwrap(); - let duplicate_b = Simplex::new(vertex_keys, Some(42)).unwrap(); + let duplicate_a = Simplex::try_new_with_data(vertex_keys.clone(), Some(42)).unwrap(); + let duplicate_b = Simplex::try_new_with_data(vertex_keys, Some(42)).unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test(duplicate_a) .unwrap(); tds.insert_simplex_bypassing_topology_checks_for_test(duplicate_b) @@ -1384,11 +1382,11 @@ mod tests { fn test_deterministic_repeated_runs() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let config = DelaunayizeConfig::default(); diff --git a/src/delaunay/flips.rs b/src/delaunay/flips.rs index b507c637..58783d4f 100644 --- a/src/delaunay/flips.rs +++ b/src/delaunay/flips.rs @@ -13,7 +13,7 @@ pub use crate::core::algorithms::flips::{ DelaunayRepairStats, DelaunayRepairVerificationContext, FlipContextError, FlipDirection, FlipEdgeAdjacencyError, FlipError, FlipInfo, FlipMutationError, FlipNeighborWiringError, FlipPredicateError, FlipPredicateOperation, FlipTriangleAdjacencyError, - FlipVertexAdjacencyError, RepairQueueOrder, RidgeHandle, TriangleHandle, + FlipVertexAdjacencyError, RepairQueueOrder, RidgeHandle, TriangleHandle, TriangleHandleError, verify_delaunay_for_triangulation, verify_delaunay_via_flip_predicates, }; pub use crate::tds::{EdgeKey, FacetHandle, SimplexKey, VertexKey}; @@ -45,10 +45,10 @@ use crate::triangulation::DelaunayTriangulation; /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices) /// .topology_guarantee(TopologyGuarantee::PLManifold) @@ -58,14 +58,11 @@ use crate::triangulation::DelaunayTriangulation; /// }; /// /// // Split a simplex by inserting a vertex (k=1 move). -/// let _info = dt.flip_k1_insert(simplex_key, vertex!([0.1, 0.1, 0.1]))?; +/// let _info = dt.flip_k1_insert(simplex_key, delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.1, 0.1]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// # Ok(()) /// # } /// ``` pub trait BistellarFlips { - /// Coordinate scalar type used by vertices inserted through k=1 flips. - type Scalar; - /// User data type stored on vertices inserted through k=1 flips. type VertexData; @@ -91,10 +88,10 @@ pub trait BistellarFlips { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices) /// .topology_guarantee(TopologyGuarantee::PLManifold) @@ -104,7 +101,7 @@ pub trait BistellarFlips { /// }; /// /// // Insert a vertex into the simplex - /// let info = dt.flip_k1_insert(simplex_key, vertex!([0.25, 0.25, 0.25]))?; + /// let info = dt.flip_k1_insert(simplex_key, delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// assert!(!info.new_simplices.is_empty()); /// # Ok(()) /// # } @@ -112,7 +109,7 @@ pub trait BistellarFlips { fn flip_k1_insert( &mut self, simplex_key: SimplexKey, - vertex: Vertex, + vertex: Vertex, ) -> Result, FlipError>; /// Apply an inverse k=1 move (vertex collapse). @@ -137,10 +134,10 @@ pub trait BistellarFlips { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices) /// .topology_guarantee(TopologyGuarantee::PLManifold) @@ -148,7 +145,7 @@ pub trait BistellarFlips { /// let Some((simplex_key, _)) = dt.simplices().next() else { /// return Ok(()); /// }; - /// let inserted = dt.flip_k1_insert(simplex_key, vertex!([0.25, 0.25, 0.25]))?; + /// let inserted = dt.flip_k1_insert(simplex_key, delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// let inserted_vertex = inserted.inserted_face_vertices[0]; /// /// // Remove the inserted vertex @@ -172,13 +169,20 @@ pub trait BistellarFlips { /// use delaunay::prelude::construction::DelaunayTriangulationBuilder; /// use delaunay::prelude::flips::*; /// - /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] delaunay::DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Facet(#[from] delaunay::prelude::tds::FacetError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), - /// vertex!([0.5, 0.5, 0.3]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.3]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -191,7 +195,7 @@ pub trait BistellarFlips { /// .map(|mut neighbors| neighbors.any(|n| n.is_some())) /// .unwrap_or(false); /// if has_neighbor { - /// let facet = FacetHandle::new(key, 0); + /// let facet = FacetHandle::try_new(dt.tds(), key, 0)?; /// let _ = dt.flip_k2(facet); // May succeed or fail depending on configuration /// } /// } @@ -215,11 +219,11 @@ pub trait BistellarFlips { /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), - /// vertex!([1.0, 1.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -255,17 +259,16 @@ pub trait BistellarFlips { impl BistellarFlips for Triangulation where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { - type Scalar = K::Scalar; type VertexData = U; fn flip_k1_insert( &mut self, simplex_key: SimplexKey, - vertex: Vertex, + vertex: Vertex, ) -> Result, FlipError> { apply_bistellar_flip_k1(&mut self.tds, simplex_key, vertex) } @@ -311,17 +314,16 @@ where impl BistellarFlips for DelaunayTriangulation where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { - type Scalar = K::Scalar; type VertexData = U; fn flip_k1_insert( &mut self, simplex_key: SimplexKey, - vertex: Vertex, + vertex: Vertex, ) -> Result, FlipError> { let result = self.tri.flip_k1_insert(simplex_key, vertex); if result.is_ok() { @@ -382,16 +384,15 @@ mod tests { use crate::TopologyGuarantee; use crate::core::collections::spatial_hash_grid::HashGridIndex; use crate::geometry::kernel::{AdaptiveKernel, FastKernel}; - use crate::vertex; use slotmap::KeyData; #[test] fn triangulation_flip_k1_insert_and_remove_roundtrip() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new_with_topology_guarantee( @@ -403,7 +404,10 @@ mod tests { let simplex_key = tri.simplices().next().unwrap().0; let inserted = tri - .flip_k1_insert(simplex_key, vertex!([0.25, 0.25, 0.25])) + .flip_k1_insert( + simplex_key, + crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), + ) .unwrap(); let inserted_vertex = inserted.inserted_face_vertices[0]; assert!(!inserted.new_simplices.is_empty()); @@ -416,25 +420,28 @@ mod tests { #[test] fn flip_k1_insert_invalidates_caches() { - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); let simplex_key = dt.simplices().next().unwrap().0; dt.insertion_state.last_inserted_simplex = Some(simplex_key); - let mut spatial_index = HashGridIndex::::try_new(1.0).unwrap(); + let mut spatial_index = HashGridIndex::<3>::try_new(1.0).unwrap(); for (vertex_key, vertex) in dt.vertices() { spatial_index.insert_vertex(vertex_key, vertex.point().coords()); } dt.spatial_index = Some(spatial_index); - dt.flip_k1_insert(simplex_key, vertex!([0.2, 0.2, 0.2])) - .unwrap(); + dt.flip_k1_insert( + simplex_key, + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(), + ) + .unwrap(); assert!(dt.insertion_state.last_inserted_simplex.is_none()); assert!(dt.spatial_index.is_none()); @@ -444,10 +451,10 @@ mod tests { #[test] fn triangulation_flip_k2_rejects_invalid_facet_index() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new_with_topology_guarantee( @@ -455,20 +462,17 @@ mod tests { TopologyGuarantee::PLManifold, ) .unwrap(); - let mut tri = dt.as_triangulation().clone(); + let tri = dt.as_triangulation().clone(); let simplex_key = tri.simplices().next().unwrap().0; - let err = tri - .flip_k2(FacetHandle::new(simplex_key, u8::MAX)) - .unwrap_err(); + let err = FacetHandle::try_new(&tri.tds, simplex_key, u8::MAX).unwrap_err(); assert_matches!( err, - FlipError::InvalidFacetIndex { - simplex_key: key, - facet_index: u8::MAX, - vertex_count: 4, - } if key == simplex_key + crate::prelude::tds::FacetError::InvalidFacetIndex { + index: u8::MAX, + facet_count: 4, + } ); } @@ -481,7 +485,7 @@ mod tests { let c = VertexKey::from(KeyData::from_ffi(3)); let err = dt - .flip_k3_inverse_from_triangle(TriangleHandle::new(a, b, c)) + .flip_k3_inverse_from_triangle(TriangleHandle::try_new(a, b, c).unwrap()) .unwrap_err(); assert_matches!(err, FlipError::UnsupportedDimension { dimension: 3 }); @@ -496,7 +500,7 @@ mod tests { let c = VertexKey::from(KeyData::from_ffi(3)); let err = tri - .flip_k3_inverse_from_triangle(TriangleHandle::new(a, b, c)) + .flip_k3_inverse_from_triangle(TriangleHandle::try_new(a, b, c).unwrap()) .unwrap_err(); assert_matches!(err, FlipError::UnsupportedDimension { dimension: 0 }); diff --git a/src/delaunay/insertion.rs b/src/delaunay/insertion.rs index 4a262aa4..42ddeb8a 100644 --- a/src/delaunay/insertion.rs +++ b/src/delaunay/insertion.rs @@ -35,12 +35,10 @@ use crate::core::validation::{ }; use crate::core::vertex::Vertex; use crate::geometry::kernel::Kernel; -use crate::geometry::traits::coordinate::CoordinateScalar; use crate::repair::{DelaunayRepairOperation, DelaunayRepairPolicy}; use crate::topology::manifold::{ManifoldError, validate_ridge_links_for_simplices}; use crate::triangulation::DelaunayTriangulation; use crate::validation::DelaunayTriangulationValidationError; -use num_traits::NumCast; use std::env; const RIDGE_LINK_REPAIR_VALIDATION_MESSAGE: &str = "Topology invalid after Delaunay repair"; @@ -60,14 +58,12 @@ fn ridge_link_repair_validation_error(err: ManifoldError) -> InsertionError { // ============================================================================= // // Incremental insertion, removal, and post-insertion repair/check helpers. -// These require `NumCast` for spatial-index construction, Triangulation-layer -// insertion, and Triangulation-layer removal. `Kernel` already guarantees -// `CoordinateScalar`. +// These require an f64-backed kernel for spatial-index construction, +// Triangulation-layer insertion, and Triangulation-layer removal. impl DelaunayTriangulation where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -78,8 +74,7 @@ where return Ok(()); } - let duplicate_tolerance: K::Scalar = - ::from(1e-10_f64).unwrap_or_else(K::Scalar::default_tolerance); + let duplicate_tolerance = 1e-10_f64; let mut index = HashGridIndex::try_new(duplicate_tolerance)?; for (vkey, vertex) in self.tri.tds.vertices() { @@ -127,7 +122,7 @@ where /// Incremental insertion from empty triangulation: /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulation, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulation}; /// use delaunay::prelude::insertion::InsertionError; /// /// # fn main() -> Result<(), InsertionError> { @@ -137,19 +132,19 @@ where /// assert_eq!(dt.number_of_simplices(), 0); /// /// // Insert vertices one by one - bootstrap phase (no simplices yet) - /// dt.insert(vertex!([0.0, 0.0, 0.0]))?; - /// dt.insert(vertex!([1.0, 0.0, 0.0]))?; - /// dt.insert(vertex!([0.0, 1.0, 0.0]))?; + /// dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); + /// dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); + /// dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// assert_eq!(dt.number_of_vertices(), 3); /// assert_eq!(dt.number_of_simplices(), 0); // Still no simplices /// /// // 4th vertex triggers initial simplex creation - /// dt.insert(vertex!([0.0, 0.0, 1.0]))?; + /// dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// assert_eq!(dt.number_of_vertices(), 4); /// assert_eq!(dt.number_of_simplices(), 1); // First simplex created! /// /// // Further insertions use cavity-based algorithm - /// dt.insert(vertex!([0.2, 0.2, 0.2]))?; + /// dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// assert_eq!(dt.number_of_vertices(), 5); /// assert!(dt.number_of_simplices() > 1); /// # Ok(()) @@ -159,7 +154,7 @@ where /// Using batch construction (traditional approach): /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -171,23 +166,23 @@ where /// # fn main() -> Result<(), ExampleError> { /// // Create initial triangulation with 5 vertices (4-simplex) /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.number_of_vertices(), 5); /// /// // Insert additional interior vertex - /// dt.insert(vertex!([0.2, 0.2, 0.2, 0.2]))?; + /// dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// assert_eq!(dt.number_of_vertices(), 6); /// assert!(dt.number_of_simplices() > 1); /// # Ok(()) /// # } /// ``` - pub fn insert(&mut self, vertex: Vertex) -> Result { + pub fn insert(&mut self, vertex: Vertex) -> Result { self.ensure_spatial_index_seeded()?; // Fully delegate to Triangulation layer @@ -284,19 +279,27 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulation, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulation}; /// use delaunay::prelude::insertion::{InsertionError, InsertionOutcome}; /// - /// # fn main() -> Result<(), InsertionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Coordinates(#[from] delaunay::prelude::geometry::CoordinateConversionError), + /// # #[error(transparent)] + /// # Insertion(#[from] InsertionError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::empty(); /// - /// let (outcome, stats) = dt - /// .insert_with_statistics(vertex!([0.0, 0.0, 0.0]))?; + /// let vertex = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?; + /// let (outcome, stats) = dt.insert_with_statistics(vertex)?; /// /// assert!(stats.success()); /// std::assert_matches!(outcome, InsertionOutcome::Inserted { .. }); /// - /// let duplicate = dt.insert_with_statistics(vertex!([0.0, 0.0, 0.0])); + /// let duplicate_vertex = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?; + /// let duplicate = dt.insert_with_statistics(duplicate_vertex); /// std::assert_matches!( /// duplicate, /// Err(InsertionError::DuplicateCoordinates { .. }) @@ -306,7 +309,7 @@ where /// ``` pub fn insert_with_statistics( &mut self, - vertex: Vertex, + vertex: Vertex, ) -> Result<(InsertionOutcome, InsertionStatistics), InsertionError> { match self.insert_best_effort_with_statistics(vertex)? { (outcome @ InsertionOutcome::Inserted { .. }, stats) => Ok((outcome, stats)), @@ -332,21 +335,21 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulation, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulation}; /// use delaunay::prelude::insertion::InsertionOutcome; /// /// # fn main() -> Result<(), delaunay::InsertionError> { /// let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::empty(); /// /// let (outcome, stats) = dt - /// .insert_best_effort_with_statistics(vertex!([0.0, 0.0, 0.0]))?; + /// .insert_best_effort_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// /// assert!(stats.success()); /// std::assert_matches!(outcome, InsertionOutcome::Inserted { .. }); /// /// let vertices_before_duplicate = dt.number_of_vertices(); /// let (duplicate_outcome, duplicate_stats) = dt - /// .insert_best_effort_with_statistics(vertex!([0.0, 0.0, 0.0]))?; + /// .insert_best_effort_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// /// std::assert_matches!(duplicate_outcome, InsertionOutcome::Skipped { .. }); /// assert!(duplicate_stats.skipped_duplicate()); @@ -356,7 +359,7 @@ where /// ``` pub fn insert_best_effort_with_statistics( &mut self, - vertex: Vertex, + vertex: Vertex, ) -> Result<(InsertionOutcome, InsertionStatistics), InsertionError> { self.ensure_spatial_index_seeded()?; @@ -471,7 +474,7 @@ where if !self.tri.tds.contains_simplex(ck) { if env::var_os("DELAUNAY_REPAIR_TRACE").is_some() { tracing::debug!( - "[repair] insertion seed hint missing (simplex={ck:?}, vertex={vertex_key:?})" + "[repair] insertion seed hint missing (simplex={ck:?}={vertex_key:?})" ); } return None; @@ -484,7 +487,7 @@ where .is_some_and(|simplex| simplex.contains_vertex(vertex_key)); if !contains_vertex && env::var_os("DELAUNAY_REPAIR_TRACE").is_some() { tracing::debug!( - "[repair] insertion seed hint does not contain vertex (simplex={ck:?}, vertex={vertex_key:?})" + "[repair] insertion seed hint does not contain vertex (simplex={ck:?}={vertex_key:?})" ); } @@ -695,7 +698,7 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -705,12 +708,12 @@ where /// # Invariant(#[from] delaunay::tds::InvariantError), /// # } /// # fn main() -> Result<(), ExampleError> { - /// let interior = vertex!([0.3, 0.3]); + /// let interior = delaunay::prelude::Vertex::<(), _>::try_new([0.3, 0.3]).expect("finite vertex coordinates"); /// let interior_uuid = interior.uuid(); /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// interior, /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -733,15 +736,15 @@ where /// Removals that would leave a non-manifold remnant fail and roll back: /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// use delaunay::prelude::tds::InvariantError; /// use delaunay::prelude::triangulation::TriangulationValidationError; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((vertex_key, _)) = dt.vertices().next() else { @@ -852,7 +855,6 @@ mod tests { use crate::flips::BistellarFlips; use crate::geometry::kernel::{AdaptiveKernel, RobustKernel}; use crate::geometry::util::safe_usize_to_scalar; - use crate::vertex; use slotmap::KeyData; use std::assert_matches; use std::sync::Once; @@ -888,33 +890,32 @@ mod tests { } } - fn simplex_vertices() -> Vec> { + fn simplex_vertices() -> Vec> { let mut vertices = Vec::with_capacity(D + 1); - vertices.push(vertex!([0.0; D])); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new([0.0; D]).unwrap()); for axis in 0..D { let mut coords = [0.0; D]; coords[axis] = 1.0; - vertices.push(vertex!(coords)); + vertices.push(crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap()); } vertices } - fn interior_vertex_for_k1_insert() -> Vertex { - let denominator = safe_usize_to_scalar::(D + 2) + fn interior_vertex_for_k1_insert() -> Vertex<(), D> { + let denominator = safe_usize_to_scalar(D + 2) .expect("D + 2 should convert exactly for rollback test dimensions"); let coord = 1.0 / denominator; - vertex!([coord; D]) + crate::core::vertex::Vertex::<(), _>::try_new([coord; D]).unwrap() } - fn rollback_probe_vertex(point_index: usize) -> Vertex { - let dimension = - safe_usize_to_scalar::(D).expect("test dimensions should convert exactly"); + fn rollback_probe_vertex(point_index: usize) -> Vertex<(), D> { + let dimension = safe_usize_to_scalar(D).expect("test dimensions should convert exactly"); let point_index_scalar = - safe_usize_to_scalar::(point_index).expect("point index should convert exactly"); + safe_usize_to_scalar(point_index).expect("point index should convert exactly"); let mut coords = [0.2 / dimension; D]; let axis = point_index % D; coords[axis] += point_index_scalar.mul_add(0.005, 0.02); - vertex!(coords) + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap() } fn incident_simplex_count( @@ -935,7 +936,7 @@ mod tests { let simplex_count_before = dt.number_of_simplices(); let hint_simplex_before = dt.simplices().next().map(|(key, _)| key); dt.insertion_state.last_inserted_simplex = hint_simplex_before; - let mut spatial_index = HashGridIndex::::try_new(1.0).unwrap(); + let mut spatial_index = HashGridIndex::::try_new(1.0).unwrap(); for (vertex_key, vertex) in dt.vertices() { spatial_index.insert_vertex(vertex_key, vertex.point().coords()); } @@ -944,7 +945,7 @@ mod tests { let spatial_index_before = dt .spatial_index .as_ref() - .map(HashGridIndex::::debug_snapshot); + .map(HashGridIndex::::debug_snapshot); let _guard = ForceRepairNonconvergentGuard::enable(); let result = dt.remove_vertex(vertex_key); @@ -977,7 +978,7 @@ mod tests { assert_eq!( dt.spatial_index .as_ref() - .map(HashGridIndex::::debug_snapshot), + .map(HashGridIndex::::debug_snapshot), spatial_index_before, "remove_vertex rollback should restore spatial_index" ); @@ -1106,11 +1107,11 @@ mod tests { #[test] fn test_remove_vertex_fast_path_inverse_k1() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = @@ -1120,7 +1121,8 @@ mod tests { let original_simplex_count = dt.number_of_simplices(); let simplex_key = dt.simplices().next().unwrap().0; - let inserted_vertex = vertex!([0.2, 0.2, 0.2]); + let inserted_vertex = + crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(); let inserted_uuid = inserted_vertex.uuid(); dt.flip_k1_insert(simplex_key, inserted_vertex).unwrap(); @@ -1146,18 +1148,20 @@ mod tests { #[test] fn remove_vertex_invalidates_locate_hint_and_prunes_spatial_index() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); - let vertex_key = dt.insert(vertex!([0.25, 0.25])).unwrap(); + let vertex_key = dt + .insert(crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25]).unwrap()) + .unwrap(); let hint_simplex = dt.simplices().next().map(|(key, _)| key); dt.insertion_state.last_inserted_simplex = hint_simplex; - let mut spatial_index = HashGridIndex::::try_new(1.0).unwrap(); + let mut spatial_index = HashGridIndex::<2>::try_new(1.0).unwrap(); for (vertex_key, vertex) in dt.vertices() { spatial_index.insert_vertex(vertex_key, vertex.point().coords()); } @@ -1189,9 +1193,9 @@ mod tests { fn test_insert_single_interior_point_2d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -1200,7 +1204,9 @@ mod tests { assert_eq!(dt.number_of_vertices(), 3); assert_eq!(dt.number_of_simplices(), 1); - let v_key = dt.insert(vertex!([0.3, 0.3])).unwrap(); + let v_key = dt + .insert(crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap()) + .unwrap(); // Verify insertion succeeded assert_eq!(dt.number_of_vertices(), 4); @@ -1214,22 +1220,25 @@ mod tests { fn test_insert_multiple_sequential_points_2d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); // Insert 3 interior points sequentially - dt.insert(vertex!([0.3, 0.3])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 4); - dt.insert(vertex!([0.5, 0.2])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.2]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 5); - dt.insert(vertex!([0.2, 0.5])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.5]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 6); // All vertices should be present @@ -1240,23 +1249,26 @@ mod tests { fn test_insert_multiple_sequential_points_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); // Insert 3 interior points sequentially (well inside the tetrahedron) - dt.insert(vertex!([0.1, 0.1, 0.1])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.1, 0.1]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 5); - dt.insert(vertex!([0.15, 0.15, 0.1])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.15, 0.15, 0.1]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 6); - dt.insert(vertex!([0.1, 0.15, 0.15])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.15, 0.15]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 7); assert!(dt.number_of_simplices() > 1); @@ -1266,9 +1278,9 @@ mod tests { fn test_insert_updates_last_inserted_simplex() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -1279,7 +1291,8 @@ mod tests { assert!(dt.insertion_state.last_inserted_simplex.is_none()); // After insertion, should have a cached simplex - dt.insert(vertex!([0.3, 0.3])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap()) + .unwrap(); assert!(dt.insertion_state.last_inserted_simplex.is_some()); } @@ -1293,12 +1306,16 @@ mod tests { assert_eq!(dt.number_of_vertices(), 0); // Bootstrap with robust predicates - dt.insert(vertex!([0.0, 0.0, 0.0])).unwrap(); - dt.insert(vertex!([1.0, 0.0, 0.0])).unwrap(); - dt.insert(vertex!([0.0, 1.0, 0.0])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap()) + .unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap()) + .unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_simplices(), 0); // Still bootstrapping - dt.insert(vertex!([0.0, 0.0, 1.0])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_simplices(), 1); // Initial simplex created assert!(dt.is_valid().is_ok()); @@ -1309,16 +1326,16 @@ mod tests { #[test] fn test_maybe_repair_after_insertion_robust_fallback_on_forced_nonconvergent() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); let _guard = ForceRepairNonconvergentGuard::enable(); - let result = dt.insert(vertex!([0.5, 0.5])); + let result = dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap()); let inserted_key = result .as_ref() .copied() @@ -1342,50 +1359,88 @@ mod tests { assert!(dt.validate().is_ok()); } - fn wedge_two_spheres_share_vertex_tds_2d() -> (Tds, SimplexKey, SimplexKey) { + fn wedge_two_spheres_share_vertex_tds_2d() -> (Tds<(), (), 2>, SimplexKey, SimplexKey) { // Two closed 2D spheres (boundaries of tetrahedra) sharing one vertex are // pseudomanifold but not PL-manifold: the shared vertex has a disconnected link. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let incident = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) .unwrap(); let nonincident = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([10.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0]).unwrap(), + ) .unwrap(); let v5 = tds - .insert_vertex_with_mapping(vertex!([11.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 10.0]).unwrap(), + ) .unwrap(); let v6 = tds - .insert_vertex_with_mapping(vertex!([10.0, 11.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 11.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v4, v5], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v4, v5], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v4, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v4, v6], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v5, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v5, v6], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v4, v5, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v4, v5, v6], None).unwrap(), + ) .unwrap(); (tds, incident, nonincident) @@ -1394,11 +1449,11 @@ mod tests { #[test] fn test_collect_local_repair_seed_simplices_merges_adjacent_extra_and_ignores_stale() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); diff --git a/src/delaunay/locality.rs b/src/delaunay/locality.rs index 37c11f34..0bca49b7 100644 --- a/src/delaunay/locality.rs +++ b/src/delaunay/locality.rs @@ -26,8 +26,8 @@ pub struct LocalConflictSeedSimplices { /// Adds live, deduplicated candidate simplices to a pending local repair frontier. /// /// Returns the number of simplices newly appended to `pending_seed_simplices`. -pub fn accumulate_live_simplex_seeds( - tds: &Tds, +pub fn accumulate_live_simplex_seeds( + tds: &Tds, candidate_seed_simplices: &[SimplexKey], pending_seed_simplices: &mut Vec, pending_seen: &mut FastHashSet, @@ -49,8 +49,8 @@ where /// Adds live, deduplicated candidate simplices to a compact repair seed buffer. /// /// Returns the number of simplices newly appended to `seed_simplices`. -pub fn append_live_unique_simplex_seeds( - tds: &Tds, +pub fn append_live_unique_simplex_seeds( + tds: &Tds, candidate_seed_simplices: &[SimplexKey], seed_simplices: &mut SimplexKeyBuffer, ) -> usize @@ -76,8 +76,8 @@ where } /// Retains only live, deduplicated simplices in a pending local repair frontier. -pub fn retain_live_simplex_seeds( - tds: &Tds, +pub fn retain_live_simplex_seeds( + tds: &Tds, seed_simplices: &mut Vec, seen: &mut FastHashSet, ) where @@ -135,13 +135,13 @@ pub fn replace_simplices_and_record_removed( /// entire triangulation. If no circumsphere conflict is found, the terminal simplex /// itself is still a useful local seed. pub fn collect_local_exterior_conflict_seed_simplices( - tds: &Tds, + tds: &Tds, kernel: &K, - point: &Point, + point: &Point, terminal_simplex: SimplexKey, ) -> Result where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -173,17 +173,15 @@ mod tests { use crate::core::triangulation::Triangulation; use crate::geometry::kernel::FastKernel; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::Coordinate; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use slotmap::KeyData; fn simplex_triangulation_3d() -> Triangulation, (), (), 3> { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices).unwrap(); @@ -193,11 +191,11 @@ mod tests { #[test] fn accumulate_live_simplex_seeds_dedupes_and_ignores_stale() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -247,11 +245,11 @@ mod tests { #[test] fn append_live_unique_simplex_seeds_dedupes_and_ignores_stale() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -286,11 +284,11 @@ mod tests { #[test] fn retain_live_simplex_seeds_filters_stale_and_dedupes() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -374,7 +372,7 @@ mod tests { let result = collect_local_exterior_conflict_seed_simplices( &tri.tds, &FastKernel::new(), - &Point::new([2.0, 2.0, 2.0]), + &Point::from_validated_coords([2.0, 2.0, 2.0]), terminal_simplex, ) .unwrap(); @@ -393,7 +391,7 @@ mod tests { let result = collect_local_exterior_conflict_seed_simplices( &tri.tds, &FastKernel::new(), - &Point::new([0.5, 0.5, 0.5]), + &Point::from_validated_coords([0.5, 0.5, 0.5]), terminal_simplex, ) .unwrap(); diff --git a/src/delaunay/query.rs b/src/delaunay/query.rs index e00d18d1..57e31db7 100644 --- a/src/delaunay/query.rs +++ b/src/delaunay/query.rs @@ -26,15 +26,15 @@ use crate::triangulation::DelaunayTriangulation; // QUERY, ACCESSORS, AND CONFIGURATION (Minimal Bounds) // ============================================================================= // -// Methods that only need `K: Kernel` — no scalar arithmetic. Downstream -// generic code (e.g. `delaunayize_by_flips`) does not need to carry -// `CoordinateScalar + NumCast` bounds when calling these methods. +// Methods that only need f64-backed kernels. Downstream generic code +// (e.g. `delaunayize_by_flips`) does not need extra coordinate-conversion +// bounds when calling these methods. // // Follows the precedent of the existing PURE STRUCT ASSEMBLY impl block. impl DelaunayTriangulation where - K: Kernel, + K: Kernel, { // ------------------------------------------------------------------------- // QUERY / ACCESSORS @@ -45,16 +45,16 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), - /// vertex!([0.2, 0.2, 0.2, 0.2]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -72,15 +72,15 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -101,15 +101,15 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -130,7 +130,7 @@ where /// /// # Returns /// - /// An iterator over `(SimplexKey, &Simplex)` pairs. + /// An iterator over `(SimplexKey, &Simplex)` pairs. /// /// # Examples /// @@ -140,10 +140,10 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -154,7 +154,7 @@ where /// # Ok(()) /// # } /// ``` - pub fn simplices(&self) -> impl Iterator)> { + pub fn simplices(&self) -> impl Iterator)> { self.tri.tds.simplices() } @@ -166,7 +166,7 @@ where /// /// # Returns /// - /// An iterator over `(VertexKey, &Vertex)` pairs. + /// An iterator over `(VertexKey, &Vertex)` pairs. /// /// # Examples /// @@ -176,10 +176,10 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -190,11 +190,11 @@ where /// # Ok(()) /// # } /// ``` - pub fn vertices(&self) -> impl Iterator)> { + pub fn vertices(&self) -> impl Iterator)> { self.tri.vertices() } - /// Sets the auxiliary data on a vertex, returning the previous value. + /// Sets the auxiliary data on a returning the previous value. /// /// This is a safe O(1) operation that modifies only the user-data field. /// It does not affect geometry, topology, or Delaunay invariants, so @@ -209,14 +209,14 @@ where /// /// ``` /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex, /// }; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { - /// let vertices: [Vertex; 3] = [ - /// vertex!([0.0, 0.0], 10i32), - /// vertex!([1.0, 0.0], 20), - /// vertex!([0.0, 1.0], 30), + /// let vertices: [Vertex; 3] = [ + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 10i32).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 20).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 30).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((key, _)) = dt.vertices().next() else { @@ -252,13 +252,13 @@ where /// # Examples /// /// ``` - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = [ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::()?; /// let Some((key, _)) = dt.simplices().next() else { @@ -289,16 +289,23 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Query(#[from] delaunay::query::QueryError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -308,7 +315,7 @@ where /// # } /// ``` #[must_use] - pub const fn tds(&self) -> &Tds { + pub const fn tds(&self) -> &Tds { &self.tri.tds } @@ -322,7 +329,7 @@ where /// Modifying the Tds directly can break Delaunay invariants. Use this only /// when you know what you're doing (typically in tests or specialized algorithms). #[cfg(test)] - pub(crate) fn tds_mut(&mut self) -> &mut Tds { + pub(crate) fn tds_mut(&mut self) -> &mut Tds { // Direct mutable access can invalidate performance caches. self.invalidate_repair_caches(); &mut self.tri.tds @@ -341,7 +348,7 @@ where /// /// Repair passes may rewrite topology and invalidate locate hints, so this /// deliberately clears the ephemeral caches before handing out the borrow. - pub(crate) fn tds_mut_for_repair(&mut self) -> &mut Tds { + pub(crate) fn tds_mut_for_repair(&mut self) -> &mut Tds { self.invalidate_repair_caches(); &mut self.tri.tds } @@ -356,7 +363,6 @@ where /// ```rust /// use delaunay::prelude::construction::DelaunayTriangulationBuilder; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::prelude::construction::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -367,10 +373,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices: Vec<_> = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -396,7 +402,7 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -407,10 +413,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -425,9 +431,7 @@ where /// Returns [`QueryError::TriangulationCorrupted`] if facet-map construction /// detects invalid simplex or facet bookkeeping. The variant preserves the /// lower-level [`TdsError`](crate::tds::TdsError) for diagnostics. - pub fn boundary_facets( - &self, - ) -> Result, QueryError> { + pub fn boundary_facets(&self) -> Result, QueryError> { self.tri.boundary_facets() } @@ -441,15 +445,15 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::validation::ValidationPolicy; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -562,12 +566,12 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::repair::DelaunayRepairPolicy; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// assert_eq!(dt.delaunay_repair_policy(), DelaunayRepairPolicy::EveryInsertion); @@ -589,12 +593,12 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::repair::DelaunayRepairPolicy; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// dt.set_delaunay_repair_policy(DelaunayRepairPolicy::Never); @@ -613,12 +617,12 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::repair::DelaunayCheckPolicy; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// /// assert_eq!(dt.delaunay_check_policy(), DelaunayCheckPolicy::EndOnly); @@ -640,13 +644,13 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::repair::DelaunayCheckPolicy; /// use std::num::NonZeroUsize; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some(every_two) = NonZeroUsize::new(2) else { /// return Ok(()); @@ -669,7 +673,7 @@ where impl DelaunayTriangulation where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -683,11 +687,11 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, TopologyGuarantee, vertex, + /// DelaunayTriangulationBuilder, TopologyGuarantee, /// }; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.topology_guarantee(), TopologyGuarantee::PLManifold); /// # Ok(()) @@ -705,11 +709,11 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, GlobalTopology, vertex, + /// DelaunayTriangulationBuilder, GlobalTopology, /// }; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert!(dt.global_topology().is_euclidean()); /// # Ok(()) @@ -727,11 +731,11 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, TopologyKind, vertex, + /// DelaunayTriangulationBuilder, TopologyKind, /// }; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// assert_eq!(dt.topology_kind(), TopologyKind::Euclidean); /// # Ok(()) @@ -749,11 +753,11 @@ where /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, GlobalTopology, vertex, + /// DelaunayTriangulationBuilder, GlobalTopology, /// }; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { - /// let vertices = vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])]; + /// let vertices = vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// dt.set_global_topology(GlobalTopology::Euclidean); /// assert!(dt.global_topology().is_euclidean()); @@ -797,28 +801,40 @@ where /// /// An iterator yielding `FacetView` objects for all facets. /// + /// # Errors + /// + /// Returns [`QueryError::TriangulationCorrupted`] if the facet iterator cannot + /// represent facet indices for this dimension. + /// /// # Examples /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// - /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { + /// # #[derive(Debug, thiserror::Error)] + /// # enum ExampleError { + /// # #[error(transparent)] + /// # Construction(#[from] DelaunayTriangulationConstructionError), + /// # #[error(transparent)] + /// # Query(#[from] delaunay::query::QueryError), + /// # } + /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// - /// let facet_count = dt.facets().count(); + /// let facet_count = dt.facets()?.count(); /// assert_eq!(facet_count, 4); // Tetrahedron has 4 facets /// # Ok(()) /// # } /// ``` - pub fn facets(&self) -> AllFacetsIter<'_, K::Scalar, U, V, D> { + pub fn facets(&self) -> Result, QueryError> { self.tri.facets() } @@ -848,10 +864,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -880,10 +896,10 @@ where /// // A single 3D tetrahedron has 6 unique edges. /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -918,10 +934,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -954,10 +970,10 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -997,10 +1013,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1038,10 +1054,10 @@ where /// // A single tetrahedron has no simplex neighbors. /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1080,12 +1096,12 @@ where /// # fn main() -> Result<(), ExampleError> { /// let vertices: Vec<_> = vec![ /// // Shared triangle - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([2.0, 0.0, 0.0]), - /// vertex!([1.0, 2.0, 0.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).expect("finite vertex coordinates"), /// // Two apices - /// vertex!([1.0, 0.7, 1.5]), - /// vertex!([1.0, 0.7, -1.5]), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, 1.5]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, -1.5]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1122,9 +1138,9 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -1158,9 +1174,9 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -1177,7 +1193,7 @@ where /// # } /// ``` #[must_use] - pub fn vertex_coords(&self, v: VertexKey) -> Option<&[K::Scalar]> { + pub fn vertex_coords(&self, v: VertexKey) -> Option<&[f64]> { self.as_triangulation().vertex_coords(v) } } @@ -1187,7 +1203,6 @@ mod tests { use crate::core::operations::DelaunayInsertionState; use crate::core::tds::TdsError; use crate::geometry::kernel::{AdaptiveKernel, FastKernel}; - use crate::vertex; use std::{collections::HashSet, num::NonZeroUsize, sync::Once}; struct Payload; @@ -1207,10 +1222,10 @@ mod tests { #[test] fn test_delaunay_constructors_default_to_pl_manifold_mode() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_new: DelaunayTriangulation<_, (), (), 2> = @@ -1263,10 +1278,10 @@ mod tests { #[test] fn test_boundary_facets_propagates_core_query_error() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1309,9 +1324,9 @@ mod tests { assert_eq!(dt_empty.validation_policy(), ValidationPolicy::ExplicitOnly); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; // new() -> with_kernel() -> explicit validation_policy initialization @@ -1343,9 +1358,9 @@ mod tests { fn test_validation_policy_setter_and_getter_roundtrip() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -1394,10 +1409,10 @@ mod tests { fn test_number_of_vertices_minimal_simplex() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -1410,10 +1425,10 @@ mod tests { fn test_number_of_simplices_minimal_simplex() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -1427,9 +1442,9 @@ mod tests { fn test_number_of_simplices_after_insertion() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -1438,7 +1453,8 @@ mod tests { assert_eq!(dt.number_of_simplices(), 1); // Insert interior point - should create 3 triangles - dt.insert(vertex!([0.3, 0.3])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_simplices(), 3); } @@ -1446,30 +1462,30 @@ mod tests { fn test_dim_returns_correct_dimension() { init_tracing(); let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_2d: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_2d).unwrap(); assert_eq!(dt_2d.dim(), 2); let vertices_3d = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt_3d: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices_3d).unwrap(); assert_eq!(dt_3d.dim(), 3); let vertices_4d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt_4d: DelaunayTriangulation<_, (), (), 4> = DelaunayTriangulation::new(&vertices_4d).unwrap(); @@ -1481,9 +1497,9 @@ mod tests { init_tracing(); // 2D: exactly 3 vertices (minimum for 2D simplex) let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_2d: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_2d).unwrap(); @@ -1492,10 +1508,10 @@ mod tests { // 3D: exactly 4 vertices (minimum for 3D simplex) let vertices_3d = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt_3d: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices_3d).unwrap(); @@ -1507,9 +1523,9 @@ mod tests { fn test_tds_accessor_provides_readonly_access() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1528,10 +1544,10 @@ mod tests { fn test_internal_tds_access() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1552,9 +1568,9 @@ mod tests { fn test_tds_accessor_reflects_insertions() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1563,7 +1579,8 @@ mod tests { assert_eq!(dt.tds().number_of_vertices(), 3); // Insert a new vertex - dt.insert(vertex!([0.3, 0.3])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap()) + .unwrap(); // After insertion, TDS accessor reflects the change assert_eq!(dt.tds().number_of_vertices(), 4); @@ -1574,11 +1591,11 @@ mod tests { fn test_tds_accessors_maintain_validation_invariants() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 4> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1587,7 +1604,8 @@ mod tests { assert!(dt.tds().is_valid().is_ok()); // Insert additional vertex - dt.insert(vertex!([0.2, 0.2, 0.2, 0.2])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap()) + .unwrap(); // TDS should still be valid after mutation assert!(dt.tds().is_valid().is_ok()); @@ -1599,10 +1617,10 @@ mod tests { init_tracing(); // Single tetrahedron: 4 vertices, 1 simplex, 6 unique edges. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = diff --git a/src/delaunay/repair.rs b/src/delaunay/repair.rs index b4100159..a95837a1 100644 --- a/src/delaunay/repair.rs +++ b/src/delaunay/repair.rs @@ -5,7 +5,7 @@ //! paths may run local flip repair, while [`DelaunayCheckPolicy`] controls //! global Level 4 validation cadence without mutating topology. //! -//! Import these APIs through [`delaunay::prelude::repair`](crate::prelude::repair) +//! Import these APIs through [`crate::prelude::repair`](crate::prelude::repair) //! for downstream examples, tests, and applications. #![forbid(unsafe_code)] @@ -25,7 +25,6 @@ use crate::core::validation::TopologyGuarantee; use crate::core::vertex::Vertex; use crate::geometry::kernel::{ExactPredicates, Kernel, RobustKernel}; use crate::triangulation::DelaunayTriangulation; -use num_traits::NumCast; use rand::SeedableRng; use rand::seq::SliceRandom; use std::{ @@ -331,7 +330,7 @@ impl DelaunayCheckPolicy { impl DelaunayTriangulation where - K: Kernel, + K: Kernel, { /// Runs flip-based Delaunay repair over the full triangulation. /// @@ -348,7 +347,7 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// use delaunay::prelude::repair::DelaunayRepairStats; /// /// # #[derive(Debug, thiserror::Error)] @@ -360,10 +359,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -374,7 +373,7 @@ where /// ``` pub fn repair_delaunay_with_flips(&mut self) -> Result where - K: ExactPredicates, + K: ExactPredicates, U: DataType, V: DataType, { @@ -388,7 +387,7 @@ where max_flips: Option, ) -> Result where - K: ExactPredicates, + K: ExactPredicates, U: DataType, V: DataType, { @@ -456,7 +455,7 @@ where V: DataType, { let topology = self.tri.topology_guarantee(); - let kernel = RobustKernel::::new(); + let kernel = RobustKernel::::new(); self.invalidate_locate_hint_cache(); let (tds, kernel) = (&mut self.tri.tds, &kernel); repair_delaunay_with_flips_k2_k3_run(tds, kernel, seed_simplices, topology, max_flips) @@ -529,17 +528,15 @@ where } // ============================================================================= -// ADVANCED REPAIR & HEURISTIC REBUILD (Requires Numeric Scalar Bounds) +// ADVANCED REPAIR & HEURISTIC REBUILD // ============================================================================= // // `repair_delaunay_with_flips_advanced` can fall back to `rebuild_with_heuristic`, -// which constructs a new triangulation and therefore adds `NumCast` on top of -// the scalar requirements guaranteed by `Kernel`. +// which constructs a new triangulation from the stored f64 vertex coordinates. impl DelaunayTriangulation where - K: Kernel, - K::Scalar: NumCast, + K: Kernel, U: DataType, V: DataType, { @@ -563,7 +560,7 @@ where /// # Examples /// /// ```rust - /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// use delaunay::prelude::repair::DelaunayRepairHeuristicConfig; /// /// # #[derive(Debug, thiserror::Error)] @@ -575,10 +572,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -594,7 +591,7 @@ where config: DelaunayRepairHeuristicConfig, ) -> Result where - K: ExactPredicates, + K: ExactPredicates, { if Self::force_heuristic_rebuild_enabled() { let base_seed = self.heuristic_rebuild_base_seed(); @@ -669,7 +666,7 @@ where max_flips_override: Option, ) -> Result<(Self, DelaunayRepairStats, DelaunayRepairHeuristicSeeds), DelaunayRepairError> where - K: ExactPredicates, + K: ExactPredicates, { let base_vertices = self.collect_vertices_for_rebuild(); @@ -847,7 +844,7 @@ where /// Preserves vertex UUIDs and data so heuristic rebuilds remain an internal /// repair strategy, not a user-visible remapping. - fn collect_vertices_for_rebuild(&self) -> Vec> { + fn collect_vertices_for_rebuild(&self) -> Vec> { self.tri .tds .vertices() @@ -883,7 +880,6 @@ mod tests { use crate::geometry::kernel::{AdaptiveKernel, RobustKernel}; use crate::topology::traits::topological_space::{GlobalTopology, ToroidalConstructionMode}; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use std::{num::NonZeroUsize, sync::Once}; fn init_tracing() { @@ -932,17 +928,37 @@ mod tests { } } - fn non_delaunay_quad_tds() -> Tds { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([4.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([4.0, 2.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 2.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + fn non_delaunay_quad_tds() -> Tds<(), (), 2> { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 2.0]).unwrap(), + ) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), + ) .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) + .unwrap(); tds.construction_state = TriangulationConstructionState::Constructed; tds.assign_neighbors().unwrap(); tds.assign_incident_simplices().unwrap(); @@ -956,7 +972,10 @@ mod tests { #[test] fn test_should_run_delaunay_repair_for_skips_for_dimension_lt_2() { init_tracing(); - let vertices: Vec> = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let dt: DelaunayTriangulation<_, (), (), 1> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -984,10 +1003,10 @@ mod tests { #[test] fn test_should_run_delaunay_repair_for_skips_when_policy_never() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1000,10 +1019,10 @@ mod tests { #[test] fn test_should_run_delaunay_repair_for_respects_every_n_schedule() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1019,10 +1038,10 @@ mod tests { #[test] fn test_non_insertion_mutation_repair_gate_ignores_insertion_cadence() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1038,17 +1057,17 @@ mod tests { #[test] fn test_vertex_key_valid_after_explicit_heuristic_rebuild() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); // Insert a vertex normally (no heuristic rebuild during insert). - let inserted = vertex!([0.25, 0.25]); + let inserted = crate::core::vertex::Vertex::<(), _>::try_new([0.25, 0.25]).unwrap(); let inserted_uuid = inserted.uuid(); let (outcome, _stats) = dt.insert_with_statistics(inserted).unwrap(); @@ -1085,11 +1104,11 @@ mod tests { #[test] fn test_heuristic_rebuild_preserves_global_topology() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1114,11 +1133,11 @@ mod tests { #[test] fn test_repair_delaunay_with_flips_allows_pl_manifold() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -1139,11 +1158,11 @@ mod tests { #[test] fn test_repair_delaunay_with_flips_advanced_robust_fallback_succeeds() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1170,11 +1189,11 @@ mod tests { #[test] fn test_repair_advanced_max_flips_zero_on_valid_triangulation_succeeds() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1198,11 +1217,11 @@ mod tests { #[test] fn test_repair_advanced_max_flips_zero_forced_nonconvergent_hits_robust_fallback() { init_tracing(); - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1295,7 +1314,10 @@ mod tests { #[test] fn test_repair_delaunay_with_flips_returns_flip_error_for_1d() { init_tracing(); - let vertices: Vec> = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let mut dt: DelaunayTriangulation, (), (), 1> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1320,7 +1342,10 @@ mod tests { #[test] fn test_repair_delaunay_with_flips_advanced_passes_through_non_retryable_error() { init_tracing(); - let vertices: Vec> = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let mut dt: DelaunayTriangulation, (), (), 1> = DelaunayTriangulation::new(&vertices).unwrap(); diff --git a/src/delaunay/serialization.rs b/src/delaunay/serialization.rs index 7c842d41..3f9e15c3 100644 --- a/src/delaunay/serialization.rs +++ b/src/delaunay/serialization.rs @@ -44,7 +44,7 @@ where /// ```rust /// # use delaunay::prelude::geometry::*; /// # use delaunay::prelude::tds::Tds; -/// # use delaunay::prelude::construction::{DelaunayTriangulation, DelaunayTriangulationBuilder, vertex}; +/// # use delaunay::prelude::construction::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { /// # #[error(transparent)] @@ -56,15 +56,15 @@ where /// # } /// # fn example() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let json = serde_json::to_string(&dt)?; /// -/// let tds: Tds = serde_json::from_str(&json)?; +/// let tds: Tds<(), (), 3> = serde_json::from_str(&json)?; /// let dt_adaptive = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new())?; /// # let _ = dt_adaptive; /// # Ok(()) @@ -72,7 +72,7 @@ where /// ``` impl<'de, const D: usize> Deserialize<'de> for DelaunayTriangulation, (), (), D> where - Tds: Deserialize<'de>, + Tds<(), (), D>: Deserialize<'de>, { fn deserialize(deserializer: De) -> Result where @@ -89,7 +89,6 @@ mod tests { use crate::core::simplex::Simplex; use crate::core::tds::TriangulationConstructionState; use crate::geometry::kernel::AdaptiveKernel; - use crate::vertex; use std::sync::Once; fn init_tracing() { @@ -104,17 +103,37 @@ mod tests { }); } - fn non_delaunay_quad_tds() -> Tds { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([4.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([4.0, 2.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 2.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + fn non_delaunay_quad_tds() -> Tds<(), (), 2> { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + ) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 2.0]).unwrap(), + ) .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), + ) + .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) + .unwrap(); tds.construction_state = TriangulationConstructionState::Constructed; tds.assign_neighbors().unwrap(); tds.assign_incident_simplices().unwrap(); @@ -141,10 +160,10 @@ mod tests { fn serde_roundtrip_uses_custom_deserialize_impl() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -152,7 +171,7 @@ mod tests { let json = serde_json::to_string(&dt).unwrap(); - let tds: Tds = serde_json::from_str(&json).unwrap(); + let tds: Tds<(), (), 3> = serde_json::from_str(&json).unwrap(); let roundtrip_adaptive = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new()).unwrap(); diff --git a/src/delaunay/triangulation.rs b/src/delaunay/triangulation.rs index 095d22ac..089f4242 100644 --- a/src/delaunay/triangulation.rs +++ b/src/delaunay/triangulation.rs @@ -8,7 +8,6 @@ use crate::core::collections::spatial_hash_grid::HashGridIndex; use crate::core::operations::DelaunayInsertionState; use crate::core::triangulation::Triangulation; -use crate::geometry::kernel::Kernel; /// Delaunay triangulation with incremental insertion support. /// @@ -53,15 +52,15 @@ use crate::geometry::kernel::Kernel; /// /// ```rust /// use delaunay::prelude::construction::{ -/// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, +/// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -70,7 +69,7 @@ use crate::geometry::kernel::Kernel; /// # } /// ``` #[derive(Clone, Debug)] -pub struct DelaunayTriangulation, U, V, const D: usize> { +pub struct DelaunayTriangulation { /// The underlying generic triangulation. pub(crate) tri: Triangulation, /// Ephemeral insertion/repair state (hint caching + repair scheduling). @@ -82,5 +81,5 @@ pub struct DelaunayTriangulation, U, V, const D: usize> { /// Query paths validate returned vertex keys against the live TDS, so the /// cache can survive transactional rollbacks even if they leave behind stale /// keys from an insertion that did not commit. - pub(crate) spatial_index: Option>, + pub(crate) spatial_index: Option>, } diff --git a/src/delaunay/validation.rs b/src/delaunay/validation.rs index 077e34e4..4b766786 100644 --- a/src/delaunay/validation.rs +++ b/src/delaunay/validation.rs @@ -46,15 +46,15 @@ use thiserror::Error; /// # Examples /// /// ```rust -/// use delaunay::prelude::construction::{DelaunayTriangulationBuilder, vertex}; +/// use delaunay::prelude::construction::{DelaunayTriangulationBuilder}; /// use delaunay::prelude::validation::DelaunayTriangulationValidationError; /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -189,7 +189,7 @@ impl ValidationCadence { } else { // Logically unreachable because `Some(0)` is matched above. // Keep this branch so the function remains const without - // introducing unsafe code for `NonZeroUsize::new_unchecked`. + // introducing unsafe code for `NonZeroUsize::from_validated_coords`. Self::Never } } @@ -228,7 +228,7 @@ impl ValidationCadence { impl DelaunayTriangulation where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -261,11 +261,11 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices_4d = [ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; /// @@ -303,10 +303,10 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -339,11 +339,11 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices_4d = [ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; /// @@ -383,10 +383,10 @@ where /// /// # fn main() -> Result<(), delaunay::DelaunayTriangulationConstructionError> { /// let vertices = [ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -480,7 +480,7 @@ where /// ```rust /// use delaunay::prelude::geometry::FastKernel; /// use delaunay::prelude::tds::Tds; - /// use delaunay::prelude::construction::{DelaunayTriangulation, DelaunayTriangulationBuilder, vertex}; + /// use delaunay::prelude::construction::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -493,11 +493,11 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -505,7 +505,7 @@ where /// let json = serde_json::to_string(dt.tds())?; /// /// // Deserialize Tds and reconstruct DelaunayTriangulation - /// let tds: Tds = serde_json::from_str(&json)?; + /// let tds: Tds<(), (), 4> = serde_json::from_str(&json)?; /// let reconstructed = DelaunayTriangulation::try_from_tds(tds, FastKernel::new())?; /// assert_eq!(reconstructed.number_of_vertices(), 5); /// # Ok(()) @@ -517,7 +517,7 @@ where /// Returns [`DelaunayTriangulationValidationError`] if the TDS violates /// structural, topological, or Delaunay invariants. pub fn try_from_tds( - tds: Tds, + tds: Tds, kernel: K, ) -> Result { Self::try_from_tds_with_topology_context( @@ -538,7 +538,7 @@ where /// ```rust /// use delaunay::prelude::geometry::FastKernel; /// use delaunay::prelude::construction::{ - /// DelaunayTriangulation, DelaunayTriangulationBuilder, TopologyGuarantee, vertex, + /// DelaunayTriangulation, DelaunayTriangulationBuilder, TopologyGuarantee, /// }; /// /// # #[derive(Debug, thiserror::Error)] @@ -550,9 +550,9 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -575,7 +575,7 @@ where /// Returns [`DelaunayTriangulationValidationError`] if the TDS violates /// structural, topological, or Delaunay invariants. pub fn try_from_tds_with_topology_guarantee( - tds: Tds, + tds: Tds, kernel: K, topology_guarantee: TopologyGuarantee, ) -> Result { @@ -599,7 +599,6 @@ where /// use delaunay::prelude::geometry::FastKernel; /// use delaunay::prelude::construction::{ /// DelaunayTriangulation, DelaunayTriangulationBuilder, GlobalTopology, TopologyGuarantee, - /// vertex, /// }; /// /// # #[derive(Debug, thiserror::Error)] @@ -611,9 +610,9 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -639,7 +638,7 @@ where /// structural, topological, or Delaunay invariants under the supplied /// topology context. pub fn try_from_tds_with_topology_context( - tds: Tds, + tds: Tds, kernel: K, topology_guarantee: TopologyGuarantee, global_topology: GlobalTopology, @@ -678,7 +677,7 @@ where /// [`OnSuspicion`](crate::ValidationPolicy::OnSuspicion). #[must_use] pub(crate) const fn from_tds_with_topology_guarantee( - tds: Tds, + tds: Tds, kernel: K, topology_guarantee: TopologyGuarantee, ) -> Self { @@ -704,7 +703,6 @@ mod tests { use crate::core::simplex::Simplex; use crate::core::tds::{SimplexKey, TriangulationConstructionState, VertexKey}; use crate::geometry::kernel::AdaptiveKernel; - use crate::vertex; use std::{error::Error, sync::Once}; use uuid::Uuid; @@ -720,17 +718,37 @@ mod tests { }); } - fn non_delaunay_quad_tds() -> Tds { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([4.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([4.0, 2.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 2.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + fn non_delaunay_quad_tds() -> Tds<(), (), 2> { + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + ) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([4.0, 2.0]).unwrap(), + ) .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), + ) + .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) + .unwrap(); tds.construction_state = TriangulationConstructionState::Constructed; tds.assign_neighbors().unwrap(); tds.assign_incident_simplices().unwrap(); @@ -865,10 +883,10 @@ mod tests { fn test_validation_report_ok_for_valid_triangulation() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -880,10 +898,10 @@ mod tests { fn test_validation_report_returns_mapping_failures_only() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = @@ -915,10 +933,10 @@ mod tests { fn test_validation_report_includes_vertex_incidence_violation() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = @@ -945,10 +963,10 @@ mod tests { fn test_dt_validate_maps_tds_error_to_tds_variant() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -971,10 +989,10 @@ mod tests { fn test_dt_validate_maps_topology_error_to_triangulation_variant() { init_tracing(); let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -982,7 +1000,9 @@ mod tests { // Add an isolated vertex so Level 3 (topology) fails. let _ = dt .tds_mut() - .insert_vertex_with_mapping(vertex!([0.5, 0.5, 0.5])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), + ) .unwrap(); match dt.validate() { diff --git a/src/geometry/algorithms/convex_hull.rs b/src/geometry/algorithms/convex_hull.rs index 6d580e31..a17ecae7 100644 --- a/src/geometry/algorithms/convex_hull.rs +++ b/src/geometry/algorithms/convex_hull.rs @@ -23,20 +23,22 @@ //! # Hull(#[from] delaunay::prelude::query::ConvexHullConstructionError), //! # #[error(transparent)] //! # Insertion(#[from] delaunay::prelude::InsertionError), +//! # #[error(transparent)] +//! # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), //! # #[error("expected tetrahedron hull to have a first facet")] //! # MissingFacet, //! # } //! # fn main() -> Result<(), ExampleError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let dt: DelaunayTriangulation<_, (), (), 3> = //! DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! let hull = ConvexHull::from_triangulation(dt.as_triangulation())?; -//! let outside = Point::new([2.0, 2.0, 2.0]); +//! let outside = Point::try_from([2.0, 2.0, 2.0])?; //! assert!(hull.is_point_outside(&outside, dt.as_triangulation())?); //! # Ok(()) //! # } @@ -58,14 +60,10 @@ use crate::core::vertex::Vertex; use crate::geometry::kernel::Kernel; use crate::geometry::point::Point; use crate::geometry::predicates::simplex_orientation; -use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateScalar, -}; +use crate::geometry::traits::coordinate::{CoordinateConversionError, DEFAULT_TOLERANCE_F64}; use crate::geometry::util::{safe_usize_to_scalar, squared_norm}; use arc_swap::ArcSwapOption; -use num_traits::Zero; use std::marker::PhantomData; -use std::ops::{DivAssign, Sub}; use std::sync::{ Arc, OnceLock, atomic::{AtomicU64, Ordering}, @@ -299,7 +297,6 @@ pub enum ConvexHullConstructionError { /// ```rust /// # use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// # use delaunay::prelude::query::ConvexHull; -/// # use delaunay::vertex; /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { /// # #[error(transparent)] @@ -313,16 +310,16 @@ pub enum ConvexHullConstructionError { /// # } /// # fn main() -> Result<(), ExampleError> { /// # let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulationBuilder::new(&vec![ -/// # vertex!([0.0, 0.0, 0.0]), -/// # vertex!([1.0, 0.0, 0.0]), -/// # vertex!([0.0, 1.0, 0.0]), -/// # vertex!([0.0, 0.0, 1.0]), +/// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// # delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// # delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// # ]).build::<()>()?; /// let hull = ConvexHull::from_triangulation(dt.as_triangulation())?; /// assert!(hull.is_valid_for_triangulation(dt.as_triangulation())); // Valid initially /// /// // Hull extension is now implemented - inserting outside points works! -/// dt.insert(vertex!([2.0, 2.0, 2.0]))?; +/// dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); /// # Ok(()) /// # } /// ``` @@ -340,7 +337,6 @@ pub enum ConvexHullConstructionError { /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; -/// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -355,10 +351,10 @@ pub enum ConvexHullConstructionError { /// # } /// # fn main() -> Result<(), ExampleError> { /// let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulationBuilder::new(&vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]).build::<()>()?; /// /// // Create initial hull (note: immutable binding) @@ -368,7 +364,7 @@ pub enum ConvexHullConstructionError { /// /// // Hull extension is now implemented - inserting outside points works! /// // Note: The hull becomes invalid after modification and needs to be recreated -/// let new_vertex = vertex!([2.0, 2.0, 2.0]); +/// let new_vertex = delaunay::prelude::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).expect("finite vertex coordinates"); /// dt.insert(new_vertex)?; // Now works with hull extension! /// assert!(!hull.is_valid_for_triangulation(dt.as_triangulation())); // Hull is stale /// # Ok(()) @@ -377,7 +373,6 @@ pub enum ConvexHullConstructionError { /// /// # Type Parameters /// -/// * `T` - The coordinate scalar type (`f64` in this crate) /// * `U` - The vertex data type /// * `V` - The simplex data type /// * `D` - The dimension of the triangulation @@ -415,7 +410,7 @@ pub enum ConvexHullConstructionError { /// *Computational Geometry* 5, no. 2 (1995): 115-116. DOI: [10.1016/0925-7721(95)00013-Y](https://doi.org/10.1016/0925-7721(95)00013-Y) #[must_use] #[derive(Debug)] -pub struct ConvexHull { +pub struct ConvexHull { /// The boundary facets that form the convex hull /// Stored as `FacetHandle` tuples (`SimplexKey`, `facet_index`) to enable reconstruction of `FacetView` /// @@ -428,7 +423,6 @@ pub struct ConvexHull { hull_facets: Vec, /// Cache for the facet-to-simplices mapping to avoid rebuilding it for each facet check /// Uses `ArcSwapOption` for lock-free atomic updates when cache needs invalidation - /// This avoids Some/None wrapping boilerplate compared to `ArcSwap>` facet_to_simplices_cache: ArcSwapOption, /// Immutable triangulation generation at hull creation time. /// Set once in `from_triangulation()` and never modified. Used to detect stale hulls. @@ -445,11 +439,11 @@ pub struct ConvexHull { /// Uses `Arc` for consistent tracking across cloned `ConvexHull` instances. cached_generation: Arc, /// Phantom data to mark unused type parameters - _phantom: PhantomData<(K, U, V)>, + _phantom: PhantomData<(U, V)>, } // Minimal impl block for simple accessor methods that don't require arithmetic operations -impl ConvexHull { +impl ConvexHull { /// Returns the number of hull facets /// /// # Examples @@ -457,7 +451,6 @@ impl ConvexHull { /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -473,10 +466,10 @@ impl ConvexHull { /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -507,7 +500,6 @@ impl ConvexHull { /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -517,16 +509,18 @@ impl ConvexHull { /// # Hull(#[from] delaunay::prelude::query::ConvexHullConstructionError), /// # #[error(transparent)] /// # Insertion(#[from] delaunay::prelude::InsertionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error("expected tetrahedron hull to have a first facet")] /// # MissingFacet, /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -552,7 +546,6 @@ impl ConvexHull { /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -570,10 +563,10 @@ impl ConvexHull { /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -588,7 +581,7 @@ impl ConvexHull { /// // Note: facets() returns FacetHandle structs - need to create FacetView to access vertices /// use delaunay::prelude::tds::FacetView; /// for facet_handle in hull.facets() { - /// if let Ok(facet_view) = FacetView::new(&dt.tds(), facet_handle.simplex_key(), facet_handle.facet_index()) { + /// if let Ok(facet_view) = FacetView::try_new(&dt.tds(), facet_handle.simplex_key(), facet_handle.facet_index()) { /// assert_eq!(facet_view.vertices()?.count(), 3); // 3D facets have 3 vertices /// } /// } @@ -606,7 +599,6 @@ impl ConvexHull { /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -621,15 +613,15 @@ impl ConvexHull { /// # } /// # fn main() -> Result<(), ExampleError> { /// // Empty hull - /// let empty_hull: ConvexHull, (), (), 3> = ConvexHull::default(); + /// let empty_hull: ConvexHull<(), (), 3> = ConvexHull::default(); /// assert!(empty_hull.is_empty()); /// /// // Non-empty hull /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -653,7 +645,6 @@ impl ConvexHull { /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -669,9 +660,9 @@ impl ConvexHull { /// # fn main() -> Result<(), ExampleError> { /// // Create different dimensional hulls /// let vertices_2d: Vec<_> = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt_2d: DelaunayTriangulation<_, (), (), 2> = /// DelaunayTriangulationBuilder::new(&vertices_2d).build::<()>()?; @@ -680,10 +671,10 @@ impl ConvexHull { /// assert_eq!(hull_2d.dimension(), 2); /// /// let vertices_3d: Vec<_> = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt_3d: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices_3d).build::<()>()?; @@ -699,10 +690,7 @@ impl ConvexHull { } } -impl ConvexHull -where - K: Kernel, -{ +impl ConvexHull { /// Checks if this convex hull is valid for the given triangulation /// /// Returns `true` if the hull's creation generation matches the triangulation generation, @@ -725,7 +713,6 @@ where /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -740,10 +727,10 @@ where /// # } /// # fn main() -> Result<(), ExampleError> { /// let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulationBuilder::new(&vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]).build::<()>()?; /// /// // Create hull and verify it's valid @@ -757,7 +744,10 @@ where /// # } /// ``` #[must_use] - pub fn is_valid_for_triangulation(&self, tri: &Triangulation) -> bool { + pub fn is_valid_for_triangulation(&self, tri: &Triangulation) -> bool + where + K: Kernel, + { // Use creation_generation (immutable) for validity check, not cached_generation (mutable) // Empty hull (with creation_generation unset) is always valid - it has no facets to be stale let Some(&creation_generation) = self.creation_generation.get() else { @@ -776,7 +766,10 @@ where .map_or_else(uuid::Uuid::nil, |identity| *identity.as_ref()) } - fn tds_identity_uuid(tri: &Triangulation) -> uuid::Uuid { + fn tds_identity_uuid(tri: &Triangulation) -> uuid::Uuid + where + K: Kernel, + { *tri.tds.identity().as_ref() } @@ -784,7 +777,10 @@ where /// /// Centralizes the error construction pattern to avoid duplication. #[inline] - fn stale_hull_error(&self, tri: &Triangulation) -> ConvexHullValidationError { + fn stale_hull_error(&self, tri: &Triangulation) -> ConvexHullValidationError + where + K: Kernel, + { ConvexHullValidationError::StaleHull { hull_generation: self.creation_generation.get().copied().unwrap_or(0), tds_generation: tri.tds.generation(), @@ -792,10 +788,13 @@ where } #[inline] - fn identity_mismatch_error( + fn identity_mismatch_error( &self, tri: &Triangulation, - ) -> ConvexHullValidationError { + ) -> ConvexHullValidationError + where + K: Kernel, + { ConvexHullValidationError::IdentityMismatch { hull_identity: self.hull_identity_uuid(), tds_identity: Self::tds_identity_uuid(tri), @@ -806,10 +805,13 @@ where /// /// Centralizes the error construction pattern to avoid duplication. #[inline] - fn stale_hull_construction_error( + fn stale_hull_construction_error( &self, tri: &Triangulation, - ) -> ConvexHullConstructionError { + ) -> ConvexHullConstructionError + where + K: Kernel, + { ConvexHullConstructionError::StaleHull { hull_generation: self.creation_generation.get().copied().unwrap_or(0), tds_generation: tri.tds.generation(), @@ -817,20 +819,26 @@ where } #[inline] - fn identity_mismatch_construction_error( + fn identity_mismatch_construction_error( &self, tri: &Triangulation, - ) -> ConvexHullConstructionError { + ) -> ConvexHullConstructionError + where + K: Kernel, + { ConvexHullConstructionError::IdentityMismatch { hull_identity: self.hull_identity_uuid(), tds_identity: Self::tds_identity_uuid(tri), } } - fn ensure_current_for_construction( + fn ensure_current_for_construction( &self, tri: &Triangulation, - ) -> Result<(), ConvexHullConstructionError> { + ) -> Result<(), ConvexHullConstructionError> + where + K: Kernel, + { if self.is_empty() && self.creation_generation.get().is_none() { return Ok(()); } @@ -847,10 +855,13 @@ where Ok(()) } - fn ensure_current_for_validation( + fn ensure_current_for_validation( &self, tri: &Triangulation, - ) -> Result<(), ConvexHullValidationError> { + ) -> Result<(), ConvexHullValidationError> + where + K: Kernel, + { if self.is_empty() && self.creation_generation.get().is_none() { return Ok(()); } @@ -868,7 +879,7 @@ where } } -impl ConvexHull { +impl ConvexHull { /// Invalidates the internal facet-to-simplices cache and resets the cached generation counter /// /// This method forces the cache to be rebuilt on the next visibility test. @@ -897,7 +908,6 @@ impl ConvexHull { /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -907,16 +917,18 @@ impl ConvexHull { /// # Hull(#[from] delaunay::prelude::query::ConvexHullConstructionError), /// # #[error(transparent)] /// # Insertion(#[from] delaunay::prelude::InsertionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error("expected tetrahedron hull to have a first facet")] /// # MissingFacet, /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -943,10 +955,8 @@ impl ConvexHull { } // Full impl block for construction and geometric operations -impl ConvexHull +impl ConvexHull where - K: Kernel, - K::Scalar: Sub + DivAssign + Copy, U: DataType, V: DataType, { @@ -974,7 +984,6 @@ where /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -990,24 +999,24 @@ where /// # fn main() -> Result<(), ExampleError> { /// // 3D example /// let vertices_3d: Vec<_> = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt_3d: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices_3d).build::<()>()?; - /// let hull_3d: ConvexHull<_, (), (), 3> = + /// let hull_3d: ConvexHull<(), (), 3> = /// ConvexHull::from_triangulation(dt_3d.as_triangulation())?; /// assert_eq!(hull_3d.number_of_facets(), 4); // Tetrahedron has 4 faces /// /// // 4D example /// let vertices_4d: Vec<_> = vec![ - /// vertex!([0.0, 0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt_4d: DelaunayTriangulation<_, (), (), 4> = /// DelaunayTriangulationBuilder::new(&vertices_4d).build::<()>()?; @@ -1017,9 +1026,12 @@ where /// # Ok(()) /// # } /// ``` - pub fn from_triangulation( + pub fn from_triangulation( tri: &Triangulation, - ) -> Result { + ) -> Result + where + K: Kernel, + { let tds = &tri.tds; // Validate input triangulation @@ -1043,7 +1055,9 @@ where // Collect facet handles (SimplexKey, facet_index) for storage // These can be used to reconstruct FacetViews when needed let hull_facets: Vec<_> = hull_facets_iter - .map(|facet_view| FacetHandle::new(facet_view.simplex_key(), facet_view.facet_index())) + .map(|facet_view| { + FacetHandle::from_validated(facet_view.simplex_key(), facet_view.facet_index()) + }) .collect(); // Additional validation: ensure we have at least one boundary facet @@ -1085,7 +1099,7 @@ where /// 2. Find the "inside" vertex of the adjacent simplex (vertex not in the facet) /// 3. Create two simplices: facet + `inside_vertex` and facet + `test_point` /// 4. Compare orientations - different orientations mean opposite sides - /// 5. If test point is on opposite side from inside vertex, facet is visible + /// 5. If test point is on opposite side from inside facet is visible /// /// # Arguments /// @@ -1123,7 +1137,6 @@ where /// use delaunay::prelude::query::ConvexHull; /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1133,16 +1146,18 @@ where /// # Hull(#[from] delaunay::prelude::query::ConvexHullConstructionError), /// # #[error(transparent)] /// # Insertion(#[from] delaunay::prelude::InsertionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error("expected tetrahedron hull to have a first facet")] /// # MissingFacet, /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1155,8 +1170,8 @@ where /// }; /// /// // Test visibility from different points - /// let inside_point = Point::new([0.2, 0.2, 0.2]); // Inside the tetrahedron - /// let outside_point = Point::new([2.0, 2.0, 2.0]); // Outside the tetrahedron + /// let inside_point = Point::try_from([0.2, 0.2, 0.2])?; // Inside the tetrahedron + /// let outside_point = Point::try_from([2.0, 2.0, 2.0])?; // Outside the tetrahedron /// /// // Inside point should not see the facet (facet not visible) /// let inside_visible = hull.is_facet_visible_from_point(facet, &inside_point, dt.as_triangulation())?; @@ -1170,12 +1185,15 @@ where /// # Ok(()) /// # } /// ``` - pub fn is_facet_visible_from_point( + pub fn is_facet_visible_from_point( &self, facet_handle: &FacetHandle, - point: &Point, + point: &Point, tri: &Triangulation, - ) -> Result { + ) -> Result + where + K: Kernel, + { let tds = &tri.tds; // Staleness guard: fail fast before any cache work @@ -1218,13 +1236,16 @@ where clippy::too_many_lines, reason = "visibility check keeps predicate setup, cache lookup, and diagnostics together" )] - fn is_facet_visible_from_point_with_cache( + fn is_facet_visible_from_point_with_cache( &self, facet_handle: &FacetHandle, - point: &Point, + point: &Point, tri: &Triangulation, facet_to_simplices: &FacetToSimplicesMap, - ) -> Result { + ) -> Result + where + K: Kernel, + { let tds = &tri.tds; // Two-generation design: creation_generation (immutable) vs cached_generation (mutable) @@ -1348,7 +1369,7 @@ where // Materialize facet vertices only when needed for orientation computation // This happens after cache lookup and inside vertex identification // Use SmallBuffer to avoid heap allocation for typical dimensions (up to 7D on stack) - let facet_vertices: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let facet_vertices: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = facet_vertex_keys .iter() .map(|&k| { @@ -1363,7 +1384,7 @@ where // Create test simplices to compare orientations // Build facet_points from the vertices (fetched only once, when needed) // Use SmallBuffer to keep typical dimensions on-stack and avoid heap allocation - let mut facet_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut facet_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(D); for v in &facet_vertices { facet_points.push(*v.point()); @@ -1438,18 +1459,17 @@ where /// 4. Add a small relative epsilon (1e-12 scale) to avoid false positives from numeric noise /// 5. Return true if the distance exceeds the adjusted threshold (likely outside/visible) fn fallback_visibility_test( - facet_vertices: &[Vertex], - point: &Point, + facet_vertices: &[Vertex], + point: &Point, ) -> Result { // Use SmallBuffer for stack allocation (D vertices fit on stack for D ≤ 7) - let vertex_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = - facet_vertices - .iter() - .map(|vertex| *vertex.point()) - .collect(); + let vertex_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = facet_vertices + .iter() + .map(|vertex| *vertex.point()) + .collect(); // Calculate facet centroid - let mut centroid_coords = [K::Scalar::zero(); D]; + let mut centroid_coords = [0.0; D]; for vertex_point in &vertex_points { let coords = vertex_point.coords(); for (i, &coord) in coords.iter().enumerate() { @@ -1464,7 +1484,7 @@ where // Simple heuristic: if point is far from centroid, it's likely visible let point_coords = point.coords(); - let mut diff_coords = [K::Scalar::zero(); D]; + let mut diff_coords = [0.0; D]; for i in 0..D { diff_coords[i] = point_coords[i] - centroid_coords[i]; } @@ -1472,30 +1492,30 @@ where // Use a threshold to determine visibility - this is a simple heuristic // Scale-aware threshold: use the facet diameter squared (max pairwise edge length squared) - let mut max_edge_sq = K::Scalar::zero(); + let mut max_edge_sq = 0.0; for (i, vertex_a) in vertex_points.iter().enumerate() { let ai = vertex_a.coords(); for vertex_b in vertex_points.iter().skip(i + 1) { let bj = vertex_b.coords(); - let mut diff = [K::Scalar::zero(); D]; + let mut diff = [0.0; D]; for k in 0..D { diff[k] = ai[k] - bj[k]; } let edge_sq = squared_norm(&diff); - if max_edge_sq.is_zero() || edge_sq > max_edge_sq { + if max_edge_sq == 0.0 || edge_sq > max_edge_sq { max_edge_sq = edge_sq; } } } - if max_edge_sq.is_zero() { + if max_edge_sq == 0.0 { // Degenerate facet geometry; treat as not visible. return Ok(false); } // Add epsilon-based bound to avoid false positives from numeric noise // Use the f64 default tolerance to handle near-surface points. - let epsilon_factor = K::Scalar::default_tolerance(); - let adjusted_threshold = max_edge_sq + max_edge_sq * epsilon_factor; + let epsilon_factor = DEFAULT_TOLERANCE_F64; + let adjusted_threshold = max_edge_sq.mul_add(epsilon_factor, max_edge_sq); Ok(distance_squared > adjusted_threshold) } @@ -1523,7 +1543,6 @@ where /// use delaunay::prelude::query::ConvexHull; /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1533,16 +1552,18 @@ where /// # Hull(#[from] delaunay::prelude::query::ConvexHullConstructionError), /// # #[error(transparent)] /// # Insertion(#[from] delaunay::prelude::InsertionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error("expected tetrahedron hull to have a first facet")] /// # MissingFacet, /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1550,22 +1571,25 @@ where /// ConvexHull::from_triangulation(dt.as_triangulation())?; /// /// // Test with a point outside the hull - /// let outside_point = Point::new([2.0, 2.0, 2.0]); + /// let outside_point = Point::try_from([2.0, 2.0, 2.0])?; /// let visible_facets = hull.find_visible_facets(&outside_point, dt.as_triangulation())?; /// assert!(!visible_facets.is_empty(), "Outside point should see some facets"); /// /// // Test with a point inside the hull - /// let inside_point = Point::new([0.2, 0.2, 0.2]); + /// let inside_point = Point::try_from([0.2, 0.2, 0.2])?; /// let visible_facets = hull.find_visible_facets(&inside_point, dt.as_triangulation())?; /// assert!(visible_facets.is_empty(), "Inside point should see no facets"); /// # Ok(()) /// # } /// ``` - pub fn find_visible_facets( + pub fn find_visible_facets( &self, - point: &Point, + point: &Point, tri: &Triangulation, - ) -> Result, ConvexHullConstructionError> { + ) -> Result, ConvexHullConstructionError> + where + K: Kernel, + { let tds = &tri.tds; // Fail fast if hull is stale relative to this TDS (using immutable creation_generation) @@ -1613,7 +1637,6 @@ where /// use delaunay::prelude::query::ConvexHull; /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1623,16 +1646,18 @@ where /// # Hull(#[from] delaunay::prelude::query::ConvexHullConstructionError), /// # #[error(transparent)] /// # Insertion(#[from] delaunay::prelude::InsertionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error("expected tetrahedron hull to have a first facet")] /// # MissingFacet, /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1640,24 +1665,24 @@ where /// ConvexHull::from_triangulation(dt.as_triangulation())?; /// /// // Test with a point outside the hull - should find a nearest visible facet - /// let outside_point = Point::new([2.0, 2.0, 2.0]); + /// let outside_point = Point::try_from([2.0, 2.0, 2.0])?; /// let nearest_facet = hull.find_nearest_visible_facet(&outside_point, dt.as_triangulation())?; /// assert!(nearest_facet.is_some(), "Outside point should have a nearest visible facet"); /// /// // Test with a point inside the hull - should find no visible facets - /// let inside_point = Point::new([0.2, 0.2, 0.2]); + /// let inside_point = Point::try_from([0.2, 0.2, 0.2])?; /// let nearest_facet = hull.find_nearest_visible_facet(&inside_point, dt.as_triangulation())?; /// assert!(nearest_facet.is_none(), "Inside point should have no visible facets"); /// # Ok(()) /// # } /// ``` - pub fn find_nearest_visible_facet( + pub fn find_nearest_visible_facet( &self, - point: &Point, + point: &Point, tri: &Triangulation, ) -> Result, ConvexHullConstructionError> where - K::Scalar: PartialOrd + Copy, + K: Kernel, { let tds = &tri.tds; @@ -1671,26 +1696,26 @@ where } // Find the facet with minimum distance to the point - let mut min_distance: Option = None; + let mut min_distance: Option = None; let mut nearest_facet = None; for &facet_index in &visible_facets { let facet_handle = &self.hull_facets[facet_index]; // Create FacetView to access facet vertices let facet_view = - FacetView::new(tds, facet_handle.simplex_key(), facet_handle.facet_index()) + FacetView::try_new(tds, facet_handle.simplex_key(), facet_handle.facet_index()) .map_err( |source| ConvexHullConstructionError::FacetDataAccessFailed { source }, )?; // Extract points directly to avoid materializing Vertex copies - let facet_points: Vec> = facet_view + let facet_points: Vec> = facet_view .vertices() .map_err(|source| ConvexHullConstructionError::FacetDataAccessFailed { source })? .map(|v| *v.point()) .collect(); // Calculate distance from point to facet centroid as a simple heuristic - let mut centroid_coords = [K::Scalar::zero(); D]; + let mut centroid_coords = [0.0; D]; let num_vertices = safe_usize_to_scalar(facet_points.len()) .map_err(ConvexHullConstructionError::CoordinateConversion)?; @@ -1705,12 +1730,12 @@ where *coord /= num_vertices; } - let centroid = Point::new(centroid_coords); + let centroid = Point::from_validated_coords(centroid_coords); // Calculate squared distance using squared_norm let point_coords = point.coords(); let centroid_coords = centroid.coords(); - let mut diff_coords = [K::Scalar::zero(); D]; + let mut diff_coords = [0.0; D]; for i in 0..D { diff_coords[i] = point_coords[i] - centroid_coords[i]; } @@ -1750,7 +1775,6 @@ where /// use delaunay::prelude::query::ConvexHull; /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1760,16 +1784,18 @@ where /// # Hull(#[from] delaunay::prelude::query::ConvexHullConstructionError), /// # #[error(transparent)] /// # Insertion(#[from] delaunay::prelude::InsertionError), + /// # #[error(transparent)] + /// # Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError), /// # #[error("expected tetrahedron hull to have a first facet")] /// # MissingFacet, /// # } /// # fn main() -> Result<(), ExampleError> { /// // Create a 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1777,20 +1803,23 @@ where /// ConvexHull::from_triangulation(dt.as_triangulation())?; /// /// // Test with a point inside the hull - /// let inside_point = Point::new([0.2, 0.2, 0.2]); + /// let inside_point = Point::try_from([0.2, 0.2, 0.2])?; /// assert!(!hull.is_point_outside(&inside_point, dt.as_triangulation())?); /// /// // Test with a point outside the hull - /// let outside_point = Point::new([2.0, 2.0, 2.0]); + /// let outside_point = Point::try_from([2.0, 2.0, 2.0])?; /// assert!(hull.is_point_outside(&outside_point, dt.as_triangulation())?); /// # Ok(()) /// # } /// ``` - pub fn is_point_outside( + pub fn is_point_outside( &self, - point: &Point, + point: &Point, tri: &Triangulation, - ) -> Result { + ) -> Result + where + K: Kernel, + { let visible_facets = self.find_visible_facets(point, tri)?; Ok(!visible_facets.is_empty()) } @@ -1812,7 +1841,6 @@ where /// ```rust /// use delaunay::prelude::{DelaunayTriangulation, DelaunayTriangulationBuilder}; /// use delaunay::prelude::query::ConvexHull; - /// use delaunay::vertex; /// /// # #[derive(Debug, thiserror::Error)] /// # enum ExampleError { @@ -1828,10 +1856,10 @@ where /// # fn main() -> Result<(), ExampleError> { /// // Create a valid 3D tetrahedron /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt: DelaunayTriangulation<_, (), (), 3> = /// DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -1842,16 +1870,19 @@ where /// assert!(hull.validate(dt.as_triangulation()).is_ok()); /// /// // Empty hull should also validate - /// let empty_hull: ConvexHull, (), (), 3> = ConvexHull::default(); + /// let empty_hull: ConvexHull<(), (), 3> = ConvexHull::default(); /// // Note: validate() requires a TDS, so use an empty TDS for validation /// assert!(empty_hull.validate(dt.as_triangulation()).is_ok()); /// # Ok(()) /// # } /// ``` - pub fn validate( + pub fn validate( &self, tri: &Triangulation, - ) -> Result<(), ConvexHullValidationError> { + ) -> Result<(), ConvexHullValidationError> + where + K: Kernel, + { let tds = &tri.tds; // Check staleness first - validate() should explicitly fail on stale hulls @@ -1863,7 +1894,7 @@ where // facets are (D-1)-dimensional and have D vertices) for (index, facet_handle) in self.hull_facets.iter().enumerate() { let facet_view = - FacetView::new(tds, facet_handle.simplex_key(), facet_handle.facet_index()) + FacetView::try_new(tds, facet_handle.simplex_key(), facet_handle.facet_index()) .map_err(|source| ConvexHullValidationError::InvalidFacet { facet_index: index, source, @@ -1927,9 +1958,8 @@ where // Implementation of FacetCacheProvider trait for ConvexHull // Separate impl block with FacetCacheProvider-specific trait bounds // (main impl block has simpler bounds that don't include Sum, DeserializeOwned, etc.) -impl FacetCacheProvider for ConvexHull +impl FacetCacheProvider for ConvexHull where - K: Kernel, U: DataType, V: DataType, { @@ -1942,7 +1972,7 @@ where } } -impl Default for ConvexHull { +impl Default for ConvexHull { fn default() -> Self { Self { hull_facets: Vec::new(), @@ -1956,12 +1986,12 @@ impl Default for ConvexHull { } // Type aliases for common use cases -/// Type alias for 2D convex hulls with `FastKernel` -pub type ConvexHull2D = ConvexHull; -/// Type alias for 3D convex hulls with `FastKernel` -pub type ConvexHull3D = ConvexHull; -/// Type alias for 4D convex hulls with `FastKernel` -pub type ConvexHull4D = ConvexHull; +/// Type alias for 2D convex hulls. +pub type ConvexHull2D = ConvexHull; +/// Type alias for 3D convex hulls. +pub type ConvexHull3D = ConvexHull; +/// Type alias for 4D convex hulls. +pub type ConvexHull4D = ConvexHull; #[cfg(test)] mod tests { @@ -1978,7 +2008,6 @@ mod tests { CoordinateConversionError, CoordinateConversionValue, InvalidCoordinateValue, }; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use std::assert_matches; use std::error::Error; use std::sync::atomic::Ordering; @@ -2007,7 +2036,7 @@ mod tests { /// This uses `DelaunayTriangulation::with_kernel` and returns the owned /// triangulation directly. fn create_triangulation( - vertices: &[Vertex], + vertices: &[Vertex<(), D>], ) -> DelaunayTriangulation, (), (), D> { DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), vertices).unwrap() } @@ -2020,7 +2049,7 @@ mod tests { fn extract_facet_vertices( facet_handle: &FacetHandle, tri: &Triangulation, - ) -> Result>, ConvexHullConstructionError> + ) -> Result>, ConvexHullConstructionError> where K: Kernel, U: DataType, @@ -2028,7 +2057,7 @@ mod tests { { let tds = &tri.tds; let facet_view = - FacetView::new(tds, facet_handle.simplex_key(), facet_handle.facet_index()) + FacetView::try_new(tds, facet_handle.simplex_key(), facet_handle.facet_index()) .map_err(|source| ConvexHullConstructionError::FacetDataAccessFailed { source })?; // Use the shared utility for extracting vertices facet_view_to_vertices(&facet_view) @@ -2064,7 +2093,7 @@ mod tests { /// ```ignore /// test_hull_dimensions! { /// hull_2d => 2 => "triangle" => 3 => - /// vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0]), vertex!([0.0, 1.0])], + /// vec![delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates")], /// } /// ``` /// @@ -2091,7 +2120,7 @@ mod tests { fn $test_name() { let vertices = $vertices; let dt = create_triangulation(&vertices); - let hull: ConvexHull, (), (), $dim> = + let hull: ConvexHull<(), (), $dim> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); assert_eq!( @@ -2122,7 +2151,7 @@ mod tests { fn [<$test_name _facet_access>]() { let vertices = $vertices; let dt = create_triangulation(&vertices); - let hull: ConvexHull, (), (), $dim> = + let hull: ConvexHull<(), (), $dim> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test facet iterator @@ -2151,14 +2180,14 @@ mod tests { fn [<$test_name _point_containment>]() { let vertices = $vertices; let dt = create_triangulation(&vertices); - let hull: ConvexHull, (), (), $dim> = + let hull: ConvexHull<(), (), $dim> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test with inside point (scaled to ensure it's inside the unit simplex) // For a unit simplex, a point is inside if sum of coordinates <= 1 // Use 0.1 per dimension to ensure sum stays well below 1 for all dimensions let inside_coords = [0.1; $dim]; - let inside_point = Point::new(inside_coords); + let inside_point = Point::from_validated_coords(inside_coords); assert!( !hull.is_point_outside(&inside_point, dt.as_triangulation()).unwrap(), "{}D hull: point with small coordinates should be inside", @@ -2167,7 +2196,7 @@ mod tests { // Test with outside point let outside_coords = [2.0; $dim]; - let outside_point = Point::new(outside_coords); + let outside_point = Point::from_validated_coords(outside_coords); assert!( hull.is_point_outside(&outside_point, dt.as_triangulation()).unwrap(), "{}D hull: point far outside should be detected", @@ -2182,43 +2211,43 @@ mod tests { // Generate tests for dimensions 1D through 6D test_hull_dimensions! { hull_1d_segment => 1 => "line segment" => 2 => vec![ - vertex!([0.0]), - vertex!([1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), ], hull_2d_triangle => 2 => "triangle" => 3 => vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ], hull_3d_tetrahedron => 3 => "tetrahedron" => 4 => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.5, 1.0, 0.0]), - vertex!([0.5, 0.5, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 1.0]).unwrap(), ], hull_4d_simplex => 4 => "4-simplex" => 5 => vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ], hull_5d_simplex => 5 => "5-simplex" => 6 => vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ], hull_6d_simplex => 6 => "6-simplex" => 7 => vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ], } @@ -2226,15 +2255,15 @@ mod tests { fn test_empty_hull_comprehensive() { // Create a triangulation for validation purposes let vertices_3d = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt_3d = create_triangulation(&vertices_3d); // Test empty hull (default constructor) - let empty_hull: ConvexHull3D, (), ()> = ConvexHull::default(); + let empty_hull: ConvexHull3D<(), ()> = ConvexHull::default(); // Basic empty hull properties assert_eq!( @@ -2301,16 +2330,16 @@ mod tests { // ======================================================================== test_debug!(" Testing 2D visibility algorithms..."); let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_2d = create_triangulation(&vertices_2d); - let hull_2d: ConvexHull2D, (), ()> = + let hull_2d: ConvexHull2D<(), ()> = ConvexHull::from_triangulation(dt_2d.as_triangulation()).unwrap(); - let inside_point_2d = Point::new([0.1, 0.1]); - let outside_point_2d = Point::new([2.0, 2.0]); + let inside_point_2d = Point::from_validated_coords([0.1, 0.1]); + let outside_point_2d = Point::from_validated_coords([2.0, 2.0]); // Test point outside detection assert!( @@ -2348,17 +2377,17 @@ mod tests { // ======================================================================== test_debug!(" Testing 3D visibility algorithms..."); let vertices_3d = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt_3d = create_triangulation(&vertices_3d); - let hull_3d: ConvexHull3D, (), ()> = + let hull_3d: ConvexHull3D<(), ()> = ConvexHull::from_triangulation(dt_3d.as_triangulation()).unwrap(); - let inside_point_3d = Point::new([0.2, 0.2, 0.2]); - let outside_point_3d = Point::new([2.0, 2.0, 2.0]); + let inside_point_3d = Point::from_validated_coords([0.2, 0.2, 0.2]); + let outside_point_3d = Point::from_validated_coords([2.0, 2.0, 2.0]); // Test point outside detection assert!( @@ -2409,7 +2438,7 @@ mod tests { ); // Test find_visible_facets with far outside point (multiple visible facets) - let far_outside_point = Point::new([10.0, 10.0, 10.0]); + let far_outside_point = Point::from_validated_coords([10.0, 10.0, 10.0]); let visible_facets = hull_3d .find_visible_facets(&far_outside_point, dt_3d.as_triangulation()) .unwrap(); @@ -2445,10 +2474,10 @@ mod tests { // ======================================================================== test_debug!(" Testing visibility edge cases..."); let boundary_points = [ - Point::new([0.5, 0.5, 0.0]), // On a face - Point::new([0.3, 0.3, 0.3]), // Near centroid - Point::new([0.0, 0.0, 0.0]), // At a vertex - Point::new([0.5, 0.0, 0.0]), // On an edge + Point::from_validated_coords([0.5, 0.5, 0.0]), // On a face + Point::from_validated_coords([0.3, 0.3, 0.3]), // Near centroid + Point::from_validated_coords([0.0, 0.0, 0.0]), // At a vertex + Point::from_validated_coords([0.5, 0.0, 0.0]), // On an edge ]; for (i, point) in boundary_points.iter().enumerate() { @@ -2461,10 +2490,10 @@ mod tests { // Test with points very close to the hull let close_points = vec![ - Point::new([1e-10, 1e-10, 1e-10]), - Point::new([0.999_999, 0.0, 0.0]), - Point::new([0.0, 0.999_999, 0.0]), - Point::new([0.0, 0.0, 0.999_999]), + Point::from_validated_coords([1e-10, 1e-10, 1e-10]), + Point::from_validated_coords([0.999_999, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.999_999, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.999_999]), ]; for point in close_points { @@ -2481,7 +2510,7 @@ mod tests { test_debug!(" Testing edge case distance calculations..."); // Test find_nearest_visible_facet with equidistant points - let equidistant_point = Point::new([0.5, 0.5, 0.5]); + let equidistant_point = Point::from_validated_coords([0.5, 0.5, 0.5]); let result = hull_3d.find_nearest_visible_facet(&equidistant_point, dt_3d.as_triangulation()); assert!( @@ -2490,7 +2519,7 @@ mod tests { ); // Test with very large coordinates that might cause overflow - let large_point = Point::new([1e15, 1e15, 1e15]); + let large_point = Point::from_validated_coords([1e15, 1e15, 1e15]); let result = hull_3d.find_nearest_visible_facet(&large_point, dt_3d.as_triangulation()); assert!( result.is_ok(), @@ -2498,7 +2527,7 @@ mod tests { ); // Test with very small coordinates that might cause underflow - let small_point = Point::new([1e-15, 1e-15, 1e-15]); + let small_point = Point::from_validated_coords([1e-15, 1e-15, 1e-15]); let result = hull_3d.find_nearest_visible_facet(&small_point, dt_3d.as_triangulation()); assert!( result.is_ok(), @@ -2510,18 +2539,18 @@ mod tests { // ======================================================================== test_debug!(" Testing 4D visibility algorithms..."); let vertices_4d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt_4d = create_triangulation(&vertices_4d); - let hull_4d: ConvexHull4D, (), ()> = + let hull_4d: ConvexHull4D<(), ()> = ConvexHull::from_triangulation(dt_4d.as_triangulation()).unwrap(); - let inside_point_4d = Point::new([0.1, 0.1, 0.1, 0.1]); - let outside_point_4d = Point::new([2.0, 2.0, 2.0, 2.0]); + let inside_point_4d = Point::from_validated_coords([0.1, 0.1, 0.1, 0.1]); + let outside_point_4d = Point::from_validated_coords([2.0, 2.0, 2.0, 2.0]); assert!( !hull_4d @@ -2541,19 +2570,19 @@ mod tests { // ======================================================================== test_debug!(" Testing 5D visibility algorithms..."); let vertices_5d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt_5d = create_triangulation(&vertices_5d); - let hull_5d: ConvexHull, (), (), 5> = + let hull_5d: ConvexHull<(), (), 5> = ConvexHull::from_triangulation(dt_5d.as_triangulation()).unwrap(); - let inside_point_5d = Point::new([0.1, 0.1, 0.1, 0.1, 0.1]); - let outside_point_5d = Point::new([2.0, 2.0, 2.0, 2.0, 2.0]); + let inside_point_5d = Point::from_validated_coords([0.1, 0.1, 0.1, 0.1, 0.1]); + let outside_point_5d = Point::from_validated_coords([2.0, 2.0, 2.0, 2.0, 2.0]); assert!( !hull_5d @@ -2588,10 +2617,10 @@ mod tests { // Test distance-based heuristic with 3D tetrahedron test_debug!(" Testing distance-based heuristic..."); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -2602,20 +2631,29 @@ mod tests { // Test with points at various distances to verify scale-adaptive threshold let distance_test_points = vec![ - (Point::new([0.1, 0.1, 0.1]), "Very close to centroid"), - (Point::new([0.5, 0.5, 0.5]), "Medium distance from centroid"), - (Point::new([2.0, 2.0, 2.0]), "Far from centroid"), - (Point::new([5.0, 5.0, 5.0]), "Very far point"), + ( + Point::from_validated_coords([0.1, 0.1, 0.1]), + "Very close to centroid", + ), + ( + Point::from_validated_coords([0.5, 0.5, 0.5]), + "Medium distance from centroid", + ), + ( + Point::from_validated_coords([2.0, 2.0, 2.0]), + "Far from centroid", + ), + ( + Point::from_validated_coords([5.0, 5.0, 5.0]), + "Very far point", + ), ]; let mut visibility_results = Vec::new(); for (point, description) in &distance_test_points { let is_visible = - ConvexHull::, (), (), 3>::fallback_visibility_test( - &test_facet_vertices, - point, - ) - .unwrap(); + ConvexHull::<(), (), 3>::fallback_visibility_test(&test_facet_vertices, point) + .unwrap(); visibility_results.push(is_visible); let coords = point.coords(); test_debug!(" Point {coords:?} ({description}) - Visible: {is_visible}"); @@ -2630,19 +2668,23 @@ mod tests { // Test degenerate cases test_debug!(" Testing degenerate and edge cases..."); let degenerate_points = vec![ - (Point::new([0.0, 0.0, 0.0]), "Origin point"), ( - Point::new([f64::EPSILON, f64::EPSILON, f64::EPSILON]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + "Origin point", + ), + ( + Point::from_validated_coords([f64::EPSILON, f64::EPSILON, f64::EPSILON]), "Very small coordinates", ), - (Point::new([1e-15, 1e-15, 1e-15]), "Near-zero coordinates"), + ( + Point::from_validated_coords([1e-15, 1e-15, 1e-15]), + "Near-zero coordinates", + ), ]; for (point, description) in degenerate_points { - let result = ConvexHull::, (), (), 3>::fallback_visibility_test( - &test_facet_vertices, - &point, - ); + let result = + ConvexHull::<(), (), 3>::fallback_visibility_test(&test_facet_vertices, &point); assert!( result.is_ok(), "{description} should not cause fallback to error" @@ -2652,10 +2694,10 @@ mod tests { // Test consistency - same point multiple times should give same result test_debug!(" Testing consistency..."); - let consistency_point = Point::new([2.0, 2.0, 2.0]); + let consistency_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let consistency_results: Vec = (0..5) .map(|_| { - ConvexHull::, (), (), 3>::fallback_visibility_test( + ConvexHull::<(), (), 3>::fallback_visibility_test( &test_facet_vertices, &consistency_point, ) @@ -2679,13 +2721,13 @@ mod tests { // Test numerical precision with high-precision coordinates test_debug!(" Testing numerical precision..."); let precise_points = vec![ - Point::new([1e-15, 1e-15, 1e-15]), - Point::new([ + Point::from_validated_coords([1e-15, 1e-15, 1e-15]), + Point::from_validated_coords([ 1.000_000_000_000_000_1, 1.000_000_000_000_000_1, 1.000_000_000_000_000_1, ]), - Point::new([ + Point::from_validated_coords([ 0.999_999_999_999_999_9, 0.999_999_999_999_999_9, 0.999_999_999_999_999_9, @@ -2693,10 +2735,8 @@ mod tests { ]; for point in precise_points { - let result = ConvexHull::, (), (), 3>::fallback_visibility_test( - &test_facet_vertices, - &point, - ); + let result = + ConvexHull::<(), (), 3>::fallback_visibility_test(&test_facet_vertices, &point); assert!( result.is_ok(), "High precision coordinates should not cause errors" @@ -2712,17 +2752,17 @@ mod tests { // Test 2D fallback let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_2d = create_triangulation(&vertices_2d); - let hull_2d: ConvexHull, (), (), 2> = + let hull_2d: ConvexHull<(), (), 2> = ConvexHull::from_triangulation(dt_2d.as_triangulation()).unwrap(); let test_facet_2d_vertices = extract_facet_vertices(&hull_2d.hull_facets[0], dt_2d.as_triangulation()).unwrap(); - let test_point_2d = Point::new([2.0, 2.0]); - let result_2d = ConvexHull::, (), (), 2>::fallback_visibility_test( + let test_point_2d = Point::from_validated_coords([2.0, 2.0]); + let result_2d = ConvexHull::<(), (), 2>::fallback_visibility_test( &test_facet_2d_vertices, &test_point_2d, ); @@ -2731,19 +2771,19 @@ mod tests { // Test 4D fallback let vertices_4d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt_4d = create_triangulation(&vertices_4d); - let hull_4d: ConvexHull, (), (), 4> = + let hull_4d: ConvexHull<(), (), 4> = ConvexHull::from_triangulation(dt_4d.as_triangulation()).unwrap(); let test_facet_4d_vertices = extract_facet_vertices(&hull_4d.hull_facets[0], dt_4d.as_triangulation()).unwrap(); - let test_point_4d = Point::new([2.0, 2.0, 2.0, 2.0]); - let result_4d = ConvexHull::, (), (), 4>::fallback_visibility_test( + let test_point_4d = Point::from_validated_coords([2.0, 2.0, 2.0, 2.0]); + let result_4d = ConvexHull::<(), (), 4>::fallback_visibility_test( &test_facet_4d_vertices, &test_point_4d, ); @@ -2767,9 +2807,9 @@ mod tests { // Create a minimal valid triangulation first to ensure the path works let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()); @@ -2782,17 +2822,17 @@ mod tests { #[test] fn test_is_facet_visible_from_point_error_cases() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Get a valid facet let facet = &hull.hull_facets[0]; - let test_point = Point::new([1.0, 1.0, 1.0]); + let test_point = Point::from_validated_coords([1.0, 1.0, 1.0]); // Test normal case first let result = hull.is_facet_visible_from_point(facet, &test_point, dt.as_triangulation()); @@ -2810,13 +2850,13 @@ mod tests { // Most validation edge cases are covered by the existing comprehensive tests // Test empty hull validation (should pass) - let empty_hull: ConvexHull, (), (), 3> = ConvexHull::default(); + let empty_hull: ConvexHull<(), (), 3> = ConvexHull::default(); // Create a dummy triangulation for validation let dummy_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dummy_dt = create_triangulation(&dummy_vertices); assert!( @@ -2826,10 +2866,10 @@ mod tests { // Test valid hull validation (should pass) let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -2862,9 +2902,12 @@ mod tests { test_debug!(" Testing valid hull validation in different dimensions..."); // Test 1D hull (empty hull validation) - let dummy_vertices_1d = vec![vertex!([0.0]), vertex!([1.0])]; + let dummy_vertices_1d = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let dummy_dt_1d = create_triangulation(&dummy_vertices_1d); - let hull_1d: ConvexHull, (), (), 1> = ConvexHull::default(); + let hull_1d: ConvexHull<(), (), 1> = ConvexHull::default(); assert!( hull_1d.validate(dummy_dt_1d.as_triangulation()).is_ok(), "1D empty hull should validate" @@ -2873,12 +2916,12 @@ mod tests { // Test 2D hull let vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_2d = create_triangulation(&vertices_2d); - let hull_2d: ConvexHull, (), (), 2> = + let hull_2d: ConvexHull<(), (), 2> = ConvexHull::from_triangulation(dt_2d.as_triangulation()).unwrap(); assert!( hull_2d.validate(dt_2d.as_triangulation()).is_ok(), @@ -2886,7 +2929,7 @@ mod tests { ); // Validate vertices through FacetView for (i, facet_handle) in hull_2d.hull_facets.iter().enumerate() { - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt_2d.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -2901,7 +2944,7 @@ mod tests { ); // Test empty 2D hull validation - let empty_hull_2d: ConvexHull, (), (), 2> = ConvexHull::default(); + let empty_hull_2d: ConvexHull<(), (), 2> = ConvexHull::default(); assert!( empty_hull_2d.validate(dt_2d.as_triangulation()).is_ok(), "2D empty hull should validate successfully" @@ -2909,13 +2952,13 @@ mod tests { // Test 3D hull let vertices_3d = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt_3d = create_triangulation(&vertices_3d); - let hull_3d: ConvexHull, (), (), 3> = + let hull_3d: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(dt_3d.as_triangulation()).unwrap(); assert!( hull_3d.validate(dt_3d.as_triangulation()).is_ok(), @@ -2923,7 +2966,7 @@ mod tests { ); // Validate vertices through FacetView for (i, facet_handle) in hull_3d.hull_facets.iter().enumerate() { - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt_3d.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -2938,7 +2981,7 @@ mod tests { ); // Test empty 3D hull validation - let empty_hull_3d: ConvexHull, (), (), 3> = ConvexHull::default(); + let empty_hull_3d: ConvexHull<(), (), 3> = ConvexHull::default(); assert!( empty_hull_3d.validate(dt_3d.as_triangulation()).is_ok(), "3D empty hull should validate successfully" @@ -2946,14 +2989,14 @@ mod tests { // Test 4D hull let vertices_4d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt_4d = create_triangulation(&vertices_4d); - let hull_4d: ConvexHull, (), (), 4> = + let hull_4d: ConvexHull<(), (), 4> = ConvexHull::from_triangulation(dt_4d.as_triangulation()).unwrap(); assert!( hull_4d.validate(dt_4d.as_triangulation()).is_ok(), @@ -2961,7 +3004,7 @@ mod tests { ); // Validate vertices through FacetView for (i, facet_handle) in hull_4d.hull_facets.iter().enumerate() { - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt_4d.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -2976,7 +3019,7 @@ mod tests { ); // Test empty 4D hull validation - let empty_hull_4d: ConvexHull, (), (), 4> = ConvexHull::default(); + let empty_hull_4d: ConvexHull<(), (), 4> = ConvexHull::default(); assert!( empty_hull_4d.validate(dt_4d.as_triangulation()).is_ok(), "4D empty hull should validate successfully" @@ -2984,15 +3027,15 @@ mod tests { // Test 5D hull (minimum required coverage) let vertices_5d: Vec<_> = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt_5d = create_triangulation(&vertices_5d); - let hull_5d: ConvexHull, (), (), 5> = + let hull_5d: ConvexHull<(), (), 5> = ConvexHull::from_triangulation(dt_5d.as_triangulation()).unwrap(); assert!( hull_5d.validate(dt_5d.as_triangulation()).is_ok(), @@ -3000,7 +3043,7 @@ mod tests { ); // Validate vertices through FacetView for (i, facet_handle) in hull_5d.hull_facets.iter().enumerate() { - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt_5d.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -3015,7 +3058,7 @@ mod tests { ); // Test empty 5D hull validation - let empty_hull_5d: ConvexHull, (), (), 5> = ConvexHull::default(); + let empty_hull_5d: ConvexHull<(), (), 5> = ConvexHull::default(); assert!( empty_hull_5d.validate(dt_5d.as_triangulation()).is_ok(), "5D empty hull should validate successfully" @@ -3031,10 +3074,10 @@ mod tests { // Test with integer vertex data let vertices_int = vec![ - vertex!([0.0, 0.0, 0.0], 1i32), - vertex!([1.0, 0.0, 0.0], 2i32), - vertex!([0.0, 1.0, 0.0], 3i32), - vertex!([0.0, 0.0, 1.0], 4i32), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 2i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 3i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 4i32).unwrap(), ]; let dt_int = DelaunayTriangulation::, i32, (), 3>::with_kernel( &AdaptiveKernel::new(), @@ -3049,10 +3092,10 @@ mod tests { // Test with character vertex data let vertices_char = vec![ - vertex!([0.0, 0.0, 0.0], 'A'), - vertex!([1.0, 0.0, 0.0], 'B'), - vertex!([0.0, 1.0, 0.0], 'C'), - vertex!([0.0, 0.0, 1.0], 'D'), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 'A').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 'B').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 'C').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 'D').unwrap(), ]; let dt_char = DelaunayTriangulation::, char, (), 3>::with_kernel( &AdaptiveKernel::new(), @@ -3075,30 +3118,33 @@ mod tests { // Large coordinates ( vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1e15, 0.0, 0.0]), - vertex!([0.0, 1e15, 0.0]), - vertex!([0.0, 0.0, 1e15]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e15, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e15, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e15]).unwrap(), ], "large", ), // Small coordinates ( vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1e-15, 0.0, 0.0]), - vertex!([0.0, 1e-15, 0.0]), - vertex!([0.0, 0.0, 1e-15]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-15, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e-15, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e-15]).unwrap(), ], "small", ), // Mixed extreme coordinates ( vec![ - vertex!([f64::MIN_POSITIVE, 0.0, 0.0]), - vertex!([f64::MAX / 1e10, 0.0, 0.0]), - vertex!([0.0, f64::EPSILON, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([f64::MIN_POSITIVE, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([f64::MAX / 1e10, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, f64::EPSILON, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ], "mixed", ), @@ -3110,7 +3156,7 @@ mod tests { match dt_result { Ok(dt) => { - let hull: ConvexHull<_, (), (), 3> = + let hull: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); assert!( hull.validate(dt.as_triangulation()).is_ok(), @@ -3212,7 +3258,7 @@ mod tests { } // Test validation with empty hull - let empty_hull: ConvexHull, (), (), 3> = ConvexHull::default(); + let empty_hull: ConvexHull<(), (), 3> = ConvexHull::default(); assert!( empty_hull.validate(dt_3d.as_triangulation()).is_ok(), "Empty hull validation should succeed" @@ -3220,13 +3266,13 @@ mod tests { // Performance test - validate 100 times let perf_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let perf_dt = create_triangulation(&perf_vertices); - let perf_hull: ConvexHull, (), (), 3> = + let perf_hull: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(perf_dt.as_triangulation()).unwrap(); let start_time = std::time::Instant::now(); @@ -3256,16 +3302,16 @@ mod tests { #[test] fn test_find_nearest_visible_facet_comprehensive() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test with inside point - should return None - let inside_point = Point::new([0.1, 0.1, 0.1]); + let inside_point = Point::from_validated_coords([0.1, 0.1, 0.1]); let result = hull .find_nearest_visible_facet(&inside_point, dt.as_triangulation()) .unwrap(); @@ -3275,7 +3321,7 @@ mod tests { ); // Test with outside point - should return Some index - let outside_point = Point::new([2.0, 2.0, 2.0]); + let outside_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let result = hull .find_nearest_visible_facet(&outside_point, dt.as_triangulation()) .unwrap(); @@ -3290,10 +3336,10 @@ mod tests { // Test with point at various distances to verify distance calculation let test_points = vec![ - Point::new([1.5, 1.5, 1.5]), - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, 3.0, 0.0]), - Point::new([0.0, 0.0, 3.0]), + Point::from_validated_coords([1.5, 1.5, 1.5]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 3.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 3.0]), ]; for point in test_points { @@ -3306,10 +3352,10 @@ mod tests { #[test] fn test_facet_access_edge_cases() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3339,7 +3385,7 @@ mod tests { // Verify all facets in iterator are valid - create FacetView to check vertices for facet_handle in hull.facets() { - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -3354,10 +3400,10 @@ mod tests { fn test_generic_types_comprehensive() { // Test coordinate type validation with f64 let vertices_f64 = vec![ - vertex!([0.0f64, 0.0f64, 0.0f64]), - vertex!([1.0f64, 0.0f64, 0.0f64]), - vertex!([0.0f64, 1.0f64, 0.0f64]), - vertex!([0.0f64, 0.0f64, 1.0f64]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0f64, 0.0f64, 0.0f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0f64, 0.0f64, 0.0f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0f64, 1.0f64, 0.0f64]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0f64, 0.0f64, 1.0f64]).unwrap(), ]; let dt_f64 = create_triangulation(&vertices_f64); let hull_f64 = ConvexHull::from_triangulation(dt_f64.as_triangulation()).unwrap(); @@ -3366,16 +3412,20 @@ mod tests { assert!(!hull_f64.is_empty()); // Test point operations with f64 - let test_point_f64 = Point::new([2.0f64, 2.0f64, 2.0f64]); + let test_point_f64 = Point::from_validated_coords([2.0f64, 2.0f64, 2.0f64]); let result = hull_f64.is_point_outside(&test_point_f64, dt_f64.as_triangulation()); assert!(result.is_ok()); // Test with high precision f64 coordinates let vertices_high_precision = vec![ - vertex!([0.000_000_000_000_001, 0.0, 0.0]), - vertex!([1.000_000_000_000_001, 0.0, 0.0]), - vertex!([0.0, 1.000_000_000_000_001, 0.0]), - vertex!([0.0, 0.0, 1.000_000_000_000_001]), + crate::core::vertex::Vertex::<(), _>::try_new([0.000_000_000_000_001, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.000_000_000_000_001, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.000_000_000_000_001, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.000_000_000_000_001]) + .unwrap(), ]; let dt_hp = create_triangulation(&vertices_high_precision); let hull_hp = ConvexHull::from_triangulation(dt_hp.as_triangulation()).unwrap(); @@ -3387,10 +3437,10 @@ mod tests { // Test with integer vertex data let vertices_int = vec![ - vertex!([0.0, 0.0, 0.0], 1i32), - vertex!([1.0, 0.0, 0.0], 2i32), - vertex!([0.0, 1.0, 0.0], 3i32), - vertex!([0.0, 0.0, 1.0], 4i32), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 2i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 3i32).unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 4i32).unwrap(), ]; let dt_int = DelaunayTriangulation::, i32, (), 3>::with_kernel( &AdaptiveKernel::new(), @@ -3405,10 +3455,10 @@ mod tests { // Test with character vertex data let vertices_char = vec![ - vertex!([0.0, 0.0, 0.0], 'A'), - vertex!([1.0, 0.0, 0.0], 'B'), - vertex!([0.0, 1.0, 0.0], 'C'), - vertex!([0.0, 0.0, 1.0], 'D'), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 'A').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 'B').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 'C').unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 'D').unwrap(), ]; let dt_char = DelaunayTriangulation::, char, (), 3>::with_kernel( &AdaptiveKernel::new(), @@ -3423,10 +3473,14 @@ mod tests { // Test with Option vertex data let vertices_with_data = vec![ - vertex!([0.0, 0.0, 0.0], Some(1)), - vertex!([1.0, 0.0, 0.0], Some(2)), - vertex!([0.0, 1.0, 0.0], Some(3)), - vertex!([0.0, 0.0, 1.0], Some(4)), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], Some(1)) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], Some(2)) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], Some(3)) + .unwrap(), + crate::core::vertex::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], Some(4)) + .unwrap(), ]; let dt_with_data = DelaunayTriangulation::, Option, (), 3>::with_kernel( @@ -3449,10 +3503,10 @@ mod tests { fn test_extreme_coordinate_values() { // Test with very large coordinates let vertices_large = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1e6, 0.0, 0.0]), - vertex!([0.0, 1e6, 0.0]), - vertex!([0.0, 0.0, 1e6]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e6, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e6, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e6]).unwrap(), ]; let dt_large = create_triangulation(&vertices_large); let hull_large = ConvexHull::from_triangulation(dt_large.as_triangulation()).unwrap(); @@ -3461,8 +3515,8 @@ mod tests { assert!(hull_large.validate(dt_large.as_triangulation()).is_ok()); // Test visibility with large coordinates - let inside_large = Point::new([1000.0, 1000.0, 1000.0]); - let outside_large = Point::new([2e6, 2e6, 2e6]); + let inside_large = Point::from_validated_coords([1000.0, 1000.0, 1000.0]); + let outside_large = Point::from_validated_coords([2e6, 2e6, 2e6]); assert!( !hull_large @@ -3478,10 +3532,10 @@ mod tests { // Test with very small coordinates (but still large enough to avoid being // treated as numerically degenerate by the initial simplex search) let vertices_small = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1e-3, 0.0, 0.0]), - vertex!([0.0, 1e-3, 0.0]), - vertex!([0.0, 0.0, 1e-3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-3, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e-3, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e-3]).unwrap(), ]; let dt_small = create_triangulation(&vertices_small); let hull_small = ConvexHull::from_triangulation(dt_small.as_triangulation()).unwrap(); @@ -3493,10 +3547,10 @@ mod tests { #[test] fn test_trait_implementations_comprehensive() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3518,32 +3572,32 @@ mod tests { // Test Default trait for various dimensions let dummy_vertices_2d = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dummy_dt_2d = create_triangulation(&dummy_vertices_2d); - let hull_2d: ConvexHull, (), (), 2> = ConvexHull::default(); + let hull_2d: ConvexHull<(), (), 2> = ConvexHull::default(); assert!(hull_2d.is_empty()); assert_eq!(hull_2d.number_of_facets(), 0); assert_eq!(hull_2d.dimension(), 2); assert!(hull_2d.validate(dummy_dt_2d.as_triangulation()).is_ok()); - let hull_3d_default: ConvexHull, (), (), 3> = ConvexHull::default(); + let hull_3d_default: ConvexHull<(), (), 3> = ConvexHull::default(); assert!(hull_3d_default.is_empty()); assert_eq!(hull_3d_default.number_of_facets(), 0); assert_eq!(hull_3d_default.dimension(), 3); assert!(hull_3d_default.validate(dt.as_triangulation()).is_ok()); let dummy_vertices_4d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dummy_dt_4d = create_triangulation(&dummy_vertices_4d); - let hull_4d: ConvexHull, (), (), 4> = ConvexHull::default(); + let hull_4d: ConvexHull<(), (), 4> = ConvexHull::default(); assert!(hull_4d.is_empty()); assert_eq!(hull_4d.number_of_facets(), 0); assert_eq!(hull_4d.dimension(), 4); @@ -3554,33 +3608,33 @@ mod tests { fn test_type_aliases() { // Test that type aliases compile and work correctly let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); - let _hull_2d: ConvexHull2D, (), ()> = + let _hull_2d: ConvexHull2D<(), ()> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); let vertices_3d = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt_3d = create_triangulation(&vertices_3d); - let _hull_3d: ConvexHull3D, (), ()> = + let _hull_3d: ConvexHull3D<(), ()> = ConvexHull::from_triangulation(dt_3d.as_triangulation()).unwrap(); let vertices_4d = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt_4d = create_triangulation(&vertices_4d); - let _hull_4d: ConvexHull4D, (), ()> = + let _hull_4d: ConvexHull4D<(), ()> = ConvexHull::from_triangulation(dt_4d.as_triangulation()).unwrap(); } @@ -3614,12 +3668,12 @@ mod tests { fn test_from_triangulation_no_simplices_error() { // Create a manually constructed TDS with vertices but no simplices // Note: We need to use the underlying TDS directly for this edge case test - let mut tds = Tds::::empty(); - let vertex = vertex!([0.0, 0.0, 0.0]); + let mut tds = Tds::<(), (), 3>::empty(); + let vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); tds.insert_vertex_with_mapping(vertex).unwrap(); // Use the test-only constructor to avoid brittle struct literals. - let tri = Triangulation::new_with_tds(AdaptiveKernel::new(), tds); + let tri = Triangulation::new_with_tds(AdaptiveKernel::::new(), tds); let result = ConvexHull::from_triangulation(&tri); assert!(result.is_err()); @@ -3637,10 +3691,10 @@ mod tests { // by creating a TDS that would fail boundary facet extraction // For now, just test that the error mapping works with a valid TDS let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let result = ConvexHull::from_triangulation(dt.as_triangulation()); @@ -3653,16 +3707,16 @@ mod tests { fn test_visibility_check_insufficient_vertices_error() { // Create a hull and manually create a degenerate facet to test error path let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test a normal facet first to ensure visibility checks work - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); if let Some(facet) = hull.facet(0) { let result = hull.is_facet_visible_from_point(facet, &test_point, dt.as_triangulation()); @@ -3678,10 +3732,10 @@ mod tests { fn test_fallback_visibility_test_degenerate_facet() { // Test the fallback visibility algorithm with degenerate geometry let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3689,7 +3743,7 @@ mod tests { // Test fallback with various points if let Some(facet_handle) = hull.facet(0) { // Create FacetView to get vertices - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -3698,19 +3752,15 @@ mod tests { let facet_vertices = facet_view_to_vertices(&facet_view).unwrap(); // Test with a point very close to the facet (should not be visible) - let close_point = Point::new([0.1, 0.1, 0.1]); - let result = ConvexHull::, (), (), 3>::fallback_visibility_test( - &facet_vertices, - &close_point, - ); + let close_point = Point::from_validated_coords([0.1, 0.1, 0.1]); + let result = + ConvexHull::<(), (), 3>::fallback_visibility_test(&facet_vertices, &close_point); assert!(result.is_ok()); // Test with a point far from the facet (should be visible) - let far_point = Point::new([10.0, 10.0, 10.0]); - let result = ConvexHull::, (), (), 3>::fallback_visibility_test( - &facet_vertices, - &far_point, - ); + let far_point = Point::from_validated_coords([10.0, 10.0, 10.0]); + let result = + ConvexHull::<(), (), 3>::fallback_visibility_test(&facet_vertices, &far_point); assert!(result.is_ok()); assert!(result.unwrap()); // Should be visible from far point } @@ -3721,16 +3771,16 @@ mod tests { // Consolidated test for inside point detection (no visible facets) // Tests both find_nearest_visible_facet and is_point_outside let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test with a point inside the hull - let inside_point = Point::new([0.2, 0.2, 0.2]); + let inside_point = Point::from_validated_coords([0.2, 0.2, 0.2]); // Test find_nearest_visible_facet (no facets should be visible) let result = hull.find_nearest_visible_facet(&inside_point, dt.as_triangulation()); @@ -3746,16 +3796,16 @@ mod tests { #[test] fn test_find_nearest_visible_facet_equidistant_cases() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test with a point that's equidistant from multiple facets - let equidistant_point = Point::new([5.0, 5.0, 5.0]); + let equidistant_point = Point::from_validated_coords([5.0, 5.0, 5.0]); let result = hull.find_nearest_visible_facet(&equidistant_point, dt.as_triangulation()); assert!(result.is_ok()); // Should return some facet index or None @@ -3764,10 +3814,10 @@ mod tests { #[test] fn test_hull_operations_extended() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3791,10 +3841,10 @@ mod tests { #[test] fn test_invalidate_cache() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3803,7 +3853,7 @@ mod tests { hull.invalidate_cache(); // Verify we can still perform operations after cache invalidation - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let result = hull.is_point_outside(&test_point, dt.as_triangulation()); assert!(result.is_ok()); } @@ -3811,10 +3861,10 @@ mod tests { #[test] fn test_facet_cache_provider_implementation() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3871,10 +3921,10 @@ mod tests { #[test] fn test_comprehensive_facet_iteration() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3912,7 +3962,7 @@ mod tests { .hull_facets .iter() .map(|facet_handle| { - FacetView::new( + FacetView::try_new( &dt.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -3933,27 +3983,27 @@ mod tests { #[test] fn test_dimensional_consistency() { // Test that dimension() always returns D regardless of hull state - let empty_hull_1d: ConvexHull, (), (), 1> = ConvexHull::default(); + let empty_hull_1d: ConvexHull<(), (), 1> = ConvexHull::default(); assert_eq!(empty_hull_1d.dimension(), 1); - let empty_hull_2d: ConvexHull, (), (), 2> = ConvexHull::default(); + let empty_hull_2d: ConvexHull<(), (), 2> = ConvexHull::default(); assert_eq!(empty_hull_2d.dimension(), 2); - let empty_hull_3d: ConvexHull, (), (), 3> = ConvexHull::default(); + let empty_hull_3d: ConvexHull<(), (), 3> = ConvexHull::default(); assert_eq!(empty_hull_3d.dimension(), 3); - let empty_hull_4d: ConvexHull, (), (), 4> = ConvexHull::default(); + let empty_hull_4d: ConvexHull<(), (), 4> = ConvexHull::default(); assert_eq!(empty_hull_4d.dimension(), 4); - let empty_hull_ten_d: ConvexHull, (), (), 10> = ConvexHull::default(); + let empty_hull_ten_d: ConvexHull<(), (), 10> = ConvexHull::default(); assert_eq!(empty_hull_ten_d.dimension(), 10); // Test with populated hull let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3962,7 +4012,7 @@ mod tests { // Dimension is a const generic parameter, so it never changes // Empty hulls also preserve the dimension - let empty_hull: ConvexHull, (), (), 3> = ConvexHull::default(); + let empty_hull: ConvexHull<(), (), 3> = ConvexHull::default(); assert_eq!(empty_hull.dimension(), 3); } @@ -3971,10 +4021,10 @@ mod tests { test_debug!("Testing error handling paths in convex hull methods"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -3982,20 +4032,18 @@ mod tests { // Test fallback_visibility_test with a regular facet and extreme point let test_facet_vertices = extract_facet_vertices(&hull.hull_facets[0], dt.as_triangulation()).unwrap(); - let test_point = Point::new([1e-20, 1e-20, 1e-20]); - let result = ConvexHull::, (), (), 3>::fallback_visibility_test( - &test_facet_vertices, - &test_point, - ); + let test_point = Point::from_validated_coords([1e-20, 1e-20, 1e-20]); + let result = + ConvexHull::<(), (), 3>::fallback_visibility_test(&test_facet_vertices, &test_point); // The method should handle extreme coordinates gracefully test_debug!(" Fallback visibility result with extreme point: {result:?}"); // Test normal visibility methods with edge case points let edge_points = vec![ - Point::new([0.0, 0.0, 0.0]), // At vertex - Point::new([0.5, 0.0, 0.0]), // On edge - Point::new([f64::EPSILON, f64::EPSILON, f64::EPSILON]), // Very small + Point::from_validated_coords([0.0, 0.0, 0.0]), // At vertex + Point::from_validated_coords([0.5, 0.0, 0.0]), // On edge + Point::from_validated_coords([f64::EPSILON, f64::EPSILON, f64::EPSILON]), // Very small ]; for point in edge_points { @@ -4013,19 +4061,19 @@ mod tests { // Create a triangulation that might produce degenerate orientations // while still allowing the initial simplex search to succeed. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1e-3, 0.0, 0.0]), // Small but not numerically wiped out - vertex!([0.0, 1e-3, 0.0]), - vertex!([0.0, 0.0, 1e-3]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-3, 0.0, 0.0]).unwrap(), // Small but not numerically wiped out + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e-3, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e-3]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test with points that might cause degenerate orientations let test_points = vec![ - Point::new([5e-11, 5e-11, 5e-11]), // Very close to the degenerate vertices - Point::new([1e-9, 1e-9, 1e-9]), // Slightly further but still small - Point::new([0.0, 0.0, 0.0]), // At origin + Point::from_validated_coords([5e-11, 5e-11, 5e-11]), // Very close to the degenerate vertices + Point::from_validated_coords([1e-9, 1e-9, 1e-9]), // Slightly further but still small + Point::from_validated_coords([0.0, 0.0, 0.0]), // At origin ]; for point in test_points { @@ -4052,10 +4100,10 @@ mod tests { // Test with coordinates at the limits of f64 precision let vertices_extreme = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([f64::MIN_POSITIVE, 0.0, 0.0]), - vertex!([0.0, f64::MIN_POSITIVE, 0.0]), - vertex!([0.0, 0.0, f64::MIN_POSITIVE]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([f64::MIN_POSITIVE, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, f64::MIN_POSITIVE, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, f64::MIN_POSITIVE]).unwrap(), ]; let dt_extreme_result = DelaunayTriangulation::new(&vertices_extreme); @@ -4066,7 +4114,7 @@ mod tests { ConvexHull::from_triangulation(dt_extreme.as_triangulation()).unwrap(); // Test visibility with extreme coordinates - let test_point = Point::new([ + let test_point = Point::from_validated_coords([ f64::MIN_POSITIVE * 2.0, f64::MIN_POSITIVE * 2.0, f64::MIN_POSITIVE * 2.0, @@ -4085,10 +4133,7 @@ mod tests { ) .unwrap(); let fallback_result = - ConvexHull::, (), (), 3>::fallback_visibility_test( - &facet_vertices, - &test_point, - ); + ConvexHull::<(), (), 3>::fallback_visibility_test(&facet_vertices, &test_point); test_debug!(" Extreme precision fallback result: {fallback_result:?}"); } Err(DelaunayTriangulationConstructionError::Triangulation( @@ -4110,15 +4155,16 @@ mod tests { // Test with maximum finite values let vertices_max = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt_max = create_triangulation(&vertices_max); let hull_max = ConvexHull::from_triangulation(dt_max.as_triangulation()).unwrap(); - let max_point = Point::new([f64::MAX / 2.0, f64::MAX / 2.0, f64::MAX / 2.0]); + let max_point = + Point::from_validated_coords([f64::MAX / 2.0, f64::MAX / 2.0, f64::MAX / 2.0]); let result = hull_max.is_point_outside(&max_point, dt_max.as_triangulation()); assert!( result.is_ok(), @@ -4133,16 +4179,16 @@ mod tests { test_debug!("Testing numeric cast error handling in find_nearest_visible_facet"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test with a normal point to ensure the method works correctly first - let outside_point = Point::new([2.0, 2.0, 2.0]); + let outside_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let result = hull.find_nearest_visible_facet(&outside_point, dt.as_triangulation()); assert!( result.is_ok(), @@ -4160,9 +4206,9 @@ mod tests { // Test with various edge cases that could potentially cause numeric issues let edge_points = vec![ - Point::new([0.0, 0.0, 0.0]), // At vertex - Point::new([1e-10, 1e-10, 1e-10]), // Very small but positive - Point::new([1e10, 1e10, 1e10]), // Very large + Point::from_validated_coords([0.0, 0.0, 0.0]), // At vertex + Point::from_validated_coords([1e-10, 1e-10, 1e-10]), // Very small but positive + Point::from_validated_coords([1e10, 1e10, 1e10]), // Very large ]; for point in edge_points { @@ -4190,10 +4236,10 @@ mod tests { // Create initial triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -4216,7 +4262,7 @@ mod tests { ); // Test initial cache building - first visibility test should build the cache - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let facet = hull.facet(0).unwrap(); test_debug!(" Performing initial visibility test to build cache..."); @@ -4257,7 +4303,7 @@ mod tests { let stale_hull_gen = hull.cached_generation().load(Ordering::Acquire); // Add a new vertex - this will bump the generation - let new_vertex = vertex!([0.5, 0.5, 0.5]); // Interior point + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(); // Interior point dt.insert(new_vertex) .expect("Failed to insert vertex into DelaunayTriangulation"); @@ -4406,7 +4452,7 @@ mod tests { let test_results: Vec<_> = (0..10) .map(|i| { let x = NumCast::from(i).unwrap_or(0.0f64).mul_add(0.1, 2.0); - let test_pt = Point::new([x, 2.0, 2.0]); + let test_pt = Point::from_validated_coords([x, 2.0, 2.0]); new_hull.is_facet_visible_from_point(new_facet, &test_pt, dt.as_triangulation()) }) .collect(); @@ -4430,10 +4476,10 @@ mod tests { // Create a triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -4489,7 +4535,7 @@ mod tests { let old_generation = dt.as_triangulation().tds.generation(); // Add a new vertex to trigger generation bump - let new_vertex = vertex!([0.5, 0.5, 0.5]); // Interior point + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(); // Interior point dt.insert(new_vertex).expect("Failed to insert vertex"); let new_generation = dt.as_triangulation().tds.generation(); @@ -4530,10 +4576,10 @@ mod tests { // Create a triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -4549,7 +4595,7 @@ mod tests { for i in 0..hull.number_of_facets() { let facet_handle = hull.facet(i).unwrap(); // Create FacetView to get vertices - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -4598,7 +4644,7 @@ mod tests { // Test that helper methods work correctly together in visibility testing test_debug!(" Testing helper methods in visibility context..."); - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let test_facet = hull.facet(0).unwrap(); let visibility_result = @@ -4622,17 +4668,17 @@ mod tests { // connected and that the method signature returns the right error type. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test that normal cache building succeeds and returns the right error type let test_facet = hull.facet(0).unwrap(); - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); // Call the method that should propagate TdsError as FacetCacheBuildFailed let result = @@ -4676,10 +4722,10 @@ mod tests { // Create a symmetric triangulation where multiple facets might be equidistant let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), // Origin - vertex!([1.0, 0.0, 0.0]), // X axis - vertex!([0.0, 1.0, 0.0]), // Y axis - vertex!([0.0, 0.0, 1.0]), // Z axis + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // Origin + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), // X axis + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), // Y axis + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // Z axis ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -4687,7 +4733,7 @@ mod tests { test_debug!(" Testing with point equidistant from multiple facets..."); // Point at (1,1,1) should be roughly equidistant from all facets - let equidistant_point = Point::new([1.0, 1.0, 1.0]); + let equidistant_point = Point::from_validated_coords([1.0, 1.0, 1.0]); let nearest_result = hull.find_nearest_visible_facet(&equidistant_point, dt.as_triangulation()); @@ -4730,7 +4776,7 @@ mod tests { test_debug!(" Testing with point clearly outside..."); // Point clearly outside should always find a nearest facet - let far_point = Point::new([10.0, 10.0, 10.0]); + let far_point = Point::from_validated_coords([10.0, 10.0, 10.0]); let far_result = hull.find_nearest_visible_facet(&far_point, dt.as_triangulation()); match far_result { @@ -4749,7 +4795,7 @@ mod tests { test_debug!(" Testing with point clearly inside..."); // Point clearly inside should see no facets - let inside_point = Point::new([0.1, 0.1, 0.1]); + let inside_point = Point::from_validated_coords([0.1, 0.1, 0.1]); let inside_result = hull.find_nearest_visible_facet(&inside_point, dt.as_triangulation()); match inside_result { @@ -4777,10 +4823,10 @@ mod tests { // Create a triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -4801,7 +4847,7 @@ mod tests { let handle = thread::spawn(move || { // Each thread tries to use methods that require the cache let thread_offset: f64 = NumCast::from(thread_id).unwrap(); - let test_point = Point::new([2.0 + thread_offset, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0 + thread_offset, 2.0, 2.0]); // This will internally call try_get_or_build_facet_cache let visible_facets = @@ -4862,7 +4908,7 @@ mod tests { ); // Test a few operations to make sure everything still works - let test_point = Point::new([1.5, 1.5, 1.5]); + let test_point = Point::from_validated_coords([1.5, 1.5, 1.5]); let final_result = hull.find_visible_facets(&test_point, dt.as_triangulation()); assert!( final_result.is_ok(), @@ -4881,10 +4927,10 @@ mod tests { // Create a triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -4956,7 +5002,7 @@ mod tests { // Verify functionality still works after invalidation/rebuild cycle test_debug!(" Testing functionality after invalidation/rebuild..."); - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let visible_facets = hull.find_visible_facets(&test_point, dt.as_triangulation()); assert!( visible_facets.is_ok(), @@ -4978,17 +5024,17 @@ mod tests { // Create a valid setup let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); test_debug!(" Testing error types are properly propagated..."); - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let test_facet = hull.facet(0).unwrap(); // Test is_facet_visible_from_point returns ConvexHullConstructionError @@ -5046,16 +5092,16 @@ mod tests { // Create a simple triangulation to test with let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Test normal case to verify the error type is available - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let test_facet = hull.facet(0).unwrap(); // This should succeed in normal cases @@ -5466,10 +5512,10 @@ mod tests { // Test with very large coordinates (may cause numeric issues) let large_vertices = vec![ - vertex!([1e10, 0.0, 0.0]), - vertex!([0.0, 1e10, 0.0]), - vertex!([0.0, 0.0, 1e10]), - vertex!([1e10, 1e10, 1e10]), + crate::core::vertex::Vertex::<(), _>::try_new([1e10, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e10, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e10]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e10, 1e10, 1e10]).unwrap(), ]; match DelaunayTriangulation::new(&large_vertices) { @@ -5479,7 +5525,7 @@ mod tests { assert!(!large_hull.is_empty()); assert!(large_hull.validate(large_dt.as_triangulation()).is_ok()); - let large_test_point = Point::new([2e10, 2e10, 2e10]); + let large_test_point = Point::from_validated_coords([2e10, 2e10, 2e10]); let visibility_result = large_hull .is_point_outside(&large_test_point, large_dt.as_triangulation()); assert!( @@ -5513,10 +5559,10 @@ mod tests { // Test with very small coordinates (may cause precision issues) let small_vertices = vec![ - vertex!([1e-15, 0.0, 0.0]), - vertex!([0.0, 1e-15, 0.0]), - vertex!([0.0, 0.0, 1e-15]), - vertex!([1e-15, 1e-15, 1e-15]), + crate::core::vertex::Vertex::<(), _>::try_new([1e-15, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e-15, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e-15]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-15, 1e-15, 1e-15]).unwrap(), ]; match DelaunayTriangulation::new(&small_vertices) { @@ -5536,10 +5582,10 @@ mod tests { // Test fallback visibility with extreme coordinates let normal_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let normal_dt = create_triangulation(&normal_vertices); let normal_hull = ConvexHull::from_triangulation(normal_dt.as_triangulation()).unwrap(); @@ -5548,17 +5594,14 @@ mod tests { .unwrap(); let extreme_points = [ - Point::new([1e-100, 1e-100, 1e-100]), // Extremely small - Point::new([1e100, 1e100, 1e100]), // Extremely large - Point::new([f64::EPSILON, f64::EPSILON, f64::EPSILON]), // Machine epsilon + Point::from_validated_coords([1e-100, 1e-100, 1e-100]), // Extremely small + Point::from_validated_coords([1e100, 1e100, 1e100]), // Extremely large + Point::from_validated_coords([f64::EPSILON, f64::EPSILON, f64::EPSILON]), // Machine epsilon ]; for (i, point) in extreme_points.iter().enumerate() { let fallback_result = - ConvexHull::, (), (), 3>::fallback_visibility_test( - &test_facet_vertices, - point, - ); + ConvexHull::<(), (), 3>::fallback_visibility_test(&test_facet_vertices, point); match fallback_result { Ok(is_visible) => { test_debug!(" Extreme point {i}: fallback visibility = {is_visible}"); @@ -5588,10 +5631,10 @@ mod tests { // Create basic triangulation to get valid facet structure let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -5599,7 +5642,7 @@ mod tests { test_debug!(" Testing fallback with points at various distances..."); let facet_handle = hull.facet(0).unwrap(); - let facet_view = FacetView::new( + let facet_view = FacetView::try_new( &dt.as_triangulation().tds, facet_handle.simplex_key(), facet_handle.facet_index(), @@ -5610,18 +5653,38 @@ mod tests { // Test points at different distance scales let test_cases = vec![ // (Point, expected_visibility_description, distance_category) - (Point::new([0.0, 0.0, 0.0]), "vertex point", "zero_distance"), ( - Point::new([0.1, 0.1, 0.1]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + "vertex point", + "zero_distance", + ), + ( + Point::from_validated_coords([0.1, 0.1, 0.1]), "very close point", "very_close", ), - (Point::new([0.5, 0.5, 0.5]), "moderate distance", "moderate"), - (Point::new([1.0, 1.0, 1.0]), "unit distance", "unit"), - (Point::new([2.0, 2.0, 2.0]), "double distance", "double"), - (Point::new([10.0, 10.0, 10.0]), "far point", "far"), ( - Point::new([100.0, 100.0, 100.0]), + Point::from_validated_coords([0.5, 0.5, 0.5]), + "moderate distance", + "moderate", + ), + ( + Point::from_validated_coords([1.0, 1.0, 1.0]), + "unit distance", + "unit", + ), + ( + Point::from_validated_coords([2.0, 2.0, 2.0]), + "double distance", + "double", + ), + ( + Point::from_validated_coords([10.0, 10.0, 10.0]), + "far point", + "far", + ), + ( + Point::from_validated_coords([100.0, 100.0, 100.0]), "very far point", "very_far", ), @@ -5629,10 +5692,7 @@ mod tests { for (point, description, category) in test_cases { let fallback_result = - ConvexHull::, (), (), 3>::fallback_visibility_test( - &test_facet_vertices, - &point, - ); + ConvexHull::<(), (), 3>::fallback_visibility_test(&test_facet_vertices, &point); match fallback_result { Ok(is_visible) => { @@ -5680,10 +5740,10 @@ mod tests { // Create a triangulation with near-collinear points let near_collinear_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([2.0, 1e-10, 0.0]), // Nearly collinear with first two - vertex!([0.5, 0.5, 1.0]), // Out of plane + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 1e-10, 0.0]).unwrap(), // Nearly collinear with first two + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 1.0]).unwrap(), // Out of plane ]; match DelaunayTriangulation::new(&near_collinear_vertices) { @@ -5697,13 +5757,12 @@ mod tests { collinear_dt.as_triangulation(), ) .unwrap(); - let test_point = Point::new([1.5, 0.5, 0.5]); + let test_point = Point::from_validated_coords([1.5, 0.5, 0.5]); - let collinear_result = - ConvexHull::, (), (), 3>::fallback_visibility_test( - &collinear_facet_vertices, - &test_point, - ); + let collinear_result = ConvexHull::<(), (), 3>::fallback_visibility_test( + &collinear_facet_vertices, + &test_point, + ); match collinear_result { Ok(is_visible) => { @@ -5731,10 +5790,10 @@ mod tests { // Create a triangulation where facets might have very small areas let tiny_area_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1e-6, 0.0, 0.0]), - vertex!([0.0, 1e-6, 0.0]), - vertex!([0.0, 0.0, 1e-6]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-6, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1e-6, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e-6]).unwrap(), ]; match DelaunayTriangulation::new(&tiny_area_vertices) { @@ -5748,13 +5807,12 @@ mod tests { tiny_dt.as_triangulation(), ) .unwrap(); - let test_point = Point::new([1e-3, 1e-3, 1e-3]); + let test_point = Point::from_validated_coords([1e-3, 1e-3, 1e-3]); - let tiny_result = - ConvexHull::, (), (), 3>::fallback_visibility_test( - &tiny_facet_vertices, - &test_point, - ); + let tiny_result = ConvexHull::<(), (), 3>::fallback_visibility_test( + &tiny_facet_vertices, + &test_point, + ); match tiny_result { Ok(is_visible) => { @@ -5791,10 +5849,10 @@ mod tests { // Create a well-defined triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -5812,9 +5870,9 @@ mod tests { for &multiplier in &multipliers { let distance = base_distance * multiplier; - let test_point = Point::new([distance, distance, distance]); + let test_point = Point::from_validated_coords([distance, distance, distance]); - let result = ConvexHull::, (), (), 3>::fallback_visibility_test( + let result = ConvexHull::<(), (), 3>::fallback_visibility_test( &test_facet_vertices, &test_point, ); @@ -5881,19 +5939,19 @@ mod tests { ( "equilateral-like triangle", vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.5, 0.866, 0.0]), - vertex!([0.333, 0.289, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.333, 0.289, 1.0]).unwrap(), ], ), ( "elongated triangle", vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([10.0, 0.0, 0.0]), // Very long edge - vertex!([0.1, 0.1, 0.0]), // Short edge - vertex!([1.0, 1.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0, 0.0]).unwrap(), // Very long edge + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.1, 0.0]).unwrap(), // Short edge + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ], ), ]; @@ -5909,13 +5967,12 @@ mod tests { edge_dt.as_triangulation(), ) .unwrap(); - let test_point = Point::new([5.0, 5.0, 5.0]); + let test_point = Point::from_validated_coords([5.0, 5.0, 5.0]); - let edge_result = - ConvexHull::, (), (), 3>::fallback_visibility_test( - &edge_facet_vertices, - &test_point, - ); + let edge_result = ConvexHull::<(), (), 3>::fallback_visibility_test( + &edge_facet_vertices, + &test_point, + ); match edge_result { Ok(is_visible) => { @@ -5958,9 +6015,9 @@ mod tests { test_debug!(" Testing perfectly collinear points in 2D..."); let collinear_2d_vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([2.0, 0.0]), // Collinear with first two + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), // Collinear with first two ]; match DelaunayTriangulation::new(&collinear_2d_vertices) { @@ -5976,7 +6033,7 @@ mod tests { test_debug!(" Facet count: {}", collinear_hull.number_of_facets()); // Test operations on collinear hull - let test_point = Point::new([0.5, 1.0]); + let test_point = Point::from_validated_coords([0.5, 1.0]); let visibility_result = collinear_hull .is_point_outside(&test_point, collinear_dt.as_triangulation()); match visibility_result { @@ -6017,9 +6074,9 @@ mod tests { test_debug!(" Testing nearly collinear points with small perturbations..."); let nearly_collinear_vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 1e-12]), // Nearly on the line y=0 - vertex!([2.0, -1e-12]), // Nearly on the line y=0 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1e-12]).unwrap(), // Nearly on the line y=0 + crate::core::vertex::Vertex::<(), _>::try_new([2.0, -1e-12]).unwrap(), // Nearly on the line y=0 ]; match DelaunayTriangulation::new(&nearly_collinear_vertices) { @@ -6030,7 +6087,7 @@ mod tests { assert!(nearly_hull.validate(nearly_dt.as_triangulation()).is_ok()); // Test that operations handle numerical precision gracefully - let precision_test_point = Point::new([1.0, 1e-6]); + let precision_test_point = Point::from_validated_coords([1.0, 1e-6]); let precision_result = nearly_hull .is_point_outside(&precision_test_point, nearly_dt.as_triangulation()); match precision_result { @@ -6057,10 +6114,10 @@ mod tests { test_debug!(" Testing collinear points in 3D (degenerate configuration)..."); let collinear_3d_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 1.0, 1.0]), - vertex!([2.0, 2.0, 2.0]), // Collinear with first two - vertex!([3.0, 3.0, 3.0]), // Also collinear + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 2.0, 2.0]).unwrap(), // Collinear with first two + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 3.0, 3.0]).unwrap(), // Also collinear ]; match DelaunayTriangulation::new(&collinear_3d_vertices) { @@ -6091,10 +6148,10 @@ mod tests { // Four points in the same plane (z=0) let coplanar_3d_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([1.0, 1.0, 0.0]), // All in z=0 plane + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 0.0]).unwrap(), // All in z=0 plane ]; match DelaunayTriangulation::new(&coplanar_3d_vertices) { @@ -6113,10 +6170,10 @@ mod tests { test_debug!(" Testing nearly coplanar points with small z-perturbations..."); let nearly_coplanar_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 1e-10]), - vertex!([0.0, 1.0, -1e-10]), - vertex!([1.0, 1.0, 1e-10]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 1e-10]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, -1e-10]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1e-10]).unwrap(), ]; match DelaunayTriangulation::new(&nearly_coplanar_vertices) { @@ -6139,7 +6196,7 @@ mod tests { } // Test visibility operations on nearly coplanar hull - let test_point = Point::new([0.5, 0.5, 1.0]); + let test_point = Point::from_validated_coords([0.5, 0.5, 1.0]); let visibility_result = nearly_coplanar_hull .is_point_outside(&test_point, nearly_coplanar_dt.as_triangulation()); match visibility_result { @@ -6194,11 +6251,11 @@ mod tests { // Five points in the same 3D hyperplane (w=0) let coplanar_4d_vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([1.0, 1.0, 1.0, 0.0]), // All in w=0 hyperplane + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0, 0.0]).unwrap(), // All in w=0 hyperplane ]; match DelaunayTriangulation::new(&coplanar_4d_vertices) { @@ -6224,11 +6281,11 @@ mod tests { test_debug!(" Testing exact duplicate vertices..."); let duplicate_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.0, 0.0, 0.0]), // Exact duplicate of first vertex + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // Exact duplicate of first vertex ]; match DelaunayTriangulation::new(&duplicate_vertices) { @@ -6272,11 +6329,11 @@ mod tests { test_debug!(" Testing nearly coincident vertices (within floating-point precision)..."); let nearly_coincident_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1e-15, 1e-15, 1e-15]), // Nearly coincident with first + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-15, 1e-15, 1e-15]).unwrap(), // Nearly coincident with first ]; match DelaunayTriangulation::new(&nearly_coincident_vertices) { @@ -6286,7 +6343,7 @@ mod tests { test_debug!(" ✓ Hull with nearly coincident vertices constructed"); // Test operations to see how they handle near-duplicates - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let visibility_result = nearly_coin_hull .is_point_outside(&test_point, nearly_coin_dt.as_triangulation()); match visibility_result { @@ -6337,13 +6394,13 @@ mod tests { // Create a 6D simplex (7 vertices) let vertices_6d = vec![ - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.1, 0.1, 0.1, 0.1, 0.1, 0.1]), // Interior point to make it non-degenerate + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.1, 0.1, 0.1, 0.1, 0.1]).unwrap(), // Interior point to make it non-degenerate ]; match DelaunayTriangulation::new(&vertices_6d) { @@ -6366,7 +6423,8 @@ mod tests { } // Test visibility operations in 6D - let test_point_6d = Point::new([2.0, 2.0, 2.0, 2.0, 2.0, 2.0]); + let test_point_6d = + Point::from_validated_coords([2.0, 2.0, 2.0, 2.0, 2.0, 2.0]); let visibility_result = hull_6d.is_point_outside(&test_point_6d, dt_6d.as_triangulation()); match visibility_result { @@ -6393,14 +6451,22 @@ mod tests { test_debug!(" Testing 7D convex hull..."); let vertices_7d = vec![ - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) + .unwrap(), ]; match DelaunayTriangulation::new(&vertices_7d) { @@ -6423,15 +6489,24 @@ mod tests { test_debug!(" Testing 8D convex hull (stress test)..."); let vertices_8d = vec![ - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]) + .unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) + .unwrap(), ]; match DelaunayTriangulation::new(&vertices_8d) { @@ -6485,7 +6560,7 @@ mod tests { let x = radius * angle1.cos() * angle2.sin(); let y = radius * angle1.sin() * angle2.sin(); let z = radius * angle2.cos(); - large_vertices.push(vertex!([x, y, z])); + large_vertices.push(crate::core::vertex::Vertex::<(), _>::try_new([x, y, z]).unwrap()); } test_debug!(" Generated {num_vertices} vertices"); @@ -6526,7 +6601,7 @@ mod tests { ); // Test visibility operations - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let visibility_result = large_hull.is_point_outside(&test_point, large_dt.as_triangulation()); assert!( @@ -6570,7 +6645,7 @@ mod tests { let radius = >::from(i).mul_add(0.01, 1.0); let x = radius * angle.cos(); let y = radius * angle.sin(); - large_2d_vertices.push(vertex!([x, y])); + large_2d_vertices.push(crate::core::vertex::Vertex::<(), _>::try_new([x, y]).unwrap()); } let start_2d = std::time::Instant::now(); @@ -6622,10 +6697,10 @@ mod tests { // Create a basic triangulation for testing let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -6641,7 +6716,12 @@ mod tests { // Rapidly modify the TDS to increment generation many times for i in 0..20 { - let new_vertex = vertex!([>::from(i).mul_add(0.01, 0.1), 0.1, 0.1]); + let new_vertex = crate::core::vertex::Vertex::<(), _>::try_new([ + >::from(i).mul_add(0.01, 0.1), + 0.1, + 0.1, + ]) + .unwrap(); if dt.insert(new_vertex).is_ok() { let current_gen = dt.as_triangulation().tds.generation(); test_debug!(" After modification {i}: TDS generation = {current_gen}"); @@ -6664,7 +6744,7 @@ mod tests { test_debug!(" Final TDS generation: {final_generation}"); // Force cache rebuild - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let visibility_result = hull.is_point_outside(&test_point, dt.as_triangulation()); match visibility_result { Ok(is_outside) => { @@ -6714,14 +6794,18 @@ mod tests { } #[test] + #[expect( + clippy::too_many_lines, + reason = "stress test keeps the cache mutation sequence together" + )] fn test_cache_consistency_under_stress() { test_debug!("Testing cache consistency under rapid operations"); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); @@ -6730,10 +6814,10 @@ mod tests { let num_cycles = 50; let test_points = [ - Point::new([2.0, 2.0, 2.0]), - Point::new([1.5, 1.5, 1.5]), - Point::new([0.1, 0.1, 0.1]), - Point::new([10.0, 10.0, 10.0]), + Point::from_validated_coords([2.0, 2.0, 2.0]), + Point::from_validated_coords([1.5, 1.5, 1.5]), + Point::from_validated_coords([0.1, 0.1, 0.1]), + Point::from_validated_coords([10.0, 10.0, 10.0]), ]; for cycle in 0..num_cycles { @@ -6808,7 +6892,11 @@ mod tests { // Perform multiple operations without TDS changes for i in 0..10 { - let test_point = Point::new([>::from(i).mul_add(0.1, 1.0), 2.0, 2.0]); + let test_point = Point::from_validated_coords([ + >::from(i).mul_add(0.1, 1.0), + 2.0, + 2.0, + ]); let result = hull.is_point_outside(&test_point, dt.as_triangulation()); assert!(result.is_ok(), "Visibility test {i} should succeed"); } @@ -6854,16 +6942,16 @@ mod tests { test_debug!(" Testing cache memory cleanup..."); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = create_triangulation(&vertices); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Build cache - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let _ = hull.is_point_outside(&test_point, dt.as_triangulation()); // Verify cache exists @@ -6894,9 +6982,9 @@ mod tests { test_debug!(" Testing multiple hull instances with shared TDS..."); // Create multiple hull instances from the same triangulation - let hull1: ConvexHull, (), (), 3> = + let hull1: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); - let hull2: ConvexHull, (), (), 3> = + let hull2: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Each should have independent cache @@ -6958,10 +7046,10 @@ mod tests { // Step 1: Create hull from initial DelaunayTriangulation let mut dt = DelaunayTriangulation::new(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]) .unwrap(); let initial_gen = dt.as_triangulation().tds.generation(); @@ -6975,7 +7063,8 @@ mod tests { test_debug!(" ✓ Hull created with generation {initial_gen}"); // Step 2: Mutate triangulation (increases generation) - dt.insert(vertex!([0.5, 0.5, 0.5])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap()) + .unwrap(); let new_gen = dt.as_triangulation().tds.generation(); assert_ne!( initial_gen, new_gen, @@ -6995,7 +7084,7 @@ mod tests { test_debug!(" ✓ Called invalidate_cache()"); // Step 4: Try to use the hull (this would trigger cache rebuild in the old buggy code) - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let visibility_result = hull.find_visible_facets(&test_point, dt.as_triangulation()); // The visibility operation should fail with StaleHull error @@ -7033,7 +7122,7 @@ mod tests { ); // Step 6: Verify that creating a new hull works correctly - let new_hull: ConvexHull<_, (), (), 3> = + let new_hull: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); assert!( new_hull.is_valid_for_triangulation(dt.as_triangulation()), @@ -7055,10 +7144,10 @@ mod tests { #[test] fn test_hull_rejects_different_tds_with_same_generation() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt1: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -7080,7 +7169,7 @@ mod tests { let facet = hull.facet(0).expect("tetrahedron hull should have facets"); let visibility = hull.is_facet_visible_from_point( facet, - &Point::new([2.0, 2.0, 2.0]), + &Point::from_validated_coords([2.0, 2.0, 2.0]), dt2.as_triangulation(), ); assert_matches!( @@ -7096,10 +7185,10 @@ mod tests { #[test] fn test_hull_rejects_cloned_tds_with_same_generation() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt1: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -7120,7 +7209,7 @@ mod tests { let facet = hull.facet(0).expect("tetrahedron hull should have facets"); let visibility = hull.is_facet_visible_from_point( facet, - &Point::new([2.0, 2.0, 2.0]), + &Point::from_validated_coords([2.0, 2.0, 2.0]), dt2.as_triangulation(), ); assert_matches!( @@ -7136,10 +7225,10 @@ mod tests { #[test] fn test_hull_cache_key_survives_failed_insert_rollback() { let vertices = [ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -7174,8 +7263,11 @@ mod tests { .expect("tetrahedron should have vertices") .1 .uuid(); - let duplicate_uuid_vertex = - Vertex::new_with_uuid(Point::new([0.25, 0.25, 0.125]), duplicate_uuid, None); + let duplicate_uuid_vertex = Vertex::new_with_uuid( + Point::from_validated_coords([0.25, 0.25, 0.125]), + duplicate_uuid, + None, + ); let result = dt.insert(duplicate_uuid_vertex); assert_matches!( @@ -7205,7 +7297,7 @@ mod tests { "facet cache should be reused when rollback restores the same generation" ); - let outside = Point::new([2.0, 2.0, 2.0]); + let outside = Point::from_validated_coords([2.0, 2.0, 2.0]); assert!( hull.is_point_outside(&outside, dt.as_triangulation()) .unwrap(), @@ -7223,18 +7315,19 @@ mod tests { test_debug!("Testing that all hull operations fail on stale hull"); let mut dt = DelaunayTriangulation::new(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]) .unwrap(); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); // Modify triangulation to make hull stale - dt.insert(vertex!([0.5, 0.5, 0.5])).unwrap(); + dt.insert(crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap()) + .unwrap(); - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); // Test that all operations detect staleness test_debug!(" Testing find_visible_facets..."); @@ -7291,16 +7384,16 @@ mod tests { // Create hull with nearly coplanar points (z << x,y) but still valid simplex let dt = DelaunayTriangulation::new(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 1e-6]), - vertex!([0.0, 1.0, 1e-6]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 1e-6]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 1e-6]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]) .unwrap(); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); test_debug!(" Testing point visibility with degenerate geometry..."); - let test_point = Point::new([0.5, 0.5, 1.0]); + let test_point = Point::from_validated_coords([0.5, 0.5, 1.0]); let result = hull.is_point_outside(&test_point, dt.as_triangulation()); assert!( @@ -7317,16 +7410,16 @@ mod tests { // Create a very flat triangulation (extreme aspect ratio) let dt = DelaunayTriangulation::new(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1000.0, 0.0, 0.0]), // Very long in x - vertex!([0.0, 0.001, 0.0]), // Very short in y - vertex!([0.0, 0.0, 0.001]), // Very short in z + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1000.0, 0.0, 0.0]).unwrap(), // Very long in x + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.001, 0.0]).unwrap(), // Very short in y + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.001]).unwrap(), // Very short in z ]) .unwrap(); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); test_debug!(" Testing operations with extreme aspect ratio..."); - let test_point = Point::new([500.0, 10.0, 10.0]); + let test_point = Point::from_validated_coords([500.0, 10.0, 10.0]); let result = hull.is_point_outside(&test_point, dt.as_triangulation()); assert!( @@ -7346,23 +7439,23 @@ mod tests { test_debug!("Testing points exactly on hull surface"); let dt = create_triangulation(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); test_debug!(" Testing point on facet plane..."); // Point on the plane defined by (1,0,0), (0,1,0), (0,0,0) - let on_facet = Point::new([0.5, 0.5, 0.0]); + let on_facet = Point::from_validated_coords([0.5, 0.5, 0.0]); let result = hull.is_point_outside(&on_facet, dt.as_triangulation()); // Should handle boundary case without panic assert!(result.is_ok(), "Point on facet should be handled"); test_debug!(" ✓ Point on facet handled: inside={}", !result.unwrap()); test_debug!(" Testing point at vertex..."); - let at_vertex = Point::new([1.0, 0.0, 0.0]); + let at_vertex = Point::from_validated_coords([1.0, 0.0, 0.0]); let result = hull.is_point_outside(&at_vertex, dt.as_triangulation()); assert!(result.is_ok(), "Point at vertex should be handled"); // Point at vertex should be inside hull @@ -7370,7 +7463,7 @@ mod tests { test_debug!(" ✓ Point at vertex correctly identified as inside"); test_debug!(" Testing point on edge..."); - let on_edge = Point::new([0.5, 0.0, 0.0]); + let on_edge = Point::from_validated_coords([0.5, 0.0, 0.0]); let result = hull.is_point_outside(&on_edge, dt.as_triangulation()); assert!(result.is_ok(), "Point on edge should be handled"); test_debug!(" ✓ Point on edge handled"); @@ -7381,21 +7474,21 @@ mod tests { test_debug!("Testing points very close to hull surface (epsilon distance)"); let dt = create_triangulation(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); test_debug!(" Testing point just outside (epsilon away)..."); - let just_outside = Point::new([0.5, 0.5, f64::EPSILON * 10.0]); + let just_outside = Point::from_validated_coords([0.5, 0.5, f64::EPSILON * 10.0]); let result = hull.is_point_outside(&just_outside, dt.as_triangulation()); assert!(result.is_ok(), "Near-surface point should be handled"); test_debug!(" ✓ Epsilon-distance point handled"); test_debug!(" Testing point just inside (negative epsilon)..."); - let just_inside = Point::new([0.25, 0.25, -f64::EPSILON * 10.0]); + let just_inside = Point::from_validated_coords([0.25, 0.25, -f64::EPSILON * 10.0]); let result = hull.is_point_outside(&just_inside, dt.as_triangulation()); assert!(result.is_ok(), "Near-surface point should be handled"); test_debug!(" ✓ Negative epsilon-distance point handled"); @@ -7412,7 +7505,8 @@ mod tests { // Origin - use nested macro to get correct type macro_rules! make_vertex { ($coords:expr) => {{ - let v: Vertex = vertex!($coords); + let v: Vertex<(), $dim> = + crate::core::vertex::Vertex::<(), _>::try_new($coords).unwrap(); v }}; } @@ -7460,7 +7554,7 @@ mod tests { // Test point outside test_debug!(" Testing point outside {}D hull...", $dim); let outside_coords = [2.0; $dim]; - let outside_point = Point::new(outside_coords); + let outside_point = Point::from_validated_coords(outside_coords); let is_outside = hull .is_point_outside(&outside_point, dt.as_triangulation()) .expect(&format!("Failed to test outside point in {}D", $dim)); @@ -7477,7 +7571,7 @@ mod tests { for i in 0..$dim { inside_coords[i] = 1.0 / >::from($dim + 1); } - let inside_point = Point::new(inside_coords); + let inside_point = Point::from_validated_coords(inside_coords); let is_outside = hull .is_point_outside(&inside_point, dt.as_triangulation()) .expect(&format!("Failed to test inside point in {}D", $dim)); @@ -7526,14 +7620,14 @@ mod tests { test_debug!("Testing cache lifecycle through multiple operations"); let dt = create_triangulation(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]); let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); test_debug!(" Initial state: cache already built during construction"); let initial_gen = hull.cached_generation().load(Ordering::Acquire); @@ -7604,22 +7698,22 @@ mod tests { test_debug!("Testing cache independence between multiple hull instances"); let dt = create_triangulation(&[ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]); test_debug!(" Creating first hull and building cache..."); - let hull1: ConvexHull, (), (), 3> = + let hull1: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); - let test_point = Point::new([2.0, 2.0, 2.0]); + let test_point = Point::from_validated_coords([2.0, 2.0, 2.0]); hull1 .find_visible_facets(&test_point, dt.as_triangulation()) .unwrap(); test_debug!(" Creating second hull from same triangulation..."); - let hull2: ConvexHull, (), (), 3> = + let hull2: ConvexHull<(), (), 3> = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap(); test_debug!(" Second hull also has cache built during construction..."); diff --git a/src/geometry/kernel.rs b/src/geometry/kernel.rs index 9d3615da..052ccf09 100644 --- a/src/geometry/kernel.rs +++ b/src/geometry/kernel.rs @@ -40,8 +40,7 @@ use crate::geometry::robust_predicates::{ }; use crate::geometry::sos::{sos_insphere_sign, sos_orientation_sign}; use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateConversionValue, CoordinateScalar, - DegenerateSimplexReason, + CoordinateConversionError, CoordinateConversionValue, DegenerateSimplexReason, }; use crate::geometry::util::safe_coords_to_f64; use core::marker::PhantomData; @@ -73,15 +72,15 @@ const fn insphere_to_i32(result: InSphere) -> i32 { /// /// // Test orientation of a 2D triangle /// let points = [ -/// Point::new([0.0, 0.0]), -/// Point::new([1.0, 0.0]), -/// Point::new([0.5, 1.0]), +/// Point::try_from([0.0, 0.0])?, +/// Point::try_from([1.0, 0.0])?, +/// Point::try_from([0.5, 1.0])?, /// ]; /// let orientation = kernel.orientation(&points)?; /// assert!(orientation != 0); // Not degenerate /// /// // Test if point is inside circumcircle -/// let test_point = Point::new([0.5, 0.3]); +/// let test_point = Point::try_from([0.5, 0.3])?; /// let result = kernel.in_sphere(&points, &test_point)?; /// assert_eq!(result, 1); // Inside /// # Ok(()) @@ -91,7 +90,7 @@ pub trait Kernel: Clone { /// The scalar type used for coordinates. /// /// The currently supported caller-visible scalar is `f64`. - type Scalar: CoordinateScalar; + type Scalar; /// Compute the orientation of a simplex. /// @@ -133,20 +132,17 @@ pub trait Kernel: Clone { /// /// // 3D tetrahedron /// let points = [ - /// Point::new([0.0, 0.0, 0.0]), - /// Point::new([1.0, 0.0, 0.0]), - /// Point::new([0.0, 1.0, 0.0]), - /// Point::new([0.0, 0.0, 1.0]), + /// Point::try_from([0.0, 0.0, 0.0])?, + /// Point::try_from([1.0, 0.0, 0.0])?, + /// Point::try_from([0.0, 1.0, 0.0])?, + /// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// let orientation = kernel.orientation(&points)?; /// assert!(orientation == -1 || orientation == 1); // Non-degenerate /// # Ok(()) /// # } /// ``` - fn orientation( - &self, - points: &[Point], - ) -> Result; + fn orientation(&self, points: &[Point]) -> Result; /// Test if a point is inside, on, or outside the circumsphere of a simplex. /// @@ -187,26 +183,26 @@ pub trait Kernel: Clone { /// /// // 3D tetrahedron /// let simplex = [ - /// Point::new([0.0, 0.0, 0.0]), - /// Point::new([1.0, 0.0, 0.0]), - /// Point::new([0.0, 1.0, 0.0]), - /// Point::new([0.0, 0.0, 1.0]), + /// Point::try_from([0.0, 0.0, 0.0])?, + /// Point::try_from([1.0, 0.0, 0.0])?, + /// Point::try_from([0.0, 1.0, 0.0])?, + /// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// /// // Point inside the circumsphere - /// let inside = Point::new([0.25, 0.25, 0.25]); + /// let inside = Point::try_from([0.25, 0.25, 0.25])?; /// assert_eq!(kernel.in_sphere(&simplex, &inside)?, 1); /// /// // Point outside the circumsphere - /// let outside = Point::new([2.0, 2.0, 2.0]); + /// let outside = Point::try_from([2.0, 2.0, 2.0])?; /// assert_eq!(kernel.in_sphere(&simplex, &outside)?, -1); /// # Ok(()) /// # } /// ``` fn in_sphere( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result; /// Test circumsphere containment when the simplex is already known to have @@ -242,22 +238,22 @@ pub trait Kernel: Clone { /// # fn main() -> Result<(), CoordinateConversionError> { /// let kernel = FastKernel::::new(); /// let simplex = [ - /// Point::new([0.0, 0.0]), - /// Point::new([1.0, 0.0]), - /// Point::new([0.0, 1.0]), + /// Point::try_from([0.0, 0.0])?, + /// Point::try_from([1.0, 0.0])?, + /// Point::try_from([0.0, 1.0])?, /// ]; /// /// assert_eq!(kernel.orientation(&simplex)?, 1); /// - /// let inside = Point::new([0.25, 0.25]); + /// let inside = Point::try_from([0.25, 0.25])?; /// assert_eq!(kernel.in_sphere_positive_oriented(&simplex, &inside)?, 1); /// # Ok(()) /// # } /// ``` fn in_sphere_positive_oriented( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result { self.in_sphere(simplex_points, test_point) } @@ -322,8 +318,8 @@ pub trait ExactPredicates: Kernel {} macro_rules! impl_exact_predicates_for_supported_dims { ($($dim:literal),* $(,)?) => { $( - impl ExactPredicates<$dim> for RobustKernel {} - impl ExactPredicates<$dim> for AdaptiveKernel {} + impl ExactPredicates<$dim> for RobustKernel {} + impl ExactPredicates<$dim> for AdaptiveKernel {} )* }; } @@ -368,9 +364,9 @@ impl_exact_predicates_for_supported_dims!(0, 1, 2, 3, 4, 5); /// /// // Test with a 2D triangle /// let points = [ -/// Point::new([0.0, 0.0]), -/// Point::new([1.0, 0.0]), -/// Point::new([0.0, 1.0]), +/// Point::try_from([0.0, 0.0])?, +/// Point::try_from([1.0, 0.0])?, +/// Point::try_from([0.0, 1.0])?, /// ]; /// /// // Check orientation @@ -378,7 +374,7 @@ impl_exact_predicates_for_supported_dims!(0, 1, 2, 3, 4, 5); /// assert!(orientation != 0); /// /// // Test insphere predicate -/// let test_point = Point::new([0.25, 0.25]); +/// let test_point = Point::try_from([0.25, 0.25])?; /// let result = kernel.in_sphere(&points, &test_point)?; /// assert_eq!(result, 1); // Inside circumcircle /// # Ok(()) @@ -407,16 +403,10 @@ impl FastKernel { } } -impl Kernel for FastKernel -where - T: CoordinateScalar, -{ - type Scalar = T; +impl Kernel for FastKernel { + type Scalar = f64; - fn orientation( - &self, - points: &[Point], - ) -> Result { + fn orientation(&self, points: &[Point]) -> Result { let result = simplex_orientation(points)?; Ok(match result { Orientation::NEGATIVE => -1, @@ -427,8 +417,8 @@ where fn in_sphere( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result { // Use insphere_lifted for optimal performance (5.3x faster in 3D) let result = insphere_lifted(simplex_points, *test_point).map_err(|e| { @@ -463,8 +453,8 @@ where fn in_sphere_positive_oriented( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result { let result = robust_insphere_positive_oriented(simplex_points, test_point)?; Ok(insphere_to_i32(result)) @@ -500,16 +490,16 @@ where /// let kernel = RobustKernel::::new(); /// /// let points = [ -/// Point::new([0.0, 0.0, 0.0]), -/// Point::new([1.0, 0.0, 0.0]), -/// Point::new([0.0, 1.0, 0.0]), -/// Point::new([0.0, 0.0, 1.0]), +/// Point::try_from([0.0, 0.0, 0.0])?, +/// Point::try_from([1.0, 0.0, 0.0])?, +/// Point::try_from([0.0, 1.0, 0.0])?, +/// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// /// let orientation = kernel.orientation(&points)?; /// assert!(orientation != 0); // Non-degenerate /// -/// let test_point = Point::new([0.25, 0.25, 0.25]); +/// let test_point = Point::try_from([0.25, 0.25, 0.25])?; /// let result = kernel.in_sphere(&points, &test_point)?; /// assert_eq!(result, 1); // Inside circumsphere /// # Ok(()) @@ -538,16 +528,10 @@ impl RobustKernel { } } -impl Kernel for RobustKernel -where - T: CoordinateScalar, -{ - type Scalar = T; +impl Kernel for RobustKernel { + type Scalar = f64; - fn orientation( - &self, - points: &[Point], - ) -> Result { + fn orientation(&self, points: &[Point]) -> Result { let result = robust_orientation(points)?; Ok(match result { Orientation::NEGATIVE => -1, @@ -558,8 +542,8 @@ where fn in_sphere( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result { let result = robust_insphere(simplex_points, test_point)?; Ok(insphere_to_i32(result)) @@ -567,8 +551,8 @@ where fn in_sphere_positive_oriented( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result { let result = robust_insphere_positive_oriented(simplex_points, test_point)?; Ok(insphere_to_i32(result)) @@ -620,9 +604,9 @@ where /// /// // Collinear points get a deterministic SoS sign (never 0) /// let collinear = [ -/// Point::new([0.0, 0.0]), -/// Point::new([1.0, 0.0]), -/// Point::new([2.0, 0.0]), +/// Point::try_from([0.0, 0.0])?, +/// Point::try_from([1.0, 0.0])?, +/// Point::try_from([2.0, 0.0])?, /// ]; /// let orientation = kernel.orientation(&collinear)?; /// assert!(orientation == 1 || orientation == -1); // SoS: always non-zero @@ -652,16 +636,10 @@ impl AdaptiveKernel { } } -impl Kernel for AdaptiveKernel -where - T: CoordinateScalar, -{ - type Scalar = T; +impl Kernel for AdaptiveKernel { + type Scalar = f64; - fn orientation( - &self, - points: &[Point], - ) -> Result { + fn orientation(&self, points: &[Point]) -> Result { if points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: points.len(), @@ -682,10 +660,12 @@ where // Layer 3: SoS tie-breaking for truly degenerate orientation. // Same pattern as in_sphere() — convert to f64 points for SoS. - let mut f64_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut f64_points: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(points.len()); for point in points { - f64_points.push(Point::new(safe_coords_to_f64(point.coords())?)); + f64_points.push(Point::from_validated_coords(safe_coords_to_f64( + point.coords(), + )?)); } // SoS guarantees a non-zero sign for distinct points. If SoS @@ -698,8 +678,8 @@ where fn in_sphere( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { @@ -719,12 +699,14 @@ where } // At least one predicate needs SoS → convert to f64 points. - let mut f64_simplex: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut f64_simplex: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(simplex_points.len()); for point in simplex_points { - f64_simplex.push(Point::new(safe_coords_to_f64(point.coords())?)); + f64_simplex.push(Point::from_validated_coords(safe_coords_to_f64( + point.coords(), + )?)); } - let f64_test = Point::new(safe_coords_to_f64(test_point.coords())?); + let f64_test = Point::from_validated_coords(safe_coords_to_f64(test_point.coords())?); // Resolve orientation factor. let orient_factor: i32 = if rel_orient_sign != 0 { @@ -753,8 +735,8 @@ where fn in_sphere_positive_oriented( &self, - simplex_points: &[Point], - test_point: &Point, + simplex_points: &[Point], + test_point: &Point, ) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { @@ -769,12 +751,14 @@ where let insphere_effective = if determinant_sign != 0 { determinant_sign } else { - let mut f64_simplex: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut f64_simplex: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(simplex_points.len()); for point in simplex_points { - f64_simplex.push(Point::new(safe_coords_to_f64(point.coords())?)); + f64_simplex.push(Point::from_validated_coords(safe_coords_to_f64( + point.coords(), + )?)); } - let f64_test = Point::new(safe_coords_to_f64(test_point.coords())?); + let f64_test = Point::from_validated_coords(safe_coords_to_f64(test_point.coords())?); sos_insphere_sign(&f64_simplex, &f64_test)? }; @@ -786,69 +770,68 @@ where mod tests { use super::*; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::Coordinate; // ========================================================================= // GENERIC HELPER FUNCTIONS // ========================================================================= /// Standard D-simplex: origin + D unit vectors (non-degenerate). - fn standard_simplex() -> Vec> { + fn standard_simplex() -> Vec> { let mut points = Vec::with_capacity(D + 1); - points.push(Point::new([0.0; D])); + points.push(Point::from_validated_coords([0.0; D])); for i in 0..D { let mut coords = [0.0; D]; coords[i] = 1.0; - points.push(Point::new(coords)); + points.push(Point::from_validated_coords(coords)); } points } /// Degenerate D-simplex: all points have last coordinate = 0. - fn degenerate_simplex() -> Vec> { + fn degenerate_simplex() -> Vec> { let mut points = Vec::with_capacity(D + 1); - points.push(Point::new([0.0; D])); + points.push(Point::from_validated_coords([0.0; D])); for i in 0..D.saturating_sub(1) { let mut coords = [0.0; D]; coords[i] = 1.0; - points.push(Point::new(coords)); + points.push(Point::from_validated_coords(coords)); } let mut bary = [0.0; D]; for c in bary.iter_mut().take(D.saturating_sub(1)) { *c = 0.5; } - points.push(Point::new(bary)); + points.push(Point::from_validated_coords(bary)); points } /// Point clearly inside the circumsphere of the standard simplex. - fn inside_point() -> Point { - Point::new([0.1; D]) + fn inside_point() -> Point { + Point::from_validated_coords([0.1; D]) } /// Point clearly outside the circumsphere of the standard simplex. - fn outside_point() -> Point { - Point::new([2.0; D]) + fn outside_point() -> Point { + Point::from_validated_coords([2.0; D]) } /// Co-spherical test point: (1,1,…,1) lies on the circumsphere of the /// standard simplex for all D ≥ 2. - fn cospherical_test() -> Point { - Point::new([1.0; D]) + fn cospherical_test() -> Point { + Point::from_validated_coords([1.0; D]) } /// Test point off the degenerate hyperplane (last coord nonzero). - fn off_plane_test() -> Point { + fn off_plane_test() -> Point { let mut coords = [0.0; D]; coords[D - 1] = 1.0; - Point::new(coords) + Point::from_validated_coords(coords) } /// Test point in the degenerate hyperplane but far from the simplex. - fn coplanar_far_test() -> Point { + fn coplanar_far_test() -> Point { let mut coords = [0.0; D]; coords[0] = 3.0; - Point::new(coords) + Point::from_validated_coords(coords) } // ========================================================================= @@ -919,9 +902,9 @@ mod tests { // Three collinear points on diagonal let collinear = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 1.0]), - Point::new([2.0, 2.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 1.0]), + Point::from_validated_coords([2.0, 2.0]), ]; let orientation = kernel.orientation(&collinear).unwrap(); @@ -937,9 +920,9 @@ mod tests { // Nearly collinear points (small perturbation) let nearly_collinear = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([2.0, 1e-10]), // Tiny deviation from collinearity + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([2.0, 1e-10]), // Tiny deviation from collinearity ]; let orientation = kernel.orientation(&nearly_collinear).unwrap(); @@ -954,9 +937,9 @@ mod tests { // Triangle with large coordinates let large_triangle = [ - Point::new([1e6, 1e6]), - Point::new([1e6 + 1.0, 1e6]), - Point::new([1e6, 1e6 + 1.0]), + Point::from_validated_coords([1e6, 1e6]), + Point::from_validated_coords([1e6 + 1.0, 1e6]), + Point::from_validated_coords([1e6, 1e6 + 1.0]), ]; let orientation = kernel.orientation(&large_triangle).unwrap(); @@ -972,9 +955,9 @@ mod tests { // Very small but valid triangle let small_triangle = [ - Point::new([0.0, 0.0]), - Point::new([1e-6, 0.0]), - Point::new([0.0, 1e-6]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1e-6, 0.0]), + Point::from_validated_coords([0.0, 1e-6]), ]; let orientation = kernel.orientation(&small_triangle).unwrap(); @@ -995,9 +978,11 @@ mod tests { fn test_fast_kernel_in_sphere_insufficient_vertices() { // Exercises the SimplexValidationError::InsufficientVertices mapping. let kernel = FastKernel::::new(); - let simplex: [Point; 2] = - [Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; - let test_point = Point::new([0.5, 0.5, 0.5]); + let simplex: [Point<3>; 2] = [ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; + let test_point = Point::from_validated_coords([0.5, 0.5, 0.5]); let result = kernel.in_sphere(&simplex, &test_point); assert_eq!( result, @@ -1014,12 +999,12 @@ mod tests { // Exercises the SimplexValidationError::DegenerateSimplex mapping. let kernel = FastKernel::::new(); let simplex = [ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([1.0, 1.0, 0.0]), // Coplanar — degenerate + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 1.0, 0.0]), // Coplanar — degenerate ]; - let test_point = Point::new([0.5, 0.5, 0.5]); + let test_point = Point::from_validated_coords([0.5, 0.5, 0.5]); let result = kernel.in_sphere(&simplex, &test_point); assert_eq!( result, @@ -1034,15 +1019,15 @@ mod tests { fn test_robust_kernel_positive_oriented_insphere_boundary_maps_to_zero() { let kernel = RobustKernel::::new(); let simplex = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; assert_eq!(kernel.orientation(&simplex).unwrap(), 1); assert_eq!( kernel - .in_sphere_positive_oriented(&simplex, &Point::new([1.0, 1.0])) + .in_sphere_positive_oriented(&simplex, &Point::from_validated_coords([1.0, 1.0])) .unwrap(), 0 ); @@ -1051,10 +1036,13 @@ mod tests { #[test] fn test_robust_kernel_positive_oriented_insphere_wrong_arity_errors() { let kernel = RobustKernel::::new(); - let simplex: [Point; 2] = [Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; + let simplex: [Point<2>; 2] = [ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; let err = kernel - .in_sphere_positive_oriented(&simplex, &Point::new([0.25, 0.25])) + .in_sphere_positive_oriented(&simplex, &Point::from_validated_coords([0.25, 0.25])) .unwrap_err(); assert_eq!( @@ -1270,11 +1258,17 @@ mod tests { #[test] fn test_adaptive_kernel_wrong_point_count() { let kernel = AdaptiveKernel::::new(); - let points = [Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; + let points = [ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; assert!(kernel.orientation(&points).is_err()); - let simplex = [Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; - let test = Point::new([0.5, 0.5]); + let simplex = [ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; + let test = Point::from_validated_coords([0.5, 0.5]); assert!(kernel.in_sphere(&simplex, &test).is_err()); } @@ -1292,8 +1286,8 @@ mod tests { #[test] fn []() { let kernel = AdaptiveKernel::::new(); - let points: Vec> = - vec![Point::new([0.42; $dim]); $dim + 1]; + let points: Vec> = + vec![Point::from_validated_coords([0.42; $dim]); $dim + 1]; let result = kernel.orientation(&points).unwrap(); assert_eq!( result, 0, diff --git a/src/geometry/point.rs b/src/geometry/point.rs index 9e1ca64c..805196ee 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -1,26 +1,13 @@ //! Data and operations on d-dimensional points. //! -//! # Special Floating-Point Equality Semantics -//! -//! This module implements custom equality semantics for floating-point coordinates -//! that differ from the IEEE 754 standard. Specifically, `NaN` values are treated -//! as equal to themselves to satisfy the requirements of the `Eq` trait and enable -//! Points to be used as keys in hash-based collections. -//! -//! This means that for Points containing floating-point coordinates: -//! - `Point::new([f64::NAN]) == Point::new([f64::NAN])` returns `true` -//! - Points with NaN values can be used as `HashMap` keys -//! - All NaN bit patterns are considered equal -//! -//! If you need standard IEEE 754 equality semantics, compare the coordinates -//! directly instead of using Point equality. +//! Points are validated domain values: every stored coordinate is a finite `f64`. #![allow(clippy::similar_names)] #![forbid(unsafe_code)] use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateConversionValue, CoordinateScalar, - CoordinateValidationError, InvalidCoordinateValue, + Coordinate, CoordinateConversionError, CoordinateConversionValue, CoordinateValidationError, + HashCoordinate, InvalidCoordinateValue, }; use num_traits::cast; use serde::de::{Error, SeqAccess, Visitor}; @@ -31,57 +18,162 @@ use std::cmp::Ordering; use std::convert::TryFrom; use std::fmt; use std::hash::{Hash, Hasher}; -use std::marker::PhantomData; // ============================================================================= // POINT STRUCT DEFINITION // ============================================================================= +#[derive(Clone, Copy, Debug)] +pub(crate) struct ValidatedCoordinates { + values: [f64; D], +} + +impl ValidatedCoordinates { + #[inline] + pub(crate) fn try_new(mut values: [f64; D]) -> Result { + for (index, coord) in values.iter_mut().enumerate() { + if !coord.is_finite() { + return Err(CoordinateValidationError::InvalidCoordinate { + coordinate_index: index, + coordinate_value: InvalidCoordinateValue::from_debug(coord), + dimension: D, + }); + } + if *coord == 0.0 { + *coord = 0.0; + } + } + Ok(Self { values }) + } + + #[inline] + pub(crate) fn from_validated(values: [f64; D]) -> Self { + Self::try_new(values).expect("validated point coordinates must be finite") + } + + #[inline] + pub(crate) const fn as_array(&self) -> &[f64; D] { + &self.values + } + + #[inline] + pub(crate) const fn into_array(self) -> [f64; D] { + self.values + } + + #[inline] + fn ordered_equals(&self, other: &Self) -> bool { + self.values + .iter() + .zip(other.values.iter()) + .all(|(a, b)| a.to_bits() == b.to_bits()) + } + + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + for (a, b) in self.values.iter().zip(other.values.iter()) { + let ordering = a.total_cmp(b); + if ordering != Ordering::Equal { + return ordering; + } + } + Ordering::Equal + } + + #[inline] + fn hash(&self, state: &mut H) { + for &coord in &self.values { + HashCoordinate::hash_scalar(&coord, state); + } + } +} + +impl Default for ValidatedCoordinates { + fn default() -> Self { + Self { values: [0.0; D] } + } +} + #[derive(Clone, Copy, Debug)] /// The [Point] struct represents a point in a D-dimensional space. /// -/// # Generic Type Support +/// # Invariants /// -/// `Point` stores a fixed-size coordinate array `[T; D]`. -/// -/// The type itself does **not** require `T: CoordinateScalar`, which allows the -/// surrounding combinatorial data structures (e.g. `Tds`) to avoid geometry -/// bounds at the type level. -/// -/// Most geometric APIs (construction via [`Coordinate`], validation, hashing/ordering, -/// and serialization) are only available when `T: CoordinateScalar`. The only -/// currently supported caller-visible coordinate scalar is `f64`; the generic -/// parameter keeps storage and geometry concerns separate. Future exact-coordinate -/// support, if added, will be exposed through an explicit documented API rather than -/// arbitrary scalar implementations. +/// `Point` stores a validated coordinate array. Public construction is +/// fallible so non-finite coordinates are rejected before storage, and signed +/// zero is canonicalized so equality, hashing, and ordering agree. /// /// # Properties /// -/// * `coords`: A private `[T; D]` coordinate array (length `D` is known at compile time). +/// * `coords`: A private validated coordinate array (length `D` is known at compile time). /// The field is private to keep points immutable once created. /// -/// Points are intended to be immutable once created, so the `coords` field is -/// private to prevent modification after instantiation. -/// /// # Examples /// /// ```rust /// use delaunay::prelude::geometry::Point; -/// use delaunay::prelude::geometry::Coordinate; /// -/// let p = Point::new([1.0, 2.0]); +/// let p = Point::try_new([1.0, 2.0])?; /// assert_eq!(p.coords(), &[1.0, 2.0]); +/// # Ok::<(), delaunay::prelude::geometry::CoordinateValidationError>(()) /// ``` -pub struct Point { +pub struct Point { /// The coordinates of the point. - coords: [T; D], + coords: ValidatedCoordinates, } // ============================================================================= // PUBLIC API // ============================================================================= -impl Point { +impl Point { + /// Creates a point from raw coordinates after proving every coordinate is finite. + /// + /// # Errors + /// + /// Returns [`CoordinateValidationError::InvalidCoordinate`] when any coordinate + /// is `NaN`, positive infinity, or negative infinity. + /// + /// # Examples + /// + /// ```rust + /// use delaunay::prelude::geometry::{CoordinateValidationError, Point}; + /// + /// let point = Point::try_new([1.0, 2.0])?; + /// assert_eq!(point.coords(), &[1.0, 2.0]); + /// # Ok::<(), CoordinateValidationError>(()) + /// ``` + /// + /// ```rust + /// use delaunay::prelude::geometry::{CoordinateValidationError, Point}; + /// + /// let err = Point::<2>::try_new([f64::NAN, 0.0]).unwrap_err(); + /// std::assert_matches!(err, CoordinateValidationError::InvalidCoordinate { coordinate_index: 0, .. }); + /// ``` + #[inline] + pub fn try_new(coords: [f64; D]) -> Result { + Ok(Self::from_validated_coordinates( + ValidatedCoordinates::try_new(coords)?, + )) + } + + /// Creates a point from a proof-bearing coordinate wrapper. + #[inline] + #[must_use] + pub(crate) const fn from_validated_coordinates(coords: ValidatedCoordinates) -> Self { + Self { coords } + } + + /// Creates a point from coordinates whose finite invariant has already been proved. + /// + /// This is only for internal paths that have already proved finiteness or for + /// focused invariant regression tests. + #[inline] + #[must_use] + pub(crate) fn from_validated_coords(coords: [f64; D]) -> Self { + Self::from_validated_coordinates(ValidatedCoordinates::from_validated(coords)) + } + /// Returns a reference to the point's coordinates as an array. /// /// This method provides read-only access to the internal coordinate array @@ -95,19 +187,26 @@ impl Point { /// /// ```rust /// use delaunay::prelude::geometry::Point; - /// use delaunay::prelude::geometry::Coordinate; /// - /// let point = Point::new([1.0, 2.0, 3.0]); + /// let point = Point::try_new([1.0, 2.0, 3.0])?; /// let coords = point.coords(); /// assert_eq!(coords, &[1.0, 2.0, 3.0]); /// /// // For owned coordinates, use Into /// let owned_coords: [f64; 3] = point.into(); /// assert_eq!(owned_coords, [1.0, 2.0, 3.0]); + /// # Ok::<(), delaunay::prelude::geometry::CoordinateValidationError>(()) /// ``` #[inline] #[must_use] - pub const fn coords(&self) -> &[T; D] { + pub const fn coords(&self) -> &[f64; D] { + self.validated_coords().as_array() + } + + /// Returns the invariant-carrying coordinate tuple used by internal code. + #[inline] + #[must_use] + pub(crate) const fn validated_coords(&self) -> &ValidatedCoordinates { &self.coords } } @@ -116,56 +215,39 @@ impl Point { // TRAIT IMPLEMENTATIONS // ============================================================================= -impl Coordinate for Point -where - T: CoordinateScalar, -{ - /// Create a new Point from an array of coordinates +impl Coordinate for Point { + /// Create a new Point from an array of coordinates. #[inline] - fn new(coords: [T; D]) -> Self { - Self { coords } + fn try_new(coords: [f64; D]) -> Result { + Self::try_new(coords) } /// Extract the coordinates as an array #[inline] - fn to_array(&self) -> [T; D] { - self.coords + fn to_array(&self) -> [f64; D] { + self.coords.into_array() } /// Get the coordinate at the specified index #[inline] - fn get(&self, index: usize) -> Option { - self.coords.get(index).copied() + fn get(&self, index: usize) -> Option { + self.coords.as_array().get(index).copied() } - /// Validate that all coordinates are finite (no NaN or infinite values) + /// Validates the point's stored coordinate invariant. + #[inline] fn validate(&self) -> Result<(), CoordinateValidationError> { - // Verify all coordinates are finite - for (index, &coord) in self.coords.iter().enumerate() { - if !coord.is_finite_generic() { - return Err(CoordinateValidationError::InvalidCoordinate { - coordinate_index: index, - coordinate_value: InvalidCoordinateValue::from_debug(&coord), - dimension: D, - }); - } - } Ok(()) } /// Hash the coordinate values fn hash_coordinate(&self, state: &mut H) { - for &coord in &self.coords { - coord.hash_scalar(state); - } + self.coords.hash(state); } /// Check if two coordinates are equal using `OrderedEq` fn ordered_equals(&self, other: &Self) -> bool { - self.coords - .iter() - .zip(other.coords.iter()) - .all(|(a, b)| a.ordered_eq(b)) + self.coords.ordered_equals(&other.coords) } } @@ -174,10 +256,7 @@ where // ============================================================================= // Implement Hash using the Coordinate trait -impl Hash for Point -where - T: CoordinateScalar, -{ +impl Hash for Point { #[inline] fn hash(&self, state: &mut H) { self.hash_coordinate(state); @@ -185,77 +264,42 @@ where } // Implement PartialEq using the Coordinate trait -impl PartialEq for Point -where - T: CoordinateScalar, -{ +impl PartialEq for Point { fn eq(&self, other: &Self) -> bool { self.ordered_equals(other) } } // Implement Eq using the Coordinate trait -impl Eq for Point where T: CoordinateScalar {} +impl Eq for Point {} // Implement PartialOrd using OrderedCmp for consistent ordering with special floating-point values -impl PartialOrd for Point -where - T: CoordinateScalar, -{ +impl PartialOrd for Point { fn partial_cmp(&self, other: &Self) -> Option { - // Perform lexicographic comparison using ordered comparison for each coordinate - for (a, b) in self.coords.iter().zip(other.coords.iter()) { - match a.ordered_partial_cmp(b) { - Some(Ordering::Equal) => {} - other_ordering => return other_ordering, - } - } - Some(Ordering::Equal) + Some(self.coords.cmp(&other.coords)) } } // Manual implementations for traits that can't be derived due to [T; D] limitations // Implement Default manually -impl Default for Point -where - T: CoordinateScalar, -{ +impl Default for Point { fn default() -> Self { Self { - coords: [T::default(); D], + coords: ValidatedCoordinates::default(), } } } // Implement Serialize manually -impl Serialize for Point -where - T: CoordinateScalar, -{ +impl Serialize for Point { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut tuple = serializer.serialize_tuple(D)?; - for coord in &self.coords { - if coord.is_finite_generic() { - tuple.serialize_element(coord)?; - } else if coord.is_nan() { - // Serialize NaN as null for JSON compatibility - tuple.serialize_element(&Option::::None)?; - } else if coord.is_infinite() { - if coord.is_sign_positive() { - // Serialize positive infinity as the string "Infinity" - tuple.serialize_element("Infinity")?; - } else { - // Serialize negative infinity as the string "-Infinity" - tuple.serialize_element("-Infinity")?; - } - } else { - // Fallback for any other non-finite values - tuple.serialize_element(&Option::::None)?; - } + for coord in self.coords.as_array() { + tuple.serialize_element(coord)?; } tuple.end() } @@ -266,36 +310,28 @@ where /// without being tied to specific format types. #[derive(Deserialize)] #[serde(untagged)] -enum CoordRepr { +enum CoordRepr { /// Regular numeric value - Num(T), - /// String representation (case-insensitive special values: "Infinity"/"Inf", "-Infinity"/"-Inf", "NaN") + Num(f64), + /// String representation, rejected because `Point` stores only finite coordinates. Str(String), - /// Null value (will be converted to NaN) + /// Null value, rejected because `Point` stores only finite coordinates. Null, } -// Implement Deserialize manually with null -> NaN mapping -impl<'de, T, const D: usize> Deserialize<'de> for Point -where - T: CoordinateScalar, -{ +// Implement Deserialize manually so raw serialized coordinates parse through the validated boundary. +impl<'de, const D: usize> Deserialize<'de> for Point { fn deserialize(deserializer: DE) -> Result where DE: serde::Deserializer<'de>, { - struct ArrayVisitor(PhantomData); + struct ArrayVisitor; - impl<'de, T, const D: usize> Visitor<'de> for ArrayVisitor - where - T: CoordinateScalar, - { - type Value = Point; + impl<'de, const D: usize> Visitor<'de> for ArrayVisitor { + type Value = Point; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_fmt(format_args!( - "an array of {D} coordinates (numbers, null, \"Infinity\", \"-Infinity\", \"NaN\", or their case-insensitive variants)" - )) + formatter.write_fmt(format_args!("an array of {D} finite numeric coordinates")) } fn visit_seq(self, mut seq: A) -> Result @@ -306,32 +342,19 @@ where let mut coords = Vec::with_capacity(D); for i in 0..D { // Deserialize each element using the format-agnostic enum - let element: CoordRepr = seq + let element: CoordRepr = seq .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; let coord = match element { - CoordRepr::Num(value) => { - // Handle regular numeric values - already the correct type - value - } + CoordRepr::Num(value) => value, CoordRepr::Str(s) => { - // Handle special string representations (case-insensitive) - let sl = s.trim().to_ascii_lowercase(); - match sl.as_str() { - "infinity" | "inf" => T::infinity(), - "-infinity" | "-inf" => T::neg_infinity(), - "nan" => T::nan(), - _ => { - return Err(Error::custom(format!( - "Unknown special value: {s}" - ))); - } - } + return Err(Error::custom(format!( + "non-finite coordinate string is not valid for Point: {s}" + ))); } CoordRepr::Null => { - // Handle null values as NaN - T::nan() + return Err(Error::custom("null is not a valid Point coordinate")); } }; @@ -340,15 +363,15 @@ where // Convert Vec to array let coords_len = coords.len(); - let coords_array: [T; D] = coords + let coords_array: [f64; D] = coords .try_into() .map_err(|_| Error::invalid_length(coords_len, &self))?; - Ok(Point::new(coords_array)) + Point::try_new(coords_array).map_err(Error::custom) } } - deserializer.deserialize_tuple(D, ArrayVisitor(PhantomData)) + deserializer.deserialize_tuple(D, ArrayVisitor::) } } @@ -356,77 +379,90 @@ where // TYPE CONVERSION IMPLEMENTATIONS // ============================================================================= -/// Fallible conversions for [Point] from arrays into supported coordinate scalars. +/// Fallible conversions for [Point] from arrays into finite `f64` coordinates. /// /// This replaces the previous infallible From<[T; D]> which silently defaulted on /// cast failures. Now, conversions will return an error if any coordinate cannot be /// cast into the target type, or if a non-finite value is encountered post-cast. -impl TryFrom<[T; D]> for Point +impl TryFrom<[T; D]> for Point where - T: cast::NumCast + fmt::Debug, - U: CoordinateScalar + cast::NumCast, + T: cast::NumCast + Copy + fmt::Debug + PartialEq, { type Error = CoordinateConversionError; #[inline] fn try_from(coords: [T; D]) -> Result { - let mut out: [U; D] = [U::zero(); D]; + let mut out = [0.0; D]; for (i, c) in coords.into_iter().enumerate() { let coordinate_value = CoordinateConversionValue::from_numeric_debug(&c); // Attempt numeric cast - let v: U = + let v: f64 = cast::cast(c).ok_or_else(|| CoordinateConversionError::ConversionFailed { coordinate_index: i, - coordinate_value, + coordinate_value: coordinate_value.clone(), from_type: any::type_name::(), - to_type: any::type_name::(), + to_type: any::type_name::(), })?; // Validate finiteness after cast - if !v.is_finite_generic() { + if !v.is_finite() { return Err(CoordinateConversionError::NonFiniteValue { coordinate_index: i, coordinate_value: InvalidCoordinateValue::from_debug(&v), }); } + let round_trip: T = + cast::cast(v).ok_or_else(|| CoordinateConversionError::ConversionFailed { + coordinate_index: i, + coordinate_value: coordinate_value.clone(), + from_type: any::type_name::(), + to_type: any::type_name::(), + })?; + if round_trip != c { + return Err(CoordinateConversionError::ConversionFailed { + coordinate_index: i, + coordinate_value, + from_type: any::type_name::(), + to_type: any::type_name::(), + }); + } out[i] = v; } - Ok(Self::new(out)) + Ok(Self::from_validated_coordinates( + ValidatedCoordinates::from_validated(out), + )) } } /// Enable conversions from Point to coordinate arrays - using Coordinate trait -impl From> for [T; D] { +impl From> for [f64; D] { /// # Example /// /// ```rust /// use delaunay::prelude::geometry::Point; - /// use delaunay::prelude::geometry::Coordinate; - /// let point = Point::new([1.0, 2.0]); + /// let point = Point::try_new([1.0, 2.0])?; /// let coords: [f64; 2] = point.into(); /// assert_eq!(coords, [1.0, 2.0]); + /// # Ok::<(), delaunay::prelude::geometry::CoordinateValidationError>(()) /// ``` #[inline] - fn from(point: Point) -> [T; D] { - point.coords + fn from(point: Point) -> [f64; D] { + point.coords.into_array() } } -impl From<&Point> for [T; D] -where - T: Copy, -{ +impl From<&Point> for [f64; D] { /// # Example /// /// ```rust /// use delaunay::prelude::geometry::Point; - /// use delaunay::prelude::geometry::Coordinate; - /// let point = Point::new([3.0, 4.0]); + /// let point = Point::try_new([3.0, 4.0])?; /// let coords: [f64; 2] = (&point).into(); /// assert_eq!(coords, [3.0, 4.0]); + /// # Ok::<(), delaunay::prelude::geometry::CoordinateValidationError>(()) /// ``` #[inline] - fn from(point: &Point) -> [T; D] { - point.coords + fn from(point: &Point) -> [f64; D] { + point.coords.into_array() } } @@ -448,13 +484,12 @@ mod tests { } // Helper function to test point equality and hash consistency - fn test_point_equality_and_hash( - point1: Point, - point2: Point, + fn test_point_equality_and_hash( + point1: Point, + point2: Point, should_be_equal: bool, ) where - T: CoordinateScalar, - Point: Hash, + Point: Hash, { if should_be_equal { assert_eq!(point1, point2); @@ -480,17 +515,17 @@ mod tests { #[test] fn $test_name() { // 2D - let point_2d = Point::new([1.0, 2.0]); + let point_2d = Point::from_validated_coords([1.0, 2.0]); assert_relative_eq!(point_2d.to_array().as_slice(), [1.0, 2.0].as_slice()); assert_eq!(point_2d.dim(), 2); // 3D - let point_3d = Point::new([1.0, 2.0, 3.0]); + let point_3d = Point::from_validated_coords([1.0, 2.0, 3.0]); assert_relative_eq!(point_3d.to_array().as_slice(), [1.0, 2.0, 3.0].as_slice()); assert_eq!(point_3d.dim(), 3); // 4D - let point_4d = Point::new([1.0, 2.0, 3.0, 4.0]); + let point_4d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); assert_relative_eq!( point_4d.to_array().as_slice(), [1.0, 2.0, 3.0, 4.0].as_slice() @@ -498,7 +533,7 @@ mod tests { assert_eq!(point_4d.dim(), 4); // 5D - let point_5d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let point_5d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); assert_relative_eq!( point_5d.to_array().as_slice(), [1.0, 2.0, 3.0, 4.0, 5.0].as_slice() @@ -512,30 +547,30 @@ mod tests { #[test] fn $test_name() { // 2D - let p2d_a = Point::new([1.0, 2.0]); - let p2d_b = Point::new([1.0, 2.0]); - let p2d_c = Point::new([1.0, 3.0]); + let p2d_a = Point::from_validated_coords([1.0, 2.0]); + let p2d_b = Point::from_validated_coords([1.0, 2.0]); + let p2d_c = Point::from_validated_coords([1.0, 3.0]); assert_eq!(p2d_a, p2d_b); assert_ne!(p2d_a, p2d_c); // 3D - let p3d_a = Point::new([1.0, 2.0, 3.0]); - let p3d_b = Point::new([1.0, 2.0, 3.0]); - let p3d_c = Point::new([1.0, 2.0, 4.0]); + let p3d_a = Point::from_validated_coords([1.0, 2.0, 3.0]); + let p3d_b = Point::from_validated_coords([1.0, 2.0, 3.0]); + let p3d_c = Point::from_validated_coords([1.0, 2.0, 4.0]); assert_eq!(p3d_a, p3d_b); assert_ne!(p3d_a, p3d_c); // 4D - let p4d_a = Point::new([1.0, 2.0, 3.0, 4.0]); - let p4d_b = Point::new([1.0, 2.0, 3.0, 4.0]); - let p4d_c = Point::new([1.0, 2.0, 3.0, 5.0]); + let p4d_a = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); + let p4d_b = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); + let p4d_c = Point::from_validated_coords([1.0, 2.0, 3.0, 5.0]); assert_eq!(p4d_a, p4d_b); assert_ne!(p4d_a, p4d_c); // 5D - let p5d_a = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); - let p5d_b = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); - let p5d_c = Point::new([1.0, 2.0, 3.0, 4.0, 6.0]); + let p5d_a = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); + let p5d_b = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); + let p5d_c = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 6.0]); assert_eq!(p5d_a, p5d_b); assert_ne!(p5d_a, p5d_c); } @@ -546,23 +581,23 @@ mod tests { #[test] fn $test_name() { // 2D - let p2d_a = Point::new([1.0, 2.0]); - let p2d_b = Point::new([1.0, 2.0]); + let p2d_a = Point::from_validated_coords([1.0, 2.0]); + let p2d_b = Point::from_validated_coords([1.0, 2.0]); assert_eq!(hash_of(&p2d_a), hash_of(&p2d_b)); // 3D - let p3d_a = Point::new([1.0, 2.0, 3.0]); - let p3d_b = Point::new([1.0, 2.0, 3.0]); + let p3d_a = Point::from_validated_coords([1.0, 2.0, 3.0]); + let p3d_b = Point::from_validated_coords([1.0, 2.0, 3.0]); assert_eq!(hash_of(&p3d_a), hash_of(&p3d_b)); // 4D - let p4d_a = Point::new([1.0, 2.0, 3.0, 4.0]); - let p4d_b = Point::new([1.0, 2.0, 3.0, 4.0]); + let p4d_a = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); + let p4d_b = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); assert_eq!(hash_of(&p4d_a), hash_of(&p4d_b)); // 5D - let p5d_a = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); - let p5d_b = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let p5d_a = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); + let p5d_b = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); assert_eq!(hash_of(&p5d_a), hash_of(&p5d_b)); } }; @@ -572,26 +607,26 @@ mod tests { #[test] fn $test_name() { // 2D - lexicographic ordering - let p2d_a = Point::new([1.0, 2.0]); - let p2d_b = Point::new([1.0, 3.0]); + let p2d_a = Point::from_validated_coords([1.0, 2.0]); + let p2d_b = Point::from_validated_coords([1.0, 3.0]); assert!(p2d_a < p2d_b); assert!(p2d_b > p2d_a); // 3D - let p3d_a = Point::new([1.0, 2.0, 3.0]); - let p3d_b = Point::new([1.0, 2.0, 4.0]); + let p3d_a = Point::from_validated_coords([1.0, 2.0, 3.0]); + let p3d_b = Point::from_validated_coords([1.0, 2.0, 4.0]); assert!(p3d_a < p3d_b); assert!(p3d_b > p3d_a); // 4D - let p4d_a = Point::new([1.0, 2.0, 3.0, 4.0]); - let p4d_b = Point::new([1.0, 2.0, 3.0, 5.0]); + let p4d_a = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); + let p4d_b = Point::from_validated_coords([1.0, 2.0, 3.0, 5.0]); assert!(p4d_a < p4d_b); assert!(p4d_b > p4d_a); // 5D - let p5d_a = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); - let p5d_b = Point::new([1.0, 2.0, 3.0, 4.0, 6.0]); + let p5d_a = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); + let p5d_b = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 6.0]); assert!(p5d_a < p5d_b); assert!(p5d_b > p5d_a); } @@ -602,28 +637,24 @@ mod tests { #[test] fn $test_name() { // 2D - valid and invalid - let valid_2d = Point::new([1.0, 2.0]); + let valid_2d = Point::from_validated_coords([1.0, 2.0]); assert!(valid_2d.validate().is_ok()); - let invalid_2d = Point::new([f64::NAN, 2.0]); - assert!(invalid_2d.validate().is_err()); + assert!(Point::<2>::try_new([f64::NAN, 2.0]).is_err()); // 3D - let valid_3d = Point::new([1.0, 2.0, 3.0]); + let valid_3d = Point::from_validated_coords([1.0, 2.0, 3.0]); assert!(valid_3d.validate().is_ok()); - let invalid_3d = Point::new([1.0, f64::INFINITY, 3.0]); - assert!(invalid_3d.validate().is_err()); + assert!(Point::<3>::try_new([1.0, f64::INFINITY, 3.0]).is_err()); // 4D - let valid_4d = Point::new([1.0, 2.0, 3.0, 4.0]); + let valid_4d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); assert!(valid_4d.validate().is_ok()); - let invalid_4d = Point::new([1.0, 2.0, f64::NEG_INFINITY, 4.0]); - assert!(invalid_4d.validate().is_err()); + assert!(Point::<4>::try_new([1.0, 2.0, f64::NEG_INFINITY, 4.0]).is_err()); // 5D - let valid_5d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let valid_5d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); assert!(valid_5d.validate().is_ok()); - let invalid_5d = Point::new([1.0, 2.0, 3.0, f64::NAN, 5.0]); - assert!(invalid_5d.validate().is_err()); + assert!(Point::<5>::try_new([1.0, 2.0, 3.0, f64::NAN, 5.0]).is_err()); } }; @@ -632,27 +663,27 @@ mod tests { #[test] fn $test_name() { // 2D - let p2d = Point::new([1.0, 2.0]); + let p2d = Point::from_validated_coords([1.0, 2.0]); let json2d = serde_json::to_string(&p2d).unwrap(); - let de2d: Point = serde_json::from_str(&json2d).unwrap(); + let de2d: Point<2> = serde_json::from_str(&json2d).unwrap(); assert_eq!(p2d, de2d); // 3D - let p3d = Point::new([1.0, 2.0, 3.0]); + let p3d = Point::from_validated_coords([1.0, 2.0, 3.0]); let json3d = serde_json::to_string(&p3d).unwrap(); - let de3d: Point = serde_json::from_str(&json3d).unwrap(); + let de3d: Point<3> = serde_json::from_str(&json3d).unwrap(); assert_eq!(p3d, de3d); // 4D - let p4d = Point::new([1.0, 2.0, 3.0, 4.0]); + let p4d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); let json4d = serde_json::to_string(&p4d).unwrap(); - let de4d: Point = serde_json::from_str(&json4d).unwrap(); + let de4d: Point<4> = serde_json::from_str(&json4d).unwrap(); assert_eq!(p4d, de4d); // 5D - let p5d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let p5d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); let json5d = serde_json::to_string(&p5d).unwrap(); - let de5d: Point = serde_json::from_str(&json5d).unwrap(); + let de5d: Point<5> = serde_json::from_str(&json5d).unwrap(); assert_eq!(p5d, de5d); } }; @@ -662,22 +693,22 @@ mod tests { #[test] fn $test_name() { // 2D - let origin_2d: Point = Point::origin(); + let origin_2d: Point<2> = Point::origin(); assert_relative_eq!(origin_2d.to_array().as_slice(), [0.0, 0.0].as_slice()); // 3D - let origin_3d: Point = Point::origin(); + let origin_3d: Point<3> = Point::origin(); assert_relative_eq!(origin_3d.to_array().as_slice(), [0.0, 0.0, 0.0].as_slice()); // 4D - let origin_4d: Point = Point::origin(); + let origin_4d: Point<4> = Point::origin(); assert_relative_eq!( origin_4d.to_array().as_slice(), [0.0, 0.0, 0.0, 0.0].as_slice() ); // 5D - let origin_5d: Point = Point::origin(); + let origin_5d: Point<5> = Point::origin(); assert_relative_eq!( origin_5d.to_array().as_slice(), [0.0, 0.0, 0.0, 0.0, 0.0].as_slice() @@ -690,28 +721,40 @@ mod tests { #[test] fn $test_name() { // 2D - let mut map2d: HashMap, i32> = HashMap::new(); - let p2d = Point::new([1.0, 2.0]); + let mut map2d: HashMap, i32> = HashMap::new(); + let p2d = Point::from_validated_coords([1.0, 2.0]); map2d.insert(p2d, 42); - assert_eq!(map2d.get(&Point::new([1.0, 2.0])), Some(&42)); + assert_eq!( + map2d.get(&Point::from_validated_coords([1.0, 2.0])), + Some(&42) + ); // 3D - let mut map3d: HashMap, i32> = HashMap::new(); - let p3d = Point::new([1.0, 2.0, 3.0]); + let mut map3d: HashMap, i32> = HashMap::new(); + let p3d = Point::from_validated_coords([1.0, 2.0, 3.0]); map3d.insert(p3d, 42); - assert_eq!(map3d.get(&Point::new([1.0, 2.0, 3.0])), Some(&42)); + assert_eq!( + map3d.get(&Point::from_validated_coords([1.0, 2.0, 3.0])), + Some(&42) + ); // 4D - let mut map4d: HashMap, i32> = HashMap::new(); - let p4d = Point::new([1.0, 2.0, 3.0, 4.0]); + let mut map4d: HashMap, i32> = HashMap::new(); + let p4d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); map4d.insert(p4d, 42); - assert_eq!(map4d.get(&Point::new([1.0, 2.0, 3.0, 4.0])), Some(&42)); + assert_eq!( + map4d.get(&Point::from_validated_coords([1.0, 2.0, 3.0, 4.0])), + Some(&42) + ); // 5D - let mut map5d: HashMap, i32> = HashMap::new(); - let p5d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let mut map5d: HashMap, i32> = HashMap::new(); + let p5d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); map5d.insert(p5d, 42); - assert_eq!(map5d.get(&Point::new([1.0, 2.0, 3.0, 4.0, 5.0])), Some(&42)); + assert_eq!( + map5d.get(&Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0])), + Some(&42) + ); } }; @@ -720,7 +763,7 @@ mod tests { #[test] fn $test_name() { // 2D - let p2d_original = Point::new([1.0, 2.0]); + let p2d_original = Point::from_validated_coords([1.0, 2.0]); let p2d_copy = p2d_original; assert_eq!(p2d_original, p2d_copy); assert_relative_eq!( @@ -729,17 +772,17 @@ mod tests { ); // 3D - let p3d_original = Point::new([1.0, 2.0, 3.0]); + let p3d_original = Point::from_validated_coords([1.0, 2.0, 3.0]); let p3d_copy = p3d_original; assert_eq!(p3d_original, p3d_copy); // 4D - let p4d_original = Point::new([1.0, 2.0, 3.0, 4.0]); + let p4d_original = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); let p4d_copy = p4d_original; assert_eq!(p4d_original, p4d_copy); // 5D - let p5d_original = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let p5d_original = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); let p5d_copy = p5d_original; assert_eq!(p5d_original, p5d_copy); } @@ -763,7 +806,7 @@ mod tests { #[test] fn point_default() { - let point: Point = Point::default(); + let point: Point<4> = Point::default(); let coords = point.to_array(); assert_relative_eq!( @@ -771,17 +814,12 @@ mod tests { [0.0, 0.0, 0.0, 0.0].as_slice(), epsilon = 1e-9 ); - - // Human readable output for cargo test -- --nocapture - println!("Default: {point:?}"); } - // point_new, point_copy, point_dim removed - covered by point_creation_dimensional and point_copy_dimensional - #[test] fn point_coords() { // Test coords() method provides read-only access - let point = Point::new([1.0, 2.0, 3.0]); + let point = Point::from_validated_coords([1.0, 2.0, 3.0]); let coords_ref = point.coords(); assert_relative_eq!( coords_ref.as_slice(), @@ -796,14 +834,14 @@ mod tests { assert_relative_eq!(coords_ref[2], 3.0, epsilon = 1e-9); // Test with different dimensions - let point_2d = Point::new([5.5, -2.5]); + let point_2d = Point::from_validated_coords([5.5, -2.5]); assert_relative_eq!( point_2d.coords().as_slice(), [5.5, -2.5].as_slice(), epsilon = 1e-9 ); - let point_4d = Point::new([1.0, 2.0, 3.0, 4.0]); + let point_4d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0]); assert_relative_eq!( point_4d.coords().as_slice(), [1.0, 2.0, 3.0, 4.0].as_slice(), @@ -811,25 +849,19 @@ mod tests { ); // Test with 5D - let point_5d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let point_5d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); assert_relative_eq!( point_5d.coords().as_slice(), [1.0, 2.0, 3.0, 4.0, 5.0].as_slice(), epsilon = 1e-9 ); assert_eq!(point_5d.coords().len(), 5); - - println!("coords() provides efficient read-only access to coordinates"); } - // point_origin removed - covered by point_origin_dimensional - - // point_serialization removed - basic cases covered by point_serialization_dimensional - #[test] fn point_from_integer_array_to_f64() { let coords = [1, 2, 3, 4]; - let point: Point = Point::new(coords.map(Into::into)); + let point: Point<4> = Point::from_validated_coords(coords.map(Into::into)); let result_coords = point.to_array(); assert_relative_eq!( @@ -844,7 +876,7 @@ mod tests { fn point_type_conversions() { // Test same-type conversion. let coords_f64 = [1.5, 2.5]; - let point_f64: Point = Point::new(coords_f64); + let point_f64: Point<2> = Point::from_validated_coords(coords_f64); let result_f64 = point_f64.to_array(); assert_relative_eq!(result_f64.as_slice(), [1.5, 2.5].as_slice(), epsilon = 1e-9); } @@ -855,7 +887,7 @@ mod tests { #[test] fn point_debug_format() { - let point = Point::new([1.0, 2.0, 3.0]); + let point = Point::from_validated_coords([1.0, 2.0, 3.0]); let debug_str = format!("{point:?}"); assert!(debug_str.contains("Point")); @@ -867,9 +899,9 @@ mod tests { #[test] fn point_eq_trait() { - let point1 = Point::new([1.0, 2.0, 3.0]); - let point2 = Point::new([1.0, 2.0, 3.0]); - let point3 = Point::new([1.0, 2.0, 4.0]); + let point1 = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point2 = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point3 = Point::from_validated_coords([1.0, 2.0, 4.0]); // Test Eq trait (transitivity, reflexivity, symmetry) assert_eq!(point1, point1); // reflexive @@ -882,36 +914,36 @@ mod tests { #[test] fn point_comprehensive_serialization() { // Test with different types and dimensions - let point_3d = Point::new([1.0, 2.0, 3.0]); + let point_3d = Point::from_validated_coords([1.0, 2.0, 3.0]); let serialized_3d = serde_json::to_string(&point_3d).unwrap(); - let deserialized_3d: Point = serde_json::from_str(&serialized_3d).unwrap(); + let deserialized_3d: Point<3> = serde_json::from_str(&serialized_3d).unwrap(); assert_eq!(point_3d, deserialized_3d); - let point_2d = Point::new([10.5, -5.3]); + let point_2d = Point::from_validated_coords([10.5, -5.3]); let serialized_2d = serde_json::to_string(&point_2d).unwrap(); - let deserialized_2d: Point = serde_json::from_str(&serialized_2d).unwrap(); + let deserialized_2d: Point<2> = serde_json::from_str(&serialized_2d).unwrap(); assert_eq!(point_2d, deserialized_2d); - let point_1d = Point::new([42.0]); + let point_1d = Point::from_validated_coords([42.0]); let serialized_1d = serde_json::to_string(&point_1d).unwrap(); - let deserialized_1d: Point = serde_json::from_str(&serialized_1d).unwrap(); + let deserialized_1d: Point<1> = serde_json::from_str(&serialized_1d).unwrap(); assert_eq!(point_1d, deserialized_1d); // Test with very large and small numbers (roundtrip) - let point_large = Point::new([1e100, -1e100, 0.0]); + let point_large = Point::from_validated_coords([1e100, -1e100, 0.0]); let serialized_large = serde_json::to_string(&point_large).unwrap(); - let deserialized_large: Point = serde_json::from_str(&serialized_large).unwrap(); + let deserialized_large: Point<3> = serde_json::from_str(&serialized_large).unwrap(); assert_eq!(point_large, deserialized_large); - let point_small = Point::new([1e-100, -1e-100, 0.0]); + let point_small = Point::from_validated_coords([1e-100, -1e-100, 0.0]); let serialized_small = serde_json::to_string(&point_small).unwrap(); - let deserialized_small: Point = serde_json::from_str(&serialized_small).unwrap(); + let deserialized_small: Point<3> = serde_json::from_str(&serialized_small).unwrap(); assert_eq!(point_small, deserialized_small); } #[test] fn point_negative_coordinates() { - let point = Point::new([-1.0, -2.0, -3.0]); + let point = Point::from_validated_coords([-1.0, -2.0, -3.0]); assert_relative_eq!( point.to_array().as_slice(), @@ -921,7 +953,7 @@ mod tests { assert_eq!(point.dim(), 3); // Test with mixed positive/negative - let mixed_point = Point::new([1.0, -2.0, 3.0, -4.0]); + let mixed_point = Point::from_validated_coords([1.0, -2.0, 3.0, -4.0]); assert_relative_eq!( mixed_point.to_array().as_slice(), [1.0, -2.0, 3.0, -4.0].as_slice(), @@ -931,8 +963,8 @@ mod tests { #[test] fn point_zero_coordinates() { - let zero_point = Point::new([0.0, 0.0, 0.0]); - let origin: Point = Point::origin(); + let zero_point = Point::from_validated_coords([0.0, 0.0, 0.0]); + let origin: Point<3> = Point::origin(); assert_eq!(zero_point, origin); assert_relative_eq!( @@ -944,7 +976,7 @@ mod tests { #[test] fn point_large_coordinates() { - let large_point = Point::new([1e6, 2e6, 3e6]); + let large_point = Point::from_validated_coords([1e6, 2e6, 3e6]); let coords = large_point.to_array(); assert_relative_eq!( @@ -957,7 +989,7 @@ mod tests { #[test] fn point_small_coordinates() { - let small_point = Point::new([1e-6, 2e-6, 3e-6]); + let small_point = Point::from_validated_coords([1e-6, 2e-6, 3e-6]); let coords = small_point.to_array(); assert_relative_eq!( @@ -970,8 +1002,8 @@ mod tests { #[test] fn point_ordering_edge_cases() { - let point1 = Point::new([1.0, 2.0]); - let point2 = Point::new([1.0, 2.0]); + let point1 = Point::from_validated_coords([1.0, 2.0]); + let point2 = Point::from_validated_coords([1.0, 2.0]); // Test that equal points are not less than each other assert_ne!(point1.partial_cmp(&point2), Some(Ordering::Less)); @@ -985,9 +1017,9 @@ mod tests { #[test] fn point_eq_different_types() { // Test Eq for f64 - let point_f64_1 = Point::new([1.0, 2.0]); - let point_f64_2 = Point::new([1.0, 2.0]); - let point_f64_3 = Point::new([1.0, 2.1]); + let point_f64_1 = Point::from_validated_coords([1.0, 2.0]); + let point_f64_2 = Point::from_validated_coords([1.0, 2.0]); + let point_f64_3 = Point::from_validated_coords([1.0, 2.1]); assert_eq!(point_f64_1, point_f64_2); assert_ne!(point_f64_1, point_f64_3); @@ -996,21 +1028,21 @@ mod tests { #[test] fn point_hash_consistency_floating_point() { // Test that OrderedFloat provides consistent hashing for NaN-free floats - let point1 = Point::new([1.0, 2.0, 3.5]); - let point2 = Point::new([1.0, 2.0, 3.5]); + let point1 = Point::from_validated_coords([1.0, 2.0, 3.5]); + let point2 = Point::from_validated_coords([1.0, 2.0, 3.5]); test_point_equality_and_hash(point1, point2, true); } #[test] fn point_implicit_conversion_to_coordinates() { - let point: Point = Point::new([1.0, 2.0, 3.0]); + let point: Point<3> = Point::from_validated_coords([1.0, 2.0, 3.0]); // Test implicit conversion from owned point let coords_owned: [f64; 3] = point.into(); assert_relative_eq!(coords_owned.as_slice(), [1.0, 2.0, 3.0].as_slice()); // Create a new point for reference test - let point_ref: Point = Point::new([4.0, 5.0, 6.0]); + let point_ref: Point<3> = Point::from_validated_coords([4.0, 5.0, 6.0]); // Test implicit conversion from point reference let coords_ref: [f64; 3] = (&point_ref).into(); @@ -1027,74 +1059,59 @@ mod tests { #[test] fn point_is_valid_f64() { // Test valid f64 points - let valid_point = Point::new([1.0, 2.0, 3.0]); + let valid_point = Point::from_validated_coords([1.0, 2.0, 3.0]); assert!(valid_point.validate().is_ok()); - let valid_negative = Point::new([-1.0, -2.0, -3.0]); + let valid_negative = Point::from_validated_coords([-1.0, -2.0, -3.0]); assert!(valid_negative.validate().is_ok()); - let valid_zero = Point::new([0.0, 0.0, 0.0]); + let valid_zero = Point::from_validated_coords([0.0, 0.0, 0.0]); assert!(valid_zero.validate().is_ok()); - let valid_mixed = Point::new([1.0, -2.5, 0.0, 42.7]); + let valid_mixed = Point::from_validated_coords([1.0, -2.5, 0.0, 42.7]); assert!(valid_mixed.validate().is_ok()); - // Test invalid f64 points with NaN - let invalid_nan_single = Point::new([1.0, f64::NAN, 3.0]); - assert!(invalid_nan_single.validate().is_err()); - - let invalid_nan_all = Point::new([f64::NAN, f64::NAN, f64::NAN]); - assert!(invalid_nan_all.validate().is_err()); - - let invalid_nan_first = Point::new([f64::NAN, 2.0, 3.0]); - assert!(invalid_nan_first.validate().is_err()); - - let invalid_nan_last = Point::new([1.0, 2.0, f64::NAN]); - assert!(invalid_nan_last.validate().is_err()); - - // Test invalid f64 points with infinity - let invalid_pos_inf = Point::new([1.0, f64::INFINITY, 3.0]); - assert!(invalid_pos_inf.validate().is_err()); + // Test invalid f64 input with NaN + assert!(Point::<3>::try_new([1.0, f64::NAN, 3.0]).is_err()); + assert!(Point::<3>::try_new([f64::NAN, f64::NAN, f64::NAN]).is_err()); + assert!(Point::<3>::try_new([f64::NAN, 2.0, 3.0]).is_err()); + assert!(Point::<3>::try_new([1.0, 2.0, f64::NAN]).is_err()); - let invalid_neg_inf = Point::new([1.0, f64::NEG_INFINITY, 3.0]); - assert!(invalid_neg_inf.validate().is_err()); - - let invalid_both_inf = Point::new([f64::INFINITY, f64::NEG_INFINITY]); - assert!(invalid_both_inf.validate().is_err()); + // Test invalid f64 input with infinity + assert!(Point::<3>::try_new([1.0, f64::INFINITY, 3.0]).is_err()); + assert!(Point::<3>::try_new([1.0, f64::NEG_INFINITY, 3.0]).is_err()); + assert!(Point::<2>::try_new([f64::INFINITY, f64::NEG_INFINITY]).is_err()); // Test mixed invalid cases - let invalid_nan_and_inf = Point::new([f64::NAN, f64::INFINITY, 1.0]); - assert!(invalid_nan_and_inf.validate().is_err()); + assert!(Point::<3>::try_new([f64::NAN, f64::INFINITY, 1.0]).is_err()); } #[test] fn point_is_valid_different_dimensions() { // Test 1D points - let valid_1d_f64 = Point::new([42.0]); + let valid_1d_f64 = Point::from_validated_coords([42.0]); assert!(valid_1d_f64.validate().is_ok()); - let invalid_1d_nan = Point::new([f64::NAN]); - assert!(invalid_1d_nan.validate().is_err()); + assert!(Point::<1>::try_new([f64::NAN]).is_err()); // Test 2D points - let valid_2d = Point::new([1.0, 2.0]); + let valid_2d = Point::from_validated_coords([1.0, 2.0]); assert!(valid_2d.validate().is_ok()); - let invalid_2d = Point::new([1.0, f64::INFINITY]); - assert!(invalid_2d.validate().is_err()); + assert!(Point::<2>::try_new([1.0, f64::INFINITY]).is_err()); // Test higher dimensional points - let valid_5d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let valid_5d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); assert!(valid_5d.validate().is_ok()); - let invalid_5d = Point::new([1.0, 2.0, f64::NAN, 4.0, 5.0]); - assert!(invalid_5d.validate().is_err()); + assert!(Point::<5>::try_new([1.0, 2.0, f64::NAN, 4.0, 5.0]).is_err()); // Test 10D point - let valid_10d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]); + let valid_10d = + Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]); assert!(valid_10d.validate().is_ok()); - let invalid_10d = Point::new([ + let invalid_10d = [ 1.0, 2.0, 3.0, @@ -1105,284 +1122,58 @@ mod tests { 8.0, 9.0, 10.0, - ]); - assert!(invalid_10d.validate().is_err()); + ]; + assert!(Point::<10>::try_new(invalid_10d).is_err()); } #[test] fn point_is_valid_edge_cases() { // Test with very small finite values - let tiny_valid = Point::new([f64::MIN_POSITIVE, -f64::MIN_POSITIVE, 0.0]); + let tiny_valid = Point::from_validated_coords([f64::MIN_POSITIVE, -f64::MIN_POSITIVE, 0.0]); assert!(tiny_valid.validate().is_ok()); // Test with very large finite values - let large_valid = Point::new([f64::MAX, -f64::MAX]); + let large_valid = Point::from_validated_coords([f64::MAX, -f64::MAX]); assert!(large_valid.validate().is_ok()); // Test subnormal numbers (should be valid) let subnormal = f64::MIN_POSITIVE / 2.0; - let subnormal_point = Point::new([subnormal, -subnormal]); + let subnormal_point = Point::from_validated_coords([subnormal, -subnormal]); assert!(subnormal_point.validate().is_ok()); // Test zero and negative zero - let zero_point = Point::new([0.0, -0.0]); + let zero_point = Point::from_validated_coords([0.0, -0.0]); assert!(zero_point.validate().is_ok()); // Mixed valid and invalid in same point should be invalid - let mixed_invalid = Point::new([1.0, 2.0, 3.0, f64::NAN, 5.0]); - assert!(mixed_invalid.validate().is_err()); + assert!(Point::<5>::try_new([1.0, 2.0, 3.0, f64::NAN, 5.0]).is_err()); // All coordinates must be valid for point to be valid - let one_invalid = Point::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, f64::INFINITY]); - assert!(one_invalid.validate().is_err()); + let one_invalid = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, f64::INFINITY]; + assert!(Point::<8>::try_new(one_invalid).is_err()); } #[test] - fn point_special_values_hash_consistency() { - // Test that OrderedFloat provides consistent hashing for NaN and infinity values - - // Test NaN hash consistency - let point_nan1 = Point::new([f64::NAN, 2.0]); - let point_nan2 = Point::new([f64::NAN, 2.0]); - assert_eq!(hash_of(&point_nan1), hash_of(&point_nan2)); - - // Test infinity hash consistency - let point_pos_inf1 = Point::new([f64::INFINITY, 2.0]); - let point_pos_inf2 = Point::new([f64::INFINITY, 2.0]); - assert_eq!(hash_of(&point_pos_inf1), hash_of(&point_pos_inf2)); - - let point_neg_inf1 = Point::new([f64::NEG_INFINITY, 2.0]); - let point_neg_inf2 = Point::new([f64::NEG_INFINITY, 2.0]); - assert_eq!(hash_of(&point_neg_inf1), hash_of(&point_neg_inf2)); - - // Positive and negative infinity should hash differently - assert_ne!(hash_of(&point_pos_inf1), hash_of(&point_neg_inf1)); - - // Test HashMap usage with special values - let mut map: HashMap, i32> = HashMap::new(); - let point_nan_lookup = Point::new([f64::NAN, 2.0]); - let point_inf_lookup = Point::new([f64::INFINITY, 2.0]); - - map.insert(point_nan1, 100); - map.insert(point_pos_inf1, 200); - - // Should be able to retrieve using equivalent points - assert_eq!(map.get(&point_nan_lookup), Some(&100)); - assert_eq!(map.get(&point_inf_lookup), Some(&200)); - assert_eq!(map.len(), 2); - } - - #[test] - fn point_nan_equality_comparison() { - // Test that NaN == NaN using our OrderedEq implementation - // This is different from IEEE 754 standard where NaN != NaN - - // f64 NaN comparisons - let point_nan1 = Point::new([f64::NAN, 2.0, 3.0]); - let point_nan2 = Point::new([f64::NAN, 2.0, 3.0]); - let point_nan3 = Point::new([f64::NAN, f64::NAN, f64::NAN]); - let point_nan4 = Point::new([f64::NAN, f64::NAN, f64::NAN]); - - // Points with NaN should be equal when all coordinates match - assert_eq!(point_nan1, point_nan2); - assert_eq!(point_nan3, point_nan4); - - // Points with different NaN positions should not be equal - let point_nan_diff1 = Point::new([f64::NAN, 2.0, 3.0]); - let point_nan_diff2 = Point::new([1.0, f64::NAN, 3.0]); - assert_ne!(point_nan_diff1, point_nan_diff2); - - // Mixed NaN and normal values - let point_mixed1 = Point::new([1.0, f64::NAN, 3.0, 4.0]); - let point_mixed2 = Point::new([1.0, f64::NAN, 3.0, 4.0]); - let point_mixed3 = Point::new([1.0, f64::NAN, 3.0, 5.0]); // Different last coordinate - - assert_eq!(point_mixed1, point_mixed2); - assert_ne!(point_mixed1, point_mixed3); - } - - #[test] - fn point_nan_vs_normal_comparison() { - // Test that NaN points are not equal to points with normal values - - let point_normal = Point::new([1.0, 2.0, 3.0]); - let point_nan = Point::new([f64::NAN, 2.0, 3.0]); - let point_nan_all = Point::new([f64::NAN, f64::NAN, f64::NAN]); - - // NaN points should not equal normal points - assert_ne!(point_normal, point_nan); - assert_ne!(point_normal, point_nan_all); - assert_ne!(point_nan, point_normal); - assert_ne!(point_nan_all, point_normal); - } - - #[test] - fn point_infinity_comparison() { - // Test comparison behavior with infinity values - - // Positive infinity comparisons - let point_pos_inf1 = Point::new([f64::INFINITY, 2.0]); - let point_pos_inf2 = Point::new([f64::INFINITY, 2.0]); - assert_eq!(point_pos_inf1, point_pos_inf2); - - // Negative infinity comparisons - let point_neg_inf1 = Point::new([f64::NEG_INFINITY, 2.0]); - let point_neg_inf2 = Point::new([f64::NEG_INFINITY, 2.0]); - assert_eq!(point_neg_inf1, point_neg_inf2); - - // Positive vs negative infinity should not be equal - assert_ne!(point_pos_inf1, point_neg_inf1); - - // Infinity vs normal values should not be equal - let point_normal = Point::new([1.0, 2.0]); - assert_ne!(point_pos_inf1, point_normal); - assert_ne!(point_neg_inf1, point_normal); - } - - #[test] - fn point_nan_infinity_mixed_comparison() { - // Test comparisons with mixed NaN and infinity values - - let point_nan_inf1 = Point::new([f64::NAN, f64::INFINITY, 1.0]); - let point_nan_inf2 = Point::new([f64::NAN, f64::INFINITY, 1.0]); - let point_nan_inf3 = Point::new([f64::NAN, f64::NEG_INFINITY, 1.0]); - - // Same NaN/infinity pattern should be equal - assert_eq!(point_nan_inf1, point_nan_inf2); - - // Different infinity signs should not be equal - assert_ne!(point_nan_inf1, point_nan_inf3); - - // Test various combinations - let point_all_special = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, f64::NAN]); - let point_all_special_copy = - Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, f64::NAN]); - let point_all_special_diff = - Point::new([f64::NAN, f64::NEG_INFINITY, f64::INFINITY, f64::NAN]); - - assert_eq!(point_all_special, point_all_special_copy); - assert_ne!(point_all_special, point_all_special_diff); - } - - #[test] - fn point_nan_equality_properties() { - // Test that NaN equality follows mathematical properties: reflexivity, symmetry, and transitivity - - // Test reflexivity: NaN points are equal to themselves - let point_nan = Point::new([f64::NAN, f64::NAN, f64::NAN]); - assert_eq!(point_nan, point_nan); - let point_mixed = Point::new([1.0, f64::NAN, 3.0, f64::INFINITY]); - assert_eq!(point_mixed, point_mixed); - - // Test symmetry: if a == b, then b == a - let point_a = Point::new([f64::NAN, 2.0, f64::INFINITY]); - let point_b = Point::new([f64::NAN, 2.0, f64::INFINITY]); - assert_eq!(point_a, point_b); - assert_eq!(point_b, point_a); - - // Test transitivity: if a == b and b == c, then a == c - let point_c = Point::new([f64::NAN, 2.0, f64::INFINITY]); - assert_eq!(point_a, point_b); - assert_eq!(point_b, point_c); - assert_eq!(point_a, point_c); - } - - #[test] - fn point_nan_different_bit_patterns() { - // Test that different NaN bit patterns are considered equal - // Note: Rust's f64::NAN is a specific bit pattern, but there are many possible NaN values - - // Create different NaN values + fn point_rejects_non_finite_bit_patterns() { let nan1 = f64::NAN; - #[expect( - clippy::zero_divided_by_zero, - reason = "test deliberately constructs an alternate NaN bit pattern" - )] - let nan2 = 0.0f64 / 0.0f64; // Another way to create NaN - let nan3 = f64::INFINITY - f64::INFINITY; // Yet another way + let nan2 = f64::from_bits(0x7ff8_0000_0000_0001); + let nan3 = f64::from_bits(0x7ff8_0000_0000_0002); // Verify they are all NaN assert!(nan1.is_nan()); assert!(nan2.is_nan()); assert!(nan3.is_nan()); - - // Points with different NaN bit patterns should be equal - let point1 = Point::new([nan1, 1.0]); - let point2 = Point::new([nan2, 1.0]); - let point3 = Point::new([nan3, 1.0]); - - assert_eq!(point1, point2); - assert_eq!(point2, point3); - assert_eq!(point1, point3); - } - - #[test] - fn point_nan_in_different_dimensions() { - // Test NaN behavior across different dimensionalities - - // 1D - let point_1d_a = Point::new([f64::NAN]); - let point_1d_b = Point::new([f64::NAN]); - assert_eq!(point_1d_a, point_1d_b); - - // 2D - let point_2d_a = Point::new([f64::NAN, f64::NAN]); - let point_2d_b = Point::new([f64::NAN, f64::NAN]); - assert_eq!(point_2d_a, point_2d_b); - - // 3D - let point_3d_a = Point::new([f64::NAN, 1.0, f64::NAN]); - let point_3d_b = Point::new([f64::NAN, 1.0, f64::NAN]); - assert_eq!(point_3d_a, point_3d_b); - - // 5D - let point_5d_a = Point::new([f64::NAN, 1.0, f64::NAN, f64::INFINITY, f64::NAN]); - let point_5d_b = Point::new([f64::NAN, 1.0, f64::NAN, f64::INFINITY, f64::NAN]); - assert_eq!(point_5d_a, point_5d_b); - - // 10D with mixed special values - let point_10d_a = Point::new([ - f64::NAN, - 1.0, - f64::NAN, - f64::INFINITY, - f64::NEG_INFINITY, - 0.0, - -0.0, - f64::NAN, - 42.0, - f64::NAN, - ]); - let point_10d_b = Point::new([ - f64::NAN, - 1.0, - f64::NAN, - f64::INFINITY, - f64::NEG_INFINITY, - 0.0, - -0.0, - f64::NAN, - 42.0, - f64::NAN, - ]); - assert_eq!(point_10d_a, point_10d_b); + assert!(Point::<2>::try_new([nan1, 1.0]).is_err()); + assert!(Point::<2>::try_new([nan2, 1.0]).is_err()); + assert!(Point::<2>::try_new([nan3, 1.0]).is_err()); } #[test] - fn point_nan_zero_comparison() { - // Test comparison between NaN, positive zero, and negative zero - - let point_nan = Point::new([f64::NAN, f64::NAN]); - let point_pos_zero = Point::new([0.0, 0.0]); - let point_neg_zero = Point::new([-0.0, -0.0]); - let point_mixed_zero = Point::new([0.0, -0.0]); + fn point_zero_comparison() { + let point_pos_zero = Point::from_validated_coords([0.0, 0.0]); + let point_neg_zero = Point::from_validated_coords([-0.0, -0.0]); + let point_mixed_zero = Point::from_validated_coords([0.0, -0.0]); - // NaN should not equal any zero - assert_ne!(point_nan, point_pos_zero); - assert_ne!(point_nan, point_neg_zero); - assert_ne!(point_nan, point_mixed_zero); - - // Different zeros should be equal (0.0 == -0.0 in IEEE 754) assert_eq!(point_pos_zero, point_neg_zero); assert_eq!(point_pos_zero, point_mixed_zero); assert_eq!(point_neg_zero, point_mixed_zero); @@ -1398,14 +1189,14 @@ mod tests { // Test 20D point let coords_20d = [1.0; 20]; - let point_20d = Point::new(coords_20d); + let point_20d = Point::from_validated_coords(coords_20d); assert_eq!(point_20d.dim(), 20); assert_relative_eq!(point_20d.to_array().as_slice(), coords_20d.as_slice()); assert!(point_20d.validate().is_ok()); // Test 25D point let coords_25d = [2.5; 25]; - let point_25d = Point::new(coords_25d); + let point_25d = Point::from_validated_coords(coords_25d); assert_eq!(point_25d.dim(), 25); assert_relative_eq!(point_25d.to_array().as_slice(), coords_25d.as_slice()); assert!(point_25d.validate().is_ok()); @@ -1415,7 +1206,7 @@ mod tests { for (i, coord) in coords_32d.iter_mut().enumerate() { *coord = i as f64; } - let point_32d = Point::new(coords_32d); + let point_32d = Point::from_validated_coords(coords_32d); assert_eq!(point_32d.dim(), 32); assert_relative_eq!(point_32d.to_array().as_slice(), coords_32d.as_slice()); assert!(point_32d.validate().is_ok()); @@ -1423,18 +1214,17 @@ mod tests { // Test high dimensional point with NaN let mut coords_with_nan = [1.0; 25]; coords_with_nan[12] = f64::NAN; - let point_with_nan = Point::new(coords_with_nan); - assert!(point_with_nan.validate().is_err()); + assert!(Point::<25>::try_new(coords_with_nan).is_err()); // Test equality for high dimensional points - let point_20d_copy = Point::new([1.0; 20]); + let point_20d_copy = Point::from_validated_coords([1.0; 20]); assert_eq!(point_20d, point_20d_copy); // Test with 30D points let coords_30d_a = [std::f64::consts::PI; 30]; let coords_30d_b = [std::f64::consts::PI; 30]; - let point_30d_a = Point::new(coords_30d_a); - let point_30d_b = Point::new(coords_30d_b); + let point_30d_a = Point::from_validated_coords(coords_30d_a); + let point_30d_b = Point::from_validated_coords(coords_30d_b); assert_eq!(point_30d_a, point_30d_b); assert!(point_30d_a.validate().is_ok()); } @@ -1444,17 +1234,17 @@ mod tests { // Test with extreme numeric values // Test with very large f64 values - let large_point = Point::new([f64::MAX, f64::MAX / 2.0, 1e308]); + let large_point = Point::from_validated_coords([f64::MAX, f64::MAX / 2.0, 1e308]); assert!(large_point.validate().is_ok()); assert_relative_eq!(large_point.to_array()[0], f64::MAX); // Test with very small f64 values - let small_point = Point::new([f64::MIN, f64::MIN_POSITIVE, 1e-308]); + let small_point = Point::from_validated_coords([f64::MIN, f64::MIN_POSITIVE, 1e-308]); assert!(small_point.validate().is_ok()); // Test with subnormal numbers let subnormal = f64::MIN_POSITIVE / 2.0; - let subnormal_point = Point::new([subnormal, -subnormal, 0.0]); + let subnormal_point = Point::from_validated_coords([subnormal, -subnormal, 0.0]); assert!(subnormal_point.validate().is_ok()); } @@ -1462,7 +1252,7 @@ mod tests { fn point_clone_and_copy_semantics() { // Test that Point correctly implements Clone and Copy - let original = Point::new([1.0, 2.0, 3.0]); + let original = Point::from_validated_coords([1.0, 2.0, 3.0]); // Test explicit cloning #[expect( @@ -1484,10 +1274,10 @@ mod tests { #[test] fn point_partial_ord_comprehensive() { // Test lexicographic ordering in detail - let point_a = Point::new([1.0, 2.0, 3.0]); - let point_b = Point::new([1.0, 2.0, 4.0]); // Greater in last coordinate - let point_c = Point::new([1.0, 3.0, 0.0]); // Greater in second coordinate - let point_d = Point::new([2.0, 0.0, 0.0]); // Greater in first coordinate + let point_a = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point_b = Point::from_validated_coords([1.0, 2.0, 4.0]); // Greater in last coordinate + let point_c = Point::from_validated_coords([1.0, 3.0, 0.0]); // Greater in second coordinate + let point_d = Point::from_validated_coords([2.0, 0.0, 0.0]); // Greater in first coordinate // Test all comparison operators assert!(point_a < point_b); @@ -1505,100 +1295,58 @@ mod tests { assert_eq!(point_a.partial_cmp(&point_a), Some(Ordering::Equal)); // Test with negative numbers - let neg_point_a = Point::new([-1.0, -2.0]); - let neg_point_b = Point::new([-1.0, -1.0]); + let neg_point_a = Point::from_validated_coords([-1.0, -2.0]); + let neg_point_b = Point::from_validated_coords([-1.0, -1.0]); assert!(neg_point_a < neg_point_b); // -2.0 < -1.0 // Test with mixed positive/negative - let mixed_a = Point::new([-1.0, 2.0]); - let mixed_b = Point::new([1.0, -2.0]); + let mixed_a = Point::from_validated_coords([-1.0, 2.0]); + let mixed_b = Point::from_validated_coords([1.0, -2.0]); assert!(mixed_a < mixed_b); // -1.0 < 1.0 // Test with zeros - let zero_a = Point::new([0.0, 0.0]); - let zero_b = Point::new([0.0, 0.0]); + let zero_a = Point::from_validated_coords([0.0, 0.0]); + let zero_b = Point::from_validated_coords([0.0, 0.0]); assert_eq!(zero_a.partial_cmp(&zero_b), Some(Ordering::Equal)); - // Test with special float values (where defined) - let inf_point = Point::new([f64::INFINITY]); - let normal_point = Point::new([1.0]); - // Note: PartialOrd with NaN/Infinity may have special behavior - assert!(normal_point < inf_point); + assert!(Point::<1>::try_new([f64::INFINITY]).is_err()); + assert!(Point::<1>::try_new([f64::NEG_INFINITY]).is_err()); + assert!(Point::<1>::try_new([f64::NAN]).is_err()); } #[test] - fn point_partial_ord_special_values() { - // Test NaN vs NaN comparison (should be Some(Equal) with OrderedCmp) - let point_nan1 = Point::new([f64::NAN, 1.0]); - let point_nan2 = Point::new([f64::NAN, 1.0]); - let point_normal = Point::new([1.0, 1.0]); - - // NaN should be equal to itself - assert_eq!(point_nan1.partial_cmp(&point_nan2), Some(Ordering::Equal)); - - // Test NaN vs normal comparison - // In OrderedFloat semantics, NaN is greater than all other values - assert_eq!( - point_nan1.partial_cmp(&point_normal), - Some(Ordering::Greater) - ); - assert_eq!(point_normal.partial_cmp(&point_nan1), Some(Ordering::Less)); + fn point_signed_zero_eq_hash_and_order_are_consistent() { + let positive_zero = Point::try_new([0.0, -0.0, 1.0]).unwrap(); + let negative_zero = Point::try_new([-0.0, 0.0, 1.0]).unwrap(); - // Test infinity vs normal comparison - let point_inf = Point::new([f64::INFINITY, 1.0]); - let point_neg_inf = Point::new([f64::NEG_INFINITY, 1.0]); - let point_normal2 = Point::new([2.0, 1.0]); - - // Infinity should be greater than normal values assert_eq!( - point_inf.partial_cmp(&point_normal2), - Some(Ordering::Greater) + positive_zero.coords().map(f64::to_bits), + [0.0_f64.to_bits(), 0.0_f64.to_bits(), 1.0_f64.to_bits()] ); - assert_eq!(point_normal2.partial_cmp(&point_inf), Some(Ordering::Less)); - - // Test negative infinity vs normal assert_eq!( - point_neg_inf.partial_cmp(&point_normal2), - Some(Ordering::Less) + negative_zero.coords().map(f64::to_bits), + [0.0_f64.to_bits(), 0.0_f64.to_bits(), 1.0_f64.to_bits()] ); + assert_eq!(positive_zero, negative_zero); + assert_eq!(hash_of(&positive_zero), hash_of(&negative_zero)); assert_eq!( - point_normal2.partial_cmp(&point_neg_inf), - Some(Ordering::Greater) + positive_zero.partial_cmp(&negative_zero), + Some(Ordering::Equal) ); + } - // Positive infinity should be greater than negative infinity - assert_eq!( - point_inf.partial_cmp(&point_neg_inf), - Some(Ordering::Greater) - ); - assert_eq!(point_neg_inf.partial_cmp(&point_inf), Some(Ordering::Less)); - - // NaN should be greater than infinity in OrderedFloat semantics - assert_eq!(point_nan1.partial_cmp(&point_inf), Some(Ordering::Greater)); - assert_eq!(point_inf.partial_cmp(&point_nan1), Some(Ordering::Less)); - - // Test that comparison operators work - assert!(point_normal < point_inf); // Normal < Infinity - assert!(point_normal2 < point_inf); // Normal < Infinity - assert!(point_neg_inf < point_normal); // -Infinity < Normal - assert!(point_inf > point_normal); // Infinity > Normal - assert!(point_nan1 > point_normal); // NaN > Normal (in OrderedFloat) - assert!(point_nan1 > point_inf); // NaN > Infinity (in OrderedFloat) - - // Test mixed coordinates - let point_mixed1 = Point::new([1.0, f64::NAN]); - let point_mixed2 = Point::new([1.0, 2.0]); - assert_eq!( - point_mixed1.partial_cmp(&point_mixed2), - Some(Ordering::Greater) - ); // NaN in second coordinate + #[test] + fn point_try_from_rejects_lossy_integer_coordinates() { + let err = Point::<1>::try_from([9_007_199_254_740_993_u64]) + .expect_err("integer coordinates that cannot round-trip through f64 must fail"); - let point_mixed3 = Point::new([f64::NEG_INFINITY, 2.0]); - let point_mixed4 = Point::new([1.0, 2.0]); - assert_eq!( - point_mixed3.partial_cmp(&point_mixed4), - Some(Ordering::Less) - ); // -inf in first coordinate + assert!(matches!( + err, + CoordinateConversionError::ConversionFailed { + coordinate_index: 0, + .. + } + )); } #[test] @@ -1606,30 +1354,27 @@ mod tests { // Test that Point has the expected memory layout // Point should be the same size as its coordinate array - assert_eq!(mem::size_of::>(), mem::size_of::<[f64; 3]>()); + assert_eq!(mem::size_of::>(), mem::size_of::<[f64; 3]>()); // Test alignment - assert_eq!( - mem::align_of::>(), - mem::align_of::<[f64; 3]>() - ); + assert_eq!(mem::align_of::>(), mem::align_of::<[f64; 3]>()); // Test with different dimensions - assert_eq!(mem::size_of::>(), 8); // 1 * 8 bytes - assert_eq!(mem::size_of::>(), 16); // 2 * 8 bytes - assert_eq!(mem::size_of::>(), 80); // 10 * 8 bytes + assert_eq!(mem::size_of::>(), 8); // 1 * 8 bytes + assert_eq!(mem::size_of::>(), 16); // 2 * 8 bytes + assert_eq!(mem::size_of::>(), 80); // 10 * 8 bytes } #[test] fn point_zero_dimensional() { // Test 0-dimensional points (edge case) - let point_0d: Point = Point::new([]); + let point_0d: Point<0> = Point::from_validated_coords([]); assert_eq!(point_0d.dim(), 0); assert_relative_eq!(point_0d.to_array().as_slice(), ([] as [f64; 0]).as_slice()); assert!(point_0d.validate().is_ok()); // Test equality for 0D points - let point_0d_2: Point = Point::new([]); + let point_0d_2: Point<0> = Point::from_validated_coords([]); assert_eq!(point_0d, point_0d_2); // Test hashing for 0D points @@ -1638,59 +1383,25 @@ mod tests { assert_eq!(hash_0d, hash_0d_2); // Test origin for 0D - let origin_0d: Point = Point::origin(); + let origin_0d: Point<0> = Point::origin(); assert_eq!(origin_0d, point_0d); } #[test] - fn point_serialize_nan_infinity_comprehensive() { - // Test comprehensive serialization of NaN and infinity values - - // Single NaN coordinate - let point_nan_single = Point::new([f64::NAN, 1.0, 2.0]); - let json_nan_single = serde_json::to_string(&point_nan_single).unwrap(); - assert_eq!(json_nan_single, "[null,1.0,2.0]"); - - // Multiple NaN coordinates - let point_nan_multiple = Point::new([f64::NAN, f64::NAN, 1.0]); - let json_nan_multiple = serde_json::to_string(&point_nan_multiple).unwrap(); - assert_eq!(json_nan_multiple, "[null,null,1.0]"); - - // All NaN coordinates - let point_all_nan = Point::new([f64::NAN, f64::NAN]); - let json_all_nan = serde_json::to_string(&point_all_nan).unwrap(); - assert_eq!(json_all_nan, "[null,null]"); - - // Single positive infinity - let point_pos_inf = Point::new([f64::INFINITY, 1.0]); - let json_pos_inf = serde_json::to_string(&point_pos_inf).unwrap(); - assert_eq!(json_pos_inf, "[\"Infinity\",1.0]"); - - // Single negative infinity - let point_neg_inf = Point::new([1.0, f64::NEG_INFINITY]); - let json_neg_inf = serde_json::to_string(&point_neg_inf).unwrap(); - assert_eq!(json_neg_inf, "[1.0,\"-Infinity\"]"); - - // Mixed NaN and infinity - let point_mixed = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1.0]); - let json_mixed = serde_json::to_string(&point_mixed).unwrap(); - assert_eq!(json_mixed, "[null,\"Infinity\",\"-Infinity\",1.0]"); - - // All special values - let point_all_special = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY]); - let json_all_special = serde_json::to_string(&point_all_special).unwrap(); - assert_eq!(json_all_special, "[null,\"Infinity\",\"-Infinity\"]"); + fn point_rejects_nan_infinity_public_construction() { + assert!(Point::<3>::try_new([f64::NAN, 1.0, 2.0]).is_err()); + assert!(Point::<3>::try_new([f64::NAN, f64::NAN, 1.0]).is_err()); + assert!(Point::<2>::try_new([f64::NAN, f64::NAN]).is_err()); + assert!(Point::<2>::try_new([f64::INFINITY, 1.0]).is_err()); + assert!(Point::<2>::try_new([1.0, f64::NEG_INFINITY]).is_err()); + assert!(Point::<4>::try_new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1.0,]).is_err()); } #[test] - fn point_deserialize_null_maps_to_nan() { - // With custom Deserialize, JSON null deserializes to NaN + fn point_deserialize_rejects_null() { let json = "[null,1.0,2.0]"; - let p: Point = serde_json::from_str(json).unwrap(); - let coords = p.to_array(); - assert!(coords[0].is_nan()); - assert_relative_eq!(coords[1], 1.0); - assert_relative_eq!(coords[2], 2.0); + let result: Result, _> = serde_json::from_str(json); + assert!(result.is_err()); } #[test] @@ -1699,72 +1410,40 @@ mod tests { // Test 1: Regular numeric values (NumCast improvement) let json_regular = "[1.0, 2.5, 4.25]"; - let point_regular: Point = serde_json::from_str(json_regular).unwrap(); + let point_regular: Point<3> = serde_json::from_str(json_regular).unwrap(); assert_relative_eq!( point_regular.to_array().as_slice(), [1.0, 2.5, 4.25].as_slice() ); - // Test 2: Mixed special values using format-agnostic approach + // Test 2: Mixed non-finite values are rejected at the parse boundary let json_special = "[1.0, null, \"Infinity\", \"-Infinity\"]"; - let point_special: Point = serde_json::from_str(json_special).unwrap(); - let coords = point_special.to_array(); - assert_relative_eq!(coords[0], 1.0); - assert!(coords[1].is_nan()); - assert!(coords[2].is_infinite() && coords[2].is_sign_positive()); - assert!(coords[3].is_infinite() && coords[3].is_sign_negative()); + assert!(serde_json::from_str::>(json_special).is_err()); // Test 3: All null values let json_all_null = "[null, null, null]"; - let point_all_null: Point = serde_json::from_str(json_all_null).unwrap(); - let all_null_coords = point_all_null.to_array(); - assert!(all_null_coords.iter().all(|&x| x.is_nan())); + assert!(serde_json::from_str::>(json_all_null).is_err()); // Test 4: All special string values let json_all_special = "[\"Infinity\", \"-Infinity\", \"Infinity\"]"; - let point_all_special: Point = serde_json::from_str(json_all_special).unwrap(); - let special_coords = point_all_special.to_array(); - assert!(special_coords[0].is_infinite() && special_coords[0].is_sign_positive()); - assert!(special_coords[1].is_infinite() && special_coords[1].is_sign_negative()); - assert!(special_coords[2].is_infinite() && special_coords[2].is_sign_positive()); - - // Test 5: Serialization roundtrip with format-agnostic deserialization - let original = Point::new([1.5, f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0]); - let serialized = serde_json::to_string(&original).unwrap(); - let deserialized: Point = serde_json::from_str(&serialized).unwrap(); + assert!(serde_json::from_str::>(json_all_special).is_err()); - // Compare coordinates (can't use == because of NaN) - let orig_coords = original.to_array(); - let deser_coords = deserialized.to_array(); - assert_relative_eq!(orig_coords[0], deser_coords[0]); - assert!(orig_coords[1].is_nan() && deser_coords[1].is_nan()); - assert!( - orig_coords[2].is_infinite() - && orig_coords[2].is_sign_positive() - && deser_coords[2].is_infinite() - && deser_coords[2].is_sign_positive() - ); - assert!( - orig_coords[3].is_infinite() - && orig_coords[3].is_sign_negative() - && deser_coords[3].is_infinite() - && deser_coords[3].is_sign_negative() - ); - assert_relative_eq!(orig_coords[4], deser_coords[4]); + // Test 5: Serialization roundtrip with finite values + let original = Point::from_validated_coords([1.5, -2.25, 3.75, 0.0, -0.0]); + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: Point<5> = serde_json::from_str(&serialized).unwrap(); + assert_eq!(original, deserialized); // Test 6: Invalid special string should fail gracefully let json_invalid = "[1.0, \"NotASpecialValue\", 2.0]"; - let result: Result, _> = serde_json::from_str(json_invalid); + let result: Result, _> = serde_json::from_str(json_invalid); assert!(result.is_err()); let error_msg = result.unwrap_err().to_string(); - assert!(error_msg.contains("Unknown special value")); + assert!(error_msg.contains("non-finite coordinate string")); } #[test] - fn point_deserialize_case_insensitive_special_values() { - // Test case-insensitive deserialization of special values - - // Test case-insensitive "Infinity" variants + fn point_deserialize_rejects_case_insensitive_special_values() { let test_cases = vec![ (r#"["infinity", 1.0]"#, "lowercase infinity"), (r#"["INFINITY", 1.0]"#, "uppercase infinity"), @@ -1775,18 +1454,12 @@ mod tests { ]; for (json_str, description) in test_cases { - let point: Point = serde_json::from_str(json_str).unwrap_or_else(|e| { - panic!("Failed to deserialize {description} ({json_str}): {e}") - }); - assert!( - point.coords[0].is_infinite() && point.coords[0].is_sign_positive(), - "First coordinate should be positive infinity for {description}" + serde_json::from_str::>(json_str).is_err(), + "special value should be rejected for {description}" ); - assert_relative_eq!(point.coords[1], 1.0, epsilon = 1e-10); } - // Test negative infinity variants let neg_inf_cases = vec![ (r#"["-infinity", 2.0]"#, "lowercase -infinity"), (r#"["-INFINITY", 2.0]"#, "uppercase -infinity"), @@ -1797,18 +1470,12 @@ mod tests { ]; for (json_str, description) in neg_inf_cases { - let point: Point = serde_json::from_str(json_str).unwrap_or_else(|e| { - panic!("Failed to deserialize {description} ({json_str}): {e}") - }); - assert!( - point.coords[0].is_infinite() && point.coords[0].is_sign_negative(), - "First coordinate should be negative infinity for {description}" + serde_json::from_str::>(json_str).is_err(), + "special value should be rejected for {description}" ); - assert_relative_eq!(point.coords[1], 2.0, epsilon = 1e-10); } - // Test case-insensitive "NaN" variants let nan_cases = vec![ (r#"["nan", 3.0]"#, "lowercase nan"), (r#"["NaN", 3.0]"#, "mixed case NaN"), @@ -1817,18 +1484,12 @@ mod tests { ]; for (json_str, description) in nan_cases { - let point: Point = serde_json::from_str(json_str).unwrap_or_else(|e| { - panic!("Failed to deserialize {description} ({json_str}): {e}") - }); - assert!( - point.coords[0].is_nan(), - "First coordinate should be NaN for {description}" + serde_json::from_str::>(json_str).is_err(), + "special value should be rejected for {description}" ); - assert_relative_eq!(point.coords[1], 3.0, epsilon = 1e-10); } - // Test whitespace trimming let whitespace_cases = vec![ (r#"[" infinity ", 1.0]"#, "spaces around infinity"), (r#"["\tinf\n", 2.0]"#, "tabs and newlines around inf"), @@ -1836,40 +1497,24 @@ mod tests { ]; for (json_str, description) in whitespace_cases { - let point: Point = serde_json::from_str(json_str).unwrap_or_else(|e| { - panic!("Failed to deserialize {description} ({json_str}): {e}") - }); - - if description.contains("infinity") || description.contains("inf") { - assert!( - point.coords[0].is_infinite() && point.coords[0].is_sign_positive(), - "First coordinate should be positive infinity for {description}" - ); - } else { - assert!( - point.coords[0].is_nan(), - "First coordinate should be NaN for {description}" - ); - } + assert!( + serde_json::from_str::>(json_str).is_err(), + "special value should be rejected for {description}" + ); } - // Test combined case insensitive values let combined = r#"["INFINITY", "-inf", "Nan", 42.0]"#; - let point: Point = serde_json::from_str(combined).unwrap(); - assert!(point.coords[0].is_infinite() && point.coords[0].is_sign_positive()); - assert!(point.coords[1].is_infinite() && point.coords[1].is_sign_negative()); - assert!(point.coords[2].is_nan()); - assert_relative_eq!(point.coords[3], 42.0, epsilon = 1e-10); + assert!(serde_json::from_str::>(combined).is_err()); // Test that unknown special values still fail let invalid = r#"["unknown_special", 1.0]"#; - let result: Result, _> = serde_json::from_str(invalid); + let result: Result, _> = serde_json::from_str(invalid); assert!(result.is_err()); assert!( result .unwrap_err() .to_string() - .contains("Unknown special value") + .contains("non-finite coordinate string") ); } @@ -1878,20 +1523,20 @@ mod tests { // Test serialization of edge finite values // Very large but finite values - let point_max = Point::new([f64::MAX, f64::MIN]); + let point_max = Point::from_validated_coords([f64::MAX, f64::MIN]); let json_max = serde_json::to_string(&point_max).unwrap(); assert!(!json_max.contains("null")); // Should not be null // Very small but finite values - let point_min = Point::new([f64::MIN_POSITIVE, -f64::MIN_POSITIVE]); + let point_min = Point::from_validated_coords([f64::MIN_POSITIVE, -f64::MIN_POSITIVE]); let json_min = serde_json::to_string(&point_min).unwrap(); assert!(!json_min.contains("null")); // Should not be null // Zero and negative zero - let point_zero = Point::new([0.0, -0.0]); + let point_zero = Point::from_validated_coords([0.0, -0.0]); let json_zero = serde_json::to_string(&point_zero).unwrap(); assert!(!json_zero.contains("null")); // Should not be null - assert_eq!(json_zero, "[0.0,-0.0]"); + assert_eq!(json_zero, "[0.0,0.0]"); } #[test] @@ -1900,7 +1545,7 @@ mod tests { // Test conversion with potential precision loss (should still work) let precise_coords = [1.000_000_000_000_001_f64, 2.000_000_000_000_002_f64]; - let point_precise: Point = Point::new(precise_coords); + let point_precise: Point<2> = Point::from_validated_coords(precise_coords); assert_relative_eq!( point_precise.to_array().as_slice(), precise_coords.as_slice() @@ -1908,21 +1553,21 @@ mod tests { // Test conversion from array reference let coords_ref = &[1.0, 2.0, 3.0]; - let point_from_ref: Point = Point::new(*coords_ref); + let point_from_ref: Point<3> = Point::from_validated_coords(*coords_ref); assert_relative_eq!( point_from_ref.to_array().as_slice(), [1.0f64, 2.0f64, 3.0f64].as_slice() ); // Test conversion to array with different methods - let point = Point::new([1.0, 2.0, 3.0]); + let point = Point::from_validated_coords([1.0, 2.0, 3.0]); // Using Into trait let coords_into: [f64; 3] = point.into(); assert_relative_eq!(coords_into.as_slice(), [1.0, 2.0, 3.0].as_slice()); // Using From trait with reference - let point_ref = Point::new([4.0, 5.0]); + let point_ref = Point::from_validated_coords([4.0, 5.0]); let coords_from_ref: [f64; 2] = (&point_ref).into(); assert_relative_eq!(coords_from_ref.as_slice(), [4.0, 5.0].as_slice()); @@ -1936,7 +1581,7 @@ mod tests { // Test integer to f64 conversion using TryFrom let coords_i16: [i16; 3] = [1, 2, 3]; - let point_f64: Point = Point::try_from(coords_i16).unwrap(); + let point_f64: Point<3> = Point::try_from(coords_i16).unwrap(); // Verify the conversion worked correctly assert_relative_eq!( @@ -1947,7 +1592,7 @@ mod tests { // Test same type conversion (no actual cast needed) let coords_f64: [f64; 2] = [10.0, 20.0]; - let point_f64_same: Point = Point::try_from(coords_f64).unwrap(); + let point_f64_same: Point<2> = Point::try_from(coords_f64).unwrap(); assert_relative_eq!( point_f64_same.to_array().as_slice(), [10.0, 20.0].as_slice() @@ -1955,7 +1600,7 @@ mod tests { // Test with integer type conversions let coords_i32: [i32; 4] = [1, 2, 3, 4]; - let point_f64_from_int: Point = Point::try_from(coords_i32).unwrap(); + let point_f64_from_int: Point<4> = Point::try_from(coords_i32).unwrap(); assert_relative_eq!( point_f64_from_int.to_array().as_slice(), [1.0, 2.0, 3.0, 4.0].as_slice(), @@ -1964,7 +1609,7 @@ mod tests { // Test with large values that are within range let coords_large_i32: [i32; 2] = [i32::MAX, i32::MIN]; - let point_f64_from_large: Point = Point::try_from(coords_large_i32).unwrap(); + let point_f64_from_large: Point<2> = Point::try_from(coords_large_i32).unwrap(); assert_relative_eq!( point_f64_from_large.to_array().as_slice(), [f64::from(i32::MAX), f64::from(i32::MIN)].as_slice(), @@ -1973,7 +1618,7 @@ mod tests { // Test with mixed typical values let coords_mixed: [i16; 3] = [0, 15, -35]; - let point_mixed: Point = Point::try_from(coords_mixed).unwrap(); + let point_mixed: Point<3> = Point::try_from(coords_mixed).unwrap(); assert_relative_eq!( point_mixed.to_array().as_slice(), [0.0, 15.0, -35.0].as_slice(), @@ -1982,46 +1627,9 @@ mod tests { } #[test] - fn point_hash_special_values() { - // Test for NaN - let point_nan1 = Point::new([f64::NAN, 2.0]); - let point_nan2 = Point::new([f64::NAN, 2.0]); - - let mut hasher_nan1 = DefaultHasher::new(); - let mut hasher_nan2 = DefaultHasher::new(); - - point_nan1.hash(&mut hasher_nan1); - point_nan2.hash(&mut hasher_nan2); - - assert_eq!(hasher_nan1.finish(), hasher_nan2.finish()); - - // Test for positive infinity - let point_inf1 = Point::new([f64::INFINITY, 2.0]); - let point_inf2 = Point::new([f64::INFINITY, 2.0]); - - let mut hasher_inf1 = DefaultHasher::new(); - let mut hasher_inf2 = DefaultHasher::new(); - - point_inf1.hash(&mut hasher_inf1); - point_inf2.hash(&mut hasher_inf2); - - assert_eq!(hasher_inf1.finish(), hasher_inf2.finish()); - - // Test for negative infinity - let point_neg_inf1 = Point::new([f64::NEG_INFINITY, 2.0]); - let point_neg_inf2 = Point::new([f64::NEG_INFINITY, 2.0]); - - let mut hasher_neg_inf1 = DefaultHasher::new(); - let mut hasher_neg_inf2 = DefaultHasher::new(); - - point_neg_inf1.hash(&mut hasher_neg_inf1); - point_neg_inf2.hash(&mut hasher_neg_inf2); - - assert_eq!(hasher_neg_inf1.finish(), hasher_neg_inf2.finish()); - - // Test for +0.0 and -0.0 - let point_pos_zero = Point::new([0.0, 2.0]); - let point_neg_zero = Point::new([-0.0, 2.0]); + fn point_hash_signed_zero_values() { + let point_pos_zero = Point::from_validated_coords([0.0, 2.0]); + let point_neg_zero = Point::from_validated_coords([-0.0, 2.0]); let mut hasher_pos_zero = DefaultHasher::new(); let mut hasher_neg_zero = DefaultHasher::new(); @@ -2033,44 +1641,38 @@ mod tests { } #[test] - fn point_hashmap_special_values() { - let mut map: HashMap, &str> = HashMap::new(); + fn point_hashmap_finite_values() { + let mut map: HashMap, &str> = HashMap::new(); - let point_nan = Point::new([f64::NAN, 2.0]); - let point_inf = Point::new([f64::INFINITY, 2.0]); - let point_neg_inf = Point::new([f64::NEG_INFINITY, 2.0]); - let point_zero = Point::new([0.0, 2.0]); + let point_zero = Point::from_validated_coords([0.0, 2.0]); + let point_regular = Point::from_validated_coords([1.0, 2.0]); - map.insert(point_nan, "NaN Point"); - map.insert(point_inf, "Infinity Point"); - map.insert(point_neg_inf, "Negative Infinity Point"); map.insert(point_zero, "Zero Point"); + map.insert(point_regular, "Regular Point"); - assert_eq!(map[&Point::new([f64::NAN, 2.0])], "NaN Point"); - assert_eq!(map[&Point::new([f64::INFINITY, 2.0])], "Infinity Point"); assert_eq!( - map[&Point::new([f64::NEG_INFINITY, 2.0])], - "Negative Infinity Point" + map[&Point::from_validated_coords([-0.0, 2.0])], + "Zero Point" + ); + assert_eq!( + map[&Point::from_validated_coords([1.0, 2.0])], + "Regular Point" ); - assert_eq!(map[&Point::new([-0.0, 2.0])], "Zero Point"); } #[test] - fn point_hashset_special_values() { - let mut set: HashSet> = HashSet::new(); + fn point_hashset_finite_values() { + let mut set: HashSet> = HashSet::new(); - set.insert(Point::new([f64::NAN, 2.0])); - set.insert(Point::new([f64::INFINITY, 2.0])); - set.insert(Point::new([f64::NEG_INFINITY, 2.0])); - set.insert(Point::new([0.0, 2.0])); - set.insert(Point::new([-0.0, 2.0])); + set.insert(Point::from_validated_coords([0.0, 2.0])); + set.insert(Point::from_validated_coords([-0.0, 2.0])); + set.insert(Point::from_validated_coords([1.0, 2.0])); + set.insert(Point::from_validated_coords([1.0, 2.0])); - assert_eq!(set.len(), 4); // 0.0 and -0.0 should be considered equal here + assert_eq!(set.len(), 2); - assert!(set.contains(&Point::new([f64::NAN, 2.0]))); - assert!(set.contains(&Point::new([f64::INFINITY, 2.0]))); - assert!(set.contains(&Point::new([f64::NEG_INFINITY, 2.0]))); - assert!(set.contains(&Point::new([-0.0, 2.0]))); + assert!(set.contains(&Point::from_validated_coords([-0.0, 2.0]))); + assert!(set.contains(&Point::from_validated_coords([1.0, 2.0]))); } #[test] @@ -2082,7 +1684,7 @@ mod tests { // Generate a variety of points and collect their hashes for i in 0..100 { - let point = Point::new([f64::from(i), f64::from(i * 2)]); + let point = Point::from_validated_coords([f64::from(i), f64::from(i * 2)]); let hash = hash_of(&point); hashes.insert(hash); } @@ -2096,7 +1698,8 @@ mod tests { // Test with negative values for i in -50..50 { - let point = Point::new([f64::from(i), f64::from(i * 3), f64::from(i * 5)]); + let point = + Point::from_validated_coords([f64::from(i), f64::from(i * 3), f64::from(i * 5)]); let hash = hash_of(&point); hashes.insert(hash); } @@ -2114,8 +1717,7 @@ mod tests { // Test CoordinateValidationError with specific error details // Test invalid coordinate at specific index - let invalid_point = Point::new([1.0, f64::NAN, 3.0]); - let result = invalid_point.validate(); + let result = Point::<3>::try_new([1.0, f64::NAN, 3.0]); assert!(result.is_err()); if let Err(CoordinateValidationError::InvalidCoordinate { @@ -2132,8 +1734,7 @@ mod tests { } // Test with infinity at different positions - let inf_point = Point::new([f64::INFINITY, 2.0, 3.0, 4.0]); - let result = inf_point.validate(); + let result = Point::<4>::try_new([f64::INFINITY, 2.0, 3.0, 4.0]); if let Err(CoordinateValidationError::InvalidCoordinate { coordinate_index, coordinate_value, @@ -2148,8 +1749,7 @@ mod tests { } // Test with negative infinity at last position - let neg_inf_point = Point::new([1.0, 2.0, f64::NEG_INFINITY]); - let result = neg_inf_point.validate(); + let result = Point::<3>::try_new([1.0, 2.0, f64::NEG_INFINITY]); if let Err(CoordinateValidationError::InvalidCoordinate { coordinate_index, coordinate_value, @@ -2165,8 +1765,7 @@ mod tests { #[test] fn point_validation_error_display() { // Test error message formatting - let invalid_point = Point::new([1.0, f64::NAN, 3.0]); - let result = invalid_point.validate(); + let result = Point::<3>::try_new([1.0, f64::NAN, 3.0]); if let Err(error) = result { let error_msg = format!("{error}"); @@ -2178,8 +1777,7 @@ mod tests { } // Test with infinity - let inf_point = Point::new([f64::INFINITY]); - let result = inf_point.validate(); + let result = Point::<1>::try_new([f64::INFINITY]); if let Err(error) = result { let error_msg = format!("{error}"); @@ -2192,9 +1790,8 @@ mod tests { #[test] fn point_validation_error_clone_and_eq() { // Test that CoordinateValidationError can be cloned and compared - let invalid_point = Point::new([f64::NAN, 2.0]); - let result1 = invalid_point.validate(); - let result2 = invalid_point.validate(); + let result1 = Point::<2>::try_new([f64::NAN, 2.0]); + let result2 = Point::<2>::try_new([f64::NAN, 2.0]); assert!(result1.is_err()); assert!(result2.is_err()); @@ -2216,17 +1813,10 @@ mod tests { assert!(debug_output.contains("dimension")); } - #[test] - fn point_validation_accepts_finite_f64_and_rejects_nan() { - assert!(Point::new([1.0f64, 2.0f64]).validate().is_ok()); - assert!(Point::new([f64::NAN, 2.0f64]).validate().is_err()); - } - #[test] fn point_validation_first_invalid_coordinate() { // Test that validation returns the FIRST invalid coordinate found - let multi_invalid = Point::new([1.0, f64::NAN, f64::INFINITY, f64::NAN]); - let result = multi_invalid.validate(); + let result = Point::<4>::try_new([1.0, f64::NAN, f64::INFINITY, f64::NAN]); if let Err(CoordinateValidationError::InvalidCoordinate { coordinate_index, .. @@ -2239,8 +1829,7 @@ mod tests { } // Test with invalid at index 0 - let first_invalid = Point::new([f64::INFINITY, f64::NAN, 3.0]); - let result = first_invalid.validate(); + let result = Point::<3>::try_new([f64::INFINITY, f64::NAN, 3.0]); if let Err(CoordinateValidationError::InvalidCoordinate { coordinate_index, .. @@ -2251,81 +1840,48 @@ mod tests { } #[test] - fn point_hashmap_with_special_values() { - let mut point_map: HashMap, &str> = HashMap::new(); + fn point_hashmap_with_finite_values() { + let mut point_map: HashMap, &str> = HashMap::new(); - // Insert points with various special values - let point_normal = Point::new([1.0, 2.0, 3.0]); - let point_nan = Point::new([f64::NAN, 2.0, 3.0]); - let point_inf = Point::new([f64::INFINITY, 2.0, 3.0]); - let point_neg_inf = Point::new([f64::NEG_INFINITY, 2.0, 3.0]); + let point_normal = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point_other = Point::from_validated_coords([4.0, 5.0, 6.0]); point_map.insert(point_normal, "normal point"); - point_map.insert(point_nan, "point with NaN"); - point_map.insert(point_inf, "point with +∞"); - point_map.insert(point_neg_inf, "point with -∞"); + point_map.insert(point_other, "other point"); - assert_eq!(point_map.len(), 4); + assert_eq!(point_map.len(), 2); - // Test retrieval with equivalent points - let point_normal_copy = Point::new([1.0, 2.0, 3.0]); - let point_nan_copy = Point::new([f64::NAN, 2.0, 3.0]); - let point_inf_copy = Point::new([f64::INFINITY, 2.0, 3.0]); - let point_neg_inf_copy = Point::new([f64::NEG_INFINITY, 2.0, 3.0]); + let point_normal_copy = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point_other_copy = Point::from_validated_coords([4.0, 5.0, 6.0]); assert!(point_map.contains_key(&point_normal_copy)); - assert!(point_map.contains_key(&point_nan_copy)); - assert!(point_map.contains_key(&point_inf_copy)); - assert!(point_map.contains_key(&point_neg_inf_copy)); + assert!(point_map.contains_key(&point_other_copy)); - // Test retrieval of values assert_eq!(point_map.get(&point_normal_copy), Some(&"normal point")); - assert_eq!(point_map.get(&point_nan_copy), Some(&"point with NaN")); - assert_eq!(point_map.get(&point_inf_copy), Some(&"point with +∞")); - assert_eq!(point_map.get(&point_neg_inf_copy), Some(&"point with -∞")); - - // Demonstrate that NaN points can be used as keys reliably - let mut nan_counter = HashMap::new(); - for _ in 0..5 { - let nan_point = Point::new([f64::NAN, 1.0]); - *nan_counter.entry(nan_point).or_insert(0) += 1; - } - assert_eq!(*nan_counter.values().next().unwrap(), 5); + assert_eq!(point_map.get(&point_other_copy), Some(&"other point")); } #[test] - fn point_hashset_with_special_values() { - let mut point_set: HashSet> = HashSet::new(); + fn point_hashset_with_finite_values() { + let mut point_set: HashSet> = HashSet::new(); - // Add various points including duplicates with special values let points = vec![ - Point::new([1.0, 2.0]), - Point::new([1.0, 2.0]), // Duplicate normal point - Point::new([f64::NAN, 2.0]), - Point::new([f64::NAN, 2.0]), // Duplicate NaN point - Point::new([f64::INFINITY, 2.0]), - Point::new([f64::INFINITY, 2.0]), // Duplicate infinity point - Point::new([0.0, -0.0]), // Zero and negative zero (equal) - Point::new([-0.0, 0.0]), // Different zero combination + Point::from_validated_coords([1.0, 2.0]), + Point::from_validated_coords([1.0, 2.0]), // Duplicate normal point + Point::from_validated_coords([0.0, -0.0]), // Zero and negative zero (equal) + Point::from_validated_coords([-0.0, 0.0]), // Different zero combination ]; for point in points { point_set.insert(point); } - // Should have 4 unique points: normal, NaN, ∞, and two different zero combinations - // Note: [0.0, -0.0] and [-0.0, 0.0] are different points because only corresponding - // coordinates are compared for equality (0.0 == -0.0 but the positions differ) - assert_eq!(point_set.len(), 4); + assert_eq!(point_set.len(), 2); - // Test membership - let test_nan = Point::new([f64::NAN, 2.0]); - let test_inf = Point::new([f64::INFINITY, 2.0]); - let test_normal = Point::new([1.0, 2.0]); + let test_normal = Point::from_validated_coords([1.0, 2.0]); - assert!(point_set.contains(&test_nan)); - assert!(point_set.contains(&test_inf)); assert!(point_set.contains(&test_normal)); + assert!(point_set.contains(&Point::from_validated_coords([0.0, 0.0]))); } // ============================================================================= @@ -2338,7 +1894,7 @@ mod tests { // i32 to f64 let coords_i32: [i32; 4] = [1, -2, 3, -4]; - let point_from_int: Point = Point::try_from(coords_i32).unwrap(); + let point_from_int: Point<4> = Point::try_from(coords_i32).unwrap(); assert_relative_eq!( point_from_int.to_array().as_slice(), [1.0, -2.0, 3.0, -4.0].as_slice(), @@ -2347,7 +1903,7 @@ mod tests { // Same type conversion (f64 to f64) let coords_same: [f64; 2] = [10.0, 20.0]; - let point_same: Point = Point::try_from(coords_same).unwrap(); + let point_same: Point<2> = Point::try_from(coords_same).unwrap(); assert_relative_eq!(point_same.to_array().as_slice(), [10.0, 20.0].as_slice()); } @@ -2355,13 +1911,13 @@ mod tests { fn point_try_from_edge_case_values() { // Test with zero and negative zero let coords_zero: [f64; 2] = [0.0, -0.0]; - let point_zero: Point = Point::try_from(coords_zero).unwrap(); + let point_zero: Point<2> = Point::try_from(coords_zero).unwrap(); assert_relative_eq!(point_zero.to_array()[0], 0.0); assert_relative_eq!(point_zero.to_array()[1], -0.0); // Test with very small values let coords_small: [f64; 2] = [1e-10, -1e-10]; - let point_small: Point = Point::try_from(coords_small).unwrap(); + let point_small: Point<2> = Point::try_from(coords_small).unwrap(); assert!(point_small.to_array()[0].is_finite()); assert!(point_small.to_array()[1].is_finite()); } @@ -2372,7 +1928,7 @@ mod tests { // u32 to f64 let coords_u32: [u32; 3] = [100, 200, 300]; - let point_u32: Point = Point::try_from(coords_u32).unwrap(); + let point_u32: Point<3> = Point::try_from(coords_u32).unwrap(); assert_relative_eq!( point_u32.to_array().as_slice(), [100.0, 200.0, 300.0].as_slice(), @@ -2381,7 +1937,7 @@ mod tests { // i16 to f64 let coords_i16: [i16; 2] = [-100, 200]; - let point_i16: Point = Point::try_from(coords_i16).unwrap(); + let point_i16: Point<2> = Point::try_from(coords_i16).unwrap(); assert_relative_eq!( point_i16.to_array().as_slice(), [-100.0, 200.0].as_slice(), @@ -2390,7 +1946,7 @@ mod tests { // Large but representable integers let coords_large_i32: [i32; 2] = [1_000_000, -1_000_000]; - let point_large: Point = Point::try_from(coords_large_i32).unwrap(); + let point_large: Point<2> = Point::try_from(coords_large_i32).unwrap(); assert_relative_eq!( point_large.to_array().as_slice(), [1_000_000.0, -1_000_000.0].as_slice(), @@ -2404,12 +1960,12 @@ mod tests { // Valid conversion - all finite let valid_coords: [i32; 3] = [1, 2, 3]; - let result: Result, _> = Point::try_from(valid_coords); + let result: Result, _> = Point::try_from(valid_coords); assert!(result.is_ok()); // Invalid - input contains infinity let invalid_coords = [1.0, f64::INFINITY, 3.0]; - let result: Result, _> = Point::try_from(invalid_coords); + let result: Result, _> = Point::try_from(invalid_coords); assert!(result.is_err()); } @@ -2421,31 +1977,25 @@ mod tests { fn point_dim_method_explicit() { // Test the dim() method explicitly across various dimensions - let point_1d: Point = Point::new([1.0]); + let point_1d: Point<1> = Point::from_validated_coords([1.0]); assert_eq!(point_1d.dim(), 1); - let point_2d: Point = Point::new([1.0, 2.0]); + let point_2d: Point<2> = Point::from_validated_coords([1.0, 2.0]); assert_eq!(point_2d.dim(), 2); - let point_3d: Point = Point::new([1.0, 2.0, 3.0]); + let point_3d: Point<3> = Point::from_validated_coords([1.0, 2.0, 3.0]); assert_eq!(point_3d.dim(), 3); - let point_5d: Point = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let point_5d: Point<5> = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); assert_eq!(point_5d.dim(), 5); - let point_10d: Point = Point::new([0.0; 10]); + let point_10d: Point<10> = Point::from_validated_coords([0.0; 10]); assert_eq!(point_10d.dim(), 10); - let point_32d: Point = Point::new([0.0; 32]); + let point_32d: Point<32> = Point::from_validated_coords([0.0; 32]); assert_eq!(point_32d.dim(), 32); } - #[test] - fn point_dim_with_f64() { - let point_f64: Point = Point::new([1.0, 2.0, 3.0, 4.0]); - assert_eq!(point_f64.dim(), 4); - } - // ============================================================================= // TO_ARRAY() METHOD EXPLICIT TESTS // ============================================================================= @@ -2454,42 +2004,26 @@ mod tests { fn point_to_array_explicit() { // Test to_array() method explicitly - let point = Point::new([1.0, 2.0, 3.0]); + let point = Point::from_validated_coords([1.0, 2.0, 3.0]); let arr = point.to_array(); assert_relative_eq!(arr.as_slice(), [1.0, 2.0, 3.0].as_slice()); // Verify that to_array() returns a copy, not a reference - let point2 = Point::new([4.0, 5.0]); + let point2 = Point::from_validated_coords([4.0, 5.0]); let arr2 = point2.to_array(); assert_relative_eq!(arr2.as_slice(), [4.0, 5.0].as_slice()); // Test with different dimensions - let point_1d = Point::new([42.0]); + let point_1d = Point::from_validated_coords([42.0]); assert_relative_eq!(point_1d.to_array().as_slice(), [42.0].as_slice()); - let point_5d = Point::new([1.0, 2.0, 3.0, 4.0, 5.0]); + let point_5d = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0]); assert_relative_eq!( point_5d.to_array().as_slice(), [1.0, 2.0, 3.0, 4.0, 5.0].as_slice() ); } - #[test] - fn point_to_array_with_special_values() { - // Test to_array() with special floating-point values - - let point_nan = Point::new([f64::NAN, 1.0, 2.0]); - let arr = point_nan.to_array(); - assert!(arr[0].is_nan()); - assert_relative_eq!(arr[1], 1.0); - assert_relative_eq!(arr[2], 2.0); - - let point_inf = Point::new([f64::INFINITY, f64::NEG_INFINITY]); - let arr_inf = point_inf.to_array(); - assert!(arr_inf[0].is_infinite() && arr_inf[0].is_sign_positive()); - assert!(arr_inf[1].is_infinite() && arr_inf[1].is_sign_negative()); - } - // ============================================================================= // ORDERED_EQUALS() AND HASH_COORDINATE() DIRECT TESTS // ============================================================================= @@ -2498,30 +2032,20 @@ mod tests { fn point_ordered_equals_direct() { // Test ordered_equals() method directly - let point1 = Point::new([1.0, 2.0, 3.0]); - let point2 = Point::new([1.0, 2.0, 3.0]); - let point3 = Point::new([1.0, 2.0, 4.0]); + let point1 = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point2 = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point3 = Point::from_validated_coords([1.0, 2.0, 4.0]); assert!(point1.ordered_equals(&point2)); assert!(!point1.ordered_equals(&point3)); - - // Test with NaN (should be equal) - let point_nan1 = Point::new([f64::NAN, 2.0]); - let point_nan2 = Point::new([f64::NAN, 2.0]); - assert!(point_nan1.ordered_equals(&point_nan2)); - - // Test with infinity - let point_inf1 = Point::new([f64::INFINITY, 1.0]); - let point_inf2 = Point::new([f64::INFINITY, 1.0]); - assert!(point_inf1.ordered_equals(&point_inf2)); } #[test] fn point_hash_coordinate_direct() { // Test hash_coordinate() method directly - let point1 = Point::new([1.0, 2.0, 3.0]); - let point2 = Point::new([1.0, 2.0, 3.0]); + let point1 = Point::from_validated_coords([1.0, 2.0, 3.0]); + let point2 = Point::from_validated_coords([1.0, 2.0, 3.0]); let mut hasher1 = DefaultHasher::new(); let mut hasher2 = DefaultHasher::new(); @@ -2532,30 +2056,13 @@ mod tests { assert_eq!(hasher1.finish(), hasher2.finish()); // Test with different points - let point3 = Point::new([1.0, 2.0, 4.0]); + let point3 = Point::from_validated_coords([1.0, 2.0, 4.0]); let mut hasher3 = DefaultHasher::new(); point3.hash_coordinate(&mut hasher3); assert_ne!(hasher1.finish(), hasher3.finish()); } - #[test] - fn point_hash_coordinate_special_values() { - // Test hash_coordinate() with special values - - let point_nan1 = Point::new([f64::NAN, 2.0]); - let point_nan2 = Point::new([f64::NAN, 2.0]); - - let mut hasher1 = DefaultHasher::new(); - let mut hasher2 = DefaultHasher::new(); - - point_nan1.hash_coordinate(&mut hasher1); - point_nan2.hash_coordinate(&mut hasher2); - - // NaN values should hash consistently - assert_eq!(hasher1.finish(), hasher2.finish()); - } - // ============================================================================= // COMPREHENSIVE 1D POINT TESTS // ============================================================================= @@ -2565,15 +2072,15 @@ mod tests { // Test 1D points comprehensively // Creation - let point = Point::new([42.0]); + let point = Point::from_validated_coords([42.0]); assert_eq!(point.dim(), 1); assert_relative_eq!(point.to_array().as_slice(), [42.0].as_slice()); // Equality - let point2 = Point::new([42.0]); + let point2 = Point::from_validated_coords([42.0]); assert_eq!(point, point2); - let point3 = Point::new([43.0]); + let point3 = Point::from_validated_coords([43.0]); assert_ne!(point, point3); // Hashing @@ -2586,125 +2093,52 @@ mod tests { // Validation assert!(point.validate().is_ok()); - let invalid_1d = Point::new([f64::NAN]); - assert!(invalid_1d.validate().is_err()); + assert!(Point::<1>::try_new([f64::NAN]).is_err()); // Origin - let origin: Point = Point::origin(); + let origin: Point<1> = Point::origin(); assert_relative_eq!(origin.to_array().as_slice(), [0.0].as_slice()); // Serialization let json = serde_json::to_string(&point).unwrap(); assert_eq!(json, "[42.0]"); - let deserialized: Point = serde_json::from_str(&json).unwrap(); + let deserialized: Point<1> = serde_json::from_str(&json).unwrap(); assert_eq!(point, deserialized); } - #[test] - fn point_1d_special_values() { - // Test 1D points with special values - - let point_nan = Point::new([f64::NAN]); - let point_nan2 = Point::new([f64::NAN]); - assert_eq!(point_nan, point_nan2); - - let point_inf = Point::new([f64::INFINITY]); - let point_neg_inf = Point::new([f64::NEG_INFINITY]); - assert_ne!(point_inf, point_neg_inf); - assert!(point_neg_inf < point_inf); - - // NaN should be greater than infinity in OrderedFloat semantics - assert!(point_nan > point_inf); - } - - #[test] - fn point_mathematical_properties_comprehensive() { - // Test mathematical properties with various special values - let point_a = Point::new([f64::NAN, 2.0, f64::INFINITY]); - let point_b = Point::new([f64::NAN, 2.0, f64::INFINITY]); - let point_c = Point::new([f64::NAN, 2.0, f64::INFINITY]); - - // Reflexivity: a == a - assert_eq!(point_a, point_a); - - // Symmetry: if a == b, then b == a - let symmetry_ab = point_a == point_b; - let symmetry_ba = point_b == point_a; - assert_eq!(symmetry_ab, symmetry_ba); - assert!(symmetry_ab && symmetry_ba); - - // Transitivity: if a == b and b == c, then a == c - let trans_ab = point_a == point_b; - let trans_bc = point_b == point_c; - let trans_ac = point_a == point_c; - assert!(trans_ab && trans_bc && trans_ac); - - // Test with mixed special values - let point_mixed1 = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0]); - let point_mixed2 = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0]); - let point_mixed3 = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0]); - - // All should be equal - assert_eq!(point_mixed1, point_mixed2); - assert_eq!(point_mixed2, point_mixed3); - assert_eq!(point_mixed1, point_mixed3); - - // Test reflexivity with mixed values - assert_eq!(point_mixed1, point_mixed1); - } - - #[test] - fn point_integer_like_values() { - // Test integer-like values using f64 - let point_int_1 = Point::new([10.0, 20.0, 30.0]); - let point_int_2 = Point::new([10.0, 20.0, 30.0]); - let point_int_3 = Point::new([10.0, 20.0, 31.0]); - - assert_eq!(point_int_1, point_int_2); - assert_ne!(point_int_1, point_int_3); - - // Test in HashMap - let mut int_map: HashMap, String> = HashMap::new(); - int_map.insert(Point::new([1.0, 2.0]), "integer-like point".to_string()); - - let lookup_key = Point::new([1.0, 2.0]); - assert!(int_map.contains_key(&lookup_key)); - assert_eq!(int_map.get(&lookup_key).unwrap(), "integer-like point"); - } - #[test] fn point_floating_point_precision() { // Test that we can distinguish between very close floating point values - let point_epsilon1 = Point::new([1.0 + f64::EPSILON, 2.0]); - let point_epsilon2 = Point::new([1.0, 2.0]); + let point_epsilon1 = Point::from_validated_coords([1.0 + f64::EPSILON, 2.0]); + let point_epsilon2 = Point::from_validated_coords([1.0, 2.0]); assert_ne!(point_epsilon1, point_epsilon2); // Test with values that should be exactly equal - let point_exact1 = Point::new([0.1 + 0.2, 1.0]); - let point_exact2 = Point::new([0.3, 1.0]); + let point_exact1 = Point::from_validated_coords([0.1 + 0.2, 1.0]); + let point_exact2 = Point::from_validated_coords([0.3, 1.0]); // Note: Due to floating point representation, 0.1 + 0.2 != 0.3 // This test demonstrates the exact equality behavior assert_ne!(point_exact1, point_exact2); // Test that points with slightly different values are not approximately equal // (demonstrating that we use exact equality, not approximate) - let point_a = Point::new([1.0, 2.0]); - let point_b = Point::new([1.0 + f64::EPSILON, 2.0]); + let point_a = Point::from_validated_coords([1.0, 2.0]); + let point_b = Point::from_validated_coords([1.0 + f64::EPSILON, 2.0]); assert_ne!(point_a, point_b); // But points with exactly the same values are equal - let point_same1 = Point::new([1.0, 2.0]); - let point_same2 = Point::new([1.0, 2.0]); + let point_same1 = Point::from_validated_coords([1.0, 2.0]); + let point_same2 = Point::from_validated_coords([1.0, 2.0]); assert_eq!(point_same1, point_same2); } #[test] fn point_zero_and_negative_zero() { // Test zero and negative zero behavior - let point_pos_zero = Point::new([0.0, 0.0]); - let point_neg_zero = Point::new([-0.0, -0.0]); - let point_mixed_zero = Point::new([0.0, -0.0]); - let point_mixed_zero2 = Point::new([-0.0, 0.0]); + let point_pos_zero = Point::from_validated_coords([0.0, 0.0]); + let point_neg_zero = Point::from_validated_coords([-0.0, -0.0]); + let point_mixed_zero = Point::from_validated_coords([0.0, -0.0]); + let point_mixed_zero2 = Point::from_validated_coords([-0.0, 0.0]); // All should be equal (0.0 == -0.0 in IEEE 754) assert_eq!(point_pos_zero, point_neg_zero); @@ -2725,56 +2159,6 @@ mod tests { assert_eq!(hash_pos, hash_mixed2); } - #[test] - fn point_nan_different_creation_methods() { - // Test that different ways of creating NaN are treated as equal - let nan1 = f64::NAN; - let nan2 = f64::NAN; - let nan3 = f64::NAN; - - let point_nan_variant1 = Point::new([nan1, 1.0]); - let point_nan_variant2 = Point::new([nan2, 1.0]); - let point_nan_variant3 = Point::new([nan3, 1.0]); - - assert_eq!(point_nan_variant1, point_nan_variant2); - assert_eq!(point_nan_variant2, point_nan_variant3); - assert_eq!(point_nan_variant1, point_nan_variant3); - - // Test hash consistency - let hash1 = hash_of(&point_nan_variant1); - let hash2 = hash_of(&point_nan_variant2); - let hash3 = hash_of(&point_nan_variant3); - - assert_eq!(hash1, hash2); - assert_eq!(hash2, hash3); - } - - #[test] - fn point_mixed_special_values_comprehensive() { - // Test various combinations of special values - let point_all_special = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0, -0.0]); - let point_all_special_copy = - Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0, -0.0]); - - assert_eq!(point_all_special, point_all_special_copy); - - // Test different combinations - let point_combo1 = Point::new([f64::NAN, 1.0, f64::INFINITY]); - let point_combo2 = Point::new([f64::NAN, 1.0, f64::INFINITY]); - let point_combo3 = Point::new([f64::NAN, 1.0, f64::NEG_INFINITY]); // Different - - assert_eq!(point_combo1, point_combo2); - assert_ne!(point_combo1, point_combo3); - - // Test in collections - let mut special_set: HashSet> = HashSet::new(); - special_set.insert(point_combo1); - special_set.insert(point_combo2); // Should not increase size - special_set.insert(point_combo3); // Should increase size - - assert_eq!(special_set.len(), 2); - } - // ============================================================================= // CONVERSION ERROR TESTS // ============================================================================= @@ -2796,13 +2180,13 @@ mod tests { fn point_try_from_conversion_errors() { // Test non-finite value errors (NaN) let coords_with_nan = [f64::NAN, 1.0, 2.0]; - let result: Result, _> = Point::try_from(coords_with_nan); + let result: Result, _> = Point::try_from(coords_with_nan); let error = result.unwrap_err(); assert_non_finite_coordinate_index(&error, 0); // Test non-finite value errors (infinity) let coords_with_inf = [1.0, f64::INFINITY, 2.0]; - let result: Result, _> = Point::try_from(coords_with_inf); + let result: Result, _> = Point::try_from(coords_with_inf); let error = result.unwrap_err(); assert_non_finite_coordinate_index(&error, 1); @@ -2818,7 +2202,7 @@ mod tests { // i32 to f64 let coords_i32 = [1i32, -2i32, 3i32]; - let result: Result, _> = Point::try_from(coords_i32); + let result: Result, _> = Point::try_from(coords_i32); assert!(result.is_ok()); let point = result.unwrap(); assert_relative_eq!( @@ -2829,7 +2213,7 @@ mod tests { // Same type (f64 to f64) let coords_f64 = [1.0f64, 2.0f64]; - let result: Result, _> = Point::try_from(coords_f64); + let result: Result, _> = Point::try_from(coords_f64); assert!(result.is_ok()); let point = result.unwrap(); assert_relative_eq!( @@ -2843,7 +2227,7 @@ mod tests { fn point_try_from_error_details() { // Test error message formatting for NonFiniteValue with NaN let coords_with_nan = [f64::NAN, 1.0]; - let result: Result, _> = Point::try_from(coords_with_nan); + let result: Result, _> = Point::try_from(coords_with_nan); assert!(result.is_err()); let error = result.unwrap_err(); @@ -2854,7 +2238,7 @@ mod tests { // Test error cloning and equality with infinity let coords_with_inf = [f64::INFINITY, 2.0]; - let result2: Result, _> = Point::try_from(coords_with_inf); + let result2: Result, _> = Point::try_from(coords_with_inf); let error2 = result2.unwrap_err(); let error2_clone = error2.clone(); assert_eq!(error2, error2_clone); @@ -2871,7 +2255,7 @@ mod tests { ]; for &(coords, expected_index) in &test_cases { - let result: Result, _> = Point::try_from(coords); + let result: Result, _> = Point::try_from(coords); let error = result.unwrap_err(); assert_non_finite_coordinate_index(&error, expected_index); } @@ -2881,42 +2265,11 @@ mod tests { fn point_try_from_first_error_reported() { // When multiple coordinates have errors, the first one should be reported let coords_multi_error = [f64::NAN, f64::INFINITY, f64::NEG_INFINITY]; - let result: Result, _> = Point::try_from(coords_multi_error); + let result: Result, _> = Point::try_from(coords_multi_error); let error = result.unwrap_err(); assert_non_finite_coordinate_index(&error, 0); } - #[test] - fn point_deserialize_nan_handling() { - // Test deserialization of null values mapping to NaN - - // Create JSON with null value - let json_with_null = "[null,1.0,2.0]"; - - let result: Result, _> = serde_json::from_str(json_with_null); - - // Should successfully deserialize with null mapped to NaN - assert!(result.is_ok()); - let point = result.unwrap(); - - // First coordinate should be NaN - let coords = point.to_array(); - assert!(coords[0].is_nan()); - assert_relative_eq!(coords[1], 1.0); - assert_relative_eq!(coords[2], 2.0); - - // Test with multiple nulls - let json_multiple_nulls = "[null,null,3.0]"; - let result_multi: Result, _> = serde_json::from_str(json_multiple_nulls); - assert!(result_multi.is_ok()); - let point_multi = result_multi.unwrap(); - - let coords_multi = point_multi.to_array(); - assert!(coords_multi[0].is_nan()); - assert!(coords_multi[1].is_nan()); - assert_relative_eq!(coords_multi[2], 3.0); - } - #[test] fn point_trait_completeness() { // Helper functions for compile-time trait checks @@ -2925,7 +2278,7 @@ mod tests { // Test that Point implements all expected traits - let point = Point::new([1.0, 2.0, 3.0]); + let point = Point::from_validated_coords([1.0, 2.0, 3.0]); // Test Debug trait let debug_output = format!("{point:?}"); @@ -2933,14 +2286,14 @@ mod tests { assert!(debug_output.contains("Point")); // Test Default trait - let default_point: Point = Point::default(); + let default_point: Point<3> = Point::default(); assert_relative_eq!( default_point.to_array().as_slice(), [0.0, 0.0, 0.0].as_slice() ); // Test PartialOrd trait (ordering) - let point_smaller = Point::new([1.0, 2.0, 2.9]); + let point_smaller = Point::from_validated_coords([1.0, 2.0, 2.9]); assert!(point_smaller < point); // Test that Send and Sync are implemented (compile-time check) diff --git a/src/geometry/predicates.rs b/src/geometry/predicates.rs index 8e9a8f89..3083358b 100644 --- a/src/geometry/predicates.rs +++ b/src/geometry/predicates.rs @@ -14,15 +14,12 @@ use crate::geometry::matrix::{ use crate::geometry::point::Point; use crate::geometry::sos::exact_det_sign; use crate::geometry::traits::coordinate::{ - CoordinateConversionError, CoordinateScalar, DegenerateSimplexReason, -}; -use crate::geometry::util::{ - circumcenter, circumradius_with_center, hypot, safe_coords_to_f64, safe_scalar_to_f64, - squared_norm, + CoordinateConversionError, DEFAULT_TOLERANCE_F64, DegenerateSimplexReason, + InvalidCoordinateValue, }; +use crate::geometry::util::{circumcenter, circumradius_with_center, hypot, squared_norm}; use crate::prelude::CircumcenterError; use core::hint::cold_path; -use num_traits::Float; /// Convert an exact determinant sign (from `det_sign_exact`) to an [`Orientation`]. #[inline] @@ -119,21 +116,18 @@ pub(crate) const fn relative_insphere_classification(signs: RelativeInsphereSign /// Fill the lifted relative-coordinate insphere matrix for exact determinant evaluation. #[inline] -fn fill_relative_insphere_matrix( +fn fill_relative_insphere_matrix( matrix: &mut Matrix, - simplex_points: &[Point], - test_point: &Point, -) -> Result<(), CoordinateConversionError> -where - T: CoordinateScalar, -{ + simplex_points: &[Point], + test_point: &Point, +) -> Result<(), CoordinateConversionError> { debug_assert_eq!(K, D + 1); debug_assert_eq!(simplex_points.len(), D + 1); let reference_coords = simplex_points[0].coords(); for (row, point) in simplex_points.iter().skip(1).enumerate() { - let mut relative_coords: [T; D] = [T::zero(); D]; + let mut relative_coords = [0.0; D]; for (dst, (point_coord, reference_coord)) in relative_coords .iter_mut() .zip(point.coords().iter().zip(reference_coords.iter())) @@ -141,16 +135,21 @@ where *dst = *point_coord - *reference_coord; } - let relative_coords_f64 = safe_coords_to_f64(&relative_coords)?; - for (column, &value) in relative_coords_f64.iter().enumerate() { + for (column, &value) in relative_coords.iter().enumerate() { matrix_set(matrix, row, column, value)?; } - let squared_norm_f64 = safe_scalar_to_f64(squared_norm(&relative_coords))?; - matrix_set(matrix, row, D, squared_norm_f64)?; + let lifted = squared_norm(&relative_coords); + if !lifted.is_finite() { + return Err(CoordinateConversionError::NonFiniteValue { + coordinate_index: D, + coordinate_value: InvalidCoordinateValue::from_debug(&lifted), + }); + } + matrix_set(matrix, row, D, lifted)?; } - let mut test_relative_coords: [T; D] = [T::zero(); D]; + let mut test_relative_coords = [0.0; D]; for (dst, (point_coord, reference_coord)) in test_relative_coords .iter_mut() .zip(test_point.coords().iter().zip(reference_coords.iter())) @@ -158,26 +157,28 @@ where *dst = *point_coord - *reference_coord; } - let test_relative_coords_f64 = safe_coords_to_f64(&test_relative_coords)?; - for (column, &value) in test_relative_coords_f64.iter().enumerate() { + for (column, &value) in test_relative_coords.iter().enumerate() { matrix_set(matrix, D, column, value)?; } - let test_squared_norm_f64 = safe_scalar_to_f64(squared_norm(&test_relative_coords))?; - matrix_set(matrix, D, D, test_squared_norm_f64)?; + let lifted = squared_norm(&test_relative_coords); + if !lifted.is_finite() { + return Err(CoordinateConversionError::NonFiniteValue { + coordinate_index: D, + coordinate_value: InvalidCoordinateValue::from_debug(&lifted), + }); + } + matrix_set(matrix, D, D, lifted)?; Ok(()) } /// Compute exact signs for the relative-coordinate lifted insphere formulation. #[inline] -pub(crate) fn relative_insphere_signs( - simplex_points: &[Point], - test_point: &Point, -) -> Result -where - T: CoordinateScalar, -{ +pub(crate) fn relative_insphere_signs( + simplex_points: &[Point], + test_point: &Point, +) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -211,13 +212,10 @@ where /// Callers that already know the simplex orientation can combine this determinant /// with their orientation sign without recomputing the orientation determinant. #[inline] -pub(crate) fn relative_insphere_determinant_sign( - simplex_points: &[Point], - test_point: &Point, -) -> Result -where - T: CoordinateScalar, -{ +pub(crate) fn relative_insphere_determinant_sign( + simplex_points: &[Point], + test_point: &Point, +) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -449,10 +447,10 @@ impl std::fmt::Display for Orientation { /// }; /// /// # fn main() -> Result<(), CoordinateConversionError> { -/// let point1 = Point::new([0.0, 0.0, 0.0]); -/// let point2 = Point::new([1.0, 0.0, 0.0]); -/// let point3 = Point::new([0.0, 1.0, 0.0]); -/// let point4 = Point::new([0.0, 0.0, 1.0]); +/// let point1 = Point::try_from([0.0, 0.0, 0.0])?; +/// let point2 = Point::try_from([1.0, 0.0, 0.0])?; +/// let point3 = Point::try_from([0.0, 1.0, 0.0])?; +/// let point4 = Point::try_from([0.0, 0.0, 1.0])?; /// let simplex_points = vec![point1, point2, point3, point4]; /// let oriented = simplex_orientation(&simplex_points)?; /// assert_eq!(oriented, Orientation::NEGATIVE); @@ -460,12 +458,9 @@ impl std::fmt::Display for Orientation { /// # } /// ``` #[inline] -pub fn simplex_orientation( - simplex_points: &[Point], -) -> Result -where - T: CoordinateScalar, -{ +pub fn simplex_orientation( + simplex_points: &[Point], +) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -479,9 +474,7 @@ where try_with_la_stack_matrix!(k, |matrix| { // Populate rows with the coordinates of the points of the simplex. for (i, p) in simplex_points.iter().enumerate() { - let point_coords_f64 = safe_coords_to_f64(p.coords())?; - - for (j, &v) in point_coords_f64.iter().enumerate() { + for (j, &v) in p.coords().iter().enumerate() { matrix_set(&mut matrix, i, j, v)?; } @@ -499,12 +492,9 @@ where /// degenerate. Callers that require a definitive answer must fall back to the /// exact orientation path. #[inline] -pub(crate) fn simplex_orientation_fast_filter_sign( - simplex_points: &[Point], -) -> Result, CoordinateConversionError> -where - T: CoordinateScalar, -{ +pub(crate) fn simplex_orientation_fast_filter_sign( + simplex_points: &[Point], +) -> Result, CoordinateConversionError> { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -516,8 +506,7 @@ where let k = D + 1; try_with_la_stack_matrix!(k, |matrix| { for (i, p) in simplex_points.iter().enumerate() { - let point_coords_f64 = safe_coords_to_f64(p.coords())?; - for (j, &v) in point_coords_f64.iter().enumerate() { + for (j, &v) in p.coords().iter().enumerate() { matrix_set(&mut matrix, i, j, v)?; } matrix_set(&mut matrix, i, D, 1.0)?; @@ -596,23 +585,20 @@ where /// }; /// /// # fn main() -> Result<(), CircumcenterError> { -/// let point1 = Point::new([0.0, 0.0, 0.0]); -/// let point2 = Point::new([1.0, 0.0, 0.0]); -/// let point3 = Point::new([0.0, 1.0, 0.0]); -/// let point4 = Point::new([0.0, 0.0, 1.0]); +/// let point1 = Point::try_from([0.0, 0.0, 0.0])?; +/// let point2 = Point::try_from([1.0, 0.0, 0.0])?; +/// let point3 = Point::try_from([0.0, 1.0, 0.0])?; +/// let point4 = Point::try_from([0.0, 0.0, 1.0])?; /// let simplex_points = vec![point1, point2, point3, point4]; -/// let test_point = Point::new([0.5, 0.5, 0.5]); +/// let test_point = Point::try_from([0.5, 0.5, 0.5])?; /// assert_eq!(insphere_distance(&simplex_points, test_point)?, InSphere::INSIDE); /// # Ok(()) /// # } /// ``` -pub fn insphere_distance( - simplex_points: &[Point], - test_point: Point, -) -> Result -where - T: CoordinateScalar, -{ +pub fn insphere_distance( + simplex_points: &[Point], + test_point: Point, +) -> Result { let circumcenter = circumcenter(simplex_points)?; let circumradius = circumradius_with_center(simplex_points, &circumcenter)?; @@ -620,7 +606,7 @@ where let point_coords = test_point.coords(); let circumcenter_coords = circumcenter.coords(); - let mut diff_coords = [T::zero(); D]; + let mut diff_coords = [0.0; D]; for (dst, (p, c)) in diff_coords .iter_mut() .zip(point_coords.iter().zip(circumcenter_coords.iter())) @@ -631,16 +617,13 @@ where // Scale tolerance with geometric magnitude to avoid absolute-epsilon // misclassification for large circumradii in near-degenerate simplices. - let base_tolerance = T::default_tolerance(); - let scale = Float::max( - T::one(), - Float::max(Float::abs(circumradius), Float::abs(radius)), - ); + let base_tolerance = DEFAULT_TOLERANCE_F64; + let scale = 1.0_f64.max(circumradius.abs().max(radius.abs())); let tolerance = base_tolerance * scale; let signed_margin = circumradius - radius; - if Float::abs(signed_margin) <= tolerance { + if signed_margin.abs() <= tolerance { Ok(InSphere::BOUNDARY) - } else if signed_margin > T::zero() { + } else if signed_margin > 0.0 { Ok(InSphere::INSIDE) } else { Ok(InSphere::OUTSIDE) @@ -747,18 +730,18 @@ where /// }; /// /// # fn main() -> Result<(), CoordinateConversionError> { -/// let point1 = Point::new([0.0, 0.0, 0.0]); -/// let point2 = Point::new([1.0, 0.0, 0.0]); -/// let point3 = Point::new([0.0, 1.0, 0.0]); -/// let point4 = Point::new([0.0, 0.0, 1.0]); +/// let point1 = Point::try_from([0.0, 0.0, 0.0])?; +/// let point2 = Point::try_from([1.0, 0.0, 0.0])?; +/// let point3 = Point::try_from([0.0, 1.0, 0.0])?; +/// let point4 = Point::try_from([0.0, 0.0, 1.0])?; /// let simplex_points = vec![point1, point2, point3, point4]; /// /// // Test with a point clearly outside the circumsphere -/// let outside_point = Point::new([2.0, 2.0, 2.0]); +/// let outside_point = Point::try_from([2.0, 2.0, 2.0])?; /// assert_eq!(insphere(&simplex_points, outside_point)?, InSphere::OUTSIDE); /// /// // Test with a point clearly inside the circumsphere -/// let inside_point = Point::new([0.25, 0.25, 0.25]); +/// let inside_point = Point::try_from([0.25, 0.25, 0.25])?; /// assert_eq!(insphere(&simplex_points, inside_point)?, InSphere::INSIDE); /// # Ok(()) /// # } @@ -766,13 +749,10 @@ where /// /// See function-level docs above for detailed explanation and references. #[inline] -pub fn insphere( - simplex_points: &[Point], - test_point: Point, -) -> Result -where - T: CoordinateScalar, -{ +pub fn insphere( + simplex_points: &[Point], + test_point: Point, +) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -791,30 +771,21 @@ where try_with_la_stack_matrix!(k, |matrix| { for (i, p) in simplex_points.iter().enumerate() { let point_coords = p.coords(); - let point_coords_f64 = safe_coords_to_f64(point_coords)?; - for (j, &v) in point_coords_f64.iter().enumerate() { + for (j, &v) in point_coords.iter().enumerate() { matrix_set(&mut matrix, i, j, v)?; } - let squared_norm_t = squared_norm(point_coords); - matrix_set(&mut matrix, i, D, safe_scalar_to_f64(squared_norm_t)?)?; + matrix_set(&mut matrix, i, D, squared_norm(point_coords))?; matrix_set(&mut matrix, i, D + 1, 1.0)?; } let test_point_coords = test_point.coords(); - let test_point_coords_f64 = safe_coords_to_f64(test_point_coords)?; - for (j, &v) in test_point_coords_f64.iter().enumerate() { + for (j, &v) in test_point_coords.iter().enumerate() { matrix_set(&mut matrix, D + 1, j, v)?; } - let test_squared_norm_t = squared_norm(test_point_coords); - matrix_set( - &mut matrix, - D + 1, - D, - safe_scalar_to_f64(test_squared_norm_t)?, - )?; + matrix_set(&mut matrix, D + 1, D, squared_norm(test_point_coords))?; matrix_set(&mut matrix, D + 1, D + 1, 1.0)?; // Extract simplex orientation from the insphere matrix, avoiding a @@ -950,24 +921,24 @@ where /// use delaunay::prelude::geometry::Point; /// use delaunay::prelude::geometry::Coordinate; /// use delaunay::prelude::geometry::insphere_lifted; -/// let point1 = Point::new([0.0, 0.0, 0.0]); -/// let point2 = Point::new([1.0, 0.0, 0.0]); -/// let point3 = Point::new([0.0, 1.0, 0.0]); -/// let point4 = Point::new([0.0, 0.0, 1.0]); +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let point1 = Point::try_from([0.0, 0.0, 0.0])?; +/// let point2 = Point::try_from([1.0, 0.0, 0.0])?; +/// let point3 = Point::try_from([0.0, 1.0, 0.0])?; +/// let point4 = Point::try_from([0.0, 0.0, 1.0])?; /// let simplex_points = vec![point1, point2, point3, point4]; /// /// // Test with a point that should be inside according to the lifted paraboloid method -/// let test_point = Point::new([0.1, 0.1, 0.1]); +/// let test_point = Point::try_from([0.1, 0.1, 0.1])?; /// let result = insphere_lifted(&simplex_points, test_point); /// assert!(result.is_ok()); // Should execute without error +/// # Ok(()) +/// # } /// ``` -pub fn insphere_lifted( - simplex_points: &[Point], - test_point: Point, -) -> Result -where - T: CoordinateScalar, -{ +pub fn insphere_lifted( + simplex_points: &[Point], + test_point: Point, +) -> Result { if simplex_points.len() != D + 1 { return Err(SimplexValidationError::InsufficientVertices { actual: simplex_points.len(), @@ -989,7 +960,6 @@ where mod tests { use super::*; use crate::geometry::matrix::{LaError, matrix_set as try_matrix_set}; - use crate::geometry::traits::coordinate::Coordinate; use crate::prelude::circumradius; use approx::assert_relative_eq; use std::assert_matches; @@ -1034,9 +1004,9 @@ mod tests { // 2D: Right triangle with legs of length 1 let triangle_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; let radius_2d = circumradius(&triangle_2d).unwrap(); // For a right triangle with legs of length 1, circumradius is sqrt(2)/2 @@ -1045,10 +1015,10 @@ mod tests { // 3D: Unit tetrahedron (origin + unit basis vectors) let tetrahedron_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let radius_3d = circumradius(&tetrahedron_3d).unwrap(); println!("3D circumradius: {radius_3d}"); @@ -1059,11 +1029,11 @@ mod tests { // 4D: Unit 4-simplex let simplex_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; let radius_4d = circumradius(&simplex_4d).unwrap(); println!("4D circumradius: {radius_4d}"); @@ -1073,12 +1043,12 @@ mod tests { // 5D: Unit 5-simplex let simplex_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; let radius_5d = circumradius(&simplex_5d).unwrap(); println!("5D circumradius: {radius_5d}"); @@ -1118,107 +1088,139 @@ mod tests { } #[test] + #[expect( + clippy::too_many_lines, + reason = "dimension sweep keeps comparable insphere cases together" + )] fn test_insphere_basic_functionality_2d_to_5d() { // Test basic insphere functionality across dimensions 2D-5D // 2D triangle case let simplex_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; // Test far outside, inside, and boundary points for 2D assert_eq!( - insphere_lifted(&simplex_2d, Point::new([10.0, 10.0])).unwrap(), + insphere_lifted(&simplex_2d, Point::from_validated_coords([10.0, 10.0])).unwrap(), InSphere::OUTSIDE, "2D far outside point should be OUTSIDE" ); assert_eq!( - insphere_lifted(&simplex_2d, Point::new([0.1, 0.1])).unwrap(), + insphere_lifted(&simplex_2d, Point::from_validated_coords([0.1, 0.1])).unwrap(), InSphere::INSIDE, "2D inside point should be INSIDE" ); assert_eq!( - insphere_lifted(&simplex_2d, Point::new([0.0, 0.0])).unwrap(), + insphere_lifted(&simplex_2d, Point::from_validated_coords([0.0, 0.0])).unwrap(), InSphere::BOUNDARY, "2D vertex should be BOUNDARY" ); // 3D tetrahedron case let simplex_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; // Test far outside, inside, and boundary points for 3D assert_eq!( - insphere_lifted(&simplex_3d, Point::new([10.0, 10.0, 10.0])).unwrap(), + insphere_lifted( + &simplex_3d, + Point::from_validated_coords([10.0, 10.0, 10.0]) + ) + .unwrap(), InSphere::OUTSIDE, "3D far outside point should be OUTSIDE" ); assert_eq!( - insphere_lifted(&simplex_3d, Point::new([0.1, 0.1, 0.1])).unwrap(), + insphere_lifted(&simplex_3d, Point::from_validated_coords([0.1, 0.1, 0.1])).unwrap(), InSphere::INSIDE, "3D inside point should be INSIDE" ); assert_eq!( - insphere_lifted(&simplex_3d, Point::new([0.0, 0.0, 0.0])).unwrap(), + insphere_lifted(&simplex_3d, Point::from_validated_coords([0.0, 0.0, 0.0])).unwrap(), InSphere::BOUNDARY, "3D vertex should be BOUNDARY" ); // 4D simplex case let simplex_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; // Test far outside, inside, and boundary points for 4D assert_eq!( - insphere_lifted(&simplex_4d, Point::new([10.0, 10.0, 10.0, 10.0])).unwrap(), + insphere_lifted( + &simplex_4d, + Point::from_validated_coords([10.0, 10.0, 10.0, 10.0]) + ) + .unwrap(), InSphere::OUTSIDE, "4D far outside point should be OUTSIDE" ); assert_eq!( - insphere_lifted(&simplex_4d, Point::new([0.1, 0.1, 0.1, 0.1])).unwrap(), + insphere_lifted( + &simplex_4d, + Point::from_validated_coords([0.1, 0.1, 0.1, 0.1]) + ) + .unwrap(), InSphere::INSIDE, "4D inside point should be INSIDE" ); assert_eq!( - insphere_lifted(&simplex_4d, Point::new([0.0, 0.0, 0.0, 0.0])).unwrap(), + insphere_lifted( + &simplex_4d, + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]) + ) + .unwrap(), InSphere::BOUNDARY, "4D vertex should be BOUNDARY" ); // 5D simplex case let simplex_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; // Test far outside, inside, and boundary points for 5D assert_eq!( - insphere_lifted(&simplex_5d, Point::new([10.0, 10.0, 10.0, 10.0, 10.0])).unwrap(), + insphere_lifted( + &simplex_5d, + Point::from_validated_coords([10.0, 10.0, 10.0, 10.0, 10.0]) + ) + .unwrap(), InSphere::OUTSIDE, "5D far outside point should be OUTSIDE" ); assert_eq!( - insphere_lifted(&simplex_5d, Point::new([0.1, 0.1, 0.1, 0.1, 0.1])).unwrap(), + insphere_lifted( + &simplex_5d, + Point::from_validated_coords([0.1, 0.1, 0.1, 0.1, 0.1]) + ) + .unwrap(), InSphere::INSIDE, "5D inside point should be INSIDE" ); assert_eq!( - insphere_lifted(&simplex_5d, Point::new([0.0, 0.0, 0.0, 0.0, 0.0])).unwrap(), + insphere_lifted( + &simplex_5d, + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]) + ) + .unwrap(), InSphere::BOUNDARY, "5D vertex should be BOUNDARY" ); @@ -1229,9 +1231,12 @@ mod tests { // Test edge cases across dimensions including 1D // 1D case (line segment) - let simplex_1d = vec![Point::new([0.0]), Point::new([2.0])]; - let midpoint_1d = Point::new([1.0]); - let far_point_1d = Point::new([10.0]); + let simplex_1d = vec![ + Point::from_validated_coords([0.0]), + Point::from_validated_coords([2.0]), + ]; + let midpoint_1d = Point::from_validated_coords([1.0]); + let far_point_1d = Point::from_validated_coords([10.0]); assert!( insphere_lifted(&simplex_1d, midpoint_1d).is_ok(), @@ -1244,14 +1249,14 @@ mod tests { // Test circumcenter points for various dimensions let simplex_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; // Circumcenter should be inside the circumsphere - let circumcenter_3d = Point::new([0.5, 0.5, 0.5]); + let circumcenter_3d = Point::from_validated_coords([0.5, 0.5, 0.5]); assert_eq!( insphere_lifted(&simplex_3d, circumcenter_3d).unwrap(), InSphere::INSIDE, @@ -1260,23 +1265,30 @@ mod tests { // Test regular 4D simplex with symmetric properties let regular_4d_simplex = vec![ - Point::new([1.0, 1.0, 1.0, 1.0]), - Point::new([1.0, -1.0, -1.0, -1.0]), - Point::new([-1.0, 1.0, -1.0, -1.0]), - Point::new([-1.0, -1.0, 1.0, -1.0]), - Point::new([-1.0, -1.0, -1.0, 1.0]), + Point::from_validated_coords([1.0, 1.0, 1.0, 1.0]), + Point::from_validated_coords([1.0, -1.0, -1.0, -1.0]), + Point::from_validated_coords([-1.0, 1.0, -1.0, -1.0]), + Point::from_validated_coords([-1.0, -1.0, 1.0, -1.0]), + Point::from_validated_coords([-1.0, -1.0, -1.0, 1.0]), ]; // Origin should be inside this symmetric simplex assert_eq!( - insphere_lifted(®ular_4d_simplex, Point::new([0.0, 0.0, 0.0, 0.0])).unwrap(), + insphere_lifted( + ®ular_4d_simplex, + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]) + ) + .unwrap(), InSphere::INSIDE, "Origin should be inside symmetric 4D simplex" ); // Error case: insufficient vertices - let incomplete_simplex = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; // Only 2 vertices for 3D - let test_point = Point::new([0.5, 0.5, 0.5]); + let incomplete_simplex = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; // Only 2 vertices for 3D + let test_point = Point::from_validated_coords([0.5, 0.5, 0.5]); assert!( insphere_lifted(&incomplete_simplex, test_point).is_err(), @@ -1286,8 +1298,11 @@ mod tests { #[test] fn test_relative_insphere_determinant_sign_rejects_wrong_point_count() { - let incomplete_simplex = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; - let test_point = Point::new([0.5, 0.5, 0.5]); + let incomplete_simplex = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; + let test_point = Point::from_validated_coords([0.5, 0.5, 0.5]); let err = relative_insphere_determinant_sign(&incomplete_simplex, &test_point).unwrap_err(); @@ -1304,11 +1319,11 @@ mod tests { #[test] fn test_relative_insphere_determinant_sign_boundary_matches_exact_signs() { let simplex = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; - let boundary = Point::new([1.0, 1.0]); + let boundary = Point::from_validated_coords([1.0, 1.0]); let determinant_sign = relative_insphere_determinant_sign(&simplex, &boundary).unwrap(); let signs = relative_insphere_signs(&simplex, &boundary).unwrap(); @@ -1320,7 +1335,10 @@ mod tests { #[test] fn predicates_circumcenter_error_cases() { // Test circumcenter calculation with degenerate cases - let points = vec![Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; // Only 2 points for 2D + let points = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; // Only 2 points for 2D // Test with insufficient vertices for proper simplex let center_result = circumcenter(&points); @@ -1331,10 +1349,10 @@ mod tests { fn predicates_circumcenter_collinear_points() { // Test circumcenter with collinear points (should fail) let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([2.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([2.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), ]; // This should fail because points are collinear @@ -1346,10 +1364,10 @@ mod tests { fn predicates_circumradius_with_center() { // Test the circumradius_with_center function let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let center = circumcenter(&points).unwrap(); @@ -1363,17 +1381,17 @@ mod tests { fn predicates_circumsphere_edge_cases() { // Test circumsphere containment with simple cases let simplex_points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; // Test that the methods run without error - let test_point = Point::new([0.25, 0.25]); + let test_point = Point::from_validated_coords([0.25, 0.25]); assert!(insphere_distance(&simplex_points, test_point).is_ok()); // At minimum, both methods should give the same result for the same input - let far_point = Point::new([100.0, 100.0]); + let far_point = Point::from_validated_coords([100.0, 100.0]); assert!(insphere_distance(&simplex_points, far_point).is_ok()); } @@ -1385,9 +1403,9 @@ mod tests { fn test_simplex_orientation_comprehensive() { // Test 2D orientation - positive case let positive_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; assert_eq!( simplex_orientation(&positive_2d).unwrap(), @@ -1397,9 +1415,9 @@ mod tests { // Test 2D orientation - negative case (reversed order) let negative_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([0.0, 1.0]), - Point::new([1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), + Point::from_validated_coords([1.0, 0.0]), ]; assert_eq!( simplex_orientation(&negative_2d).unwrap(), @@ -1409,9 +1427,9 @@ mod tests { // Test 2D degenerate case - collinear points let degenerate_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([2.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([2.0, 0.0]), ]; assert_eq!( simplex_orientation(°enerate_2d).unwrap(), @@ -1421,10 +1439,10 @@ mod tests { // Test 3D orientation - positive case let positive_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; assert_eq!( simplex_orientation(&positive_3d).unwrap(), @@ -1434,10 +1452,10 @@ mod tests { // Test 3D orientation - negative case let negative_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; assert_eq!( simplex_orientation(&negative_3d).unwrap(), @@ -1447,10 +1465,10 @@ mod tests { // Test 3D degenerate case - coplanar points let degenerate_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([1.0, 1.0, 0.0]), // All points on z=0 plane + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 1.0, 0.0]), // All points on z=0 plane ]; assert_eq!( simplex_orientation(°enerate_3d).unwrap(), @@ -1460,11 +1478,11 @@ mod tests { // Test 4D orientation - positive case let positive_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; assert_eq!( simplex_orientation(&positive_4d).unwrap(), @@ -1474,11 +1492,11 @@ mod tests { // Test 4D orientation - negative case (different ordering) let negative_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; assert_eq!( simplex_orientation(&negative_4d).unwrap(), @@ -1488,11 +1506,11 @@ mod tests { // Test 4D degenerate case - points in 3D subspace let degenerate_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([1.0, 1.0, 1.0, 0.0]), // All points have w=0 + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 1.0, 1.0, 0.0]), // All points have w=0 ]; assert_eq!( simplex_orientation(°enerate_4d).unwrap(), @@ -1503,12 +1521,12 @@ mod tests { // Test 5D orientation - positive case // For even dimensions, we need to adjust vertex order to get positive orientation let positive_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; assert_eq!( simplex_orientation(&positive_5d).unwrap(), @@ -1518,12 +1536,12 @@ mod tests { // Test 5D orientation - negative case (reversed from positive) let negative_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; assert_eq!( simplex_orientation(&negative_5d).unwrap(), @@ -1533,12 +1551,12 @@ mod tests { // Test 5D degenerate case - points in 4D subspace let degenerate_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([1.0, 1.0, 1.0, 1.0, 0.0]), // All points have v=0 + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 1.0, 1.0, 1.0, 0.0]), // All points have v=0 ]; assert_eq!( simplex_orientation(°enerate_5d).unwrap(), @@ -1547,7 +1565,10 @@ mod tests { ); // Test error case: insufficient vertices - let insufficient_vertices = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; // Only 2 vertices for 3D + let insufficient_vertices = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; // Only 2 vertices for 3D assert!( simplex_orientation(&insufficient_vertices).is_err(), "Should error with insufficient vertices" @@ -1558,12 +1579,12 @@ mod tests { fn test_insphere_degenerate_simplex_error_handling() { // Create a degenerate simplex (coplanar points in 3D) let degenerate_simplex = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([1.0, 1.0, 0.0]), // All points lie on the same plane (z=0) + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 1.0, 0.0]), // All points lie on the same plane (z=0) ]; - let test_point = Point::new([0.5, 0.5, 0.5]); + let test_point = Point::from_validated_coords([0.5, 0.5, 0.5]); // Test that insphere errors with a typed degenerate-simplex variant. let result = insphere(°enerate_simplex, test_point); @@ -1590,7 +1611,10 @@ mod tests { } // Test error handling for insufficient vertices - let insufficient_vertices = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; // Only 2 vertices for 3D + let insufficient_vertices = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; // Only 2 vertices for 3D assert!( insphere_distance(&insufficient_vertices, test_point).is_err(), @@ -1603,13 +1627,13 @@ mod tests { // Create a simplex and test with a point on or near the boundary // For 2D case, use a right triangle let simplex_points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; // Test with one of the original vertices (should be on boundary) - let vertex_point = Point::new([0.0, 0.0]); + let vertex_point = Point::from_validated_coords([0.0, 0.0]); let result = insphere_lifted(&simplex_points, vertex_point).unwrap(); assert_eq!( result, @@ -1618,7 +1642,7 @@ mod tests { ); // Test with a point clearly inside - let inside_point = Point::new([0.1, 0.1]); + let inside_point = Point::from_validated_coords([0.1, 0.1]); let inside_result = insphere_lifted(&simplex_points, inside_point).unwrap(); assert_eq!( inside_result, @@ -1627,7 +1651,7 @@ mod tests { ); // Test with a point clearly outside - let outside_point = Point::new([10.0, 10.0]); + let outside_point = Point::from_validated_coords([10.0, 10.0]); let outside_result = insphere_lifted(&simplex_points, outside_point).unwrap(); assert_eq!( outside_result, @@ -1640,20 +1664,29 @@ mod tests { fn test_insphere_and_insphere_lifted_consistency() { // Test that both insphere implementations give consistent results for various cases let simplex_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; // Test multiple points let test_cases = [ // Inside points - (Point::new([0.2, 0.2, 0.2]), InSphere::INSIDE), + ( + Point::from_validated_coords([0.2, 0.2, 0.2]), + InSphere::INSIDE, + ), // Outside points - (Point::new([2.0, 2.0, 2.0]), InSphere::OUTSIDE), + ( + Point::from_validated_coords([2.0, 2.0, 2.0]), + InSphere::OUTSIDE, + ), // Boundary points (simplex vertices should be on boundary) - (Point::new([0.0, 0.0, 0.0]), InSphere::BOUNDARY), + ( + Point::from_validated_coords([0.0, 0.0, 0.0]), + InSphere::BOUNDARY, + ), ]; for (point, expected) in &test_cases { @@ -1682,17 +1715,17 @@ mod tests { fn test_insphere_methods_2d_comprehensive() { // 2D triangle: origin, (1,0), (0,1) let simplex = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; // Test various points and verify all methods agree let test_cases = [ - (Point::new([0.1, 0.1]), "inside"), // Clearly inside - (Point::new([10.0, 10.0]), "outside"), // Clearly outside - (Point::new([0.0, 0.0]), "boundary"), // Vertex (on boundary) - (Point::new([0.5, 0.0]), "boundary"), // Edge midpoint + (Point::from_validated_coords([0.1, 0.1]), "inside"), // Clearly inside + (Point::from_validated_coords([10.0, 10.0]), "outside"), // Clearly outside + (Point::from_validated_coords([0.0, 0.0]), "boundary"), // Vertex (on boundary) + (Point::from_validated_coords([0.5, 0.0]), "boundary"), // Edge midpoint ]; for (test_point, description) in &test_cases { @@ -1720,17 +1753,17 @@ mod tests { fn test_insphere_methods_3d_comprehensive() { // 3D tetrahedron: origin and unit basis vectors let simplex = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let test_cases = [ - (Point::new([0.1, 0.1, 0.1]), "inside"), - (Point::new([10.0, 10.0, 10.0]), "outside"), - (Point::new([0.0, 0.0, 0.0]), "boundary"), // Vertex - (Point::new([0.25, 0.25, 0.25]), "inside"), // Centroid region + (Point::from_validated_coords([0.1, 0.1, 0.1]), "inside"), + (Point::from_validated_coords([10.0, 10.0, 10.0]), "outside"), + (Point::from_validated_coords([0.0, 0.0, 0.0]), "boundary"), // Vertex + (Point::from_validated_coords([0.25, 0.25, 0.25]), "inside"), // Centroid region ]; for (test_point, description) in &test_cases { @@ -1759,18 +1792,24 @@ mod tests { fn test_insphere_methods_4d_comprehensive() { // 4D simplex: origin and unit basis vectors let simplex = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; let test_cases = [ - (Point::new([0.1, 0.1, 0.1, 0.1]), "inside"), - (Point::new([10.0, 10.0, 10.0, 10.0]), "outside"), - (Point::new([0.0, 0.0, 0.0, 0.0]), "boundary"), // Vertex - (Point::new([0.2, 0.2, 0.2, 0.2]), "inside"), + (Point::from_validated_coords([0.1, 0.1, 0.1, 0.1]), "inside"), + ( + Point::from_validated_coords([10.0, 10.0, 10.0, 10.0]), + "outside", + ), + ( + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + "boundary", + ), // Vertex + (Point::from_validated_coords([0.2, 0.2, 0.2, 0.2]), "inside"), ]; for (test_point, description) in &test_cases { @@ -1799,19 +1838,31 @@ mod tests { fn test_insphere_methods_5d_comprehensive() { // 5D simplex: origin and unit basis vectors let simplex = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; let test_cases = [ - (Point::new([0.1, 0.1, 0.1, 0.1, 0.1]), "inside"), - (Point::new([10.0, 10.0, 10.0, 10.0, 10.0]), "outside"), - (Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), "boundary"), // Vertex - (Point::new([0.15, 0.15, 0.15, 0.15, 0.15]), "inside"), + ( + Point::from_validated_coords([0.1, 0.1, 0.1, 0.1, 0.1]), + "inside", + ), + ( + Point::from_validated_coords([10.0, 10.0, 10.0, 10.0, 10.0]), + "outside", + ), + ( + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + "boundary", + ), // Vertex + ( + Point::from_validated_coords([0.15, 0.15, 0.15, 0.15, 0.15]), + "inside", + ), ]; for (test_point, description) in &test_cases { @@ -1842,34 +1893,34 @@ mod tests { // 2D: Test with very small simplex let tiny_simplex_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1e-6, 0.0]), - Point::new([0.0, 1e-6]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1e-6, 0.0]), + Point::from_validated_coords([0.0, 1e-6]), ]; - let test_point_2d = Point::new([1e-7, 1e-7]); + let test_point_2d = Point::from_validated_coords([1e-7, 1e-7]); let result_2d = insphere(&tiny_simplex_2d, test_point_2d); assert!(result_2d.is_ok(), "2D tiny simplex should work"); // 3D: Test with large coordinates let large_simplex_3d = vec![ - Point::new([1e6, 0.0, 0.0]), - Point::new([1e6 + 1.0, 0.0, 0.0]), - Point::new([1e6, 1.0, 0.0]), - Point::new([1e6, 0.0, 1.0]), + Point::from_validated_coords([1e6, 0.0, 0.0]), + Point::from_validated_coords([1e6 + 1.0, 0.0, 0.0]), + Point::from_validated_coords([1e6, 1.0, 0.0]), + Point::from_validated_coords([1e6, 0.0, 1.0]), ]; - let test_point_3d = Point::new([1e6 + 0.1, 0.1, 0.1]); + let test_point_3d = Point::from_validated_coords([1e6 + 0.1, 0.1, 0.1]); let result_3d = insphere(&large_simplex_3d, test_point_3d); assert!(result_3d.is_ok(), "3D large coordinates should work"); // 4D: Test with negative coordinates let negative_simplex_4d = vec![ - Point::new([-1.0, -1.0, -1.0, -1.0]), - Point::new([0.0, -1.0, -1.0, -1.0]), - Point::new([-1.0, 0.0, -1.0, -1.0]), - Point::new([-1.0, -1.0, 0.0, -1.0]), - Point::new([-1.0, -1.0, -1.0, 0.0]), + Point::from_validated_coords([-1.0, -1.0, -1.0, -1.0]), + Point::from_validated_coords([0.0, -1.0, -1.0, -1.0]), + Point::from_validated_coords([-1.0, 0.0, -1.0, -1.0]), + Point::from_validated_coords([-1.0, -1.0, 0.0, -1.0]), + Point::from_validated_coords([-1.0, -1.0, -1.0, 0.0]), ]; - let test_point_4d = Point::new([-0.5, -0.5, -0.5, -0.5]); + let test_point_4d = Point::from_validated_coords([-0.5, -0.5, -0.5, -0.5]); let result_4d = insphere(&negative_simplex_4d, test_point_4d); assert!(result_4d.is_ok(), "4D negative coordinates should work"); } @@ -1882,21 +1933,21 @@ mod tests { // Test 3D case with various random-ish points let simplex_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let test_points = [ - Point::new([0.1, 0.1, 0.1]), - Point::new([0.3, 0.2, 0.1]), - Point::new([0.5, 0.5, 0.5]), - Point::new([1.0, 1.0, 1.0]), - Point::new([2.0, 2.0, 2.0]), - Point::new([-0.1, -0.1, -0.1]), - Point::new([0.25, 0.25, 0.25]), - Point::new([0.01, 0.01, 0.01]), + Point::from_validated_coords([0.1, 0.1, 0.1]), + Point::from_validated_coords([0.3, 0.2, 0.1]), + Point::from_validated_coords([0.5, 0.5, 0.5]), + Point::from_validated_coords([1.0, 1.0, 1.0]), + Point::from_validated_coords([2.0, 2.0, 2.0]), + Point::from_validated_coords([-0.1, -0.1, -0.1]), + Point::from_validated_coords([0.25, 0.25, 0.25]), + Point::from_validated_coords([0.01, 0.01, 0.01]), ]; for test_point in &test_points { @@ -1936,16 +1987,14 @@ mod tests { /// 1. Simplex has expected orientation /// 2. `insphere_lifted` and insphere produce identical results /// 3. Test point produces expected `InSphere` result - fn check_insphere_parity( - simplex: &[Point], - test_point: Point, + fn check_insphere_parity( + simplex: &[Point], + test_point: Point, expected_orientation: Orientation, expected_result: InSphere, dimension: usize, orientation_label: &str, - ) where - T: CoordinateScalar, - { + ) { let orientation = simplex_orientation(simplex).unwrap(); assert_eq!( orientation, expected_orientation, @@ -1972,11 +2021,11 @@ mod tests { // 2D (even dimension) with POSITIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ], - Point::new([0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1]), Orientation::POSITIVE, InSphere::INSIDE, 2, @@ -1986,12 +2035,12 @@ mod tests { // 3D (odd dimension) with POSITIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ], - Point::new([0.1, 0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1, 0.1]), Orientation::POSITIVE, InSphere::INSIDE, 3, @@ -2001,13 +2050,13 @@ mod tests { // 4D (even dimension) with POSITIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ], - Point::new([0.1, 0.1, 0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1, 0.1, 0.1]), Orientation::POSITIVE, InSphere::INSIDE, 4, @@ -2017,14 +2066,14 @@ mod tests { // 5D (odd dimension) with POSITIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ], - Point::new([0.1, 0.1, 0.1, 0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1, 0.1, 0.1, 0.1]), Orientation::POSITIVE, InSphere::INSIDE, 5, @@ -2040,11 +2089,11 @@ mod tests { // 2D (even dimension) with NEGATIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0]), - Point::new([0.0, 1.0]), - Point::new([1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), + Point::from_validated_coords([1.0, 0.0]), ], - Point::new([0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1]), Orientation::NEGATIVE, InSphere::INSIDE, 2, @@ -2054,12 +2103,12 @@ mod tests { // 3D (odd dimension) with NEGATIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ], - Point::new([0.1, 0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1, 0.1]), Orientation::NEGATIVE, InSphere::INSIDE, 3, @@ -2069,13 +2118,13 @@ mod tests { // 4D (even dimension) with NEGATIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ], - Point::new([0.1, 0.1, 0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1, 0.1, 0.1]), Orientation::NEGATIVE, InSphere::INSIDE, 4, @@ -2085,14 +2134,14 @@ mod tests { // 5D (odd dimension) with NEGATIVE orientation check_insphere_parity( &[ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ], - Point::new([0.1, 0.1, 0.1, 0.1, 0.1]), + Point::from_validated_coords([0.1, 0.1, 0.1, 0.1, 0.1]), Orientation::NEGATIVE, InSphere::INSIDE, 5, @@ -2239,9 +2288,9 @@ mod tests { // thresholds but produces a non-zero exact determinant. let eps = f64::from_bits(0x3CD0_0000_0000_0000); // 2^-50 let nearly_collinear = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, eps]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, eps]), ]; // Exact arithmetic should detect this is NOT degenerate. let orientation = simplex_orientation(&nearly_collinear).unwrap(); @@ -2257,10 +2306,10 @@ mod tests { // Near-degenerate 3D tetrahedron: fourth point is almost coplanar. let eps = f64::from_bits(0x3CD0_0000_0000_0000); // 2^-50 let nearly_coplanar = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, eps]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, eps]), ]; let orientation = simplex_orientation(&nearly_coplanar).unwrap(); assert_ne!( @@ -2380,9 +2429,11 @@ mod tests { #[test] fn test_insphere_wrong_point_count() { - let two_points: Vec> = - vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; - let result = insphere(&two_points, Point::new([0.5, 0.5, 0.5])); + let two_points: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; + let result = insphere(&two_points, Point::from_validated_coords([0.5, 0.5, 0.5])); assert!(result.is_err(), "insphere with 2 points in 3D should error"); } @@ -2395,13 +2446,13 @@ mod tests { // Test point very far from the simplex: relative squared norm overflows // f64, triggering the map_err conversion on the test-point path. let simplex = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; // 1e155² = 1e310 overflows f64::MAX ≈ 1.8e308. - let far_point = Point::new([1e155, 0.0, 0.0]); + let far_point = Point::from_validated_coords([1e155, 0.0, 0.0]); let result = insphere_lifted(&simplex, far_point); assert!( result.is_err(), @@ -2413,9 +2464,9 @@ mod tests { fn test_exact_orientation_truly_degenerate() { // Truly degenerate cases should still be detected correctly. let collinear_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([2.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([2.0, 0.0]), ]; assert_eq!( simplex_orientation(&collinear_2d).unwrap(), @@ -2425,10 +2476,10 @@ mod tests { // Row 2 = row 0 + row 1 in exact arithmetic (linear combination). let coplanar_3d = vec![ - Point::new([1.0, 2.0, 3.0]), - Point::new([4.0, 5.0, 6.0]), - Point::new([5.0, 7.0, 9.0]), - Point::new([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 2.0, 3.0]), + Point::from_validated_coords([4.0, 5.0, 6.0]), + Point::from_validated_coords([5.0, 7.0, 9.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), ]; assert_eq!( simplex_orientation(&coplanar_3d).unwrap(), diff --git a/src/geometry/quality.rs b/src/geometry/quality.rs index f84aeacd..c38438f6 100644 --- a/src/geometry/quality.rs +++ b/src/geometry/quality.rs @@ -41,12 +41,11 @@ use crate::core::{ use crate::geometry::{ kernel::Kernel, point::Point, - traits::coordinate::{CoordinateConversionValue, CoordinateScalar}, + traits::coordinate::CoordinateConversionValue, util::{CircumcenterError, circumradius, hypot, inradius as simplex_inradius, simplex_volume}, }; use core::fmt; -use num_traits::{NumCast, One}; -use std::ops::{AddAssign, Div}; +use num_traits::One; use thiserror::Error; /// Numeric operation being performed when quality metric computation failed. @@ -248,9 +247,9 @@ pub enum QualityError { fn simplex_points( tri: &Triangulation, simplex_key: SimplexKey, -) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, QualityError> +) -> Result, MAX_PRACTICAL_DIMENSION_SIZE>, QualityError> where - K: Kernel, + K: Kernel, { let vertex_keys = tri.tds @@ -291,18 +290,15 @@ where /// # Errors /// /// Returns `QualityError` if edge count conversion or epsilon conversion fails. -fn scale_aware_epsilon( - points: &SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE>, -) -> Result<(T, T), QualityError> -where - T: CoordinateScalar + AddAssign, -{ - let mut total_edge_length = T::zero(); +fn scale_aware_epsilon( + points: &SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE>, +) -> (f64, f64) { + let mut total_edge_length = 0.0; let mut edge_count = 0; for i in 0..points.len() { for j in (i + 1)..points.len() { - let mut diff_coords = [T::zero(); D]; + let mut diff_coords = [0.0; D]; for (idx, diff) in diff_coords.iter_mut().enumerate() { *diff = points[i].coords()[idx] - points[j].coords()[idx]; } @@ -314,26 +310,15 @@ where // If there are no edges (e.g., D == 0), fall back to floor epsilon. if edge_count == 0 { - let floor: T = NumCast::from(1e-12).ok_or(QualityError::NumericConversion { - operation: QualityNumericOperation::EpsilonFloorConversion, - })?; - return Ok((T::zero(), floor)); + return (0.0, 1e-12); } - let edge_count_t = NumCast::from(edge_count).ok_or(QualityError::NumericConversion { - operation: QualityNumericOperation::EdgeCountConversion, - })?; - let avg_edge_length = total_edge_length / edge_count_t; - - let floor: T = NumCast::from(1e-12).ok_or(QualityError::NumericConversion { - operation: QualityNumericOperation::EpsilonFloorConversion, - })?; - let relative_factor: T = NumCast::from(1e-8).ok_or(QualityError::NumericConversion { - operation: QualityNumericOperation::RelativeFactorConversion, - })?; + let avg_edge_length = total_edge_length / f64::from(edge_count); + let floor: f64 = 1e-12; + let relative_factor: f64 = 1e-8; let epsilon = floor.max(avg_edge_length * relative_factor); - Ok((avg_edge_length, epsilon)) + (avg_edge_length, epsilon) } /// Computes the radius ratio quality metric for a simplex. @@ -381,9 +366,9 @@ where /// # fn main() -> Result<(), ExampleError> { /// // Create a 2D equilateral triangle /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.5, 0.866]), // approximately sqrt(3)/2 +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.866]).expect("finite vertex coordinates"), // approximately sqrt(3)/2 /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { @@ -399,10 +384,9 @@ where pub fn radius_ratio( tri: &Triangulation, simplex_key: SimplexKey, -) -> Result +) -> Result where - K: Kernel, - K::Scalar: Div, + K: Kernel, { // Extract simplex points using helper let points = simplex_points(tri, simplex_key)?; @@ -426,7 +410,7 @@ where })?; // Check for near-zero inradius (degenerate simplex) using scale-aware tolerance - let (avg_edge_length, epsilon) = scale_aware_epsilon(&points)?; + let (avg_edge_length, epsilon) = scale_aware_epsilon(&points); if inradius_val < epsilon { return Err(QualityError::DegenerateSimplex { @@ -492,9 +476,9 @@ where /// # fn main() -> Result<(), ExampleError> { /// // Create a 2D triangle /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let Some((simplex_key, _)) = dt.simplices().next() else { @@ -509,10 +493,9 @@ where pub fn normalized_volume( tri: &Triangulation, simplex_key: SimplexKey, -) -> Result +) -> Result where - K: Kernel, - K::Scalar: Div, + K: Kernel, { // Extract simplex points using helper let points = simplex_points(tri, simplex_key)?; @@ -531,10 +514,10 @@ where })?; // Compute scale-aware epsilon and average edge length - let (avg_edge_length, epsilon) = scale_aware_epsilon(&points)?; - let mut epsilon_pow = K::Scalar::one(); + let (avg_edge_length, epsilon) = scale_aware_epsilon(&points); + let mut epsilon_pow = f64::one(); for _ in 0..D { - epsilon_pow = epsilon_pow * epsilon; + epsilon_pow *= epsilon; } // Check for degenerate simplex (volume too small) @@ -562,9 +545,9 @@ where } // Normalize volume by (avg_edge_length)^D for scale invariance - let mut edge_length_power = K::Scalar::one(); + let mut edge_length_power = f64::one(); for _ in 0..D { - edge_length_power = edge_length_power * avg_edge_length; + edge_length_power *= avg_edge_length; } // Check edge_length_power for numerical underflow using a D-dimensional threshold. @@ -597,9 +580,7 @@ mod tests { use crate::core::tds::Tds; use crate::core::triangulation::Triangulation; use crate::geometry::kernel::FastKernel; - use crate::geometry::traits::coordinate::Coordinate; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use approx::assert_relative_eq; use std::assert_matches; @@ -658,7 +639,7 @@ let key_base = dt_base.simplices().next().unwrap().0; // Scale by 10x let vertices_scaled: Vec<_> = vertices_base.iter().map(|v| { let coords: [f64; $dim] = std::array::from_fn(|idx| v.point().coords()[idx] * 10.0); - vertex!(coords) + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap() }).collect(); let dt_scaled: DelaunayTriangulation<_, (), (), $dim> = DelaunayTriangulation::new(&vertices_scaled).unwrap(); let key_scaled = dt_scaled.simplices().next().unwrap().0; @@ -682,7 +663,7 @@ let key_base = dt_base.simplices().next().unwrap().0; // Translate by [5.0, 5.0, ...] let vertices_translated: Vec<_> = vertices_base.iter().map(|v| { let coords: [f64; $dim] = std::array::from_fn(|idx| v.point().coords()[idx] + 5.0); - vertex!(coords) + crate::core::vertex::Vertex::<(), _>::try_new(coords).unwrap() }).collect(); let dt_translated: DelaunayTriangulation<_, (), (), $dim> = DelaunayTriangulation::new(&vertices_translated).unwrap(); let key_translated = dt_translated.simplices().next().unwrap().0; @@ -704,74 +685,74 @@ let key_translated = dt_translated.simplices().next().unwrap().0; test_quality_dimensions! { quality_2d_unit => 2 => "unit simplex" => vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ], 2.0, 3.0, quality_2d_equilateral => 2 => "equilateral triangle" => vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025]).unwrap(), ], 1.9, 2.1, quality_2d_right => 2 => "right triangle" => vec![ - vertex!([0.0, 0.0]), - vertex!([3.0, 0.0]), - vertex!([0.0, 4.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 4.0]).unwrap(), ], 2.0, 5.0, quality_3d_unit => 3 => "unit simplex" => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ], 3.0, 5.0, quality_3d_regular => 3 => "regular tetrahedron" => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.5, 0.866_025, 0.0]), - vertex!([0.5, 0.288_675, 0.816_497]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.288_675, 0.816_497]).unwrap(), ], 2.8, 3.2, quality_4d_unit => 4 => "unit simplex" => vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ], 4.0, 7.0, quality_4d_regular => 4 => "regular simplex" => vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.5, 0.866_025, 0.0, 0.0]), - vertex!([0.5, 0.288_675, 0.816_497, 0.0]), - vertex!([0.5, 0.288_675, 0.204_124, 0.790_569]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.288_675, 0.816_497, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.288_675, 0.204_124, 0.790_569]).unwrap(), ], 3.8, 4.2, quality_5d_unit => 5 => "unit simplex" => vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ], 5.0, 15.0, } @@ -784,9 +765,9 @@ let key_translated = dt_translated.simplices().next().unwrap().0; fn test_degenerate_nearly_collinear() { // Nearly collinear points - tests both metrics let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([2.0, 0.001]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.001]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -859,9 +840,9 @@ let key_translated = dt_translated.simplices().next().unwrap().0; #[test] fn scale_aware_epsilon_uses_floor_when_point_set_has_no_edges() { let mut points = SmallBuffer::new(); - points.push(Point::::new([])); + points.push(Point::from_validated_coords([])); - let (avg_edge_length, epsilon) = scale_aware_epsilon(&points).unwrap(); + let (avg_edge_length, epsilon) = scale_aware_epsilon(&points); assert_relative_eq!(avg_edge_length, 0.0); assert_relative_eq!(epsilon, 1e-12); @@ -941,9 +922,9 @@ let key_translated = dt_translated.simplices().next().unwrap().0; // Both metrics should agree on relative quality // Good quality triangle (equilateral) let vertices_good = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025]).unwrap(), ]; let dt_good: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_good).unwrap(); @@ -951,9 +932,9 @@ let key_translated = dt_translated.simplices().next().unwrap().0; // Poor quality triangle (very flat) let vertices_poor = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.01]), // Nearly flat + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.01]).unwrap(), // Nearly flat ]; let dt_poor: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_poor).unwrap(); @@ -982,9 +963,9 @@ let key_translated = dt_translated.simplices().next().unwrap().0; // Currently, collinear points are accepted during construction but produce // degenerate simplices with very poor quality metrics. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([2.0, 0.0]), // Collinear + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), // Collinear ]; let dt_result: Result< DelaunayTriangulation<_, (), (), 2>, @@ -1011,9 +992,9 @@ let key_translated = dt_translated.simplices().next().unwrap().0; fn test_quality_near_duplicate_vertices() { // Two vertices very close together let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([1.000_000_1, 0.000_000_1]), // Nearly duplicate + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.000_000_1, 0.000_000_1]).unwrap(), // Nearly duplicate ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1029,9 +1010,9 @@ let key_translated = dt_translated.simplices().next().unwrap().0; fn test_quality_mixed_scale_coordinates() { // Mix of large and small coordinates let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1e10, 0.0]), - vertex!([1e-10, 1e10]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e10, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1e-10, 1e10]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1054,13 +1035,13 @@ let key_translated = dt_translated.simplices().next().unwrap().0; fn test_quality_6d_simplex() { // 6D simplex at unit hypercube corners let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 6> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1099,54 +1080,54 @@ let simplex_key = dt.simplices().next().unwrap().0; test_poor_quality! { poor_quality_2d_flat => 2 => "very flat triangle" => vec![ - vertex!([0.0, 0.0]), - vertex!([100.0, 0.0]), - vertex!([50.0, 0.1]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([100.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([50.0, 0.1]).unwrap(), ], 50.0, poor_quality_3d_nearly_coplanar => 3 => "nearly coplanar tetrahedron" => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([10.0, 0.0, 0.0]), - vertex!([5.0, 8.66, 0.0]), - vertex!([5.0, 2.89, 0.01]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([5.0, 8.66, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([5.0, 2.89, 0.01]).unwrap(), ], 30.0, poor_quality_4d_degenerate => 4 => "nearly 3D subspace" => vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.5, 0.866, 0.0, 0.0]), - vertex!([0.5, 0.289, 0.816, 0.0]), - vertex!([0.5, 0.289, 0.204, 0.001]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.289, 0.816, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.289, 0.204, 0.001]).unwrap(), ], 10.0, poor_quality_3d_sliver => 3 => "sliver tetrahedron" => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.5, 0.866_025, 0.0]), - vertex!([0.5, 0.288_675, 0.001]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.288_675, 0.001]).unwrap(), ], 100.0, poor_quality_2d_needle => 2 => "needle triangle" => vec![ - vertex!([0.0, 0.0]), - vertex!([100.0, 0.0]), - vertex!([0.0, 0.1]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([100.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.1]).unwrap(), ], 50.0, poor_quality_3d_cap => 3 => "cap tetrahedron" => vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.5, 0.866_025, 0.0]), - vertex!([0.5, 0.288_675, 10.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.288_675, 10.0]).unwrap(), ], 10.0, } @@ -1159,9 +1140,9 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_quality_invalid_simplex_key() { // Create triangulation let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1216,9 +1197,9 @@ let simplex_key = dt.simplices().next().unwrap().0; // Test both ranking consistency and threshold validation in one test // Best: equilateral (good quality threshold) let vertices_best = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025]).unwrap(), ]; let dt_best: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_best).unwrap(); @@ -1226,9 +1207,9 @@ let simplex_key = dt.simplices().next().unwrap().0; // Medium: right triangle (acceptable quality) let vertices_medium = vec![ - vertex!([0.0, 0.0]), - vertex!([3.0, 0.0]), - vertex!([0.0, 4.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 4.0]).unwrap(), ]; let dt_medium: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_medium).unwrap(); @@ -1236,9 +1217,9 @@ let simplex_key = dt.simplices().next().unwrap().0; // Worst: very flat (poor quality) let vertices_worst = vec![ - vertex!([0.0, 0.0]), - vertex!([10.0, 0.0]), - vertex!([5.0, 0.1]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([5.0, 0.1]).unwrap(), ]; let dt_worst: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_worst).unwrap(); @@ -1272,9 +1253,9 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_special_triangles() { // Test right triangle let vertices_right = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_right: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_right).unwrap(); @@ -1284,9 +1265,9 @@ let simplex_key = dt.simplices().next().unwrap().0; // Test isosceles triangle let vertices_iso = vec![ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([1.0, 2.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), ]; let dt_iso: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices_iso).unwrap(); @@ -1307,9 +1288,9 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_simplex_points_valid() { // Test simplex_points helper with valid simplex let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1323,9 +1304,9 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_simplex_points_invalid_key() { // Test simplex_points with invalid simplex key let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1339,11 +1320,11 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_scale_aware_epsilon_2d() { // Test epsilon computation for 2D simplex let mut points = SmallBuffer::new(); - points.push(Point::new([0.0, 0.0])); - points.push(Point::new([1.0, 0.0])); - points.push(Point::new([0.5, 0.866_025])); + points.push(Point::from_validated_coords([0.0, 0.0])); + points.push(Point::from_validated_coords([1.0, 0.0])); + points.push(Point::from_validated_coords([0.5, 0.866_025])); - let (avg_edge_length, epsilon) = scale_aware_epsilon(&points).unwrap(); + let (avg_edge_length, epsilon) = scale_aware_epsilon(&points); assert!( avg_edge_length > 0.0, "Average edge length should be positive" @@ -1356,11 +1337,11 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_scale_aware_epsilon_tiny_simplex() { // Test epsilon computation with very small coordinates let mut points = SmallBuffer::new(); - points.push(Point::new([0.0, 0.0])); - points.push(Point::new([1e-10, 0.0])); - points.push(Point::new([0.5e-10, 0.866_025e-10])); + points.push(Point::from_validated_coords([0.0, 0.0])); + points.push(Point::from_validated_coords([1e-10, 0.0])); + points.push(Point::from_validated_coords([0.5e-10, 0.866_025e-10])); - let (avg_edge_length, epsilon) = scale_aware_epsilon(&points).unwrap(); + let (avg_edge_length, epsilon) = scale_aware_epsilon(&points); // For tiny simplices, epsilon should use the floor (1e-12) assert!(epsilon >= 1e-12); assert!(avg_edge_length > 0.0); @@ -1370,11 +1351,11 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_scale_aware_epsilon_large_simplex() { // Test epsilon computation with large coordinates let mut points = SmallBuffer::new(); - points.push(Point::new([0.0, 0.0])); - points.push(Point::new([1e6, 0.0])); - points.push(Point::new([0.5e6, 0.866_025e6])); + points.push(Point::from_validated_coords([0.0, 0.0])); + points.push(Point::from_validated_coords([1e6, 0.0])); + points.push(Point::from_validated_coords([0.5e6, 0.866_025e6])); - let (avg_edge_length, epsilon) = scale_aware_epsilon(&points).unwrap(); + let (avg_edge_length, epsilon) = scale_aware_epsilon(&points); // For large simplices, epsilon scales with average edge length assert!(epsilon > 1e-12); assert!(avg_edge_length > 1e5); @@ -1384,12 +1365,12 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_scale_aware_epsilon_3d() { // Test epsilon computation for 3D simplex let mut points = SmallBuffer::new(); - points.push(Point::new([0.0, 0.0, 0.0])); - points.push(Point::new([1.0, 0.0, 0.0])); - points.push(Point::new([0.0, 1.0, 0.0])); - points.push(Point::new([0.0, 0.0, 1.0])); + points.push(Point::from_validated_coords([0.0, 0.0, 0.0])); + points.push(Point::from_validated_coords([1.0, 0.0, 0.0])); + points.push(Point::from_validated_coords([0.0, 1.0, 0.0])); + points.push(Point::from_validated_coords([0.0, 0.0, 1.0])); - let (avg_edge_length, epsilon) = scale_aware_epsilon(&points).unwrap(); + let (avg_edge_length, epsilon) = scale_aware_epsilon(&points); assert!(avg_edge_length > 0.0); assert!(epsilon > 0.0); } @@ -1402,9 +1383,9 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_radius_ratio_wrong_vertex_count() { // Create a triangulation to test vertex count validation in radius_ratio let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.866_025]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1419,10 +1400,10 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_normalized_volume_wrong_vertex_count() { // Test normalized_volume with proper vertex count let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1437,9 +1418,9 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_radius_ratio_numerical_edge_cases() { // Test with coordinates near numerical limits let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1e-15]), // Very small but non-zero height + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1e-15]).unwrap(), // Very small but non-zero height ]; let dt_result: Result< DelaunayTriangulation<_, (), (), 2>, @@ -1475,10 +1456,10 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_normalized_volume_numerical_edge_cases() { // Test normalized_volume with numerical edge cases let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1e-14]), // Very small but non-zero + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1e-14]).unwrap(), // Very small but non-zero ]; let dt_result: Result< DelaunayTriangulation<_, (), (), 3>, @@ -1515,14 +1496,26 @@ let simplex_key = dt.simplices().next().unwrap().0; #[test] fn normalized_volume_uses_dimensionally_consistent_volume_threshold() { - let mut tds: Tds = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); + let mut tds: Tds<(), (), 2> = Tds::empty(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([1.0, 1.0e-30])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0e-30]).unwrap(), + ) .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let tri = Triangulation::, (), (), 2>::new_with_tds(FastKernel::new(), tds); @@ -1563,9 +1556,9 @@ let simplex_key = dt.simplices().next().unwrap().0; fn test_degenerate_simplex_error_details() { // Test that degenerate errors include helpful details when they occur. let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([2.0, 1e-20]), // Nearly collinear + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 1e-20]).unwrap(), // Nearly collinear ]; let dt_result: Result< DelaunayTriangulation<_, (), (), 2>, diff --git a/src/geometry/robust_predicates.rs b/src/geometry/robust_predicates.rs index a94a0f2a..32a7f7d3 100644 --- a/src/geometry/robust_predicates.rs +++ b/src/geometry/robust_predicates.rs @@ -11,14 +11,11 @@ use super::predicates::{ InSphere, Orientation, relative_insphere_classification, relative_insphere_determinant_sign, relative_insphere_signs, }; -use super::util::safe_coords_to_f64; use crate::core::collections::{MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer}; use crate::geometry::matrix::{MAX_STACK_MATRIX_DIM, matrix_set}; use crate::geometry::point::Point; use crate::geometry::sos::{sos_insphere_sign, sos_orientation_sign}; -use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateScalar, -}; +use crate::geometry::traits::coordinate::CoordinateConversionError; use core::{cmp::Ordering, hint::cold_path}; #[cfg(test)] use std::cell::Cell; @@ -191,14 +188,14 @@ pub enum InsphereConsistencyError { /// # fn main() -> Result<(), CoordinateConversionError> { /// /// let tetra = vec![ -/// Point::new([0.0, 0.0, 0.0]), -/// Point::new([1.0, 0.0, 0.0]), -/// Point::new([0.0, 1.0, 0.0]), -/// Point::new([0.0, 0.0, 1.0]), +/// Point::try_from([0.0, 0.0, 0.0])?, +/// Point::try_from([1.0, 0.0, 0.0])?, +/// Point::try_from([0.0, 1.0, 0.0])?, +/// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// -/// let inside = Point::new([0.25, 0.25, 0.25]); -/// let outside = Point::new([2.0, 2.0, 2.0]); +/// let inside = Point::try_from([0.25, 0.25, 0.25])?; +/// let outside = Point::try_from([2.0, 2.0, 2.0])?; /// /// let r_in = robust_insphere(&tetra, &inside)?; /// let r_out = robust_insphere(&tetra, &outside)?; @@ -215,13 +212,10 @@ pub enum InsphereConsistencyError { /// [`crate::geometry::predicates::insphere_lifted`] for D ≤ 5. If a relative /// squared norm is non-finite, the error is returned instead of falling through /// to a symbolic classification. -pub fn robust_insphere( - simplex_points: &[Point], - test_point: &Point, -) -> Result -where - T: CoordinateScalar, -{ +pub fn robust_insphere( + simplex_points: &[Point], + test_point: &Point, +) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -278,12 +272,12 @@ where // insphere_distance itself failed). The SoS cofactor minors are one // size smaller, so this succeeds where the full insphere matrix // dispatch does not. - let mut f64_simplex: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = + let mut f64_simplex: SmallBuffer, MAX_PRACTICAL_DIMENSION_SIZE> = SmallBuffer::with_capacity(simplex_points.len()); for point in simplex_points { - f64_simplex.push(Point::new(safe_coords_to_f64(point.coords())?)); + f64_simplex.push(*point); } - let f64_test: Point = Point::new(safe_coords_to_f64(test_point.coords())?); + let f64_test = *test_point; // Use exact orientation when available; fall back to SoS only when the // exact predicate reports DEGENERATE (or fails entirely). @@ -317,13 +311,10 @@ where /// This skips the orientation determinant that [`robust_insphere`] normally uses /// to normalize the lifted determinant sign. It is intended for hot triangulation /// paths that evaluate stored simplices after orientation canonicalization. -pub(crate) fn robust_insphere_positive_oriented( - simplex_points: &[Point], - test_point: &Point, -) -> Result -where - T: CoordinateScalar, -{ +pub(crate) fn robust_insphere_positive_oriented( + simplex_points: &[Point], + test_point: &Point, +) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -374,13 +365,10 @@ const fn should_use_sos_fallback(error: &CoordinateConversionError) -> bool { /// Uses the relative-coordinate lifted formulation shared with /// [`super::predicates::insphere_lifted`] for provably correct sign /// classification on finite local geometry. -fn relative_exact_insphere( - simplex_points: &[Point], - test_point: &Point, -) -> Result -where - T: CoordinateScalar, -{ +fn relative_exact_insphere( + simplex_points: &[Point], + test_point: &Point, +) -> Result { if D > 5 { return Err(CoordinateConversionError::UnsupportedMatrixDimension { requested: D + 2, @@ -417,21 +405,18 @@ where /// /// # fn main() -> Result<(), CoordinateConversionError> { /// let tri = vec![ -/// Point::new([0.0, 0.0]), -/// Point::new([1.0, 0.0]), -/// Point::new([0.0, 1.0]), +/// Point::try_from([0.0, 0.0])?, +/// Point::try_from([1.0, 0.0])?, +/// Point::try_from([0.0, 1.0])?, /// ]; /// let orientation = robust_orientation(&tri)?; /// assert_eq!(orientation, Orientation::POSITIVE); /// # Ok(()) /// # } /// ``` -pub fn robust_orientation( - simplex_points: &[Point], -) -> Result -where - T: CoordinateScalar, -{ +pub fn robust_orientation( + simplex_points: &[Point], +) -> Result { if simplex_points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { actual: simplex_points.len(), @@ -444,11 +429,7 @@ where try_with_la_stack_matrix!(k, |matrix| { for (i, point) in simplex_points.iter().enumerate() { - let coords = point.coords(); - - // Add coordinates using safe conversion - let coords_f64 = safe_coords_to_f64(coords)?; - for (j, &v) in coords_f64.iter().enumerate() { + for (j, &v) in point.coords().iter().enumerate() { matrix_set(&mut matrix, i, j, v)?; } @@ -494,14 +475,11 @@ where /// - [`ConsistencyResult::Consistent`] if the two methods agree /// - [`ConsistencyResult::Inconsistent`] if there's a direct contradiction /// - [`ConsistencyResult::Unverifiable`] if the verification method fails -fn verify_insphere_consistency( - simplex_points: &[Point], - test_point: &Point, +fn verify_insphere_consistency( + simplex_points: &[Point], + test_point: &Point, determinant_result: InSphere, -) -> ConsistencyResult -where - T: CoordinateScalar, -{ +) -> ConsistencyResult { // Use the existing distance-based insphere test for verification super::predicates::insphere_distance(simplex_points, *test_point).map_or( ConsistencyResult::Unverifiable, @@ -549,19 +527,19 @@ mod tests { #[test] fn test_robust_insphere_general() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; // Test point clearly inside - let inside_point = Point::new([0.25, 0.25, 0.25]); + let inside_point = Point::from_validated_coords([0.25, 0.25, 0.25]); let result = robust_insphere(&points, &inside_point).unwrap(); assert_eq!(result, InSphere::INSIDE); // Test point clearly outside - let outside_point = Point::new([2.0, 2.0, 2.0]); + let outside_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let result = robust_insphere(&points, &outside_point).unwrap(); assert_eq!(result, InSphere::OUTSIDE); } @@ -569,15 +547,18 @@ mod tests { #[test] fn test_positive_oriented_insphere_matches_robust_insphere() { let simplex_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; assert_eq!( robust_orientation(&simplex_2d).unwrap(), Orientation::POSITIVE ); - for point in [Point::new([0.2, 0.2]), Point::new([2.0, 2.0])] { + for point in [ + Point::from_validated_coords([0.2, 0.2]), + Point::from_validated_coords([2.0, 2.0]), + ] { assert_eq!( robust_insphere_positive_oriented(&simplex_2d, &point).unwrap(), robust_insphere(&simplex_2d, &point).unwrap() @@ -585,16 +566,19 @@ mod tests { } let simplex_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; assert_eq!( robust_orientation(&simplex_3d).unwrap(), Orientation::POSITIVE ); - for point in [Point::new([0.2, 0.2, 0.2]), Point::new([2.0, 2.0, 2.0])] { + for point in [ + Point::from_validated_coords([0.2, 0.2, 0.2]), + Point::from_validated_coords([2.0, 2.0, 2.0]), + ] { assert_eq!( robust_insphere_positive_oriented(&simplex_3d, &point).unwrap(), robust_insphere(&simplex_3d, &point).unwrap() @@ -602,19 +586,19 @@ mod tests { } let simplex_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; assert_eq!( robust_orientation(&simplex_4d).unwrap(), Orientation::POSITIVE ); for point in [ - Point::new([0.2, 0.2, 0.2, 0.2]), - Point::new([2.0, 2.0, 2.0, 2.0]), + Point::from_validated_coords([0.2, 0.2, 0.2, 0.2]), + Point::from_validated_coords([2.0, 2.0, 2.0, 2.0]), ] { assert_eq!( robust_insphere_positive_oriented(&simplex_4d, &point).unwrap(), @@ -623,20 +607,20 @@ mod tests { } let simplex_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; assert_eq!( robust_orientation(&simplex_5d).unwrap(), Orientation::POSITIVE ); for point in [ - Point::new([0.2, 0.2, 0.2, 0.2, 0.2]), - Point::new([2.0, 2.0, 2.0, 2.0, 2.0]), + Point::from_validated_coords([0.2, 0.2, 0.2, 0.2, 0.2]), + Point::from_validated_coords([2.0, 2.0, 2.0, 2.0, 2.0]), ] { assert_eq!( robust_insphere_positive_oriented(&simplex_5d, &point).unwrap(), @@ -648,21 +632,27 @@ mod tests { #[test] fn test_positive_oriented_insphere_boundary_and_invalid_count() { let simplex = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; assert_eq!(robust_orientation(&simplex).unwrap(), Orientation::POSITIVE); - let boundary = Point::new([1.0, 1.0]); + let boundary = Point::from_validated_coords([1.0, 1.0]); assert_eq!( robust_insphere_positive_oriented(&simplex, &boundary).unwrap(), InSphere::BOUNDARY ); - let too_few = vec![Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; - let err = - robust_insphere_positive_oriented(&too_few, &Point::new([0.25, 0.25])).unwrap_err(); + let too_few = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; + let err = robust_insphere_positive_oriented( + &too_few, + &Point::from_validated_coords([0.25, 0.25]), + ) + .unwrap_err(); assert_eq!( err, CoordinateConversionError::InvalidSimplexPointCount { @@ -675,17 +665,17 @@ mod tests { #[test] fn test_positive_oriented_insphere_uses_robust_fallback_above_stack_dimension() { - let simplex: Vec> = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), + let simplex: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), ]; assert_eq!(robust_orientation(&simplex).unwrap(), Orientation::POSITIVE); - let test_point = Point::new([0.15, 0.15, 0.15, 0.15, 0.15, 0.15]); + let test_point = Point::from_validated_coords([0.15, 0.15, 0.15, 0.15, 0.15, 0.15]); assert_eq!( robust_insphere_positive_oriented(&simplex, &test_point).unwrap(), @@ -696,12 +686,12 @@ mod tests { #[test] fn test_verify_insphere_consistency_reports_direct_contradiction() { let simplex = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; - let inside = Point::new([0.25, 0.25, 0.25]); + let inside = Point::from_validated_coords([0.25, 0.25, 0.25]); assert_eq!( verify_insphere_consistency(&simplex, &inside, InSphere::OUTSIDE), @@ -741,12 +731,12 @@ mod tests { fn test_strict_insphere_consistency_override_exercises_error_path() { let _guard = set_strict_insphere_consistency_for_current_test(true); let simplex = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; - let test_point = Point::new([0.25, 0.25, 0.25]); + let test_point = Point::from_validated_coords([0.25, 0.25, 0.25]); assert_eq!( robust_insphere(&simplex, &test_point).unwrap(), @@ -764,10 +754,10 @@ mod tests { #[test] fn test_robust_orientation() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let result = robust_orientation(&points).unwrap(); @@ -781,9 +771,9 @@ mod tests { // Canonical CCW triangle to exercise the robust_orientation matrix path // and confirm the exact-sign helper returns POSITIVE. let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; let robust = robust_orientation(&points).unwrap(); @@ -798,9 +788,9 @@ mod tests { // Standard CCW triangle — robust_orientation uses provable // det_errbound and exact Bareiss arithmetic, no configuration. let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; let result = robust_orientation(&points); @@ -813,9 +803,9 @@ mod tests { // but exact determinant sign should remain POSITIVE. let eps = 2f64.powi(-50); let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, eps]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, eps]), ]; let result = robust_orientation(&points).unwrap(); @@ -831,10 +821,10 @@ mod tests { // Near-degenerate tetrahedron where exact sign should prevent false DEGENERATE. let eps = 2f64.powi(-50); let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, eps]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, eps]), ]; let result = robust_orientation(&points).unwrap(); @@ -852,15 +842,15 @@ mod tests { // tolerance band and the exact-sign path (Stage 2) must resolve it. let eps = 2f64.powi(-50); let triangle = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; // Circumcenter = (0.5, 0.5), circumradius = sqrt(0.5). // Place test points along the +x direction from the circumcenter. let radius = 0.5_f64.sqrt(); - let inside_point = Point::new([0.5 + radius - eps, 0.5]); - let outside_point = Point::new([0.5 + radius + eps, 0.5]); + let inside_point = Point::from_validated_coords([0.5 + radius - eps, 0.5]); + let outside_point = Point::from_validated_coords([0.5 + radius + eps, 0.5]); assert_eq!( robust_insphere(&triangle, &inside_point).unwrap(), @@ -881,16 +871,16 @@ mod tests { // and exact-sign arithmetic (Stage 2) must resolve the classification. let eps = 2f64.powi(-50); let tetra = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; // Circumcenter = (0.5, 0.5, 0.5), circumradius = sqrt(0.75). // Place test points along the +x direction from the circumcenter. let radius = 0.75_f64.sqrt(); - let inside_point = Point::new([0.5 + radius - eps, 0.5, 0.5]); - let outside_point = Point::new([0.5 + radius + eps, 0.5, 0.5]); + let inside_point = Point::from_validated_coords([0.5 + radius - eps, 0.5, 0.5]); + let outside_point = Point::from_validated_coords([0.5 + radius + eps, 0.5, 0.5]); assert_eq!( robust_insphere(&tetra, &inside_point).unwrap(), @@ -912,15 +902,16 @@ mod tests { let base = 1.0e154; let delta = 1.0e140; let simplex = vec![ - Point::new([base, base, base]), - Point::new([base + delta, base, base]), - Point::new([base, base + delta, base]), - Point::new([base, base, base + delta]), + Point::from_validated_coords([base, base, base]), + Point::from_validated_coords([base + delta, base, base]), + Point::from_validated_coords([base, base + delta, base]), + Point::from_validated_coords([base, base, base + delta]), ]; let inside_coord = 0.25_f64.mul_add(delta, base); let outside_coord = 2.0_f64.mul_add(delta, base); - let inside_point = Point::new([inside_coord, inside_coord, inside_coord]); - let outside_point = Point::new([outside_coord, outside_coord, outside_coord]); + let inside_point = Point::from_validated_coords([inside_coord, inside_coord, inside_coord]); + let outside_point = + Point::from_validated_coords([outside_coord, outside_coord, outside_coord]); assert_eq!( relative_exact_insphere(&simplex, &inside_point).unwrap(), @@ -943,12 +934,12 @@ mod tests { #[test] fn test_robust_insphere_errors_when_relative_squared_norm_overflows() { let simplex = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; - let far_point = Point::new([1.0e155, 0.0, 0.0]); + let far_point = Point::from_validated_coords([1.0e155, 0.0, 0.0]); let error = robust_insphere(&simplex, &far_point).unwrap_err(); assert!( @@ -961,14 +952,14 @@ mod tests { fn test_degenerate_case_handling() { // Create nearly coplanar points let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.5, 0.5, 1e-15]), // Very slightly off-plane + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.5, 0.5, 1e-15]), // Very slightly off-plane ]; // Should handle gracefully - let test_point = Point::new([0.25, 0.25, 1e-16]); + let test_point = Point::from_validated_coords([0.25, 0.25, 1e-16]); let result = robust_insphere(&points, &test_point); assert!(result.is_ok()); } @@ -980,26 +971,26 @@ mod tests { #[test] fn test_verify_insphere_consistency_comprehensive() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; // Test exact matches - all should be consistent let test_cases = [ ( - Point::new([0.25, 0.25, 0.25]), + Point::from_validated_coords([0.25, 0.25, 0.25]), InSphere::INSIDE, "inside point", ), ( - Point::new([2.0, 2.0, 2.0]), + Point::from_validated_coords([2.0, 2.0, 2.0]), InSphere::OUTSIDE, "outside point", ), ( - Point::new([0.5, 0.5, 0.5]), + Point::from_validated_coords([0.5, 0.5, 0.5]), InSphere::BOUNDARY, "boundary point", ), @@ -1013,7 +1004,7 @@ mod tests { } // Test that BOUNDARY results are always considered consistent - let boundary_test_point = Point::new([0.3, 0.3, 0.3]); + let boundary_test_point = Point::from_validated_coords([0.3, 0.3, 0.3]); for expected_result in [InSphere::INSIDE, InSphere::OUTSIDE, InSphere::BOUNDARY] { if expected_result == InSphere::BOUNDARY { assert!( @@ -1025,11 +1016,11 @@ mod tests { // Test different dimensions let triangle_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([2.0, 0.0]), - Point::new([1.0, 2.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([2.0, 0.0]), + Point::from_validated_coords([1.0, 2.0]), ]; - let test_2d = Point::new([1.0, 0.5]); + let test_2d = Point::from_validated_coords([1.0, 0.5]); assert!( verify_insphere_consistency(&triangle_2d, &test_2d, InSphere::BOUNDARY).is_consistent() ); @@ -1038,22 +1029,22 @@ mod tests { let edge_cases = [ ( vec![ - Point::new([1e-10, 0.0, 0.0]), - Point::new([0.0, 1e-10, 0.0]), - Point::new([0.0, 0.0, 1e-10]), - Point::new([1e-10, 1e-10, 1e-10]), + Point::from_validated_coords([1e-10, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1e-10, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1e-10]), + Point::from_validated_coords([1e-10, 1e-10, 1e-10]), ], - Point::new([5e-11, 5e-11, 5e-11]), + Point::from_validated_coords([5e-11, 5e-11, 5e-11]), "small coordinates", ), ( vec![ - Point::new([1e6, 0.0, 0.0]), - Point::new([0.0, 1e6, 0.0]), - Point::new([0.0, 0.0, 1e6]), - Point::new([1e6, 1e6, 1e6]), + Point::from_validated_coords([1e6, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1e6, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1e6]), + Point::from_validated_coords([1e6, 1e6, 1e6]), ], - Point::new([5e5, 5e5, 5e5]), + Point::from_validated_coords([5e5, 5e5, 5e5]), "large coordinates", ), ]; @@ -1066,34 +1057,28 @@ mod tests { ); } + assert!(Point::<3>::try_new([f64::NAN, 0.0, 0.0]).is_err()); + // Test error conditions that should return Unverifiable let error_cases = [ // Invalid simplex size - ( - vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])], - Point::new([0.5, 0.0, 0.0]), - "too few points", - ), - // Non-finite coordinates ( vec![ - Point::new([f64::NAN, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), ], - Point::new([0.1, 0.1, 0.1]), - "NaN coordinates", + Point::from_validated_coords([0.5, 0.0, 0.0]), + "too few points", ), // Degenerate simplex ( vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([2.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([2.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), ], - Point::new([1.5, 0.0, 0.0]), + Point::from_validated_coords([1.5, 0.0, 0.0]), "collinear points", ), ]; @@ -1139,7 +1124,7 @@ mod tests { const PERIODIC_IMAGE_JITTER_UNITS: i64 = 64; const PERIODIC_FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325; const PERIODIC_FNV_PRIME: u64 = 0x0100_0000_01b3; - type PeriodicWitness3d = ([Point; 4], Point, InSphere, InSphere); + type PeriodicWitness3d = ([Point<3>; 4], Point<3>, InSphere, InSphere); fn periodic_builder_perturb_units(canon_idx: usize, axis: usize) -> i64 { let mut h = PERIODIC_FNV_OFFSET_BASIS; @@ -1165,28 +1150,26 @@ mod tests { i64::try_from(h % span).expect("residue fits in i64") - PERIODIC_IMAGE_JITTER_UNITS } - fn periodic_3d_canonical_points() -> Vec> { + fn periodic_3d_canonical_points() -> Vec> { vec![ - Point::new([0.1_f64, 0.2, 0.3]), - Point::new([0.4, 0.7, 0.1]), - Point::new([0.7, 0.3, 0.8]), - Point::new([0.2, 0.9, 0.5]), - Point::new([0.8, 0.6, 0.2]), - Point::new([0.5, 0.1, 0.7]), - Point::new([0.3, 0.5, 0.9]), - Point::new([0.6, 0.8, 0.4]), - Point::new([0.9, 0.2, 0.6]), - Point::new([0.0, 0.4, 0.1]), - Point::new([0.15, 0.65, 0.45]), - Point::new([0.75, 0.15, 0.85]), - Point::new([0.45, 0.55, 0.25]), - Point::new([0.85, 0.45, 0.65]), + Point::from_validated_coords([0.1_f64, 0.2, 0.3]), + Point::from_validated_coords([0.4, 0.7, 0.1]), + Point::from_validated_coords([0.7, 0.3, 0.8]), + Point::from_validated_coords([0.2, 0.9, 0.5]), + Point::from_validated_coords([0.8, 0.6, 0.2]), + Point::from_validated_coords([0.5, 0.1, 0.7]), + Point::from_validated_coords([0.3, 0.5, 0.9]), + Point::from_validated_coords([0.6, 0.8, 0.4]), + Point::from_validated_coords([0.9, 0.2, 0.6]), + Point::from_validated_coords([0.0, 0.4, 0.1]), + Point::from_validated_coords([0.15, 0.65, 0.45]), + Point::from_validated_coords([0.75, 0.15, 0.85]), + Point::from_validated_coords([0.45, 0.55, 0.25]), + Point::from_validated_coords([0.85, 0.45, 0.65]), ] } - fn periodic_3d_builder_style_expansion( - canonical_points: &[Point], - ) -> Vec> { + fn periodic_3d_builder_style_expansion(canonical_points: &[Point<3>]) -> Vec> { let canonical_f64: Vec<[f64; 3]> = canonical_points .iter() .enumerate() @@ -1239,7 +1222,7 @@ mod tests { }; image_coords[axis] = quantized[axis] + shift + jitter; } - expanded.push(Point::new(image_coords)); + expanded.push(Point::from_validated_coords(image_coords)); } } @@ -1247,7 +1230,7 @@ mod tests { } fn find_periodic_3d_inconsistency_witness( - expanded: &[Point], + expanded: &[Point<3>], seed: u64, sample_budget: usize, ) -> Option { @@ -1313,11 +1296,11 @@ mod tests { // Test 2D - Valid triangle let triangle_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, 1.0]), ]; - let test_2d = Point::new([0.5, 0.3]); + let test_2d = Point::from_validated_coords([0.5, 0.3]); assert!( robust_insphere(&triangle_2d, &test_2d).is_ok(), "2D insphere should work" @@ -1329,12 +1312,12 @@ mod tests { // Test 3D - Valid tetrahedron let tetrahedron_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; - let test_3d = Point::new([0.25, 0.25, 0.25]); + let test_3d = Point::from_validated_coords([0.25, 0.25, 0.25]); assert!( robust_insphere(&tetrahedron_3d, &test_3d).is_ok(), "3D insphere should work" @@ -1346,13 +1329,13 @@ mod tests { // Test 4D - Valid hypersimplex let simplex_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; - let test_4d = Point::new([0.2, 0.2, 0.2, 0.2]); + let test_4d = Point::from_validated_coords([0.2, 0.2, 0.2, 0.2]); assert!( robust_insphere(&simplex_4d, &test_4d).is_ok(), "4D insphere should work" @@ -1364,14 +1347,14 @@ mod tests { // Test 5D - Valid hypersimplex let simplex_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; - let test_5d = Point::new([0.15, 0.15, 0.15, 0.15, 0.15]); + let test_5d = Point::from_validated_coords([0.15, 0.15, 0.15, 0.15, 0.15]); assert!( robust_insphere(&simplex_5d, &test_5d).is_ok(), "5D insphere should work" @@ -1383,7 +1366,7 @@ mod tests { // Test error cases - wrong number of points for each dimension // 2D error case - too few points - let too_few_2d = vec![Point::new([0.0, 0.0])]; + let too_few_2d = vec![Point::from_validated_coords([0.0, 0.0])]; let insphere_2d_err = robust_insphere(&too_few_2d, &test_2d); let orientation_2d_err = robust_orientation(&too_few_2d); assert!( @@ -1392,7 +1375,10 @@ mod tests { ); // 3D error case - too few points - let too_few_3d = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; + let too_few_3d = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; let insphere_3d_err = robust_insphere(&too_few_3d, &test_3d); let orientation_3d_err = robust_orientation(&too_few_3d); assert!( @@ -1402,9 +1388,9 @@ mod tests { // 4D error case - too few points let too_few_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), ]; let insphere_4d_err = robust_insphere(&too_few_4d, &test_4d); assert!(insphere_4d_err.is_err(), "4D should fail with 3 points"); @@ -1415,13 +1401,13 @@ mod tests { // Near-degenerate configuration that exercises robust exact-sign paths. let nearly_coplanar_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.5, 1.0, 0.0]), - Point::new([0.5, 0.5, 1e-16]), // Extremely close to coplanar + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.5, 1.0, 0.0]), + Point::from_validated_coords([0.5, 0.5, 1e-16]), // Extremely close to coplanar ]; - let boundary_test_point = Point::new([0.5, 0.5, 5e-17]); + let boundary_test_point = Point::from_validated_coords([0.5, 0.5, 5e-17]); let result = robust_insphere(&nearly_coplanar_points, &boundary_test_point); assert!(result.is_ok()); @@ -1542,22 +1528,22 @@ mod tests { // Test 1: 2D - Degenerate triangle (nearly collinear) let triangle_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, 1e-15]), // Nearly collinear + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, 1e-15]), // Nearly collinear ]; - let test_2d = Point::new([0.5, 1e-16]); + let test_2d = Point::from_validated_coords([0.5, 1e-16]); let result_2d = robust_insphere(&triangle_2d, &test_2d); assert!(result_2d.is_ok(), "2D tie-breaking should work"); // Test 2: 3D - Coplanar points (forces SoS tie-breaking) let coplanar_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.5, 0.5, 0.0]), // All z = 0 + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.5, 0.5, 0.0]), // All z = 0 ]; - let test_3d = Point::new([0.25, 0.25, 0.0]); + let test_3d = Point::from_validated_coords([0.25, 0.25, 0.0]); let result_3d = robust_insphere(&coplanar_3d, &test_3d); assert!( result_3d.is_ok(), @@ -1566,26 +1552,26 @@ mod tests { // Test 3: 4D - Nearly degenerate hypersimplex let simplex_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([1e-14, 1e-14, 1e-14, 1.0]), // Nearly in 3D subspace + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([1e-14, 1e-14, 1e-14, 1.0]), // Nearly in 3D subspace ]; - let test_4d = Point::new([0.2, 0.2, 0.2, 1e-15]); + let test_4d = Point::from_validated_coords([0.2, 0.2, 0.2, 1e-15]); let result_4d = robust_insphere(&simplex_4d, &test_4d); assert!(result_4d.is_ok(), "4D tie-breaking should work"); // Test 4: 5D - Degenerate case let simplex_5d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([1e-12, 1e-12, 1e-12, 1e-12, 1.0]), // Nearly in 4D subspace + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([1e-12, 1e-12, 1e-12, 1e-12, 1.0]), // Nearly in 4D subspace ]; - let test_5d = Point::new([0.1, 0.1, 0.1, 0.1, 1e-13]); + let test_5d = Point::from_validated_coords([0.1, 0.1, 0.1, 0.1, 1e-13]); let result_5d = robust_insphere(&simplex_5d, &test_5d); assert!(result_5d.is_ok(), "5D tie-breaking should work"); @@ -1601,23 +1587,23 @@ mod tests { // Very small coordinates ( vec![ - Point::new([1e-100, 0.0, 0.0]), - Point::new([0.0, 1e-100, 0.0]), - Point::new([0.0, 0.0, 1e-100]), - Point::new([1e-101, 1e-101, 1e-101]), + Point::from_validated_coords([1e-100, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1e-100, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1e-100]), + Point::from_validated_coords([1e-101, 1e-101, 1e-101]), ], - Point::new([5e-102, 5e-102, 5e-102]), + Point::from_validated_coords([5e-102, 5e-102, 5e-102]), "tiny coordinates", ), // Very large coordinates ( vec![ - Point::new([1e50, 0.0, 0.0]), - Point::new([0.0, 1e50, 0.0]), - Point::new([0.0, 0.0, 1e50]), - Point::new([1e49, 1e49, 1e49]), + Point::from_validated_coords([1e50, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1e50, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1e50]), + Point::from_validated_coords([1e49, 1e49, 1e49]), ], - Point::new([5e48, 5e48, 5e48]), + Point::from_validated_coords([5e48, 5e48, 5e48]), "huge coordinates", ), ]; @@ -1629,13 +1615,13 @@ mod tests { // Test geometric meaning preservation let regular_tetrahedron = vec![ - Point::new([1.0, 1.0, 1.0]), - Point::new([1.0, -1.0, -1.0]), - Point::new([-1.0, 1.0, -1.0]), - Point::new([-1.0, -1.0, 1.0]), + Point::from_validated_coords([1.0, 1.0, 1.0]), + Point::from_validated_coords([1.0, -1.0, -1.0]), + Point::from_validated_coords([-1.0, 1.0, -1.0]), + Point::from_validated_coords([-1.0, -1.0, 1.0]), ]; - let clearly_inside = Point::new([0.0, 0.0, 0.0]); - let clearly_outside = Point::new([5.0, 5.0, 5.0]); + let clearly_inside = Point::from_validated_coords([0.0, 0.0, 0.0]); + let clearly_outside = Point::from_validated_coords([5.0, 5.0, 5.0]); assert_eq!( robust_insphere(®ular_tetrahedron, &clearly_inside).unwrap(), @@ -1655,14 +1641,14 @@ mod tests { // Create points where the test point has identical coordinates to a simplex point let identical_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.5, 1.0, 0.0]), - Point::new([0.5, 0.5, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.5, 1.0, 0.0]), + Point::from_validated_coords([0.5, 0.5, 1.0]), ]; // Test point identical to first simplex point - let identical_test = Point::new([0.0, 0.0, 0.0]); + let identical_test = Point::from_validated_coords([0.0, 0.0, 0.0]); // This should exercise the deterministic tie-breaking logic let result = robust_insphere(&identical_points, &identical_test); @@ -1670,19 +1656,19 @@ mod tests { // Create a case where coordinates are lexicographically ordered let ordered_points = vec![ - Point::new([1.0, 2.0, 3.0]), - Point::new([4.0, 5.0, 6.0]), - Point::new([7.0, 8.0, 9.0]), - Point::new([10.0, 11.0, 12.0]), + Point::from_validated_coords([1.0, 2.0, 3.0]), + Point::from_validated_coords([4.0, 5.0, 6.0]), + Point::from_validated_coords([7.0, 8.0, 9.0]), + Point::from_validated_coords([10.0, 11.0, 12.0]), ]; // Test point that's lexicographically smaller - let smaller_test = Point::new([0.0, 1.0, 2.0]); + let smaller_test = Point::from_validated_coords([0.0, 1.0, 2.0]); let result_smaller = robust_insphere(&ordered_points, &smaller_test); assert!(result_smaller.is_ok()); // Test point that's lexicographically larger - let larger_test = Point::new([15.0, 16.0, 17.0]); + let larger_test = Point::from_validated_coords([15.0, 16.0, 17.0]); let result_larger = robust_insphere(&ordered_points, &larger_test); assert!(result_larger.is_ok()); } @@ -1697,13 +1683,13 @@ mod tests { // Use points that are challenging for numerical precision let challenging_points = vec![ - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), - Point::new([1e-10, 1e-10, 1e-10]), // Very close to origin but not exactly + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), + Point::from_validated_coords([1e-10, 1e-10, 1e-10]), // Very close to origin but not exactly ]; - let test_point = Point::new([0.5, 0.5, 0.5]); + let test_point = Point::from_validated_coords([0.5, 0.5, 0.5]); // The function should still return a valid result even with challenging input let result = robust_insphere(&challenging_points, &test_point); @@ -1723,13 +1709,13 @@ mod tests { // the exact-sign insphere path with no configuration. let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; - let test_point = Point::new([0.25, 0.25, 0.25]); + let test_point = Point::from_validated_coords([0.25, 0.25, 0.25]); let result = robust_insphere(&points, &test_point); assert!( @@ -1739,13 +1725,13 @@ mod tests { // Test with a more realistic scenario: very ill-conditioned matrix let ill_conditioned_points = vec![ - Point::new([1e-15, 0.0, 0.0]), - Point::new([0.0, 1e15, 0.0]), - Point::new([0.0, 0.0, 1e-8]), - Point::new([1e8, 1e-12, 1e4]), + Point::from_validated_coords([1e-15, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1e15, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1e-8]), + Point::from_validated_coords([1e8, 1e-12, 1e4]), ]; - let ill_test_point = Point::new([1e-10, 1e10, 1e-5]); + let ill_test_point = Point::from_validated_coords([1e-10, 1e10, 1e-5]); // Should still get a result even with ill-conditioned input let ill_result = robust_insphere(&ill_conditioned_points, &ill_test_point); @@ -1759,12 +1745,12 @@ mod tests { // 3D: all-zero coordinates let zero_points = [ - Point::new([0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), ]; - let zero_test = Point::new([0.0, 0.0, 0.0]); + let zero_test = Point::from_validated_coords([0.0, 0.0, 0.0]); let all_finite_insphere_3d = with_la_stack_matrix!(5, |matrix| { for (i, point) in zero_points.iter().enumerate() { @@ -1802,11 +1788,11 @@ mod tests { // 2D: very large coordinates should remain finite (avoid overflow to infinity) let large_points = [ - Point::new([1e100, 0.0]), - Point::new([0.0, 1e100]), - Point::new([1e100, 1e100]), + Point::from_validated_coords([1e100, 0.0]), + Point::from_validated_coords([0.0, 1e100]), + Point::from_validated_coords([1e100, 1e100]), ]; - let large_test = Point::new([5e99, 5e99]); + let large_test = Point::from_validated_coords([5e99, 5e99]); let all_finite_insphere_2d = with_la_stack_matrix!(4, |matrix| { for (i, point) in large_points.iter().enumerate() { @@ -1837,21 +1823,21 @@ mod tests { // robust_insphere falls through to the SoS fallback (Strategy 3). // SoS cofactor minors are 6×6 (within the 7-dim limit), so this // succeeds where the full matrix dispatch does not. - let simplex: Vec> = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), + let simplex: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 1.0]), ]; // Exactly cospherical point: (1,1,0,…,0) lies on the circumsphere // of the standard 6-simplex (circumcenter = (1/2,…,1/2), // circumradius² = 3/2, |(1,1,0,…,0) - c|² = 3/2). // insphere_distance returns BOUNDARY, forcing the SoS path. - let cospherical = Point::new([1.0, 1.0, 0.0, 0.0, 0.0, 0.0]); + let cospherical = Point::from_validated_coords([1.0, 1.0, 0.0, 0.0, 0.0, 0.0]); let result = robust_insphere(&simplex, &cospherical).unwrap(); assert!( result == InSphere::INSIDE || result == InSphere::OUTSIDE, diff --git a/src/geometry/sos.rs b/src/geometry/sos.rs index 4995d545..a844a836 100644 --- a/src/geometry/sos.rs +++ b/src/geometry/sos.rs @@ -85,7 +85,7 @@ use crate::geometry::traits::coordinate::{ /// - [`CoordinateConversionError::NonFiniteValue`] if any coordinate is /// NaN or infinite. pub fn sos_orientation_sign( - points: &[Point], + points: &[Point], ) -> Result { if points.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { @@ -197,8 +197,8 @@ pub fn sos_orientation_sign( /// - [`CoordinateConversionError::NonFiniteValue`] if any coordinate is /// NaN or infinite. pub fn sos_insphere_sign( - simplex: &[Point], - test: &Point, + simplex: &[Point], + test: &Point, ) -> Result { if simplex.len() != D + 1 { return Err(CoordinateConversionError::InvalidSimplexPointCount { @@ -411,7 +411,6 @@ pub(crate) fn exact_det_sign(matrix: &Matrix) -> i32 { mod tests { use super::*; use crate::geometry::point::Point; - use crate::geometry::traits::coordinate::Coordinate; // ========================================================================= // GENERIC HELPER FUNCTIONS @@ -421,19 +420,19 @@ mod tests { /// /// Construction: origin + (D−1) axis-aligned unit vectors (last coord = 0) /// + a barycentric combination with weight 0.5 in each active axis. - fn degenerate_orient_points() -> Vec> { + fn degenerate_orient_points() -> Vec> { let mut points = Vec::with_capacity(D + 1); - points.push(Point::new([0.0; D])); + points.push(Point::from_validated_coords([0.0; D])); for i in 0..D.saturating_sub(1) { let mut coords = [0.0; D]; coords[i] = 1.0; - points.push(Point::new(coords)); + points.push(Point::from_validated_coords(coords)); } let mut bary = [0.0; D]; for c in bary.iter_mut().take(D.saturating_sub(1)) { *c = 0.5; } - points.push(Point::new(bary)); + points.push(Point::from_validated_coords(bary)); points } @@ -442,25 +441,25 @@ mod tests { /// The simplex is the origin plus D axis-aligned unit vectors. /// The test point (1,1,…,1) lies on the circumsphere (distance from /// center = circumradius for all D ≥ 2). - fn cospherical_points() -> (Vec>, Point) { + fn cospherical_points() -> (Vec>, Point) { let mut simplex = Vec::with_capacity(D + 1); - simplex.push(Point::new([0.0; D])); + simplex.push(Point::from_validated_coords([0.0; D])); for i in 0..D { let mut coords = [0.0; D]; coords[i] = 1.0; - simplex.push(Point::new(coords)); + simplex.push(Point::from_validated_coords(coords)); } - (simplex, Point::new([1.0; D])) + (simplex, Point::from_validated_coords([1.0; D])) } /// Translate a point by a deterministic per-axis offset. - fn translate_point(p: &Point) -> Point { + fn translate_point(p: &Point) -> Point { const OFFSETS: [f64; 5] = [1e6, -5e5, 7.77, -3.33e4, 42.0]; let mut coords = [0.0; D]; for (i, c) in coords.iter_mut().enumerate() { *c = p.coords()[i] + OFFSETS[i % OFFSETS.len()]; } - Point::new(coords) + Point::from_validated_coords(coords) } // ========================================================================= @@ -539,7 +538,7 @@ mod tests { #[test] fn []() { - let points = vec![Point::new([0.0; $dim]); $dim + 1]; + let points = vec![Point::from_validated_coords([0.0; $dim]); $dim + 1]; assert_eq!( sos_orientation_sign(&points), Err(CoordinateConversionError::DegenerateSimplex { @@ -551,8 +550,8 @@ mod tests { #[test] fn []() { - let simplex = vec![Point::new([1.0; $dim]); $dim + 1]; - let test_pt = Point::new([1.0; $dim]); + let simplex = vec![Point::from_validated_coords([1.0; $dim]); $dim + 1]; + let test_pt = Point::from_validated_coords([1.0; $dim]); assert_eq!( sos_insphere_sign(&simplex, &test_pt), Err(CoordinateConversionError::DegenerateSimplex { @@ -581,9 +580,9 @@ mod tests { // orientation. (SoS is only guaranteed correct for degenerate inputs; // the caller should never invoke SoS for non-degenerate cases.) let positive = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; let sign = sos_orientation_sign(&positive).unwrap(); assert_eq!(sign, 1, "Non-degenerate positive triangle should return +1"); @@ -595,7 +594,10 @@ mod tests { #[test] fn test_sos_orientation_wrong_point_count_returns_error() { - let points = vec![Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; + let points = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; let result = sos_orientation_sign(&points); assert_eq!( result, @@ -609,8 +611,11 @@ mod tests { #[test] fn test_sos_insphere_wrong_simplex_count_returns_error() { - let simplex = vec![Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; - let test = Point::new([0.5, 0.5]); + let simplex = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; + let test = Point::from_validated_coords([0.5, 0.5]); let result = sos_insphere_sign(&simplex, &test); assert_eq!( result, @@ -735,7 +740,10 @@ mod tests { fn test_sos_orientation_1d_identical_points() { // Two identical 1D points: orientation determinant is exactly zero. // SoS must still resolve to ±1. - let points = vec![Point::new([5.0]), Point::new([5.0])]; + let points = vec![ + Point::from_validated_coords([5.0]), + Point::from_validated_coords([5.0]), + ]; let sign = sos_orientation_sign(&points).unwrap(); assert!( sign == 1 || sign == -1, @@ -746,7 +754,10 @@ mod tests { #[test] fn test_sos_orientation_1d_distinct_degenerate() { // D=1 with distinct points is non-degenerate, but SoS still works. - let points = vec![Point::new([0.0]), Point::new([1.0])]; + let points = vec![ + Point::from_validated_coords([0.0]), + Point::from_validated_coords([1.0]), + ]; let sign = sos_orientation_sign(&points).unwrap(); assert!( sign == 1 || sign == -1, diff --git a/src/geometry/traits/coordinate.rs b/src/geometry/traits/coordinate.rs index 0104e0d3..2a76fa1a 100644 --- a/src/geometry/traits/coordinate.rs +++ b/src/geometry/traits/coordinate.rs @@ -11,8 +11,7 @@ //! //! ## Core Traits //! -//! - **`Coordinate`**: Main abstraction for coordinate storage and operations -//! - **`CoordinateScalar`**: Trait alias consolidating all scalar type requirements +//! - **`Coordinate`**: Main abstraction for coordinate storage and operations //! - **`FiniteCheck`**: Validation of coordinate values (no NaN or infinity) //! - **`OrderedEq`**: NaN-aware equality that treats NaN values as equal to themselves //! - **`HashCoordinate`**: Consistent hashing of floating-point values @@ -39,21 +38,23 @@ //! use delaunay::prelude::geometry::*; //! use delaunay::prelude::geometry::Point; //! +//! # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { //! // Create coordinates using Point (which implements Coordinate) -//! let coord: Point = Coordinate::new([1.0, 2.0, 3.0]); +//! let coord: Point<3> = Point::try_from([1.0, 2.0, 3.0])?; //! //! // All coordinate operations are available //! assert_eq!(coord.dim(), 3); //! assert_eq!(coord.to_array(), [1.0, 2.0, 3.0]); //! assert!(coord.validate().is_ok()); //! -//! // Special value handling -//! let nan_coord: Point = Coordinate::new([f64::NAN, 1.0]); -//! assert!(nan_coord.validate().is_err()); // NaN detected +//! // Point parsing rejects non-finite coordinates at the boundary. +//! assert!(Point::<2>::try_from([f64::NAN, 1.0]).is_err()); //! -//! // But NaN coordinates can still be compared and hashed consistently -//! let nan_coord2: Point = Coordinate::new([f64::NAN, 1.0]); -//! assert!(nan_coord.ordered_equals(&nan_coord2)); // NaN == NaN +//! // Scalar helper traits still provide explicit special-value semantics. +//! assert!(f64::NAN.ordered_eq(&f64::NAN)); +//! assert!(!f64::NAN.is_finite_generic()); +//! # Ok(()) +//! # } //! ``` //! //! The coordinate trait system enables geometric structures (`Point`, `Vertex`, @@ -67,14 +68,11 @@ #![forbid(unsafe_code)] use crate::geometry::matrix::{LaError, MatrixError, StackMatrixDispatchError}; -use num_traits::{Float, Zero}; use ordered_float::OrderedFloat; use serde::{Serialize, de::DeserializeOwned}; use std::{ fmt::{self, Debug, Display}, hash::{Hash, Hasher}, - iter::Sum, - ops::{AddAssign, SubAssign}, }; /// A non-finite coordinate value category. @@ -943,119 +941,8 @@ macro_rules! impl_hash_coordinate { // Implement HashCoordinate for the supported coordinate scalar. impl_hash_coordinate!(float: f64); -/// Consolidated trait for the supported coordinate scalar requirements. -/// -/// This trait captures the bounds required by coordinate algorithms while keeping -/// the current public scalar contract explicit: `f64` is the only supported -/// caller-visible coordinate scalar. The trait remains generic at call sites to -/// keep storage and geometry APIs orthogonal, not to promise arbitrary numeric -/// coordinate support. Future exact-coordinate support, if added, will use a -/// deliberate documented API. -/// -/// # Required Traits -/// -/// - `Float`: Floating-point arithmetic operations (includes `Copy`, `PartialOrd`, etc.) -/// - `Zero`: Zero value construction -/// - `OrderedEq`: NaN-aware equality comparison -/// - `OrderedCmp`: NaN-aware ordering comparison -/// - `HashCoordinate`: Consistent hashing of floating-point values -/// - `FiniteCheck`: Validation of coordinate values -/// - `Default`: Default value construction -/// - `Debug`: Debug formatting -/// - `Serialize`: Serialization support -/// - `DeserializeOwned`: Deserialization support -/// - `AddAssign`: In-place addition (`+=`) for accumulation loops -/// - `SubAssign`: In-place subtraction (`-=`) for accumulation loops -/// - `Sum`: Iterator summation via `iter.sum()` -/// -/// # Usage -/// -/// ```rust -/// use delaunay::prelude::geometry::CoordinateScalar; -/// -/// fn process_coordinate(value: T) { -/// // T has all the necessary bounds for coordinate operations, -/// // including accumulation via AddAssign, SubAssign, and Sum. -/// } -/// ``` -pub trait CoordinateScalar: - Float - + Zero - + OrderedEq - + OrderedCmp - + HashCoordinate - + FiniteCheck - + Default - + Debug - + Serialize - + DeserializeOwned - + AddAssign - + SubAssign - + Sum -{ - /// Returns the appropriate default tolerance for this coordinate scalar type. - /// - /// This method provides the tolerance value used for `f64` floating-point - /// comparisons and geometric computations. - /// - /// # Returns - /// - /// The default tolerance value for `f64` coordinates. - /// - /// # Examples - /// - /// ``` - /// use delaunay::prelude::geometry::CoordinateScalar; - /// - /// // Get appropriate tolerance for f64 coordinates. - /// let tolerance_f64 = f64::default_tolerance(); - /// assert_eq!(tolerance_f64, 1e-15_f64); - /// ``` - /// - /// # Usage in Generic Functions - /// - /// This method is particularly useful in functions generic over - /// [`CoordinateScalar`] while the supported implementation is `f64`: - /// - /// ``` - /// use delaunay::prelude::geometry::CoordinateScalar; - /// - /// fn compare_with_tolerance(a: T, b: T) -> bool { - /// (a - b).abs() < T::default_tolerance() - /// } - /// ``` - fn default_tolerance() -> Self; - - /// Returns the number of mantissa digits for `f64`. - /// - /// This is useful for determining the maximum integer value that can be - /// represented exactly by the supported coordinate scalar. - /// - /// # Returns - /// - /// The number of mantissa digits for `f64` coordinates. - /// - /// # Examples - /// - /// ``` - /// use delaunay::prelude::geometry::CoordinateScalar; - /// - /// // f64 has 53 mantissa bits - /// assert_eq!(f64::mantissa_digits(), 53); - /// ``` - fn mantissa_digits() -> u32; -} - -// Supported coordinate scalar implementation. -impl CoordinateScalar for f64 { - fn default_tolerance() -> Self { - DEFAULT_TOLERANCE_F64 - } - - fn mantissa_digits() -> u32 { - Self::MANTISSA_DIGITS - } -} +/// Number of mantissa digits in the supported `f64` coordinate scalar. +pub const F64_MANTISSA_DIGITS: u32 = f64::MANTISSA_DIGITS; /// Storage and formatting requirements for coordinate container types. /// @@ -1080,37 +967,32 @@ pub trait CoordinateIdentity: Eq + Hash + PartialOrd {} impl CoordinateIdentity for T where T: Eq + Hash + PartialOrd {} -/// A comprehensive trait that encapsulates all coordinate functionality. +/// Coordinate storage and access over the supported `f64` scalar model. /// -/// This trait combines all the necessary traits for coordinate containers used in -/// geometric computations, providing a single unified interface for coordinate -/// storage and operations. It abstracts the storage mechanism, allowing for -/// different implementations (arrays, vectors, hash maps, etc.) while ensuring -/// consistent behavior. +/// This trait provides the coordinate operations used by geometric code without +/// forcing unrelated serde, formatting, hash, or ordering bounds onto every +/// implementation. APIs that need those contracts should add +/// [`CoordinateRepresentation`] or [`CoordinateIdentity`] explicitly. /// /// # Type Parameters /// -/// * `T` - The scalar type for coordinates (`f64` in this crate) /// * `const D: usize` - The dimension of the coordinate system /// /// # Required Functionality /// -/// The trait requires implementors to support: -/// - Floating-point arithmetic operations -/// - [`CoordinateRepresentation`] for copyable, default, debug, and serde support -/// - [`CoordinateIdentity`] for NaN-aware equality, hashing, and partial ordering -/// - Validation of coordinate values -/// - Coordinate access and manipulation -/// - Zero/origin creation +/// The trait requires implementors to support coordinate access, validation, +/// hashing, ordered equality, and origin creation over finite `f64` +/// coordinates. /// /// # Examples /// /// ``` /// use delaunay::prelude::geometry::{Coordinate, Point}; /// +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { /// // Create coordinates using Point (which implements Coordinate) -/// let coord1: Point = Coordinate::new([1.0, 2.0, 3.0]); -/// let coord2: Point = Coordinate::new([1.0, 2.0, 3.0]); +/// let coord1: Point<3> = Point::try_from([1.0, 2.0, 3.0])?; +/// let coord2: Point<3> = Point::try_from([1.0, 2.0, 3.0])?; /// /// // f64 coordinates implement the coordinate trait /// assert_eq!(coord1.dim(), 3); @@ -1121,8 +1003,10 @@ impl CoordinateIdentity for T where T: Eq + Hash + PartialOrd {} /// assert!(coord1.validate().is_ok()); /// /// // Create origin coordinate -/// let origin: Point = Point::origin(); +/// let origin: Point<3> = Point::origin(); /// assert_eq!(origin.to_array(), [0.0, 0.0, 0.0]); +/// # Ok(()) +/// # } /// ``` /// /// # Future Storage Implementations @@ -1134,18 +1018,18 @@ impl CoordinateIdentity for T where T: Eq + Hash + PartialOrd {} /// use delaunay::prelude::geometry::{Coordinate, Point}; /// use std::collections::HashMap; /// +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { /// // Current Point implementation uses arrays -/// let point_coord: Point = Coordinate::new([1.0, 2.0]); +/// let point_coord: Point<2> = Point::try_from([1.0, 2.0])?; /// assert_eq!(point_coord.dim(), 2); /// assert_eq!(point_coord.to_array(), [1.0, 2.0]); /// /// // Future implementations could use other storage types /// // while maintaining the same Coordinate trait interface +/// # Ok(()) +/// # } /// ``` -pub trait Coordinate: CoordinateRepresentation + CoordinateIdentity -where - T: CoordinateScalar, -{ +pub trait Coordinate: Sized { /// Get the dimensionality of the coordinate system. /// /// # Returns @@ -1157,24 +1041,32 @@ where /// ``` /// use delaunay::prelude::geometry::{Coordinate, Point}; /// - /// let coord: Point = Coordinate::new([1.0, 2.0, 3.0]); + /// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { + /// let coord: Point<3> = Point::try_from([1.0, 2.0, 3.0])?; /// assert_eq!(coord.dim(), 3); + /// # Ok(()) + /// # } /// ``` #[must_use] fn dim(&self) -> usize { D } - /// Create a new coordinate from an array of scalar values. + /// Try to create a new coordinate from an array of scalar values. /// /// # Arguments /// - /// * `coords` - Array of coordinates of type T with dimension D + /// * `coords` - Array of `f64` coordinates with dimension D /// /// # Returns /// /// A new coordinate instance with the specified values. - fn new(coords: [T; D]) -> Self; + /// + /// # Errors + /// + /// Returns [`CoordinateValidationError`] if the raw coordinate values violate + /// the coordinate type's storage invariants. + fn try_new(coords: [f64; D]) -> Result; /// Convert the coordinate to an array of scalar values. /// @@ -1182,7 +1074,7 @@ where /// /// An array containing the coordinate values. #[must_use] - fn to_array(&self) -> [T; D]; + fn to_array(&self) -> [f64; D]; /// Get a specific coordinate by index. /// @@ -1199,12 +1091,15 @@ where /// ``` /// use delaunay::prelude::geometry::{Coordinate, Point}; /// - /// let coord: Point = Coordinate::new([1.0, 2.0, 3.0]); + /// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { + /// let coord: Point<3> = Point::try_from([1.0, 2.0, 3.0])?; /// assert_eq!(coord.get(0), Some(1.0)); /// assert_eq!(coord.get(3), None); + /// # Ok(()) + /// # } /// ``` #[must_use] - fn get(&self, index: usize) -> Option; + fn get(&self, index: usize) -> Option; /// Create a coordinate at the origin (all zeros). /// @@ -1217,15 +1112,12 @@ where /// ``` /// use delaunay::prelude::geometry::{Coordinate, Point}; /// - /// let origin: Point = Coordinate::origin(); + /// let origin: Point<3> = Coordinate::origin(); /// assert_eq!(origin.to_array(), [0.0, 0.0, 0.0]); /// ``` #[must_use] - fn origin() -> Self - where - T: Zero, - { - Self::new([T::zero(); D]) + fn origin() -> Self { + Self::try_new([0.0; D]).expect("zero coordinates satisfy Coordinate::origin") } /// Validate that all coordinate values are finite. @@ -1246,11 +1138,13 @@ where /// ``` /// use delaunay::prelude::geometry::{Coordinate, Point}; /// - /// let valid: Point = Coordinate::new([1.0, 2.0, 3.0]); + /// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { + /// let valid: Point<3> = Point::try_from([1.0, 2.0, 3.0])?; /// assert!(valid.validate().is_ok()); /// - /// let invalid: Point = Coordinate::new([1.0, f64::NAN, 3.0]); - /// assert!(invalid.validate().is_err()); + /// assert!(Point::<3>::try_from([1.0, f64::NAN, 3.0]).is_err()); + /// # Ok(()) + /// # } /// ``` fn validate(&self) -> Result<(), CoordinateValidationError>; @@ -1496,7 +1390,7 @@ mod tests { #[test] fn coordinate_trait_basic_functionality() { // Test through Point implementation of Coordinate trait with multiple dimensions. - let coord: Point = Point::new([1.0, 2.0, 3.0]); + let coord: Point<3> = Point::from_validated_coords([1.0, 2.0, 3.0]); assert_eq!(coord.dim(), 3); assert_relative_eq!( coord.to_array().as_slice(), @@ -1510,7 +1404,7 @@ mod tests { assert_eq!(coord.get(10), None); // Test with different dimensions - let coord_single: Point = Point::new([42.0]); + let coord_single: Point<1> = Point::from_validated_coords([42.0]); assert_eq!(coord_single.dim(), 1); assert_relative_eq!( coord_single.get(0).unwrap(), @@ -1520,15 +1414,15 @@ mod tests { assert_eq!(coord_single.get(1), None); // Test zero-dimensional - let coord_zero: Point = Point::new([]); + let coord_zero: Point<0> = Point::from_validated_coords([]); assert_eq!(coord_zero.dim(), 0); assert_eq!(coord_zero.to_array().len(), 0); assert_eq!(coord_zero.get(0), None); assert!(coord_zero.validate().is_ok()); // Test large dimension - let coord_large: Point = - Point::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]); + let coord_large: Point<10> = + Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]); assert_eq!(coord_large.dim(), 10); assert_eq!(coord_large.get(10), None); assert!(coord_large.validate().is_ok()); @@ -1537,7 +1431,7 @@ mod tests { #[test] fn coordinate_trait_new() { // Test new() method - let coord1: Point = Coordinate::new([5.0, 6.0]); + let coord1: Point<2> = Point::from_validated_coords([5.0, 6.0]); assert_relative_eq!( coord1.to_array().as_slice(), [5.0, 6.0].as_slice(), @@ -1545,7 +1439,7 @@ mod tests { ); // Test multiple creations with new() - let coord2: Point = Coordinate::new([5.0, 6.0]); + let coord2: Point<2> = Point::from_validated_coords([5.0, 6.0]); assert_relative_eq!( coord2.to_array().as_slice(), [5.0, 6.0].as_slice(), @@ -1559,14 +1453,14 @@ mod tests { #[test] fn coordinate_trait_origin() { // Test origin for different dimensions. - let origin_single: Point = Point::origin(); + let origin_single: Point<1> = Point::origin(); assert_relative_eq!( origin_single.to_array().as_slice(), [0.0].as_slice(), epsilon = DEFAULT_TOLERANCE_F64 ); - let origin_triple: Point = Point::origin(); + let origin_triple: Point<3> = Point::origin(); assert_relative_eq!( origin_triple.to_array().as_slice(), [0.0, 0.0, 0.0].as_slice(), @@ -1574,11 +1468,11 @@ mod tests { ); // Test zero-dimensional edge case - let origin_zero: Point = Point::origin(); + let origin_zero: Point<0> = Point::origin(); assert_eq!(origin_zero.to_array().len(), 0); // Test large dimension - let origin_large: Point = Point::origin(); + let origin_large: Point<10> = Point::origin(); assert_relative_eq!( origin_large.to_array().as_slice(), [0.0; 10].as_slice(), @@ -1598,7 +1492,7 @@ mod tests { ]; for &(coords, description) in &valid_cases { - let coord: Point = Point::new(coords); + let coord: Point<3> = Point::from_validated_coords(coords); assert!(coord.validate().is_ok(), "Valid case failed: {description}"); } @@ -1612,8 +1506,7 @@ mod tests { ]; for &(coords, expected_index, expected_dim, description) in &invalid_cases { - let coord: Point = Point::new(coords); - let result = coord.validate(); + let result = Point::<3>::try_new(coords); assert!(result.is_err(), "Invalid case should fail: {description}"); if let Err(CoordinateValidationError::InvalidCoordinate { @@ -1634,12 +1527,12 @@ mod tests { } // Test first invalid coordinate is reported when multiple are invalid - let multi_invalid: Point = Point::new([f64::NAN, f64::INFINITY, f64::NAN, 1.0]); + let multi_invalid = Point::<4>::try_new([f64::NAN, f64::INFINITY, f64::NAN, 1.0]); if let Err(CoordinateValidationError::InvalidCoordinate { coordinate_index, dimension, .. - }) = multi_invalid.validate() + }) = multi_invalid { assert_eq!( coordinate_index, 0, @@ -1649,19 +1542,17 @@ mod tests { } // Test different dimensions - let invalid_1d: Point = Point::new([f64::NAN]); - if let Err(CoordinateValidationError::InvalidCoordinate { dimension, .. }) = - invalid_1d.validate() - { + let invalid_1d = Point::<1>::try_new([f64::NAN]); + if let Err(CoordinateValidationError::InvalidCoordinate { dimension, .. }) = invalid_1d { assert_eq!(dimension, 1); } - let invalid_5d: Point = Point::new([1.0, 2.0, f64::INFINITY, 4.0, 5.0]); + let invalid_5d = Point::<5>::try_new([1.0, 2.0, f64::INFINITY, 4.0, 5.0]); if let Err(CoordinateValidationError::InvalidCoordinate { coordinate_index, dimension, .. - }) = invalid_5d.validate() + }) = invalid_5d { assert_eq!(coordinate_index, 2); assert_eq!(dimension, 5); @@ -1671,9 +1562,9 @@ mod tests { #[test] fn coordinate_trait_hash_coordinate_comprehensive() { // Test normal coordinates - same values should produce same hash - let coord1: Point = Point::new([1.0, 2.0, 3.0]); - let coord2: Point = Point::new([1.0, 2.0, 3.0]); - let coord3: Point = Point::new([1.0, 2.0, 4.0]); + let coord1: Point<3> = Point::from_validated_coords([1.0, 2.0, 3.0]); + let coord2: Point<3> = Point::from_validated_coords([1.0, 2.0, 3.0]); + let coord3: Point<3> = Point::from_validated_coords([1.0, 2.0, 4.0]); let mut hasher1 = DefaultHasher::new(); let mut hasher2 = DefaultHasher::new(); @@ -1693,68 +1584,23 @@ mod tests { "Different coordinates should have different hash" ); - // Test special floating-point values - NaN should hash consistently - let nan_coord1: Point = Point::new([f64::NAN, 1.0]); - let nan_coord2: Point = Point::new([f64::NAN, 1.0]); - let mut hasher_nan1 = DefaultHasher::new(); - let mut hasher_nan2 = DefaultHasher::new(); - nan_coord1.hash_coordinate(&mut hasher_nan1); - nan_coord2.hash_coordinate(&mut hasher_nan2); - assert_eq!( - hasher_nan1.finish(), - hasher_nan2.finish(), - "NaN coordinates should hash consistently" - ); - - // Test infinity values - let inf_coord1: Point = Point::new([f64::INFINITY, 1.0]); - let inf_coord2: Point = Point::new([f64::INFINITY, 1.0]); - let mut hasher_inf1 = DefaultHasher::new(); - let mut hasher_inf2 = DefaultHasher::new(); - inf_coord1.hash_coordinate(&mut hasher_inf1); - inf_coord2.hash_coordinate(&mut hasher_inf2); - assert_eq!( - hasher_inf1.finish(), - hasher_inf2.finish(), - "Infinity coordinates should hash consistently" - ); + assert!(Point::<2>::try_new([f64::NAN, 1.0]).is_err()); + assert!(Point::<2>::try_new([f64::INFINITY, 1.0]).is_err()); } #[test] fn coordinate_trait_ordered_equals_comprehensive() { // Test normal values - let coord1: Point = Point::new([1.0, 2.0, 3.0]); - let coord2: Point = Point::new([1.0, 2.0, 3.0]); - let coord3: Point = Point::new([1.0, 2.0, 4.0]); + let coord1: Point<3> = Point::from_validated_coords([1.0, 2.0, 3.0]); + let coord2: Point<3> = Point::from_validated_coords([1.0, 2.0, 3.0]); + let coord3: Point<3> = Point::from_validated_coords([1.0, 2.0, 4.0]); assert!(coord1.ordered_equals(&coord2)); assert!(coord2.ordered_equals(&coord1)); assert!(!coord1.ordered_equals(&coord3)); - // Test NaN values - should be equal to themselves - let nan_coord1: Point = Point::new([f64::NAN, 2.0, 3.0]); - let nan_coord2: Point = Point::new([f64::NAN, 2.0, 3.0]); - let normal_coord: Point = Point::new([1.0, 2.0, 3.0]); - assert!(nan_coord1.ordered_equals(&nan_coord2)); - assert!(!nan_coord1.ordered_equals(&normal_coord)); - - // Multiple NaN coordinates - let multi_nan1: Point = Point::new([f64::NAN, f64::NAN, 3.0]); - let multi_nan2: Point = Point::new([f64::NAN, f64::NAN, 3.0]); - assert!(multi_nan1.ordered_equals(&multi_nan2)); - - // Test infinity values - let inf_coord1: Point = Point::new([f64::INFINITY, 2.0]); - let inf_coord2: Point = Point::new([f64::INFINITY, 2.0]); - let neg_inf_coord: Point = Point::new([f64::NEG_INFINITY, 2.0]); - assert!(inf_coord1.ordered_equals(&inf_coord2)); - assert!(!inf_coord1.ordered_equals(&neg_inf_coord)); - - // Test mixed special values - let mixed1: Point = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1.0]); - let mixed2: Point = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1.0]); - let mixed3: Point = Point::new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 2.0]); - assert!(mixed1.ordered_equals(&mixed2)); - assert!(!mixed1.ordered_equals(&mixed3)); + assert!(Point::<3>::try_new([f64::NAN, 2.0, 3.0]).is_err()); + assert!(Point::<2>::try_new([f64::INFINITY, 2.0]).is_err()); + assert!(Point::<4>::try_new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1.0]).is_err()); } #[test] @@ -1789,26 +1635,14 @@ mod tests { } #[test] - fn coordinate_scalar_default_tolerance() { - // Test using tolerance in generic function - fn test_tolerance(a: T, b: T) -> bool { - (a - b).abs() < T::default_tolerance() - } - - // Test that default_tolerance returns the expected value - assert_relative_eq!( - f64::default_tolerance(), - DEFAULT_TOLERANCE_F64, - epsilon = f64::EPSILON - ); - + fn coordinate_default_tolerance_constant() { // Test that the tolerance value is reasonable - assert_relative_eq!(f64::default_tolerance(), 1e-15_f64, epsilon = f64::EPSILON); + assert_relative_eq!(DEFAULT_TOLERANCE_F64, 1e-15_f64, epsilon = f64::EPSILON); // Test with f64 let a_f64 = 1.0f64; - let b_f64 = 1.0f64 + f64::default_tolerance() / 2.0; - assert!(test_tolerance(a_f64, b_f64)); + let b_f64 = 1.0f64 + DEFAULT_TOLERANCE_F64 / 2.0; + assert!((a_f64 - b_f64).abs() < DEFAULT_TOLERANCE_F64); } #[test] @@ -1828,7 +1662,7 @@ mod tests { ]; for coords in test_coords { - let coord: Point = Point::new(coords); + let coord: Point<3> = Point::from_validated_coords(coords); let mut hash_builder = DefaultHasher::new(); coord.hash_coordinate(&mut hash_builder); hashes.insert(hash_builder.finish()); @@ -1853,28 +1687,18 @@ mod tests { } #[test] - fn coordinate_scalar_trait_bounds_comprehensive() { - // Test that CoordinateScalar implementations have all required trait bounds - fn test_bounds() { - let zero = T::zero(); - let nan = T::nan(); - - // Test key trait requirements - assert!(zero.ordered_eq(&T::zero())); - assert!(nan.ordered_eq(&T::nan())); // NaN should equal itself - assert!(zero.is_finite_generic()); - assert!(!nan.is_finite_generic()); - assert_eq!(T::default(), T::zero()); - assert!(T::default_tolerance() > T::zero()); - assert!(T::mantissa_digits() > 0); - } - - // Test the supported scalar type. - test_bounds::(); - - // Verify expected values - assert_eq!(f64::mantissa_digits(), 53); - assert_relative_eq!(f64::default_tolerance(), 1e-15_f64, epsilon = f64::EPSILON); + fn coordinate_f64_helper_traits_are_consistent() { + const _: () = assert!(DEFAULT_TOLERANCE_F64 > 0.0); + + let zero = 0.0_f64; + let nan = f64::NAN; + + assert!(zero.ordered_eq(&0.0)); + assert!(nan.ordered_eq(&f64::NAN)); + assert!(zero.is_finite_generic()); + assert!(!nan.is_finite_generic()); + assert_relative_eq!(f64::default(), 0.0, epsilon = f64::EPSILON); + assert_eq!(F64_MANTISSA_DIGITS, 53); } #[test] @@ -1903,11 +1727,11 @@ mod tests { // Test that dimension methods are consistent across supported coordinate dimensions. // Test various dimensions to ensure const generic consistency - let coord_1d: Point = Point::new([42.0]); + let coord_1d: Point<1> = Point::from_validated_coords([42.0]); assert_eq!(coord_1d.dim(), 1); assert_eq!(coord_1d.to_array().len(), 1); - let coord_7d: Point = Point::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]); + let coord_7d: Point<7> = Point::from_validated_coords([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]); assert_eq!(coord_7d.dim(), 7); assert_eq!(coord_7d.to_array().len(), 7); diff --git a/src/geometry/util/circumsphere.rs b/src/geometry/util/circumsphere.rs index c113670b..8c9bbc4b 100644 --- a/src/geometry/util/circumsphere.rs +++ b/src/geometry/util/circumsphere.rs @@ -5,18 +5,14 @@ #![forbid(unsafe_code)] -use super::conversions::{ - ValueConversionError, safe_coords_to_f64, safe_scalar_from_f64, safe_scalar_to_f64, -}; +use super::conversions::{ValueConversionError, safe_coords_to_f64}; use super::norms::{hypot, squared_norm}; use crate::geometry::matrix::{ DEFAULT_SINGULAR_TOL, LaError, LaVector, Matrix, MatrixError, StackMatrixDispatchError, matrix_set, }; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateConversionValue, CoordinateScalar, -}; +use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateConversionValue}; use core::{fmt, hint::cold_path}; /// Geometric measure involved in a degenerate simplex or facet calculation. @@ -315,7 +311,7 @@ impl From for CircumcenterError { /// * `points` - A slice of points that form the simplex /// /// # Returns -/// The circumcenter as a Point if successful, or an error if the +/// The circumcenter as a `Point` if successful, or an error if the /// simplex is degenerate or the matrix inversion fails. /// /// # Errors @@ -331,22 +327,17 @@ impl From for CircumcenterError { /// use delaunay::prelude::geometry::{CircumcenterError, Coordinate, Point, circumcenter}; /// /// # fn main() -> Result<(), CircumcenterError> { -/// let point1 = Point::new([0.0, 0.0, 0.0]); -/// let point2 = Point::new([1.0, 0.0, 0.0]); -/// let point3 = Point::new([0.0, 1.0, 0.0]); -/// let point4 = Point::new([0.0, 0.0, 1.0]); +/// let point1 = Point::try_from([0.0, 0.0, 0.0])?; +/// let point2 = Point::try_from([1.0, 0.0, 0.0])?; +/// let point3 = Point::try_from([0.0, 1.0, 0.0])?; +/// let point4 = Point::try_from([0.0, 0.0, 1.0])?; /// let points = vec![point1, point2, point3, point4]; /// let center = circumcenter(&points)?; -/// assert_eq!(center, Point::new([0.5, 0.5, 0.5])); +/// assert_eq!(center, Point::try_from([0.5, 0.5, 0.5])?); /// # Ok(()) /// # } /// ``` -pub fn circumcenter( - points: &[Point], -) -> Result, CircumcenterError> -where - T: CoordinateScalar, -{ +pub fn circumcenter(points: &[Point]) -> Result, CircumcenterError> { // LCOV_EXCL_START #[cfg(debug_assertions)] if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() { @@ -393,15 +384,11 @@ where } // Calculate squared distance using squared_norm for consistency - let mut diff_coords = [T::zero(); D]; + let mut diff_coords = [0.0; D]; for j in 0..D { - diff_coords[j] = coords_point[j] - coords_0[j]; + diff_coords[j] = coords_point_f64[j] - coords_0_f64[j]; } - let squared_distance = squared_norm(&diff_coords); - - // Use safe coordinate conversion for squared distance - let squared_distance_f64: f64 = safe_scalar_to_f64(squared_distance)?; - b_arr[i] = squared_distance_f64; + b_arr[i] = squared_norm(&diff_coords); } // Solve for x, then C = x0 + 1/2 * x. @@ -442,12 +429,22 @@ where }; // Use safe coordinate conversion for solution and add back the first point - let mut circumcenter_coords = [T::zero(); D]; + let mut circumcenter_coords = [0.0; D]; for i in 0..D { - let relative_coord: T = safe_scalar_from_f64(0.5 * x[i])?; - circumcenter_coords[i] = coords_0[i] + relative_coord; + circumcenter_coords[i] = 0.5_f64.mul_add(x[i], coords_0_f64[i]); + } + for value in circumcenter_coords { + if !value.is_finite() { + return Err(CircumcenterError::MatrixInversionFailed { + reason: CircumcenterFailureReason::NonFiniteMeasure { + measure: DegenerateMeasure::Volume, + value: CoordinateConversionValue::from_numeric_debug(&value), + }, + }); + } } - Ok(Point::new(circumcenter_coords)) + + Ok(Point::from_validated_coords(circumcenter_coords)) } /// Calculate the circumradius of a set of points forming a simplex. @@ -473,10 +470,10 @@ where /// use approx::assert_relative_eq; /// /// # fn main() -> Result<(), CircumcenterError> { -/// let point1 = Point::new([0.0, 0.0, 0.0]); -/// let point2 = Point::new([1.0, 0.0, 0.0]); -/// let point3 = Point::new([0.0, 1.0, 0.0]); -/// let point4 = Point::new([0.0, 0.0, 1.0]); +/// let point1 = Point::try_from([0.0, 0.0, 0.0])?; +/// let point2 = Point::try_from([1.0, 0.0, 0.0])?; +/// let point3 = Point::try_from([0.0, 1.0, 0.0])?; +/// let point4 = Point::try_from([0.0, 0.0, 1.0])?; /// let points = vec![point1, point2, point3, point4]; /// let radius = circumradius(&points)?; /// let expected_radius = (3.0_f64.sqrt() / 2.0); @@ -484,10 +481,7 @@ where /// # Ok(()) /// # } /// ``` -pub fn circumradius(points: &[Point]) -> Result -where - T: CoordinateScalar, -{ +pub fn circumradius(points: &[Point]) -> Result { let circumcenter = circumcenter(points)?; circumradius_with_center(points, &circumcenter) } @@ -522,10 +516,10 @@ where /// use approx::assert_relative_eq; /// /// # fn main() -> Result<(), CircumcenterError> { -/// let point1 = Point::new([0.0, 0.0, 0.0]); -/// let point2 = Point::new([1.0, 0.0, 0.0]); -/// let point3 = Point::new([0.0, 1.0, 0.0]); -/// let point4 = Point::new([0.0, 0.0, 1.0]); +/// let point1 = Point::try_from([0.0, 0.0, 0.0])?; +/// let point2 = Point::try_from([1.0, 0.0, 0.0])?; +/// let point3 = Point::try_from([0.0, 1.0, 0.0])?; +/// let point4 = Point::try_from([0.0, 0.0, 1.0])?; /// let points = vec![point1, point2, point3, point4]; /// let center = circumcenter(&points)?; /// let radius = circumradius_with_center(&points, ¢er)?; @@ -534,13 +528,10 @@ where /// # Ok(()) /// # } /// ``` -pub fn circumradius_with_center( - points: &[Point], - circumcenter: &Point, -) -> Result -where - T: CoordinateScalar, -{ +pub fn circumradius_with_center( + points: &[Point], + circumcenter: &Point, +) -> Result { if points.is_empty() { return Err(CircumcenterError::EmptyPointSet); } @@ -549,7 +540,7 @@ where let circumcenter_coords = circumcenter.coords(); // Calculate distance using hypot for numerical stability - let mut diff_coords = [T::zero(); D]; + let mut diff_coords = [0.0; D]; for i in 0..D { diff_coords[i] = circumcenter_coords[i] - point_coords[i]; } @@ -691,22 +682,22 @@ mod tests { #[test] fn predicates_circumcenter() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let center = circumcenter(&points).unwrap(); - assert_eq!(center, Point::new([0.5, 0.5, 0.5])); + assert_eq!(center, Point::from_validated_coords([0.5, 0.5, 0.5])); } #[test] fn predicates_circumcenter_fail() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), ]; let center = circumcenter(&points); @@ -716,10 +707,10 @@ mod tests { #[test] fn predicates_circumradius() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let radius = circumradius(&points).unwrap(); let expected_radius: f64 = 3.0_f64.sqrt() / 2.0; @@ -730,9 +721,9 @@ mod tests { #[test] fn predicates_circumcenter_2d() { let points = vec![ - Point::new([0.0, 0.0]), - Point::new([2.0, 0.0]), - Point::new([1.0, 2.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([2.0, 0.0]), + Point::from_validated_coords([1.0, 2.0]), ]; let center = circumcenter(&points).unwrap(); @@ -746,8 +737,8 @@ mod tests { // Hits the `points.is_empty()` early-return branch in // `circumradius_with_center` (previously only exercised by // `circumcenter`). - let points: Vec> = Vec::new(); - let center = Point::new([0.0, 0.0, 0.0]); + let points: Vec> = Vec::new(); + let center = Point::from_validated_coords([0.0, 0.0, 0.0]); match circumradius_with_center(&points, ¢er) { Err(CircumcenterError::EmptyPointSet) => {} other => panic!("expected EmptyPointSet, got {other:?}"), @@ -757,9 +748,9 @@ mod tests { #[test] fn predicates_circumradius_2d() { let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; let radius = circumradius(&points).unwrap(); @@ -772,10 +763,10 @@ mod tests { fn predicates_circumradius_with_center() { // Test the circumradius_with_center function let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let center = circumcenter(&points).unwrap(); @@ -789,10 +780,10 @@ mod tests { fn test_circumcenter_regular_simplex_3d() { // Test with a regular tetrahedron - use simpler vertices let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.5, 3.0_f64.sqrt() / 2.0, 0.0]), - Point::new([0.5, 3.0_f64.sqrt() / 6.0, (2.0 / 3.0_f64).sqrt()]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.5, 3.0_f64.sqrt() / 2.0, 0.0]), + Point::from_validated_coords([0.5, 3.0_f64.sqrt() / 6.0, (2.0 / 3.0_f64).sqrt()]), ]; let center = circumcenter(&points).unwrap(); @@ -828,12 +819,12 @@ mod tests { #[test] fn test_circumcenter_regular_simplex_4d() { // Test 4D simplex - use orthonormal basis plus origin - let points: Vec> = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + let points: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; let center = circumcenter(&points).unwrap(); @@ -853,9 +844,9 @@ mod tests { fn test_circumcenter_right_triangle_2d() { // Test with right triangle - circumcenter should be at hypotenuse midpoint let points = vec![ - Point::new([0.0, 0.0]), - Point::new([4.0, 0.0]), - Point::new([0.0, 3.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([4.0, 0.0]), + Point::from_validated_coords([0.0, 3.0]), ]; let center = circumcenter(&points).unwrap(); @@ -870,15 +861,15 @@ mod tests { // Test that scaling preserves circumcenter properties let scale = 10.0; let points = vec![ - Point::new([0.0 * scale, 0.0 * scale, 0.0 * scale]), - Point::new([1.0 * scale, 0.0 * scale, 0.0 * scale]), - Point::new([0.0 * scale, 1.0 * scale, 0.0 * scale]), - Point::new([0.0 * scale, 0.0 * scale, 1.0 * scale]), + Point::from_validated_coords([0.0 * scale, 0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([1.0 * scale, 0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 1.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 0.0 * scale, 1.0 * scale]), ]; let center = circumcenter(&points).unwrap(); // Scaled simplex should have scaled circumcenter - let expected_center = Point::new([0.5 * scale, 0.5 * scale, 0.5 * scale]); + let expected_center = Point::from_validated_coords([0.5 * scale, 0.5 * scale, 0.5 * scale]); let center_coords = center.coords(); let expected_coords = expected_center.coords(); @@ -892,22 +883,22 @@ mod tests { // Test that translation preserves relative circumcenter position let translation = [10.0, 20.0, 30.0]; let points = vec![ - Point::new([ + Point::from_validated_coords([ 0.0 + translation[0], 0.0 + translation[1], 0.0 + translation[2], ]), - Point::new([ + Point::from_validated_coords([ 1.0 + translation[0], 0.0 + translation[1], 0.0 + translation[2], ]), - Point::new([ + Point::from_validated_coords([ 0.0 + translation[0], 1.0 + translation[1], 0.0 + translation[2], ]), - Point::new([ + Point::from_validated_coords([ 0.0 + translation[0], 0.0 + translation[1], 1.0 + translation[2], @@ -917,10 +908,10 @@ mod tests { // Get the circumcenter of the untranslated simplex for comparison let untranslated_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let untranslated_center = circumcenter(&untranslated_points).unwrap(); @@ -947,11 +938,11 @@ mod tests { fn test_circumcenter_nearly_degenerate_simplex() { // Test with points that are nearly collinear (may succeed or fail gracefully) let eps = 1e-3; // Use larger epsilon for more robustness - let points: Vec> = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.5, eps, 0.0]), // Slightly off the line - Point::new([0.5, 0.0, eps]), // Slightly off the plane + let points: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.5, eps, 0.0]), // Slightly off the line + Point::from_validated_coords([0.5, 0.0, eps]), // Slightly off the plane ]; let result = circumcenter(&points); @@ -970,7 +961,7 @@ mod tests { #[test] fn test_circumcenter_empty_points() { - let points: Vec> = vec![]; + let points: Vec> = vec![]; let result = circumcenter(&points); assert!(result.is_err()); @@ -983,7 +974,10 @@ mod tests { #[test] fn test_circumcenter_wrong_dimension() { // Test with 2 points for 3D (need 4 points for 3D circumcenter) - let points = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; + let points = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; let result = circumcenter(&points); assert!(result.is_err()); @@ -1008,9 +1002,9 @@ mod tests { let height = side_length * 3.0_f64.sqrt() / 2.0; let points = vec![ - Point::new([0.0, 0.0]), - Point::new([side_length, 0.0]), - Point::new([side_length / 2.0, height]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([side_length, 0.0]), + Point::from_validated_coords([side_length / 2.0, height]), ]; let center = circumcenter(&points).unwrap(); @@ -1024,7 +1018,7 @@ mod tests { assert_relative_eq!(center_coords[1], expected_y, epsilon = 1e-10); // Verify all vertices are equidistant from circumcenter - let _center_point = Point::new([center_coords[0], center_coords[1]]); + let _center_point = Point::from_validated_coords([center_coords[0], center_coords[1]]); let distances: Vec = points .iter() .map(|p| { @@ -1046,10 +1040,10 @@ mod tests { #[test] fn test_circumcenter_numerical_stability() { // Test with points that could cause numerical instability - let points: Vec> = vec![ - Point::new([1.0, 0.0]), - Point::new([1.000_000_1, 0.0]), // Very close to first point - Point::new([1.000_000_1, 0.000_000_1]), // Forms very thin triangle + let points: Vec> = vec![ + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([1.000_000_1, 0.0]), // Very close to first point + Point::from_validated_coords([1.000_000_1, 0.000_000_1]), // Forms very thin triangle ]; let result = circumcenter(&points); @@ -1069,7 +1063,10 @@ mod tests { #[test] fn test_circumcenter_1d_case() { // Test 1D case (2 points) - let points = vec![Point::new([0.0]), Point::new([2.0])]; + let points = vec![ + Point::from_validated_coords([0.0]), + Point::from_validated_coords([2.0]), + ]; let center = circumcenter(&points).unwrap(); let center_coords = center.coords(); @@ -1081,13 +1078,13 @@ mod tests { #[test] fn test_circumcenter_high_dimension() { // Test higher dimensional case (5D) - let points: Vec> = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0]), + let points: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0]), ]; let result = circumcenter(&points); @@ -1132,10 +1129,10 @@ mod tests { // Test with precisely known circumcenter values // Using a simplex where we can calculate the circumcenter analytically let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([6.0, 0.0, 0.0]), - Point::new([0.0, 8.0, 0.0]), - Point::new([0.0, 0.0, 10.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([6.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 8.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 10.0]), ]; let center = circumcenter(&points).unwrap(); @@ -1149,7 +1146,7 @@ mod tests { #[test] fn test_circumcenter_empty_point_set() { - let empty_points: Vec> = vec![]; + let empty_points: Vec> = vec![]; let result = circumcenter(&empty_points); assert_matches!(result, Err(CircumcenterError::EmptyPointSet)); @@ -1159,8 +1156,8 @@ mod tests { fn test_circumcenter_invalid_simplex() { // Test wrong number of points for dimension let points_2d = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), // Missing third point for 2D circumcenter ]; @@ -1169,10 +1166,10 @@ mod tests { // Test too many points let points_extra = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), - Point::new([0.5, 0.5]), // Extra point for 2D + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), + Point::from_validated_coords([0.5, 0.5]), // Extra point for 2D ]; let result = circumcenter(&points_extra); @@ -1183,9 +1180,9 @@ mod tests { fn test_circumcenter_degenerate_matrix() { // Test collinear points in 2D (should cause matrix inversion to fail) let collinear_points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([2.0, 0.0]), // Collinear with first two + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([2.0, 0.0]), // Collinear with first two ]; let result = circumcenter(&collinear_points); @@ -1204,11 +1201,11 @@ mod tests { // ill-conditioned enough to trip DEFAULT_SINGULAR_TOL, exercising the // solve_exact_rounded_f64 fallback path. let eps = 1e-14; // Perturbation small enough to make LU reject - let points: Vec> = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.5, 0.5, eps]), // Barely off the z=0 plane + let points: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.5, 0.5, eps]), // Barely off the z=0 plane ]; let result = circumcenter(&points); @@ -1248,10 +1245,10 @@ mod tests { // The system matrix has a row with tiny entries, likely tripping // DEFAULT_SINGULAR_TOL. let eps = 1e-15; - let points: Vec> = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, eps]), // Nearly collinear + let points: Vec> = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, eps]), // Nearly collinear ]; let result = circumcenter(&points); diff --git a/src/geometry/util/conversions.rs b/src/geometry/util/conversions.rs index 37ad87f8..b3e0fe4d 100644 --- a/src/geometry/util/conversions.rs +++ b/src/geometry/util/conversions.rs @@ -7,9 +7,10 @@ #![forbid(unsafe_code)] use crate::geometry::traits::coordinate::{ - CoordinateConversionError, CoordinateConversionValue, CoordinateScalar, InvalidCoordinateValue, + CoordinateConversionError, CoordinateConversionValue, F64_MANTISSA_DIGITS, + InvalidCoordinateValue, }; -use num_traits::cast; +use num_traits::ToPrimitive; /// Structured reason why a direct value conversion failed. #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] @@ -85,24 +86,19 @@ pub enum ValueConversionError { /// /// Returns `CoordinateConversionError::NonFiniteValue` if the value is NaN or infinite /// Returns `CoordinateConversionError::ConversionFailed` if the conversion fails -pub(in crate::geometry::util) fn safe_cast_to_f64( - value: T, +pub(in crate::geometry::util) fn safe_cast_to_f64( + value: f64, coordinate_index: usize, ) -> Result { // Check for non-finite values first - if !value.is_finite_generic() { + if !value.is_finite() { return Err(CoordinateConversionError::NonFiniteValue { coordinate_index, coordinate_value: InvalidCoordinateValue::from_debug(&value), }); } - cast(value).ok_or_else(|| CoordinateConversionError::ConversionFailed { - coordinate_index, - coordinate_value: CoordinateConversionValue::from_numeric_debug(&value), - from_type: std::any::type_name::(), - to_type: "f64", - }) + Ok(value) } /// Safely convert a coordinate value from f64 to type T. @@ -124,10 +120,10 @@ pub(in crate::geometry::util) fn safe_cast_to_f64( /// /// Returns `CoordinateConversionError::NonFiniteValue` if the value is NaN or infinite /// Returns `CoordinateConversionError::ConversionFailed` if the conversion fails -pub(in crate::geometry::util) fn safe_cast_from_f64( +pub(in crate::geometry::util) fn safe_cast_from_f64( value: f64, coordinate_index: usize, -) -> Result { +) -> Result { // Check for non-finite values first if !value.is_finite() { return Err(CoordinateConversionError::NonFiniteValue { @@ -136,12 +132,7 @@ pub(in crate::geometry::util) fn safe_cast_from_f64( }); } - cast(value).ok_or_else(|| CoordinateConversionError::ConversionFailed { - coordinate_index, - coordinate_value: CoordinateConversionValue::from_f64(value), - from_type: "f64", - to_type: std::any::type_name::(), - }) + Ok(value) } /// Safely convert an array of coordinates from type T to f64. @@ -179,8 +170,8 @@ pub(in crate::geometry::util) fn safe_cast_from_f64( /// # Ok(()) /// # } /// ``` -pub fn safe_coords_to_f64( - coords: &[T; D], +pub fn safe_coords_to_f64( + coords: &[f64; D], ) -> Result<[f64; D], CoordinateConversionError> { let mut result = [0.0_f64; D]; for (i, &coord) in coords.iter().enumerate() { @@ -224,10 +215,10 @@ pub fn safe_coords_to_f64( /// # Ok(()) /// # } /// ``` -pub fn safe_coords_from_f64( +pub fn safe_coords_from_f64( coords: &[f64; D], -) -> Result<[T; D], CoordinateConversionError> { - let mut result = [T::zero(); D]; +) -> Result<[f64; D], CoordinateConversionError> { + let mut result = [0.0_f64; D]; for (i, &coord) in coords.iter().enumerate() { result[i] = safe_cast_from_f64(coord, i)?; } @@ -265,7 +256,7 @@ pub fn safe_coords_from_f64( /// # Ok(()) /// # } /// ``` -pub fn safe_scalar_to_f64(value: T) -> Result { +pub fn safe_scalar_to_f64(value: f64) -> Result { safe_cast_to_f64(value, 0) } @@ -297,18 +288,14 @@ pub fn safe_scalar_to_f64(value: T) -> Result( - value: f64, -) -> Result { +pub fn safe_scalar_from_f64(value: f64) -> Result { safe_cast_from_f64(value, 0) } /// Safely convert a `usize` value to the supported coordinate scalar type. /// -/// This function handles the conversion from `usize` to `T: CoordinateScalar` -/// with proper precision checking. The currently supported caller-visible -/// coordinate scalar is `f64`; the generic return type exists to share the -/// coordinate-scalar contract across APIs. +/// This function handles the conversion from `usize` to `f64` with proper +/// precision checking. /// /// # Arguments /// @@ -351,15 +338,8 @@ pub fn safe_scalar_from_f64( /// - `f64` integers are exact up to and including 2^53 (9,007,199,254,740,992) /// - For f64 targets: `usize` values larger than 2^53 will cause an error /// - On 64-bit platforms, `usize` can be up to 64 bits, so f64 precision loss is possible -pub fn safe_usize_to_scalar( - value: usize, -) -> Result { - // Guard precision for both the f64 intermediate and the target T. - // Use mantissa bits min(53 (f64), T::mantissa_digits()) to bound exact integers. - const F64_MANTISSA_BITS: u32 = 53; - let t_mantissa_bits: u32 = T::mantissa_digits(); - let max_precise_bits = core::cmp::min(F64_MANTISSA_BITS, t_mantissa_bits); - let max_precise_u128: u128 = 1u128 << max_precise_bits; +pub fn safe_usize_to_scalar(value: usize) -> Result { + let max_precise_u128: u128 = 1u128 << F64_MANTISSA_DIGITS; // Use try_from to safely convert usize to u64 for comparison let value_u64 = @@ -367,7 +347,7 @@ pub fn safe_usize_to_scalar( coordinate_index: 0, coordinate_value: CoordinateConversionValue::from_usize(value), from_type: "usize", - to_type: std::any::type_name::(), + to_type: "f64", })?; if u128::from(value_u64) > max_precise_u128 { @@ -375,21 +355,18 @@ pub fn safe_usize_to_scalar( coordinate_index: 0, coordinate_value: CoordinateConversionValue::from_usize(value), from_type: "usize", - to_type: std::any::type_name::(), + to_type: "f64", }); } - // Safe to convert to f64 without precision loss, then convert to T - // Use cast from num_traits for safe conversion - let f64_value: f64 = - cast(value).ok_or_else(|| CoordinateConversionError::ConversionFailed { + value + .to_f64() + .ok_or_else(|| CoordinateConversionError::ConversionFailed { coordinate_index: 0, coordinate_value: CoordinateConversionValue::from_usize(value), from_type: "usize", to_type: "f64", - })?; - - safe_scalar_from_f64(f64_value) + }) } #[cfg(test)] diff --git a/src/geometry/util/measures.rs b/src/geometry/util/measures.rs index d4ba70c7..6909ae66 100644 --- a/src/geometry/util/measures.rs +++ b/src/geometry/util/measures.rs @@ -8,21 +8,15 @@ use super::circumsphere::{ CircumcenterError, CircumcenterFailureReason, DegenerateGeometry, DegenerateMeasure, }; -use super::conversions::{ - ValueConversionError, ValueConversionFailureReason, safe_coords_to_f64, safe_scalar_from_f64, - safe_usize_to_scalar, -}; +use super::conversions::{ValueConversionError, safe_coords_to_f64, safe_usize_to_scalar}; use super::norms::hypot; use crate::core::facet::FacetView; use crate::core::traits::data_type::DataType; use crate::geometry::matrix::{DEFAULT_SINGULAR_TOL, Matrix, matrix_get, matrix_set}; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionValue, CoordinateScalar, -}; +use crate::geometry::traits::coordinate::CoordinateConversionValue; use crate::tds::FacetError; use num_traits::Float; -use std::ops::AddAssign; /// Error type for surface measure computation operations. /// @@ -63,31 +57,25 @@ impl From for SurfaceMeasureError { const DEGENERACY_EPSILON_FACTOR: f64 = 64.0; -fn degeneracy_tolerance(scale: T) -> Result { - if scale <= T::zero() { - return Ok(T::zero()); +fn degeneracy_tolerance(scale: f64) -> f64 { + if scale <= 0.0 { + return 0.0; } - let factor = safe_scalar_from_f64(DEGENERACY_EPSILON_FACTOR) - .map_err(|source| CircumcenterError::CoordinateConversion { source })?; - Ok(scale * factor * T::epsilon()) + scale * DEGENERACY_EPSILON_FACTOR * f64::EPSILON } -fn is_zero_or_roundoff(value: T, scale: T) -> Result { +fn is_zero_or_roundoff(value: f64, scale: f64) -> bool { let magnitude = Float::abs(value); - if magnitude == T::zero() { - return Ok(true); - } - - Ok(magnitude <= degeneracy_tolerance(scale)?) + magnitude == 0.0 || magnitude <= degeneracy_tolerance(scale) } /// Preserves the finite-measure contract before callers consume derived values. -fn ensure_finite_measure_value( - value: T, +fn ensure_finite_measure_value( + value: f64, measure: DegenerateMeasure, ) -> Result<(), CircumcenterError> { - if value.is_finite_generic() { + if value.is_finite() { Ok(()) } else { Err(CircumcenterError::MatrixInversionFailed { @@ -100,8 +88,8 @@ fn ensure_finite_measure_value( } /// Validates vector-valued measure intermediates before reduction hides the source. -fn ensure_finite_measure_values( - values: &[T; N], +fn ensure_finite_measure_values( + values: &[f64; N], measure: DegenerateMeasure, ) -> Result<(), CircumcenterError> { for value in values { @@ -149,29 +137,26 @@ fn ensure_finite_measure_values( /// # fn main() -> Result<(), CircumcenterError> { /// // 2D: Triangle area /// let triangle = vec![ -/// Point::new([0.0, 0.0]), -/// Point::new([1.0, 0.0]), -/// Point::new([0.0, 1.0]), +/// Point::try_from([0.0, 0.0])?, +/// Point::try_from([1.0, 0.0])?, +/// Point::try_from([0.0, 1.0])?, /// ]; /// let area = simplex_volume(&triangle)?; /// assert_relative_eq!(area, 0.5, epsilon = 1e-10); // Area = 1*1/2 = 0.5 /// /// // 3D: Tetrahedron volume /// let tetrahedron = vec![ -/// Point::new([0.0, 0.0, 0.0]), -/// Point::new([1.0, 0.0, 0.0]), -/// Point::new([0.0, 1.0, 0.0]), -/// Point::new([0.0, 0.0, 1.0]), +/// Point::try_from([0.0, 0.0, 0.0])?, +/// Point::try_from([1.0, 0.0, 0.0])?, +/// Point::try_from([0.0, 1.0, 0.0])?, +/// Point::try_from([0.0, 0.0, 1.0])?, /// ]; /// let volume = simplex_volume(&tetrahedron)?; /// assert_relative_eq!(volume, 1.0/6.0, epsilon = 1e-10); // Volume = 1/6 /// # Ok(()) /// # } /// ``` -pub fn simplex_volume(points: &[Point]) -> Result -where - T: CoordinateScalar, -{ +pub fn simplex_volume(points: &[Point]) -> Result { #[cfg(debug_assertions)] if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() { tracing::debug!( @@ -200,7 +185,7 @@ where ensure_finite_measure_value(length, DegenerateMeasure::Length)?; // Check for degeneracy (coincident points). - if length == T::zero() { + if length == 0.0 { return Err(CircumcenterError::MatrixInversionFailed { reason: CircumcenterFailureReason::DegenerateSimplex { measure: DegenerateMeasure::Length, @@ -224,16 +209,16 @@ where ensure_finite_measure_values(&v2, DegenerateMeasure::Area)?; // 2D cross product magnitude: |v1.x * v2.y - v1.y * v2.x| - let cross_z = v1[0] * v2[1] - v1[1] * v2[0]; + let cross_z = v1[1].mul_add(-v2[0], v1[0] * v2[1]); ensure_finite_measure_value(cross_z, DegenerateMeasure::Area)?; - let area = Float::abs(cross_z) / T::from(2).unwrap_or_else(|| T::one() + T::one()); + let area = Float::abs(cross_z) / 2.0; ensure_finite_measure_value(area, DegenerateMeasure::Area)?; // Check for degeneracy (collinear points) using a scale-aware // determinant threshold instead of an absolute area cutoff. let cross_scale = Float::abs(v1[0] * v2[1]) + Float::abs(v1[1] * v2[0]); ensure_finite_measure_value(cross_scale, DegenerateMeasure::Area)?; - if is_zero_or_roundoff(cross_z, cross_scale)? { + if is_zero_or_roundoff(cross_z, cross_scale) { return Err(CircumcenterError::MatrixInversionFailed { reason: CircumcenterFailureReason::DegenerateSimplex { measure: DegenerateMeasure::Volume, @@ -260,16 +245,15 @@ where ensure_finite_measure_values(&v3, DegenerateMeasure::Volume)?; // Triple scalar product: v1 · (v2 × v3) - let cross_x = v2[1] * v3[2] - v2[2] * v3[1]; - let cross_y = v2[2] * v3[0] - v2[0] * v3[2]; - let cross_z = v2[0] * v3[1] - v2[1] * v3[0]; + let cross_x = v2[2].mul_add(-v3[1], v2[1] * v3[2]); + let cross_y = v2[0].mul_add(-v3[2], v2[2] * v3[0]); + let cross_z = v2[1].mul_add(-v3[0], v2[0] * v3[1]); ensure_finite_measure_values(&[cross_x, cross_y, cross_z], DegenerateMeasure::Volume)?; - let triple_product = v1[0] * cross_x + v1[1] * cross_y + v1[2] * cross_z; + let triple_product = v1[2].mul_add(cross_z, v1[1].mul_add(cross_y, v1[0] * cross_x)); ensure_finite_measure_value(triple_product, DegenerateMeasure::Volume)?; // Volume = |triple product| / 6 - let six = T::from(6) - .unwrap_or_else(|| T::one() + T::one() + T::one() + T::one() + T::one() + T::one()); + let six = 6.0; let volume = Float::abs(triple_product) / six; ensure_finite_measure_value(volume, DegenerateMeasure::Volume)?; @@ -282,7 +266,7 @@ where + Float::abs(v1[2] * v2[0] * v3[1]) + Float::abs(v1[2] * v2[1] * v3[0]); ensure_finite_measure_value(triple_scale, DegenerateMeasure::Volume)?; - if is_zero_or_roundoff(triple_product, triple_scale)? { + if is_zero_or_roundoff(triple_product, triple_scale) { return Err(CircumcenterError::MatrixInversionFailed { reason: CircumcenterFailureReason::DegenerateSimplex { measure: DegenerateMeasure::Volume, @@ -295,7 +279,7 @@ where } _ => { // Higher dimensions: Use Gram matrix method - simplex_volume_gram_matrix::(points) + simplex_volume_gram_matrix::(points) } } } @@ -343,15 +327,14 @@ fn validate_gram_determinant(det: f64) -> Result { fn factorial_f64(n: usize) -> Result { let mut value = 1.0f64; for k in 2..=n { - let k_f64 = - safe_usize_to_scalar::(k).map_err(|e| CircumcenterError::ValueConversion { - source: Box::new(ValueConversionError::CoordinateConversion { - value: CoordinateConversionValue::from_usize(k), - from_type: "usize", - to_type: "f64", - source: Box::new(e), - }), - })?; + let k_f64 = safe_usize_to_scalar(k).map_err(|e| CircumcenterError::ValueConversion { + source: Box::new(ValueConversionError::CoordinateConversion { + value: CoordinateConversionValue::from_usize(k), + from_type: "usize", + to_type: "f64", + source: Box::new(e), + }), + })?; value *= k_f64; } Ok(value) @@ -380,12 +363,9 @@ fn gram_determinant_ldlt(gram_matrix: Matrix) -> Result( - points: &[Point], -) -> Result -where - T: CoordinateScalar, -{ +fn simplex_volume_gram_matrix( + points: &[Point], +) -> Result { // Convert points to f64 and create edge vectors from first point to all others let p0_coords = points[0].coords(); let p0_f64 = safe_coords_to_f64(p0_coords)?; @@ -422,8 +402,7 @@ where sqrt_det / d_fact }; - safe_scalar_from_f64(volume_f64) - .map_err(|source| CircumcenterError::CoordinateConversion { source }) + Ok(volume_f64) } /// Calculate the inradius of a D-dimensional simplex. @@ -460,9 +439,9 @@ where /// # fn main() -> Result<(), CircumcenterError> { /// // 2D: Equilateral triangle with side length 1 /// let triangle = vec![ -/// Point::new([0.0, 0.0]), -/// Point::new([1.0, 0.0]), -/// Point::new([0.5, 0.866025]), // sqrt(3)/2 ≈ 0.866025 +/// Point::try_from([0.0, 0.0])?, +/// Point::try_from([1.0, 0.0])?, +/// Point::try_from([0.5, 0.866025])?, // sqrt(3)/2 ≈ 0.866025 /// ]; /// let r_in = inradius(&triangle)?; /// // For equilateral triangle: inradius ≈ 0.2887 (exact: sqrt(3)/6) @@ -470,10 +449,7 @@ where /// # Ok(()) /// # } /// ``` -pub fn inradius(points: &[Point]) -> Result -where - T: CoordinateScalar + AddAssign, -{ +pub fn inradius(points: &[Point]) -> Result { if points.len() != D + 1 { return Err(CircumcenterError::InvalidSimplex { actual: points.len(), @@ -485,13 +461,13 @@ where // Special-case 1D: segment inradius is half the length if D == 1 { let length = simplex_volume(points)?; // 1D volume = segment length - return Ok(length / T::from(2).unwrap_or_else(|| T::one() + T::one())); + return Ok(length / 2.0); } // Compute volume let volume = simplex_volume(points)?; - if volume <= T::zero() { + if volume <= 0.0 { return Err(CircumcenterError::MatrixInversionFailed { reason: CircumcenterFailureReason::NonPositiveSimplexMeasure { measure: DegenerateMeasure::Volume, @@ -501,10 +477,10 @@ where } // Compute surface area by summing all (D-1)-dimensional facet volumes - let mut surface_area = T::zero(); + let mut surface_area = 0.0; for i in 0..=D { // Create facet by omitting vertex i - let facet_points: Vec> = points + let facet_points: Vec> = points .iter() .enumerate() .filter(|(j, _)| *j != i) @@ -520,7 +496,7 @@ where } // Check for degenerate surface area. - if surface_area <= T::zero() { + if surface_area <= 0.0 { return Err(CircumcenterError::MatrixInversionFailed { reason: CircumcenterFailureReason::NonPositiveSimplexMeasure { measure: DegenerateMeasure::SurfaceArea, @@ -530,12 +506,12 @@ where } // inradius = D * volume / surface_area - let d_scalar = T::from(D).ok_or_else(|| CircumcenterError::ValueConversion { - source: Box::new(ValueConversionError::ConversionFailed { + let d_scalar = safe_usize_to_scalar(D).map_err(|e| CircumcenterError::ValueConversion { + source: Box::new(ValueConversionError::CoordinateConversion { value: CoordinateConversionValue::from_usize(D), from_type: "usize", - to_type: std::any::type_name::(), - reason: ValueConversionFailureReason::TargetTypeRejected, + to_type: "f64", + source: Box::new(e), }), })?; @@ -579,27 +555,24 @@ where /// # fn main() -> Result<(), CircumcenterError> { /// // 2D: Line segment length (1D facet in 2D space) /// let line_segment = vec![ -/// Point::new([0.0, 0.0]), -/// Point::new([3.0, 4.0]), +/// Point::try_from([0.0, 0.0])?, +/// Point::try_from([3.0, 4.0])?, /// ]; /// let length = facet_measure(&line_segment)?; /// assert_relative_eq!(length, 5.0, epsilon = 1e-10); // sqrt(3² + 4²) = 5 /// /// // 3D: Triangle area (2D facet in 3D space) /// let triangle = vec![ -/// Point::new([0.0, 0.0, 0.0]), -/// Point::new([3.0, 0.0, 0.0]), -/// Point::new([0.0, 4.0, 0.0]), +/// Point::try_from([0.0, 0.0, 0.0])?, +/// Point::try_from([3.0, 0.0, 0.0])?, +/// Point::try_from([0.0, 4.0, 0.0])?, /// ]; /// let area = facet_measure(&triangle)?; /// assert_relative_eq!(area, 6.0, epsilon = 1e-10); // 3*4/2 = 6 /// # Ok(()) /// # } /// ``` -pub fn facet_measure(points: &[Point]) -> Result -where - T: CoordinateScalar, -{ +pub fn facet_measure(points: &[Point]) -> Result { if points.len() != D { return Err(CircumcenterError::InvalidSimplex { actual: points.len(), @@ -619,7 +592,7 @@ where }); } // A 0-dimensional point has measure 0 - Ok(T::zero()) + Ok(0.0) } 2 => { // 2D: Length of line segment (1D facet in 2D space) @@ -632,7 +605,7 @@ where ensure_finite_measure_value(length, DegenerateMeasure::Length)?; // Check for degeneracy (coincident points). - if length == T::zero() { + if length == 0.0 { return Err(CircumcenterError::MatrixInversionFailed { reason: CircumcenterFailureReason::DegenerateFacet { measure: DegenerateMeasure::Length, @@ -657,15 +630,15 @@ where // Cross product v1 × v2 let cross = [ - v1[1] * v2[2] - v1[2] * v2[1], - v1[2] * v2[0] - v1[0] * v2[2], - v1[0] * v2[1] - v1[1] * v2[0], + v1[2].mul_add(-v2[1], v1[1] * v2[2]), + v1[0].mul_add(-v2[2], v1[2] * v2[0]), + v1[1].mul_add(-v2[0], v1[0] * v2[1]), ]; ensure_finite_measure_values(&cross, DegenerateMeasure::Area)?; // Area is |cross product| / 2 let cross_magnitude = hypot(&cross); - let area = cross_magnitude / (T::one() + T::one()); // Divide by 2 + let area = cross_magnitude / (2.0); // Divide by 2 ensure_finite_measure_value(cross_magnitude, DegenerateMeasure::Area)?; ensure_finite_measure_value(area, DegenerateMeasure::Area)?; @@ -678,7 +651,7 @@ where + Float::abs(v1[0] * v2[1]) + Float::abs(v1[1] * v2[0]); ensure_finite_measure_value(cross_scale, DegenerateMeasure::Area)?; - if is_zero_or_roundoff(cross_magnitude, cross_scale)? { + if is_zero_or_roundoff(cross_magnitude, cross_scale) { return Err(CircumcenterError::MatrixInversionFailed { reason: CircumcenterFailureReason::DegenerateFacet { measure: DegenerateMeasure::Area, @@ -692,11 +665,11 @@ where 4 => { // 4D: Volume of tetrahedron (3D facet in 4D space) // Use Gram matrix method for correct calculation - facet_measure_gram_matrix::(points) + facet_measure_gram_matrix::(points) } _ => { // Higher dimensions: Use Gram matrix method for correct calculation - facet_measure_gram_matrix::(points) + facet_measure_gram_matrix::(points) } } } @@ -742,12 +715,9 @@ where /// - Matrix operations fail (singular Gram matrix indicates degenerate simplex) /// - Coordinate conversion fails /// - Gram matrix determinant is negative (should never happen for valid input) -fn facet_measure_gram_matrix( - points: &[Point], -) -> Result -where - T: CoordinateScalar, -{ +fn facet_measure_gram_matrix( + points: &[Point], +) -> Result { // Convert points to f64. let mut coords_f64 = [[0.0f64; D]; D]; for (dst, p) in coords_f64.iter_mut().zip(points.iter()) { @@ -785,8 +755,7 @@ where sqrt_det / d_fact }; - safe_scalar_from_f64(volume_f64) - .map_err(|source| CircumcenterError::CoordinateConversion { source }) + Ok(volume_f64) } /// Calculate the surface area of a triangulated boundary by summing facet measures. @@ -825,10 +794,10 @@ where /// # fn main() -> Result<(), ExampleError> { /// // Create a triangulation and calculate surface measure of boundary facets /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// let tds = dt.tds(); @@ -842,26 +811,22 @@ where /// # Ok(()) /// # } /// ``` -pub fn surface_measure( - facets: &[FacetView<'_, T, U, V, D>], -) -> Result +pub fn surface_measure( + facets: &[FacetView<'_, U, V, D>], +) -> Result where - T: CoordinateScalar, U: DataType, V: DataType, { - let mut total_measure = T::zero(); + let mut total_measure = 0.0; for facet in facets { let facet_vertices = facet.vertices(); // Convert vertices to Points for measure calculation - let points: Vec> = facet_vertices + let points: Vec> = facet_vertices .map_err(SurfaceMeasureError::from)? - .map(|v| { - let coords = *v.point().coords(); - Point::new(coords) - }) + .map(|v| *v.point()) .collect(); let measure = facet_measure(&points).map_err(SurfaceMeasureError::from)?; @@ -882,7 +847,6 @@ mod tests { use crate::geometry::point::Point; use crate::geometry::traits::coordinate::InvalidCoordinateValue; use crate::triangulation::DelaunayTriangulation; - use crate::vertex; use approx::assert_relative_eq; #[test] @@ -900,19 +864,28 @@ mod tests { #[test] fn test_simplex_volume_1d_line_segment() { // 1D: Line segment length - let line = vec![Point::new([0.0]), Point::new([5.0])]; + let line = vec![ + Point::from_validated_coords([0.0]), + Point::from_validated_coords([5.0]), + ]; let volume = simplex_volume(&line).unwrap(); assert_relative_eq!(volume, 5.0, epsilon = 1e-10); // Negative direction - let line_neg = vec![Point::new([5.0]), Point::new([0.0])]; + let line_neg = vec![ + Point::from_validated_coords([5.0]), + Point::from_validated_coords([0.0]), + ]; let volume_neg = simplex_volume(&line_neg).unwrap(); assert_relative_eq!(volume_neg, 5.0, epsilon = 1e-10); } #[test] fn simplex_volume_degenerate_segment_errors() { - let line = vec![Point::new([2.0]), Point::new([2.0])]; + let line = vec![ + Point::from_validated_coords([2.0]), + Point::from_validated_coords([2.0]), + ]; assert_matches!( simplex_volume(&line), Err(CircumcenterError::MatrixInversionFailed { @@ -928,18 +901,18 @@ mod tests { fn test_simplex_volume_2d_triangle() { // 2D: Right triangle with legs 3 and 4 let triangle = vec![ - Point::new([0.0, 0.0]), - Point::new([3.0, 0.0]), - Point::new([0.0, 4.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0]), + Point::from_validated_coords([0.0, 4.0]), ]; let area = simplex_volume(&triangle).unwrap(); assert_relative_eq!(area, 6.0, epsilon = 1e-10); // Area = (3*4)/2 = 6 // Equilateral triangle with side 1 let equilateral = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, 0.866_025]), // sqrt(3)/2 + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, 0.866_025]), // sqrt(3)/2 ]; let area_eq = simplex_volume(&equilateral).unwrap(); // Area = sqrt(3)/4 ≈ 0.433013 @@ -950,10 +923,10 @@ mod tests { fn test_simplex_volume_3d_tetrahedron() { // 3D: Regular tetrahedron with vertices at unit cube corners let tetrahedron = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let volume = simplex_volume(&tetrahedron).unwrap(); assert_relative_eq!(volume, 1.0 / 6.0, epsilon = 1e-10); // Volume = 1/6 @@ -963,11 +936,11 @@ mod tests { fn test_simplex_volume_4d_simplex() { // 4D: Regular 4-simplex let simplex_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0]), ]; let volume = simplex_volume(&simplex_4d).unwrap(); // 4D simplex volume = 1/4! = 1/24 @@ -978,9 +951,9 @@ mod tests { fn test_simplex_volume_degenerate() { // Degenerate triangle (collinear points) should return an error let collinear = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 1.0]), - Point::new([2.0, 2.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 1.0]), + Point::from_validated_coords([2.0, 2.0]), ]; let result = simplex_volume(&collinear); assert_matches!( @@ -997,7 +970,10 @@ mod tests { #[test] fn simplex_volume_non_finite_1d_intermediate_is_numeric_failure() { - let points = vec![Point::new([-f64::MAX]), Point::new([f64::MAX])]; + let points = vec![ + Point::from_validated_coords([-f64::MAX]), + Point::from_validated_coords([f64::MAX]), + ]; assert_matches!( simplex_volume(&points), @@ -1015,9 +991,9 @@ mod tests { #[test] fn simplex_volume_non_finite_2d_intermediate_is_numeric_failure() { let points = vec![ - Point::new([0.0, 0.0]), - Point::new([f64::MAX, 0.0]), - Point::new([0.0, f64::MAX]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([f64::MAX, 0.0]), + Point::from_validated_coords([0.0, f64::MAX]), ]; assert_matches!( @@ -1036,10 +1012,10 @@ mod tests { #[test] fn simplex_volume_coplanar_tetrahedron_errors() { let coplanar = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.25, 0.25, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.25, 0.25, 0.0]), ]; let result = simplex_volume(&coplanar); assert_matches!( @@ -1059,18 +1035,18 @@ mod tests { let small_val = 1e-14; let triangle = vec![ - Point::new([0.0, 0.0]), - Point::new([small_val, 0.0]), - Point::new([0.0, small_val]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([small_val, 0.0]), + Point::from_validated_coords([0.0, small_val]), ]; let area = simplex_volume(&triangle).unwrap(); assert_relative_eq!(area, small_val * small_val / 2.0, max_relative = 1e-12); let tetrahedron = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([small_val, 0.0, 0.0]), - Point::new([0.0, small_val, 0.0]), - Point::new([0.0, 0.0, small_val]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([small_val, 0.0, 0.0]), + Point::from_validated_coords([0.0, small_val, 0.0]), + Point::from_validated_coords([0.0, 0.0, small_val]), ]; let volume = simplex_volume(&tetrahedron).unwrap(); assert_relative_eq!( @@ -1083,8 +1059,11 @@ mod tests { #[test] fn test_simplex_volume_wrong_point_count() { // Wrong number of points for 2D - let points = vec![Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; - let result = simplex_volume::(&points); + let points = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; + let result = simplex_volume::<2>(&points); assert!(result.is_err()); } @@ -1096,9 +1075,9 @@ mod tests { fn test_inradius_2d_equilateral_triangle() { // Equilateral triangle with side 1 let triangle = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.5, 0.866_025]), // sqrt(3)/2 + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.5, 0.866_025]), // sqrt(3)/2 ]; let r_in = inradius(&triangle).unwrap(); // For equilateral triangle: inradius = sqrt(3)/6 ≈ 0.28867513 @@ -1109,9 +1088,9 @@ mod tests { fn test_inradius_2d_right_triangle() { // Right triangle with legs 3 and 4 let triangle = vec![ - Point::new([0.0, 0.0]), - Point::new([3.0, 0.0]), - Point::new([0.0, 4.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0]), + Point::from_validated_coords([0.0, 4.0]), ]; let r_in = inradius(&triangle).unwrap(); // For right triangle: inradius = (a+b-c)/2 = (3+4-5)/2 = 1.0 @@ -1122,10 +1101,10 @@ mod tests { fn test_inradius_3d_regular_tetrahedron() { // Regular tetrahedron at unit cube corners let tetrahedron = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ]; let r_in = inradius(&tetrahedron).unwrap(); // For this tetrahedron: inradius ≈ 0.2113 @@ -1136,9 +1115,9 @@ mod tests { fn test_inradius_degenerate() { // Degenerate triangle (collinear points) let collinear = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([2.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([2.0, 0.0]), ]; let result = inradius(&collinear); assert!(result.is_err()); // Should fail for degenerate simplex @@ -1151,7 +1130,7 @@ mod tests { #[test] fn test_facet_measure_1d_point() { // 1D facet is a single point (0-dimensional) - measure should be 0 - let points = vec![Point::new([5.0])]; + let points = vec![Point::from_validated_coords([5.0])]; let measure = facet_measure(&points).unwrap(); assert_relative_eq!(measure, 0.0, epsilon = 1e-10); } @@ -1159,7 +1138,10 @@ mod tests { #[test] fn test_facet_measure_2d_line_segment() { // 2D: Line segment (1D facet in 2D space) - 3-4-5 triangle - let points = vec![Point::new([0.0, 0.0]), Point::new([3.0, 4.0])]; + let points = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([3.0, 4.0]), + ]; let measure = facet_measure(&points).unwrap(); // Length should be sqrt(3² + 4²) = 5.0 assert_relative_eq!(measure, 5.0, epsilon = 1e-10); @@ -1169,9 +1151,9 @@ mod tests { fn test_facet_measure_3d_triangle_right_angle() { // 3D: Right triangle (area = 1/2 * base * height) let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), ]; let measure = facet_measure(&points).unwrap(); // Area should be 3 * 4 / 2 = 6.0 @@ -1182,10 +1164,10 @@ mod tests { fn test_facet_measure_4d_tetrahedron() { // 4D: Unit tetrahedron (3D facet in 4D space) let points = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), ]; let measure = facet_measure(&points).unwrap(); @@ -1250,8 +1232,8 @@ mod tests { #[test] fn degeneracy_tolerance_zero_scale() { - assert_relative_eq!(degeneracy_tolerance::(0.0).unwrap(), 0.0); - assert_relative_eq!(degeneracy_tolerance::(-1.0).unwrap(), 0.0); + assert_relative_eq!(degeneracy_tolerance(0.0), 0.0); + assert_relative_eq!(degeneracy_tolerance(-1.0), 0.0); } #[test] @@ -1347,9 +1329,9 @@ mod tests { // Test 1: Unit right triangle in 3D - area 0.5 let triangle_3d = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), ]; let area_3d = facet_measure(&triangle_3d).unwrap(); assert_relative_eq!(area_3d, 0.5, epsilon = 1e-10); @@ -1359,25 +1341,25 @@ mod tests { // Test 1b: Nearly singular triangle should not error due to tiny negative det let eps = 1e-10; let near_singular = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([1.0, eps, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, eps, 0.0]), ]; let area_ns = facet_measure(&near_singular).unwrap(); assert!(area_ns >= 0.0); // Test 2: Same triangle but use direct Gram matrix calculation - let area_3d_gram = facet_measure_gram_matrix::(&triangle_3d).unwrap(); + let area_3d_gram = facet_measure_gram_matrix::<3>(&triangle_3d).unwrap(); assert_relative_eq!(area_3d_gram, 0.5, epsilon = 1e-10); #[cfg(feature = "diagnostics")] tracing::debug!("3D triangle area (Gram): {area_3d_gram} (expected: 0.5)"); // Test 3: Unit tetrahedron in 4D - should be 1/6 ≈ 0.167 let tetrahedron_4d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), ]; let volume_4d = facet_measure(&tetrahedron_4d).unwrap(); assert_relative_eq!(volume_4d, 1.0 / 6.0, epsilon = 1e-10); @@ -1389,7 +1371,7 @@ mod tests { ); // Test 4: Manual calculation for the 4D tetrahedron - let volume_4d_gram = facet_measure_gram_matrix::(&tetrahedron_4d).unwrap(); + let volume_4d_gram = facet_measure_gram_matrix::<4>(&tetrahedron_4d).unwrap(); assert_relative_eq!(volume_4d_gram, 1.0 / 6.0, epsilon = 1e-10); #[cfg(feature = "diagnostics")] tracing::debug!( @@ -1403,11 +1385,11 @@ mod tests { fn test_facet_measure_5d_simplex() { // 5D: 4-dimensional facet in 5D space (4-simplex volume) let points = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0]), ]; let measure = facet_measure(&points).unwrap(); @@ -1420,12 +1402,12 @@ mod tests { fn test_facet_measure_6d_simplex() { // 6D: 5-dimensional facet in 6D space let points = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), ]; let measure = facet_measure(&points).unwrap(); @@ -1442,8 +1424,11 @@ mod tests { fn test_facet_measure_wrong_point_count() { // Test error when wrong number of points provided // 3D expects 3 points, but provide 2 - let points = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; - let result = facet_measure::(&points); + let points = vec![ + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + ]; + let result = facet_measure::<3>(&points); assert!(result.is_err()); match result.unwrap_err() { @@ -1466,7 +1451,10 @@ mod tests { #[test] fn facet_measure_coincident_2d_points_reports_degenerate_length() { - let points = vec![Point::new([1.0, -2.0]), Point::new([1.0, -2.0])]; + let points = vec![ + Point::from_validated_coords([1.0, -2.0]), + Point::from_validated_coords([1.0, -2.0]), + ]; assert_matches!( facet_measure(&points), @@ -1482,8 +1470,8 @@ mod tests { #[test] fn facet_measure_non_finite_2d_intermediate_is_numeric_failure() { let points = vec![ - Point::new([-f64::MAX, -f64::MAX]), - Point::new([f64::MAX, f64::MAX]), + Point::from_validated_coords([-f64::MAX, -f64::MAX]), + Point::from_validated_coords([f64::MAX, f64::MAX]), ]; assert_matches!( @@ -1502,9 +1490,9 @@ mod tests { #[test] fn facet_measure_non_finite_3d_intermediate_is_numeric_failure() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([f64::MAX, 0.0, 0.0]), - Point::new([0.0, f64::MAX, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([f64::MAX, 0.0, 0.0]), + Point::from_validated_coords([0.0, f64::MAX, 0.0]), ]; assert_matches!( @@ -1524,9 +1512,9 @@ mod tests { fn test_facet_measure_zero_area_triangle() { // Degenerate triangle (collinear points) - should return an error let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([2.0, 0.0, 0.0]), // Collinear + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([2.0, 0.0, 0.0]), // Collinear ]; let result = facet_measure(&points); @@ -1539,8 +1527,8 @@ mod tests { // Test with points that are nearly collinear in 2D let eps = 1e-10; let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, eps]), // Slightly off the x-axis + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, eps]), // Slightly off the x-axis ]; let measure = facet_measure(&points).unwrap(); @@ -1553,9 +1541,9 @@ mod tests { // Test with points that are truly nearly coplanar in 3D let eps = 1e-8; let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([1.0, eps, eps]), // Very close to the line from (0,0,0) to (1,0,0) + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, eps, eps]), // Very close to the line from (0,0,0) to (1,0,0) ]; let measure = facet_measure(&points).unwrap(); @@ -1575,10 +1563,10 @@ mod tests { fn test_facet_measure_degenerate_4d_tetrahedron() { // Test with points that are coplanar in 4D (all points in 3D subspace) let points = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.5, 0.5, 0.0, 0.0]), // In the same 3D subspace + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.5, 0.5, 0.0, 0.0]), // In the same 3D subspace ]; let result = facet_measure(&points); @@ -1596,7 +1584,7 @@ mod tests { #[test] fn test_surface_measure_empty_facets() { // Test with empty facet collection - let facets: Vec> = vec![]; + let facets: Vec> = vec![]; let result = surface_measure(&facets).unwrap(); assert_relative_eq!(result, 0.0, epsilon = 1e-10); @@ -1611,11 +1599,11 @@ mod tests { // Test with single triangular facet using TDS boundary facets // Create a right triangle tetrahedron - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), // v1 - vertex!([3.0, 0.0, 0.0]), // v2 - vertex!([0.0, 4.0, 0.0]), // v3 - vertex!([0.0, 0.0, 1.0]), // v4 + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 0.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 4.0, 0.0]).unwrap(), // v3 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // v4 ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -1654,12 +1642,12 @@ mod tests { // Test that surface_measure sum equals sum of individual facet_measures // Create a triangulation with 5 vertices and 2 tetrahedra to get both boundary and internal facets - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), // v1 - vertex!([1.0, 0.0, 0.0]), // v2 - vertex!([0.0, 1.0, 0.0]), // v3 - vertex!([0.0, 0.0, 1.0]), // v4 - vertex!([1.0, 1.0, 1.0]), // v5 + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), // v3 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // v4 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), // v5 ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -1674,20 +1662,20 @@ mod tests { let total_surface = surface_measure(&[facet1, facet2]).unwrap(); // Calculate individual facet measures and sum them - let points1: Vec> = facet1 + let points1: Vec> = facet1 .vertices() .unwrap() .map(|v| { let coords = *v.point().coords(); - Point::new(coords) + Point::from_validated_coords(coords) }) .collect(); - let points2: Vec> = facet2 + let points2: Vec> = facet2 .vertices() .unwrap() .map(|v| { let coords = *v.point().coords(); - Point::new(coords) + Point::from_validated_coords(coords) }) .collect(); @@ -1707,10 +1695,13 @@ mod tests { fn test_facet_measure_scaled_simplex_2d() { // Test scaling property: measure should scale by |λ|^(D-1) let scale = 3.0; - let original_points = vec![Point::new([0.0, 0.0]), Point::new([1.0, 0.0])]; + let original_points = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + ]; let scaled_points = vec![ - Point::new([0.0 * scale, 0.0 * scale]), - Point::new([1.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([1.0 * scale, 0.0 * scale]), ]; let original_measure = facet_measure(&original_points).unwrap(); @@ -1725,14 +1716,14 @@ mod tests { // Test scaling property for 3D triangle (D=3, measure scales by λ^2) let scale = 2.5; let original_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([2.0, 0.0, 0.0]), - Point::new([0.0, 3.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([2.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 3.0, 0.0]), ]; let scaled_points = vec![ - Point::new([0.0 * scale, 0.0 * scale, 0.0 * scale]), - Point::new([2.0 * scale, 0.0 * scale, 0.0 * scale]), - Point::new([0.0 * scale, 3.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([2.0 * scale, 0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 3.0 * scale, 0.0 * scale]), ]; let original_measure = facet_measure(&original_points).unwrap(); @@ -1751,16 +1742,16 @@ mod tests { // Test scaling property for 4D tetrahedron (D=4, measure scales by λ^3) let scale = 2.0; let original_points = vec![ - Point::new([0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0]), ]; let scaled_points = vec![ - Point::new([0.0 * scale, 0.0 * scale, 0.0 * scale, 0.0 * scale]), - Point::new([1.0 * scale, 0.0 * scale, 0.0 * scale, 0.0 * scale]), - Point::new([0.0 * scale, 1.0 * scale, 0.0 * scale, 0.0 * scale]), - Point::new([0.0 * scale, 0.0 * scale, 1.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 0.0 * scale, 0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([1.0 * scale, 0.0 * scale, 0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 1.0 * scale, 0.0 * scale, 0.0 * scale]), + Point::from_validated_coords([0.0 * scale, 0.0 * scale, 1.0 * scale, 0.0 * scale]), ]; let original_measure = facet_measure(&original_points).unwrap(); @@ -1783,9 +1774,9 @@ mod tests { // Test with very large but finite coordinates let large_val = 1e8; let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([large_val, 0.0, 0.0]), - Point::new([0.0, large_val, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([large_val, 0.0, 0.0]), + Point::from_validated_coords([0.0, large_val, 0.0]), ]; let result = facet_measure(&points); @@ -1804,9 +1795,9 @@ mod tests { // should be scale-aware rather than rejecting every tiny valid facet. let small_val = 1e-14; let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([small_val, 0.0, 0.0]), - Point::new([0.0, small_val, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([small_val, 0.0, 0.0]), + Point::from_validated_coords([0.0, small_val, 0.0]), ]; let result = facet_measure(&points); @@ -1823,9 +1814,9 @@ mod tests { fn test_facet_measure_mixed_positive_negative_coordinates() { // Test with mixed positive and negative coordinates let points = vec![ - Point::new([-1.0, -1.0, 0.0]), - Point::new([2.0, -1.0, 0.0]), - Point::new([-1.0, 3.0, 0.0]), + Point::from_validated_coords([-1.0, -1.0, 0.0]), + Point::from_validated_coords([2.0, -1.0, 0.0]), + Point::from_validated_coords([-1.0, 3.0, 0.0]), ]; let measure = facet_measure(&points).unwrap(); @@ -1842,22 +1833,22 @@ mod tests { // Test that translation doesn't change facet measure let translation = [10.0, 20.0, 30.0]; let original_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), ]; let translated_points = vec![ - Point::new([ + Point::from_validated_coords([ 0.0 + translation[0], 0.0 + translation[1], 0.0 + translation[2], ]), - Point::new([ + Point::from_validated_coords([ 3.0 + translation[0], 0.0 + translation[1], 0.0 + translation[2], ]), - Point::new([ + Point::from_validated_coords([ 0.0 + translation[0], 4.0 + translation[1], 0.0 + translation[2], @@ -1875,19 +1866,19 @@ mod tests { fn test_facet_measure_vertex_permutation_invariance() { // Test that vertex order doesn't change facet measure (absolute value) let points_order1 = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), ]; let points_order2 = vec![ - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, 4.0, 0.0]), - Point::new([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), ]; let points_order3 = vec![ - Point::new([0.0, 4.0, 0.0]), - Point::new([0.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), ]; let measure1 = facet_measure(&points_order1).unwrap(); @@ -1906,27 +1897,27 @@ mod tests { let triangles = [ // XY plane vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), ], // XZ plane vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ], // YZ plane vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0]), ], // Diagonal plane vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 1.0, 0.0]), - Point::new([0.0, 1.0, 1.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 1.0]), ], ]; @@ -1963,11 +1954,11 @@ mod tests { // Test with facets of different sizes using triangulations with known boundary facets // Create first triangulation with small right triangle (area = 0.5) - let vertices1: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), // v1 - vertex!([1.0, 0.0, 0.0]), // v2 - vertex!([0.0, 1.0, 0.0]), // v3 - vertex!([0.0, 0.0, 1.0]), // v4 + let vertices1: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), // v3 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // v4 ]; let dt1: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices1).unwrap(); @@ -1995,11 +1986,11 @@ mod tests { .expect("Should find small triangle facet"); // Create second triangulation with large right triangle (area = 24.0) - let vertices2: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), // v5 - vertex!([6.0, 0.0, 0.0]), // v6 - vertex!([0.0, 8.0, 0.0]), // v7 - vertex!([0.0, 0.0, 1.0]), // v8 + let vertices2: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // v5 + crate::core::vertex::Vertex::<(), _>::try_new([6.0, 0.0, 0.0]).unwrap(), // v6 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 8.0, 0.0]).unwrap(), // v7 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // v8 ]; let dt2: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices2).unwrap(); @@ -2041,10 +2032,10 @@ mod tests { // Test 2D surface measure (perimeter of polygon) // Create 2D triangle (3-4-5 right triangle) - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), // v1 - vertex!([3.0, 0.0]), // v2 - vertex!([0.0, 4.0]), // v3 + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([3.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 4.0]).unwrap(), // v3 ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -2063,12 +2054,12 @@ mod tests { // Test 4D surface measure (3D boundary facets) // Create 4D simplex (5 vertices) - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), // v1 - vertex!([1.0, 0.0, 0.0, 0.0]), // v2 - vertex!([0.0, 1.0, 0.0, 0.0]), // v3 - vertex!([0.0, 0.0, 1.0, 0.0]), // v4 - vertex!([0.0, 0.0, 0.0, 1.0]), // v5 + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), // v3 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), // v4 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), // v5 ]; let dt: DelaunayTriangulation<_, (), (), 4> = @@ -2095,11 +2086,11 @@ mod tests { // Test error handling when facet measure calculation fails // Create a valid triangulation - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), // v1 - vertex!([1.0, 0.0, 0.0]), // v2 - vertex!([0.0, 1.0, 0.0]), // v3 - vertex!([0.0, 0.0, 1.0]), // v4 + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), // v1 + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), // v2 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), // v3 + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), // v4 ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -2123,13 +2114,13 @@ mod tests { fn test_facet_measure_performance_many_dimensions() { // Test performance with higher dimensions (7D, 8D) let points_7d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), ]; let measure_7d = facet_measure(&points_7d).unwrap(); @@ -2137,14 +2128,14 @@ mod tests { assert_relative_eq!(measure_7d, 1.0 / 720.0, epsilon = 1e-10); let points_8d = vec![ - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), - Point::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]), ]; let measure_8d = facet_measure(&points_8d).unwrap(); @@ -2156,11 +2147,11 @@ mod tests { fn test_surface_measure_many_facets() { // Test with many facets from a simple tetrahedral triangulation // Use a simple tetrahedron to avoid degenerate boundary facets - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([0.0, 2.0, 0.0]), - vertex!([0.0, 0.0, 2.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 2.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -2194,9 +2185,9 @@ mod tests { for &side in &side_lengths { let height = side * 3.0_f64.sqrt() / 2.0; let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([side, 0.0, 0.0]), - Point::new([side / 2.0, height, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([side, 0.0, 0.0]), + Point::from_validated_coords([side / 2.0, height, 0.0]), ]; let measure = facet_measure(&points).unwrap(); @@ -2215,10 +2206,10 @@ mod tests { let center_offset = side / (2.0 * 3.0_f64.sqrt()); // Regular tetrahedron vertices - let v1 = Point::new([0.0, 0.0, 0.0]); - let v2 = Point::new([side, 0.0, 0.0]); - let v3 = Point::new([side / 2.0, side * 3.0_f64.sqrt() / 2.0, 0.0]); - let v4 = Point::new([side / 2.0, center_offset, height]); + let v1 = Point::from_validated_coords([0.0, 0.0, 0.0]); + let v2 = Point::from_validated_coords([side, 0.0, 0.0]); + let v3 = Point::from_validated_coords([side / 2.0, side * 3.0_f64.sqrt() / 2.0, 0.0]); + let v4 = Point::from_validated_coords([side / 2.0, center_offset, height]); // Test each face let faces = [ @@ -2242,30 +2233,30 @@ mod tests { // Test that reflection doesn't change facet measure // Use non-collinear points to form a valid triangle let original_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), ]; // Reflect across various planes let reflections = [ // Reflect x-coordinate vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([-3.0, 0.0, 0.0]), - Point::new([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([-3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), ], // Reflect y-coordinate vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, -4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, -4.0, 0.0]), ], // Reflect z-coordinate (doesn't change since all z=0) vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([3.0, 0.0, 0.0]), - Point::new([0.0, 4.0, 0.0]), + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([3.0, 0.0, 0.0]), + Point::from_validated_coords([0.0, 4.0, 0.0]), ], ]; @@ -2281,12 +2272,15 @@ mod tests { #[test] fn test_facet_measure_rotation_invariance_2d() { // Test that rotation doesn't change 2D facet measure (line length) - let original_points = vec![Point::new([0.0, 0.0]), Point::new([3.0, 4.0])]; + let original_points = vec![ + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([3.0, 4.0]), + ]; // Rotate by 90 degrees let rotated_points = vec![ - Point::new([0.0, 0.0]), - Point::new([-4.0, 3.0]), // 90° rotation of (3,4) + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([-4.0, 3.0]), // 90° rotation of (3,4) ]; let original_measure = facet_measure(&original_points).unwrap(); @@ -2300,9 +2294,9 @@ mod tests { fn test_facet_measure_gram_matrix_degenerate() { // Test degenerate simplex (collinear points) let degenerate_points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([2.0, 0.0, 0.0]), // All collinear + Point::from_validated_coords([0.0, 0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0, 0.0]), + Point::from_validated_coords([2.0, 0.0, 0.0]), // All collinear ]; let result = facet_measure(°enerate_points); diff --git a/src/geometry/util/norms.rs b/src/geometry/util/norms.rs index 61c7ea39..fa80864d 100644 --- a/src/geometry/util/norms.rs +++ b/src/geometry/util/norms.rs @@ -5,34 +5,6 @@ #![forbid(unsafe_code)] -use super::conversions::{safe_scalar_from_f64, safe_scalar_to_f64}; -use crate::geometry::traits::coordinate::CoordinateScalar; -use num_traits::Float; - -/// Compute 2D hypot using numerically stable scaled algorithm. -/// -/// This is used as a fallback when standard library hypot conversion fails. -/// It implements the same scaling approach used in the general hypot algorithm. -/// -/// # Arguments -/// -/// * `x` - First coordinate -/// * `y` - Second coordinate -/// -/// # Returns -/// -/// The computed hypot value using scaled computation -pub(in crate::geometry::util) fn scaled_hypot_2d(x: T, y: T) -> T { - let max_abs = Float::abs(x).max(Float::abs(y)); - if max_abs == T::zero() { - return T::zero(); - } - // Use scaled computation for numerical stability - let x_scaled = x / max_abs; - let y_scaled = y / max_abs; - max_abs * Float::sqrt(x_scaled.mul_add(x_scaled, y_scaled * y_scaled)) -} - /// Helper function to compute squared norm using generic arithmetic on T. /// /// This function computes the sum of squares of coordinates using generic @@ -66,11 +38,9 @@ pub(in crate::geometry::util) fn scaled_hypot_2d(x: T, y: T /// let norm_sq_4d = squared_norm(&coords_4d); /// assert_eq!(norm_sq_4d, 4.0); // 1² + 1² + 1² + 1² = 4 /// ``` -pub fn squared_norm(coords: &[T; D]) -> T -where - T: CoordinateScalar, -{ - coords.iter().fold(T::zero(), |acc, &x| x.mul_add(x, acc)) +#[must_use] +pub fn squared_norm(coords: &[f64; D]) -> f64 { + coords.iter().fold(0.0, |acc, &x| x.mul_add(x, acc)) } /// Compute the d-dimensional hypot (Euclidean norm) of a coordinate array. @@ -112,50 +82,35 @@ where /// let distance_4d = hypot(&[1.0, 1.0, 1.0, 1.0]); /// assert_eq!(distance_4d, 2.0); /// ``` -pub fn hypot(coords: &[T; D]) -> T -where - T: CoordinateScalar, -{ +#[must_use] +pub fn hypot(coords: &[f64; D]) -> f64 { match D { - 0 => T::zero(), - 1 => Float::abs(coords[0]), + 0 => 0.0, + 1 => coords[0].abs(), 2 => { - // Use standard library hypot for optimal 2D performance and stability - // Use safe conversion with proper error handling - // If conversion fails, fall back to general algorithm - if let (Ok(a_f64), Ok(b_f64)) = - (safe_scalar_to_f64(coords[0]), safe_scalar_to_f64(coords[1])) - { - let result_f64 = a_f64.hypot(b_f64); - safe_scalar_from_f64(result_f64).unwrap_or_else(|_| { - // Fall back to scaled algorithm if conversion back fails - scaled_hypot_2d(coords[0], coords[1]) - }) - } else { - // Fall back to scaled algorithm if conversion fails - scaled_hypot_2d(coords[0], coords[1]) - } + // Use standard library hypot for optimal 2D performance and stability. + coords[0].hypot(coords[1]) } _ => { // For higher dimensions, implement generalized hypot // Find the maximum absolute value to avoid overflow/underflow let max_abs = coords .iter() - .map(|&x| Float::abs(x)) - .fold(T::zero(), |acc, x| if x > acc { x } else { acc }); + .map(|&x| x.abs()) + .fold(0.0, |acc, x| if x > acc { x } else { acc }); - if max_abs == T::zero() { - return T::zero(); + if max_abs == 0.0 { + return 0.0; } // Scale all coordinates by max_abs and compute sum of squares - let sum_of_scaled_squares = coords.iter().fold(T::zero(), |acc, &x| { + let sum_of_scaled_squares = coords.iter().fold(0.0, |acc, &x| { let scaled = x / max_abs; scaled.mul_add(scaled, acc) }); // Result is max_abs * sqrt(sum_of_scaled_squares) - max_abs * Float::sqrt(sum_of_scaled_squares) + max_abs * sum_of_scaled_squares.sqrt() } } } @@ -209,7 +164,7 @@ mod tests { #[test] fn test_hypot_edge_cases() { // Test 0D case - let distance_0d = hypot::(&[]); + let distance_0d = hypot::<0>(&[]); assert_relative_eq!(distance_0d, 0.0, epsilon = 1e-10); // Test 1D case @@ -225,51 +180,6 @@ mod tests { assert!(distance_large > 0.0); } - #[test] - fn test_scaled_hypot_2d() { - // Test our fallback scaled hypot implementation - let result = scaled_hypot_2d(3.0, 4.0); - assert_relative_eq!(result, 5.0, epsilon = 1e-10); - - // Test with zero values - let zero_result = scaled_hypot_2d(0.0, 0.0); - assert_relative_eq!(zero_result, 0.0, epsilon = 1e-10); - - // Test with negative values - let neg_result = scaled_hypot_2d(-3.0, 4.0); - assert_relative_eq!(neg_result, 5.0, epsilon = 1e-10); - - // Test with very large values to check scaling behavior - let large_result = scaled_hypot_2d(1e10, 1e10); - let expected = (2.0_f64).sqrt() * 1e10; - assert_relative_eq!(large_result, expected, epsilon = 1e-5); - } - - #[test] - fn test_scaled_hypot_2d_edge_cases() { - // Test with zeros - let result = scaled_hypot_2d(0.0, 0.0); - assert_relative_eq!(result, 0.0); - - // Test with one zero - let result = scaled_hypot_2d(0.0, 5.0); - assert_relative_eq!(result, 5.0); - - // Test with standard case - let result = scaled_hypot_2d(3.0, 4.0); - assert_relative_eq!(result, 5.0, epsilon = 1e-10); - - // Test with negative values - let result = scaled_hypot_2d(-3.0, -4.0); - assert_relative_eq!(result, 5.0, epsilon = 1e-10); - - // Test with very large values (avoid overflow) - let large_val = 1e100; - let result = scaled_hypot_2d(large_val, large_val); - assert!(result.is_finite()); - assert!(result > 0.0); - } - #[test] fn test_squared_norm_dimensions_2_to_5() { // Test across dimensions 2-5 diff --git a/src/geometry/util/point_generation.rs b/src/geometry/util/point_generation.rs index 6ae9ea87..bc51aa15 100644 --- a/src/geometry/util/point_generation.rs +++ b/src/geometry/util/point_generation.rs @@ -11,10 +11,7 @@ use crate::geometry::coordinate_range::{ CoordinateRange, CoordinateRangeError, InvalidCoordinateValue, }; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{ - Coordinate, CoordinateConversionError, CoordinateScalar, -}; -use rand::distr::uniform::SampleUniform; +use crate::geometry::traits::coordinate::CoordinateConversionError; use rand::rngs::StdRng; use rand::{RngExt, SeedableRng}; use std::{any::type_name, env, num::NonZeroUsize}; @@ -270,48 +267,43 @@ fn max_grid_bytes_safety_cap() -> usize { /// /// # fn main() -> Result<(), RandomPointGenerationError> { /// // 100 points -> side length 100, i.e. [-50, 50]. -/// let bounds = scaled_bounds_by_point_count::(100)?; +/// let bounds = scaled_bounds_by_point_count(100)?; /// assert_eq!(bounds.bounds(), (-50.0, 50.0)); /// # Ok(()) /// # } /// ``` -pub fn scaled_bounds_by_point_count( +pub fn scaled_bounds_by_point_count( n_points: usize, -) -> Result, RandomPointGenerationError> { +) -> Result, RandomPointGenerationError> { let side_len = n_points.max(1); - let side = safe_usize_to_scalar::(side_len).map_err(|source| { + let side = safe_usize_to_scalar(side_len).map_err(|source| { RandomPointGenerationError::CoordinateConversionFailed { value: side_len, - target_type: type_name::(), + target_type: type_name::(), source, } })?; - let half = side / (T::one() + T::one()); + let half = side / 2.0; Ok(CoordinateRange::from_validated_bounds(-half, half)) } /// Samples one point from an already-validated coordinate range. -fn sample_point_in_range( - range: CoordinateRange, - rng: &mut R, -) -> Point +fn sample_point_in_range(range: CoordinateRange, rng: &mut R) -> Point where - T: CoordinateScalar + SampleUniform, R: rand::Rng + ?Sized, { - let coords = [T::zero(); D].map(|_| rng.random_range(range.min()..range.max())); - Point::new(coords) + let coords = [0.0; D].map(|_| rng.random_range(range.min()..range.max())); + Point::from_validated_coords(coords) } /// Generates points from an already-validated coordinate range. -fn generate_random_points_in_range_with_rng( +fn generate_random_points_in_range_with_rng( n_points: usize, - range: CoordinateRange, + range: CoordinateRange, rng: &mut R, -) -> Vec> +) -> Vec> where - T: CoordinateScalar + SampleUniform, R: rand::Rng + ?Sized, { let mut points = Vec::with_capacity(n_points); @@ -328,11 +320,11 @@ where /// Non-positive finite distances intentionally disable the spacing constraint, /// while positive distances carry a [`PositiveScalar`] proof into the sampler. #[derive(Clone, Copy, Debug, PartialEq)] -enum PoissonSpacing { +enum PoissonSpacing { /// No minimum-distance constraint should be enforced. Disabled, /// A finite, positive minimum distance. - Minimum(PositiveScalar), + Minimum(PositiveScalar), } /// Scalar proven to be finite and strictly positive. @@ -341,21 +333,21 @@ enum PoissonSpacing { /// ball-radius error contracts so sampling code can consume positive values /// without repeating raw scalar validation. #[derive(Clone, Copy, Debug, PartialEq)] -struct PositiveScalar(T); +struct PositiveScalar(f64); -impl PositiveScalar { +impl PositiveScalar { /// Parses a raw scalar into a finite positive scalar proof. /// /// The returned [`InvalidPositiveScalar`] is embedded directly in public /// [`RandomPointGenerationError`] variants by the caller. - fn try_new(value: T) -> Result> { + fn try_new(value: f64) -> Result { if !value.is_finite() { return Err(InvalidPositiveScalar::NonFinite { value: InvalidCoordinateValue::from_debug(&value), }); } - if value <= T::zero() { + if value <= 0.0 { Err(InvalidPositiveScalar::NonPositive { value }) } else { Ok(Self(value)) @@ -363,7 +355,7 @@ impl PositiveScalar { } /// Returns the proven finite positive scalar. - const fn get(self) -> T { + const fn get(self) -> f64 { self.0 } @@ -371,22 +363,22 @@ impl PositiveScalar { /// /// The positive-radius proof guarantees `-radius < radius`, making the /// internal [`CoordinateRange`] construction infallible. - fn symmetric_range(self) -> CoordinateRange { + fn symmetric_range(self) -> CoordinateRange { let radius = self.get(); CoordinateRange::from_validated_bounds(-radius, radius) } } -impl PoissonSpacing { +impl PoissonSpacing { /// Parses raw Poisson disk spacing before the sampler starts. - fn try_new(min_distance: T) -> Result> { + fn try_new(min_distance: f64) -> Result { if !min_distance.is_finite() { return Err(RandomPointGenerationError::InvalidMinimumDistance { distance: InvalidCoordinateValue::from_debug(&min_distance), }); } - if min_distance <= T::zero() { + if min_distance <= 0.0 { Ok(Self::Disabled) } else { Ok(Self::Minimum(PositiveScalar(min_distance))) @@ -448,27 +440,27 @@ const fn ball_max_attempts(n_points: usize, dimension: usize) -> usize { /// /// # fn main() -> Result<(), RandomPointGenerationError> { /// // Generate 100 random 2D points with coordinates in [-10.0, 10.0] -/// let points_2d = try_generate_random_points::(100, (-10.0, 10.0))?; +/// let points_2d = try_generate_random_points::<2>(100, (-10.0, 10.0))?; /// assert_eq!(points_2d.len(), 100); /// /// // Generate 3D points with coordinates in [0.0, 1.0] (unit cube) -/// let points_3d = try_generate_random_points::(50, (0.0, 1.0))?; +/// let points_3d = try_generate_random_points::<3>(50, (0.0, 1.0))?; /// assert_eq!(points_3d.len(), 50); /// /// // Generate 4D points centered around origin -/// let points_4d = try_generate_random_points::(25, (-1.0, 1.0))?; +/// let points_4d = try_generate_random_points::<4>(25, (-1.0, 1.0))?; /// assert_eq!(points_4d.len(), 25); /// /// // Error handling -/// let result = try_generate_random_points::(100, (10.0, -10.0)); +/// let result = try_generate_random_points::<2>(100, (10.0, -10.0)); /// assert!(result.is_err()); // Invalid range /// # Ok(()) /// # } /// ``` -pub fn try_generate_random_points( +pub fn try_generate_random_points( n_points: usize, - range: (T, T), -) -> Result>, RandomPointGenerationError> { + range: (f64, f64), +) -> Result>, RandomPointGenerationError> { #[cfg(debug_assertions)] if env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() { tracing::debug!( @@ -508,15 +500,16 @@ pub fn try_generate_random_points Result<(), CoordinateRangeError> { /// let range = CoordinateRange::try_new(-1.0_f64, 1.0)?; -/// let points = generate_random_points_in_range::(8, range); +/// let points = generate_random_points_in_range::<2>(8, range); /// assert_eq!(points.len(), 8); /// # Ok(()) /// # } /// ``` -pub fn generate_random_points_in_range( +#[must_use] +pub fn generate_random_points_in_range( n_points: usize, - range: CoordinateRange, -) -> Vec> { + range: CoordinateRange, +) -> Vec> { let mut rng = rand::rng(); generate_random_points_in_range_with_rng(n_points, range, &mut rng) } @@ -551,27 +544,27 @@ pub fn generate_random_points_in_range Result<(), RandomPointGenerationError> { /// // Generate reproducible random points -/// let points1 = try_generate_random_points_seeded::(100, (-5.0, 5.0), 42)?; -/// let points2 = try_generate_random_points_seeded::(100, (-5.0, 5.0), 42)?; +/// let points1 = try_generate_random_points_seeded::<3>(100, (-5.0, 5.0), 42)?; +/// let points2 = try_generate_random_points_seeded::<3>(100, (-5.0, 5.0), 42)?; /// assert_eq!(points1, points2); // Same seed produces identical results /// /// // Different seeds produce different results -/// let points3 = try_generate_random_points_seeded::(100, (-5.0, 5.0), 123)?; +/// let points3 = try_generate_random_points_seeded::<3>(100, (-5.0, 5.0), 123)?; /// assert_ne!(points1, points3); /// /// // Common ranges - unit cube [0,1] -/// let unit_points = try_generate_random_points_seeded::(50, (0.0, 1.0), 42)?; +/// let unit_points = try_generate_random_points_seeded::<3>(50, (0.0, 1.0), 42)?; /// /// // Centered around origin [-1,1] -/// let centered_points = try_generate_random_points_seeded::(50, (-1.0, 1.0), 42)?; +/// let centered_points = try_generate_random_points_seeded::<3>(50, (-1.0, 1.0), 42)?; /// # Ok(()) /// # } /// ``` -pub fn try_generate_random_points_seeded( +pub fn try_generate_random_points_seeded( n_points: usize, - range: (T, T), + range: (f64, f64), seed: u64, -) -> Result>, RandomPointGenerationError> { +) -> Result>, RandomPointGenerationError> { #[cfg(debug_assertions)] if env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() { tracing::debug!( @@ -611,20 +604,18 @@ pub fn try_generate_random_points_seeded Result<(), CoordinateRangeError> { /// let range = CoordinateRange::try_new(0.0_f64, 1.0)?; -/// let points_a = generate_random_points_in_range_seeded::(4, range, 42); -/// let points_b = generate_random_points_in_range_seeded::(4, range, 42); +/// let points_a = generate_random_points_in_range_seeded::<3>(4, range, 42); +/// let points_b = generate_random_points_in_range_seeded::<3>(4, range, 42); /// assert_eq!(points_a, points_b); /// # Ok(()) /// # } /// ``` -pub fn generate_random_points_in_range_seeded< - T: CoordinateScalar + SampleUniform, - const D: usize, ->( +#[must_use] +pub fn generate_random_points_in_range_seeded( n_points: usize, - range: CoordinateRange, + range: CoordinateRange, seed: u64, -) -> Vec> { +) -> Vec> { let mut rng = StdRng::seed_from_u64(seed); generate_random_points_in_range_with_rng(n_points, range, &mut rng) } @@ -658,23 +649,23 @@ pub fn generate_random_points_in_range_seeded< /// /// # fn main() -> Result<(), RandomPointGenerationError> { /// // Generate 100 random 2D points in [0,1) × [0,2) -/// let points = generate_random_points_periodic::(100, [1.0, 2.0], 42)?; +/// let points = generate_random_points_periodic::<2>(100, [1.0, 2.0], 42)?; /// assert_eq!(points.len(), 100); /// /// // Reproducible generation -/// let points1 = generate_random_points_periodic::(50, [1.0, 1.0, 1.0], 123)?; -/// let points2 = generate_random_points_periodic::(50, [1.0, 1.0, 1.0], 123)?; +/// let points1 = generate_random_points_periodic::<3>(50, [1.0, 1.0, 1.0], 123)?; +/// let points2 = generate_random_points_periodic::<3>(50, [1.0, 1.0, 1.0], 123)?; /// assert_eq!(points1, points2); /// # Ok(()) /// # } /// ``` -pub fn generate_random_points_periodic( +pub fn generate_random_points_periodic( n_points: usize, - domain: [T; D], + domain: [f64; D], seed: u64, -) -> Result>, RandomPointGenerationError> { +) -> Result>, RandomPointGenerationError> { // Parse domain periods before sampling so later code consumes only positive periods. - let mut periods = [PositiveScalar(T::one()); D]; + let mut periods = [PositiveScalar(1.0); D]; for (axis, period) in domain.into_iter().enumerate() { periods[axis] = PositiveScalar::try_new(period) .map_err(|reason| RandomPointGenerationError::InvalidPeriodicDomain { axis, reason })?; @@ -684,8 +675,8 @@ pub fn generate_random_points_periodic( +fn generate_random_points_in_ball_with_rng( n_points: usize, - radius: T, + radius: f64, rng: &mut R, -) -> Result>, RandomPointGenerationError> +) -> Result>, RandomPointGenerationError> where - T: CoordinateScalar + SampleUniform, R: rand::Rng + ?Sized, { generate_random_points_in_ball_with_rng_and_budget( @@ -713,14 +703,13 @@ where } /// Generates ball samples through an injected RNG with an explicit attempt budget. -fn generate_random_points_in_ball_with_rng_and_budget( +fn generate_random_points_in_ball_with_rng_and_budget( n_points: usize, - radius: T, + radius: f64, rng: &mut R, max_attempts: usize, -) -> Result>, RandomPointGenerationError> +) -> Result>, RandomPointGenerationError> where - T: CoordinateScalar + SampleUniform, R: rand::Rng + ?Sized, { let radius = PositiveScalar::try_new(radius) @@ -734,7 +723,7 @@ where let bounds = radius.symmetric_range(); let radius = radius.get(); let radius_sq = radius * radius; - if !radius_sq.is_finite_generic() { + if !radius_sq.is_finite() { return Err(RandomPointGenerationError::InvalidBallRadiusSquared { value: InvalidCoordinateValue::from_debug(&radius_sq), }); @@ -745,10 +734,10 @@ where while points.len() < n_points && attempts < max_attempts { attempts += 1; - let coords = [T::zero(); D].map(|_| rng.random_range(bounds.min()..bounds.max())); - let norm_sq = coords.iter().fold(T::zero(), |acc, &c| acc + c * c); + let coords = [0.0; D].map(|_| rng.random_range(bounds.min()..bounds.max())); + let norm_sq = coords.iter().fold(0.0, |acc, &c| c.mul_add(c, acc)); if norm_sq <= radius_sq { - points.push(Point::new(coords)); + points.push(Point::from_validated_coords(coords)); } } @@ -799,15 +788,15 @@ where /// /// # fn main() -> Result<(), RandomPointGenerationError> { /// // Generate 100 random 4D points in a radius-10 ball. -/// let points = generate_random_points_in_ball::(100, 10.0)?; +/// let points = generate_random_points_in_ball::<4>(100, 10.0)?; /// assert_eq!(points.len(), 100); /// # Ok(()) /// # } /// ``` -pub fn generate_random_points_in_ball( +pub fn generate_random_points_in_ball( n_points: usize, - radius: T, -) -> Result>, RandomPointGenerationError> { + radius: f64, +) -> Result>, RandomPointGenerationError> { let mut rng = rand::rng(); generate_random_points_in_ball_with_rng(n_points, radius, &mut rng) } @@ -839,20 +828,17 @@ pub fn generate_random_points_in_ball Result<(), RandomPointGenerationError> { -/// let points1 = generate_random_points_in_ball_seeded::(10, 1.0, 42)?; -/// let points2 = generate_random_points_in_ball_seeded::(10, 1.0, 42)?; +/// let points1 = generate_random_points_in_ball_seeded::<4>(10, 1.0, 42)?; +/// let points2 = generate_random_points_in_ball_seeded::<4>(10, 1.0, 42)?; /// assert_eq!(points1, points2); /// # Ok(()) /// # } /// ``` -pub fn generate_random_points_in_ball_seeded< - T: CoordinateScalar + SampleUniform, - const D: usize, ->( +pub fn generate_random_points_in_ball_seeded( n_points: usize, - radius: T, + radius: f64, seed: u64, -) -> Result>, RandomPointGenerationError> { +) -> Result>, RandomPointGenerationError> { let mut rng = StdRng::seed_from_u64(seed); generate_random_points_in_ball_with_rng(n_points, radius, &mut rng) } @@ -918,24 +904,24 @@ pub fn generate_random_points_in_ball_seeded< /// }; /// /// // Generate 2D grid: 4x4 = 16 points with unit spacing -/// let grid_2d = generate_grid_points::(four, 1.0, [0.0, 0.0])?; +/// let grid_2d = generate_grid_points::<2>(four, 1.0, [0.0, 0.0])?; /// assert_eq!(grid_2d.len(), 16); /// /// // Generate 3D grid: 3x3x3 = 27 points with spacing 2.0 -/// let grid_3d = generate_grid_points::(three, 2.0, [0.0, 0.0, 0.0])?; +/// let grid_3d = generate_grid_points::<3>(three, 2.0, [0.0, 0.0, 0.0])?; /// assert_eq!(grid_3d.len(), 27); /// /// // Generate 4D grid centered at origin -/// let grid_4d = generate_grid_points::(two, 1.0, [-0.5, -0.5, -0.5, -0.5])?; +/// let grid_4d = generate_grid_points::<4>(two, 1.0, [-0.5, -0.5, -0.5, -0.5])?; /// assert_eq!(grid_4d.len(), 16); // 2^4 = 16 points /// # Ok(()) /// # } /// ``` -pub fn generate_grid_points( +pub fn generate_grid_points( points_per_dim: NonZeroUsize, - spacing: T, - offset: [T; D], -) -> Result>, RandomPointGenerationError> { + spacing: f64, + offset: [f64; D], +) -> Result>, RandomPointGenerationError> { let points_per_dim = points_per_dim.get(); let spacing = PositiveScalar::try_new(spacing) @@ -943,7 +929,7 @@ pub fn generate_grid_points( .get(); for (axis, coordinate) in offset.iter().enumerate() { - if !coordinate.is_finite_generic() { + if !coordinate.is_finite() { return Err(RandomPointGenerationError::InvalidGridOffset { axis, value: InvalidCoordinateValue::from_debug(coordinate), @@ -963,7 +949,7 @@ pub fn generate_grid_points( } // Dimension/type-aware memory cap: total_points * D * size_of::() - let per_point_bytes = D.saturating_mul(core::mem::size_of::()); + let per_point_bytes = D.saturating_mul(core::mem::size_of::()); let total_bytes = total_points.saturating_mul(per_point_bytes); let cap = max_grid_bytes_safety_cap(); if total_bytes > cap { @@ -978,18 +964,18 @@ pub fn generate_grid_points( // This avoids O(N) memory allocation for intermediate index vectors let mut idx = [0usize; D]; for _ in 0..total_points { - let mut coords = [T::zero(); D]; + let mut coords = [0.0; D]; for d in 0..D { - let index_as_scalar = grid_index_as_scalar::(&idx, d, points_per_dim)?; - coords[d] = offset[d] + index_as_scalar * spacing; - if !coords[d].is_finite_generic() { + let index_as_scalar = grid_index_as_scalar::(&idx, d, points_per_dim)?; + coords[d] = index_as_scalar.mul_add(spacing, offset[d]); + if !coords[d].is_finite() { return Err(RandomPointGenerationError::InvalidGeneratedGridCoordinate { axis: d, value: InvalidCoordinateValue::from_debug(&coords[d]), }); } } - points.push(Point::new(coords)); + points.push(Point::from_validated_coords(coords)); // Increment mixed-radix counter for d in (0..D).rev() { @@ -1008,15 +994,15 @@ pub fn generate_grid_points( /// /// This keeps [`generate_grid_points`] from silently rounding large grid indices /// when a scalar type cannot represent every `usize` exactly. -fn grid_index_as_scalar( +fn grid_index_as_scalar( idx: &[usize; D], coordinate_index: usize, _points_per_dim: usize, -) -> Result> { - safe_usize_to_scalar::(idx[coordinate_index]).map_err(|source| { +) -> Result { + safe_usize_to_scalar(idx[coordinate_index]).map_err(|source| { RandomPointGenerationError::CoordinateConversionFailed { value: idx[coordinate_index], - target_type: type_name::(), + target_type: type_name::(), source, } }) @@ -1070,20 +1056,20 @@ fn grid_index_as_scalar( /// /// # fn main() -> Result<(), RandomPointGenerationError> { /// // Generate ~100 2D points with minimum distance 0.1 in unit square -/// let poisson_2d = try_generate_poisson_points::(100, (0.0, 1.0), 0.1, 42)?; +/// let poisson_2d = try_generate_poisson_points::<2>(100, (0.0, 1.0), 0.1, 42)?; /// // Actual count may be less than 100 due to spacing constraints /// /// // Generate 3D points in a cube -/// let poisson_3d = try_generate_poisson_points::(50, (-1.0, 1.0), 0.2, 123)?; +/// let poisson_3d = try_generate_poisson_points::<3>(50, (-1.0, 1.0), 0.2, 123)?; /// # Ok(()) /// # } /// ``` -pub fn try_generate_poisson_points( +pub fn try_generate_poisson_points( n_points: usize, - bounds: (T, T), - min_distance: T, + bounds: (f64, f64), + min_distance: f64, seed: u64, -) -> Result>, RandomPointGenerationError> { +) -> Result>, RandomPointGenerationError> { let bounds = CoordinateRange::try_from(bounds)?; generate_poisson_points_in_range(n_points, bounds, min_distance, seed) @@ -1121,17 +1107,17 @@ pub fn try_generate_poisson_points Result<(), RandomPointGenerationError> { /// let range = CoordinateRange::try_new(0.0_f64, 1.0)?; -/// let points = generate_poisson_points_in_range::(16, range, 0.1, 42)?; +/// let points = generate_poisson_points_in_range::<2>(16, range, 0.1, 42)?; /// assert!(!points.is_empty()); /// # Ok(()) /// # } /// ``` -pub fn generate_poisson_points_in_range( +pub fn generate_poisson_points_in_range( n_points: usize, - bounds: CoordinateRange, - min_distance: T, + bounds: CoordinateRange, + min_distance: f64, seed: u64, -) -> Result>, RandomPointGenerationError> { +) -> Result>, RandomPointGenerationError> { let spacing = PoissonSpacing::try_new(min_distance)?; if n_points == 0 { @@ -1150,7 +1136,7 @@ pub fn generate_poisson_points_in_range> = Vec::new(); + let mut points: Vec> = Vec::new(); // Simple Poisson disk sampling: rejection method // Scale max attempts with dimension since higher dimensions make spacing harder @@ -1167,12 +1153,12 @@ pub fn generate_poisson_points_in_range( - result: &Result>, RandomPointGenerationError>, + result: &Result>, RandomPointGenerationError>, expected_ordering: CoordinateRangeOrdering, expected_min: f64, expected_max: f64, @@ -1329,20 +1315,20 @@ mod tests { #[test] fn test_generate_random_points_rejects_nonfinite_tuple_bounds() { assert_matches!( - try_generate_random_points::(4, (f64::NAN, 1.0)), + try_generate_random_points::<2>(4, (f64::NAN, 1.0)), Err(RandomPointGenerationError::InvalidCoordinateRange { source: CoordinateRangeError::NonFiniteBound { bound, value } }) if bound == CoordinateRangeBound::Minimum && value == InvalidCoordinateValue::Nan ); assert_matches!( - try_generate_random_points_seeded::(4, (0.0, f64::INFINITY), 42), + try_generate_random_points_seeded::<2>(4, (0.0, f64::INFINITY), 42), Err(RandomPointGenerationError::InvalidCoordinateRange { source: CoordinateRangeError::NonFiniteBound { bound, value } }) if bound == CoordinateRangeBound::Maximum && value == InvalidCoordinateValue::PositiveInfinity ); assert_matches!( - try_generate_poisson_points::(4, (f64::NEG_INFINITY, 1.0), 0.1, 42), + try_generate_poisson_points::<2>(4, (f64::NEG_INFINITY, 1.0), 0.1, 42), Err(RandomPointGenerationError::InvalidCoordinateRange { source: CoordinateRangeError::NonFiniteBound { bound, value } }) if bound == CoordinateRangeBound::Minimum @@ -1354,7 +1340,7 @@ mod tests { fn test_range_based_generators_use_validated_bounds() { let range = CoordinateRange::try_new(-1.0_f64, 1.0).unwrap(); - let points = generate_random_points_in_range::(8, range); + let points = generate_random_points_in_range::<3>(8, range); assert_eq!(points.len(), 8); for point in points { for &coord in point.coords() { @@ -1362,8 +1348,8 @@ mod tests { } } - let seeded_a = generate_random_points_in_range_seeded::(8, range, 42); - let seeded_b = generate_random_points_in_range_seeded::(8, range, 42); + let seeded_a = generate_random_points_in_range_seeded::<3>(8, range, 42); + let seeded_b = generate_random_points_in_range_seeded::<3>(8, range, 42); assert_eq!(seeded_a, seeded_b); for point in seeded_a { for &coord in point.coords() { @@ -1371,24 +1357,23 @@ mod tests { } } - let empty = generate_random_points_in_range::(0, range); + let empty = generate_random_points_in_range::<3>(0, range); assert!(empty.is_empty()); - let seeded_empty = generate_random_points_in_range_seeded::(0, range, 42); + let seeded_empty = generate_random_points_in_range_seeded::<3>(0, range, 42); assert!(seeded_empty.is_empty()); - let poisson = generate_poisson_points_in_range::(8, range, 0.1, 42).unwrap(); + let poisson = generate_poisson_points_in_range::<2>(8, range, 0.1, 42).unwrap(); assert!(!poisson.is_empty()); let unconstrained_poisson = - generate_poisson_points_in_range::(8, range, 0.0, 42).unwrap(); + generate_poisson_points_in_range::<2>(8, range, 0.0, 42).unwrap(); assert_eq!(unconstrained_poisson.len(), 8); - let empty_poisson = generate_poisson_points_in_range::(0, range, 0.1, 42).unwrap(); + let empty_poisson = generate_poisson_points_in_range::<2>(0, range, 0.1, 42).unwrap(); assert!(empty_poisson.is_empty()); - let invalid_empty_poisson = - generate_poisson_points_in_range::(0, range, f64::NAN, 42); + let invalid_empty_poisson = generate_poisson_points_in_range::<2>(0, range, f64::NAN, 42); assert_matches!( invalid_empty_poisson, Err(RandomPointGenerationError::InvalidMinimumDistance { distance }) @@ -1398,11 +1383,11 @@ mod tests { #[test] fn test_scaled_bounds_by_point_count_returns_validated_range() { - let hundred = scaled_bounds_by_point_count::(100).unwrap(); + let hundred = scaled_bounds_by_point_count(100).unwrap(); assert_relative_eq!(hundred.min(), -50.0, epsilon = f64::EPSILON); assert_relative_eq!(hundred.max(), 50.0, epsilon = f64::EPSILON); - let empty = scaled_bounds_by_point_count::(0).unwrap(); + let empty = scaled_bounds_by_point_count(0).unwrap(); assert_relative_eq!(empty.min(), -0.5, epsilon = f64::EPSILON); assert_relative_eq!(empty.max(), 0.5, epsilon = f64::EPSILON); } @@ -1485,7 +1470,7 @@ mod tests { #[test] fn test_generate_random_points_2d() { // Test 2D random point generation - let points = try_generate_random_points::(100, (-10.0, 10.0)).unwrap(); + let points = try_generate_random_points::<2>(100, (-10.0, 10.0)).unwrap(); assert_eq!(points.len(), 100); @@ -1500,7 +1485,7 @@ mod tests { #[test] fn test_generate_random_points_3d() { // Test 3D random point generation - let points = try_generate_random_points::(75, (0.0, 5.0)).unwrap(); + let points = try_generate_random_points::<3>(75, (0.0, 5.0)).unwrap(); assert_eq!(points.len(), 75); @@ -1515,7 +1500,7 @@ mod tests { #[test] fn test_generate_random_points_4d() { // Test 4D random point generation - let points = try_generate_random_points::(50, (-2.0, 2.0)).unwrap(); + let points = try_generate_random_points::<4>(50, (-2.0, 2.0)).unwrap(); assert_eq!(points.len(), 50); @@ -1530,7 +1515,7 @@ mod tests { #[test] fn test_generate_random_points_5d() { // Test 5D random point generation - let points = try_generate_random_points::(25, (-1.0, 1.0)).unwrap(); + let points = try_generate_random_points::<5>(25, (-1.0, 1.0)).unwrap(); assert_eq!(points.len(), 25); @@ -1547,23 +1532,23 @@ mod tests { // Test invalid range (non-increasing bounds) across all dimensions // 2D - let result = try_generate_random_points::(100, (10.0, -10.0)); + let result = try_generate_random_points::<2>(100, (10.0, -10.0)); assert_invalid_coordinate_range(&result, CoordinateRangeOrdering::Decreasing, 10.0, -10.0); // 3D - let result = try_generate_random_points::(50, (5.0, 5.0)); + let result = try_generate_random_points::<3>(50, (5.0, 5.0)); assert_invalid_coordinate_range(&result, CoordinateRangeOrdering::Equal, 5.0, 5.0); // 4D - let result = try_generate_random_points::(25, (1.0, 0.5)); + let result = try_generate_random_points::<4>(25, (1.0, 0.5)); assert_invalid_coordinate_range(&result, CoordinateRangeOrdering::Decreasing, 1.0, 0.5); // 5D - let result = try_generate_random_points::(10, (2.0, 2.0)); + let result = try_generate_random_points::<5>(10, (2.0, 2.0)); assert_invalid_coordinate_range(&result, CoordinateRangeOrdering::Equal, 2.0, 2.0); // Test valid edge case - very small range - let points = try_generate_random_points::(10, (0.0, 0.001)).unwrap(); + let points = try_generate_random_points::<2>(10, (0.0, 0.001)).unwrap(); assert_eq!(points.len(), 10); for point in points { for &coord in point.coords() { @@ -1575,16 +1560,16 @@ mod tests { #[test] fn test_generate_random_points_zero_points() { // Test generating zero points across all dimensions - let points_2d = try_generate_random_points::(0, (-1.0, 1.0)).unwrap(); + let points_2d = try_generate_random_points::<2>(0, (-1.0, 1.0)).unwrap(); assert_eq!(points_2d.len(), 0); - let points_3d = try_generate_random_points::(0, (-1.0, 1.0)).unwrap(); + let points_3d = try_generate_random_points::<3>(0, (-1.0, 1.0)).unwrap(); assert_eq!(points_3d.len(), 0); - let points_4d = try_generate_random_points::(0, (-1.0, 1.0)).unwrap(); + let points_4d = try_generate_random_points::<4>(0, (-1.0, 1.0)).unwrap(); assert_eq!(points_4d.len(), 0); - let points_5d = try_generate_random_points::(0, (-1.0, 1.0)).unwrap(); + let points_5d = try_generate_random_points::<5>(0, (-1.0, 1.0)).unwrap(); assert_eq!(points_5d.len(), 0); } @@ -1592,8 +1577,8 @@ mod tests { fn test_generate_random_points_seeded_2d() { // Test seeded 2D generation reproducibility let seed = 42_u64; - let points1 = try_generate_random_points_seeded::(50, (-5.0, 5.0), seed).unwrap(); - let points2 = try_generate_random_points_seeded::(50, (-5.0, 5.0), seed).unwrap(); + let points1 = try_generate_random_points_seeded::<2>(50, (-5.0, 5.0), seed).unwrap(); + let points2 = try_generate_random_points_seeded::<2>(50, (-5.0, 5.0), seed).unwrap(); assert_eq!(points1.len(), points2.len()); @@ -1612,8 +1597,8 @@ mod tests { fn test_generate_random_points_seeded_3d() { // Test seeded 3D generation reproducibility let seed = 123_u64; - let points1 = try_generate_random_points_seeded::(40, (0.0, 10.0), seed).unwrap(); - let points2 = try_generate_random_points_seeded::(40, (0.0, 10.0), seed).unwrap(); + let points1 = try_generate_random_points_seeded::<3>(40, (0.0, 10.0), seed).unwrap(); + let points2 = try_generate_random_points_seeded::<3>(40, (0.0, 10.0), seed).unwrap(); assert_eq!(points1.len(), points2.len()); @@ -1631,8 +1616,8 @@ mod tests { fn test_generate_random_points_seeded_4d() { // Test seeded 4D generation reproducibility let seed = 789_u64; - let points1 = try_generate_random_points_seeded::(30, (-2.5, 2.5), seed).unwrap(); - let points2 = try_generate_random_points_seeded::(30, (-2.5, 2.5), seed).unwrap(); + let points1 = try_generate_random_points_seeded::<4>(30, (-2.5, 2.5), seed).unwrap(); + let points2 = try_generate_random_points_seeded::<4>(30, (-2.5, 2.5), seed).unwrap(); assert_eq!(points1.len(), points2.len()); @@ -1650,8 +1635,8 @@ mod tests { fn test_generate_random_points_seeded_5d() { // Test seeded 5D generation reproducibility let seed = 456_u64; - let points1 = try_generate_random_points_seeded::(20, (-1.0, 3.0), seed).unwrap(); - let points2 = try_generate_random_points_seeded::(20, (-1.0, 3.0), seed).unwrap(); + let points1 = try_generate_random_points_seeded::<5>(20, (-1.0, 3.0), seed).unwrap(); + let points2 = try_generate_random_points_seeded::<5>(20, (-1.0, 3.0), seed).unwrap(); assert_eq!(points1.len(), points2.len()); @@ -1670,32 +1655,28 @@ mod tests { // Test that different seeds produce different results across all dimensions // 2D - let points1_2d = try_generate_random_points_seeded::(50, (0.0, 1.0), 42).unwrap(); - let points2_2d = try_generate_random_points_seeded::(50, (0.0, 1.0), 123).unwrap(); + let points1_2d = try_generate_random_points_seeded::<2>(50, (0.0, 1.0), 42).unwrap(); + let points2_2d = try_generate_random_points_seeded::<2>(50, (0.0, 1.0), 123).unwrap(); assert_ne!(points1_2d, points2_2d); // 3D - let points1_3d = try_generate_random_points_seeded::(30, (-5.0, 5.0), 42).unwrap(); - let points2_3d = try_generate_random_points_seeded::(30, (-5.0, 5.0), 999).unwrap(); + let points1_3d = try_generate_random_points_seeded::<3>(30, (-5.0, 5.0), 42).unwrap(); + let points2_3d = try_generate_random_points_seeded::<3>(30, (-5.0, 5.0), 999).unwrap(); assert_ne!(points1_3d, points2_3d); // 4D - let points1_4d = - try_generate_random_points_seeded::(25, (-1.0, 1.0), 1337).unwrap(); - let points2_4d = - try_generate_random_points_seeded::(25, (-1.0, 1.0), 7331).unwrap(); + let points1_4d = try_generate_random_points_seeded::<4>(25, (-1.0, 1.0), 1337).unwrap(); + let points2_4d = try_generate_random_points_seeded::<4>(25, (-1.0, 1.0), 7331).unwrap(); assert_ne!(points1_4d, points2_4d); // 5D - let points1_5d = - try_generate_random_points_seeded::(15, (0.0, 10.0), 2021).unwrap(); - let points2_5d = - try_generate_random_points_seeded::(15, (0.0, 10.0), 2024).unwrap(); + let points1_5d = try_generate_random_points_seeded::<5>(15, (0.0, 10.0), 2021).unwrap(); + let points2_5d = try_generate_random_points_seeded::<5>(15, (0.0, 10.0), 2024).unwrap(); assert_ne!(points1_5d, points2_5d); } #[test] fn test_generate_random_points_periodic_2d_in_domain() { - let points = generate_random_points_periodic::(100, [1.0, 2.0], 42).unwrap(); + let points = generate_random_points_periodic::<2>(100, [1.0, 2.0], 42).unwrap(); assert_eq!(points.len(), 100); for point in &points { @@ -1707,14 +1688,14 @@ mod tests { #[test] fn test_generate_random_points_periodic_seeded_reproducible() { - let points1 = generate_random_points_periodic::(50, [1.0, 1.0, 1.0], 123).unwrap(); - let points2 = generate_random_points_periodic::(50, [1.0, 1.0, 1.0], 123).unwrap(); + let points1 = generate_random_points_periodic::<3>(50, [1.0, 1.0, 1.0], 123).unwrap(); + let points2 = generate_random_points_periodic::<3>(50, [1.0, 1.0, 1.0], 123).unwrap(); assert_eq!(points1, points2); } #[test] fn test_generate_random_points_periodic_invalid_domain() { - let zero_period = generate_random_points_periodic::(10, [1.0, 0.0], 7); + let zero_period = generate_random_points_periodic::<2>(10, [1.0, 0.0], 7); let Err(RandomPointGenerationError::InvalidPeriodicDomain { axis, reason: InvalidPositiveScalar::NonPositive { value }, @@ -1725,7 +1706,7 @@ mod tests { assert_eq!(axis, 1); assert_relative_eq!(value, 0.0, epsilon = f64::EPSILON); - let negative_period = generate_random_points_periodic::(10, [1.0, -2.0], 7); + let negative_period = generate_random_points_periodic::<2>(10, [1.0, -2.0], 7); let Err(RandomPointGenerationError::InvalidPeriodicDomain { axis, reason: InvalidPositiveScalar::NonPositive { value }, @@ -1736,7 +1717,7 @@ mod tests { assert_eq!(axis, 1); assert_relative_eq!(value, -2.0, epsilon = f64::EPSILON); - let nan_period = generate_random_points_periodic::(10, [1.0, f64::NAN], 7); + let nan_period = generate_random_points_periodic::<2>(10, [1.0, f64::NAN], 7); assert_matches!( nan_period, Err(RandomPointGenerationError::InvalidPeriodicDomain { @@ -1745,8 +1726,7 @@ mod tests { }) if axis == 1 && value == InvalidCoordinateValue::Nan ); - let infinite_period = - generate_random_points_periodic::(10, [1.0, f64::INFINITY], 7); + let infinite_period = generate_random_points_periodic::<2>(10, [1.0, f64::INFINITY], 7); assert_matches!( infinite_period, Err(RandomPointGenerationError::InvalidPeriodicDomain { @@ -1758,7 +1738,7 @@ mod tests { #[test] fn test_generate_random_points_periodic_zero_points() { - let points = generate_random_points_periodic::(0, [1.0, 2.0, 3.0, 4.0], 9).unwrap(); + let points = generate_random_points_periodic::<4>(0, [1.0, 2.0, 3.0, 4.0], 9).unwrap(); assert!(points.is_empty()); } @@ -1769,7 +1749,7 @@ mod tests { #[test] fn test_generate_random_points_in_ball_4d() { let radius = 3.0_f64; - let points = generate_random_points_in_ball::(200, radius).unwrap(); + let points = generate_random_points_in_ball::<4>(200, radius).unwrap(); assert_eq!(points.len(), 200); let radius_sq = radius * radius; @@ -1786,21 +1766,21 @@ mod tests { #[test] fn test_generate_random_points_in_ball_seeded_reproducible_4d() { - let points1 = generate_random_points_in_ball_seeded::(50, 2.5, 42).unwrap(); - let points2 = generate_random_points_in_ball_seeded::(50, 2.5, 42).unwrap(); + let points1 = generate_random_points_in_ball_seeded::<4>(50, 2.5, 42).unwrap(); + let points2 = generate_random_points_in_ball_seeded::<4>(50, 2.5, 42).unwrap(); assert_eq!(points1, points2); } #[test] fn test_generate_random_points_in_ball_seeded_different_seeds_4d() { - let points1 = generate_random_points_in_ball_seeded::(50, 2.5, 42).unwrap(); - let points2 = generate_random_points_in_ball_seeded::(50, 2.5, 123).unwrap(); + let points1 = generate_random_points_in_ball_seeded::<4>(50, 2.5, 42).unwrap(); + let points2 = generate_random_points_in_ball_seeded::<4>(50, 2.5, 123).unwrap(); assert_ne!(points1, points2); } #[test] fn test_generate_random_points_in_ball_rejects_zero_radius() { - let result = generate_random_points_in_ball::(10, 0.0); + let result = generate_random_points_in_ball::<4>(10, 0.0); let Err(RandomPointGenerationError::InvalidBallRadius { reason: InvalidPositiveScalar::NonPositive { value }, }) = result @@ -1812,7 +1792,7 @@ mod tests { #[test] fn test_generate_random_points_in_ball_rejects_negative_radius() { - let result = generate_random_points_in_ball::(10, -1.0); + let result = generate_random_points_in_ball::<4>(10, -1.0); let Err(RandomPointGenerationError::InvalidBallRadius { reason: InvalidPositiveScalar::NonPositive { value }, }) = result @@ -1824,19 +1804,19 @@ mod tests { #[test] fn test_generate_random_points_in_ball_zero_points() { - let points = generate_random_points_in_ball::(0, 1.0).unwrap(); + let points = generate_random_points_in_ball::<4>(0, 1.0).unwrap(); assert!(points.is_empty()); } #[test] fn test_generate_random_points_in_ball_seeded_zero_points() { - let points = generate_random_points_in_ball_seeded::(0, 1.0, 7).unwrap(); + let points = generate_random_points_in_ball_seeded::<4>(0, 1.0, 7).unwrap(); assert!(points.is_empty()); } #[test] fn test_generate_random_points_in_ball_rejects_nan_radius() { - let result = generate_random_points_in_ball::(10, f64::NAN); + let result = generate_random_points_in_ball::<4>(10, f64::NAN); assert_matches!( result, Err(RandomPointGenerationError::InvalidBallRadius { @@ -1847,7 +1827,7 @@ mod tests { #[test] fn test_generate_random_points_in_ball_rejects_infinite_radius() { - let result = generate_random_points_in_ball::(10, f64::INFINITY); + let result = generate_random_points_in_ball::<4>(10, f64::INFINITY); assert_matches!( result, Err(RandomPointGenerationError::InvalidBallRadius { @@ -1858,7 +1838,7 @@ mod tests { #[test] fn test_generate_random_points_in_ball_rejects_overflowing_radius_squared() { - let result = generate_random_points_in_ball::(1, f64::MAX); + let result = generate_random_points_in_ball::<2>(1, f64::MAX); assert_matches!( result, Err(RandomPointGenerationError::InvalidBallRadiusSquared { value }) @@ -1869,7 +1849,7 @@ mod tests { #[test] fn test_generate_random_points_in_ball_seeded_in_ball_constraints_4d() { let radius = 1.25; - let points = generate_random_points_in_ball_seeded::(100, radius, 99).unwrap(); + let points = generate_random_points_in_ball_seeded::<4>(100, radius, 99).unwrap(); assert_eq!(points.len(), 100); let radius_sq = radius * radius; @@ -1888,7 +1868,7 @@ mod tests { fn test_generate_random_points_in_ball_returns_typed_error_when_budget_exhausts() { let mut rng = StdRng::seed_from_u64(42); let result = - generate_random_points_in_ball_with_rng_and_budget::(1, 1.0, &mut rng, 0); + generate_random_points_in_ball_with_rng_and_budget::<_, 2>(1, 1.0, &mut rng, 0); let Err(RandomPointGenerationError::BallSamplingFailed { requested_points, @@ -1910,8 +1890,8 @@ mod tests { #[test] fn test_generate_random_points_in_ball_seeded_same_seed_is_deterministic_4d() { // This is a small smoke test that ensures deterministic output for fixed seed. - let points1 = generate_random_points_in_ball_seeded::(10, 1.0, 0xBEEF).unwrap(); - let points2 = generate_random_points_in_ball_seeded::(10, 1.0, 0xBEEF).unwrap(); + let points1 = generate_random_points_in_ball_seeded::<4>(10, 1.0, 0xBEEF).unwrap(); + let points2 = generate_random_points_in_ball_seeded::<4>(10, 1.0, 0xBEEF).unwrap(); assert_eq!(points1, points2); } @@ -1920,7 +1900,7 @@ mod tests { // Test that points cover the range reasonably well across all dimensions // 2D coverage test - let points_2d = try_generate_random_points::(500, (0.0, 10.0)).unwrap(); + let points_2d = try_generate_random_points::<2>(500, (0.0, 10.0)).unwrap(); let mut min_2d = [f64::INFINITY; 2]; let mut max_2d = [f64::NEG_INFINITY; 2]; @@ -1945,7 +1925,7 @@ mod tests { } // 5D coverage test (smaller sample) - let points_5d = try_generate_random_points::(200, (-5.0, 5.0)).unwrap(); + let points_5d = try_generate_random_points::<5>(200, (-5.0, 5.0)).unwrap(); let mut min_5d = [f64::INFINITY; 5]; let mut max_5d = [f64::NEG_INFINITY; 5]; @@ -1975,10 +1955,10 @@ mod tests { // Test common useful ranges across dimensions // Unit cube [0,1] for all dimensions - let unit_2d = try_generate_random_points::(50, (0.0, 1.0)).unwrap(); - let unit_3d = try_generate_random_points::(50, (0.0, 1.0)).unwrap(); - let unit_4d = try_generate_random_points::(50, (0.0, 1.0)).unwrap(); - let unit_5d = try_generate_random_points::(50, (0.0, 1.0)).unwrap(); + let unit_2d = try_generate_random_points::<2>(50, (0.0, 1.0)).unwrap(); + let unit_3d = try_generate_random_points::<3>(50, (0.0, 1.0)).unwrap(); + let unit_4d = try_generate_random_points::<4>(50, (0.0, 1.0)).unwrap(); + let unit_5d = try_generate_random_points::<5>(50, (0.0, 1.0)).unwrap(); assert_eq!(unit_2d.len(), 50); assert_eq!(unit_3d.len(), 50); @@ -1986,10 +1966,10 @@ mod tests { assert_eq!(unit_5d.len(), 50); // Centered cube [-1,1] for all dimensions - let centered_2d = try_generate_random_points::(30, (-1.0, 1.0)).unwrap(); - let centered_3d = try_generate_random_points::(30, (-1.0, 1.0)).unwrap(); - let centered_4d = try_generate_random_points::(30, (-1.0, 1.0)).unwrap(); - let centered_5d = try_generate_random_points::(30, (-1.0, 1.0)).unwrap(); + let centered_2d = try_generate_random_points::<2>(30, (-1.0, 1.0)).unwrap(); + let centered_3d = try_generate_random_points::<3>(30, (-1.0, 1.0)).unwrap(); + let centered_4d = try_generate_random_points::<4>(30, (-1.0, 1.0)).unwrap(); + let centered_5d = try_generate_random_points::<5>(30, (-1.0, 1.0)).unwrap(); assert_eq!(centered_2d.len(), 30); assert_eq!(centered_3d.len(), 30); @@ -2012,7 +1992,7 @@ mod tests { #[test] fn test_generate_grid_points_2d() { // Test 2D grid generation - let grid = generate_grid_points::(nonzero(3), 1.0, [0.0, 0.0]).unwrap(); + let grid = generate_grid_points::<2>(nonzero(3), 1.0, [0.0, 0.0]).unwrap(); assert_eq!(grid.len(), 9); // 3^2 = 9 points @@ -2045,7 +2025,7 @@ mod tests { #[test] fn test_generate_grid_points_3d() { // Test 3D grid generation - let grid = generate_grid_points::(nonzero(2), 2.0, [1.0, 1.0, 1.0]).unwrap(); + let grid = generate_grid_points::<3>(nonzero(2), 2.0, [1.0, 1.0, 1.0]).unwrap(); assert_eq!(grid.len(), 8); // 2^3 = 8 points @@ -2061,8 +2041,7 @@ mod tests { #[test] fn test_generate_grid_points_4d() { // Test 4D grid generation - let grid = - generate_grid_points::(nonzero(2), 0.5, [-0.5, -0.5, -0.5, -0.5]).unwrap(); + let grid = generate_grid_points::<4>(nonzero(2), 0.5, [-0.5, -0.5, -0.5, -0.5]).unwrap(); assert_eq!(grid.len(), 16); // 2^4 = 16 points @@ -2078,8 +2057,7 @@ mod tests { #[test] fn test_generate_grid_points_5d() { // Test 5D grid generation - let grid = - generate_grid_points::(nonzero(2), 1.0, [0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(); + let grid = generate_grid_points::<5>(nonzero(2), 1.0, [0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(); assert_eq!(grid.len(), 32); // 2^5 = 32 points @@ -2095,7 +2073,7 @@ mod tests { #[test] fn test_generate_grid_points_edge_cases() { // Test single point grid - let grid = generate_grid_points::(nonzero(1), 1.0, [0.0, 0.0, 0.0]).unwrap(); + let grid = generate_grid_points::<3>(nonzero(1), 1.0, [0.0, 0.0, 0.0]).unwrap(); assert_eq!(grid.len(), 1); let coords = *grid[0].coords(); // Use approx for floating point comparison @@ -2103,7 +2081,7 @@ mod tests { assert!((actual - expected).abs() < 1e-15); } - let zero_spacing = generate_grid_points::(nonzero(2), 0.0, [5.0, 5.0]); + let zero_spacing = generate_grid_points::<2>(nonzero(2), 0.0, [5.0, 5.0]); let Err(RandomPointGenerationError::InvalidGridSpacing { reason: InvalidPositiveScalar::NonPositive { value }, }) = zero_spacing @@ -2112,7 +2090,7 @@ mod tests { }; assert_relative_eq!(value, 0.0, epsilon = f64::EPSILON); - let negative_spacing = generate_grid_points::(nonzero(2), -1.0, [5.0, 5.0]); + let negative_spacing = generate_grid_points::<2>(nonzero(2), -1.0, [5.0, 5.0]); let Err(RandomPointGenerationError::InvalidGridSpacing { reason: InvalidPositiveScalar::NonPositive { value }, }) = negative_spacing @@ -2127,7 +2105,7 @@ mod tests { // Zero points per dimension cannot cross the typed API boundary. assert_eq!(NonZeroUsize::new(0), None); - let nan_spacing = generate_grid_points::(nonzero(2), f64::NAN, [0.0, 0.0]); + let nan_spacing = generate_grid_points::<2>(nonzero(2), f64::NAN, [0.0, 0.0]); assert_matches!( nan_spacing, Err(RandomPointGenerationError::InvalidGridSpacing { @@ -2135,8 +2113,7 @@ mod tests { }) if value == InvalidCoordinateValue::Nan ); - let infinite_offset = - generate_grid_points::(nonzero(2), 1.0, [0.0, f64::INFINITY, 0.0]); + let infinite_offset = generate_grid_points::<3>(nonzero(2), 1.0, [0.0, f64::INFINITY, 0.0]); assert_matches!( infinite_offset, Err(RandomPointGenerationError::InvalidGridOffset { axis, value }) @@ -2144,7 +2121,7 @@ mod tests { ); // Test safety cap for excessive points (prevents OOM) - let result = generate_grid_points::(nonzero(1000), 1.0, [0.0, 0.0, 0.0]); + let result = generate_grid_points::<3>(nonzero(1000), 1.0, [0.0, 0.0, 0.0]); assert_matches!( result, Err(RandomPointGenerationError::GridAllocationTooLarge { @@ -2156,7 +2133,7 @@ mod tests { #[test] fn test_generate_grid_points_rejects_non_finite_generated_coordinate() { - let result = generate_grid_points::(nonzero(2), f64::MAX, [f64::MAX]); + let result = generate_grid_points::<1>(nonzero(2), f64::MAX, [f64::MAX]); assert_matches!( result, @@ -2174,7 +2151,7 @@ mod tests { let spacing = 0.1; // This would require 10^64 points which overflows usize let points_per_dim = 10; - let result = generate_grid_points::(nonzero(points_per_dim), spacing, offset); + let result = generate_grid_points::(nonzero(points_per_dim), spacing, offset); assert_matches!( result, Err(RandomPointGenerationError::GridSizeOverflow { @@ -2188,8 +2165,7 @@ mod tests { fn test_generate_grid_points_index_conversion_failure() { let first_inexact_f64_index = (1_usize << f64::MANTISSA_DIGITS) + 1; let idx = [first_inexact_f64_index]; - let result = - grid_index_as_scalar::(&idx, 0, first_inexact_f64_index.saturating_add(1)); + let result = grid_index_as_scalar::<1>(&idx, 0, first_inexact_f64_index.saturating_add(1)); assert_matches!( result, @@ -2218,7 +2194,7 @@ mod tests { #[test] fn test_generate_poisson_points_2d() { // Test 2D Poisson disk sampling - let points = try_generate_poisson_points::(50, (0.0, 10.0), 0.5, 42).unwrap(); + let points = try_generate_poisson_points::<2>(50, (0.0, 10.0), 0.5, 42).unwrap(); // Should generate some points (exact count depends on spacing constraints) assert!(!points.is_empty()); @@ -2251,7 +2227,7 @@ mod tests { #[test] fn test_generate_poisson_points_3d() { // Test 3D Poisson disk sampling - let points = try_generate_poisson_points::(30, (-1.0, 1.0), 0.2, 123).unwrap(); + let points = try_generate_poisson_points::<3>(30, (-1.0, 1.0), 0.2, 123).unwrap(); assert!(!points.is_empty()); @@ -2284,7 +2260,7 @@ mod tests { #[test] fn test_generate_poisson_points_4d() { // Test 4D Poisson disk sampling - let points = try_generate_poisson_points::(15, (0.0, 5.0), 0.5, 333).unwrap(); + let points = try_generate_poisson_points::<4>(15, (0.0, 5.0), 0.5, 333).unwrap(); assert!(!points.is_empty()); @@ -2317,7 +2293,7 @@ mod tests { #[test] fn test_generate_poisson_points_5d() { // Test 5D Poisson disk sampling - let points = try_generate_poisson_points::(10, (-2.0, 2.0), 0.4, 777).unwrap(); + let points = try_generate_poisson_points::<5>(10, (-2.0, 2.0), 0.4, 777).unwrap(); assert!(!points.is_empty()); @@ -2351,8 +2327,8 @@ mod tests { #[test] fn test_generate_poisson_points_reproducible() { // Test that same seed produces same results - let points1 = try_generate_poisson_points::(25, (0.0, 5.0), 0.3, 456).unwrap(); - let points2 = try_generate_poisson_points::(25, (0.0, 5.0), 0.3, 456).unwrap(); + let points1 = try_generate_poisson_points::<2>(25, (0.0, 5.0), 0.3, 456).unwrap(); + let points2 = try_generate_poisson_points::<2>(25, (0.0, 5.0), 0.3, 456).unwrap(); assert_eq!(points1.len(), points2.len()); @@ -2366,26 +2342,25 @@ mod tests { } // Different seeds should produce different results - let points3 = try_generate_poisson_points::(25, (0.0, 5.0), 0.3, 789).unwrap(); + let points3 = try_generate_poisson_points::<2>(25, (0.0, 5.0), 0.3, 789).unwrap(); assert_ne!(points1, points3); } #[test] fn test_generate_poisson_points_error_handling() { // Test invalid range - let result = try_generate_poisson_points::(50, (10.0, 5.0), 0.1, 42); + let result = try_generate_poisson_points::<2>(50, (10.0, 5.0), 0.1, 42); assert_invalid_coordinate_range(&result, CoordinateRangeOrdering::Decreasing, 10.0, 5.0); // Test non-finite minimum distance rejects at the public boundary. - let nan_distance = try_generate_poisson_points::(0, (0.0, 1.0), f64::NAN, 42); + let nan_distance = try_generate_poisson_points::<2>(0, (0.0, 1.0), f64::NAN, 42); assert_matches!( nan_distance, Err(RandomPointGenerationError::InvalidMinimumDistance { distance }) if distance == InvalidCoordinateValue::Nan ); - let infinite_distance = - try_generate_poisson_points::(50, (0.0, 1.0), f64::INFINITY, 42); + let infinite_distance = try_generate_poisson_points::<2>(50, (0.0, 1.0), f64::INFINITY, 42); assert_matches!( infinite_distance, Err(RandomPointGenerationError::InvalidMinimumDistance { distance }) @@ -2393,7 +2368,7 @@ mod tests { ); // Test minimum distance too large for bounds (should produce few/no points) - let result = try_generate_poisson_points::(100, (0.0, 1.0), 10.0, 42); + let result = try_generate_poisson_points::<2>(100, (0.0, 1.0), 10.0, 42); match result { Ok(points) => { // Should produce very few points or fail @@ -2406,11 +2381,11 @@ mod tests { } // Test zero distance optimization (should return exact count without spacing checks) - let points = try_generate_poisson_points::(100, (0.0, 10.0), 0.0, 42).unwrap(); + let points = try_generate_poisson_points::<2>(100, (0.0, 10.0), 0.0, 42).unwrap(); assert_eq!(points.len(), 100); // Should get exactly the requested number // Test negative distance optimization (should return exact count without spacing checks) - let points = try_generate_poisson_points::(50, (0.0, 10.0), -1.0, 42).unwrap(); + let points = try_generate_poisson_points::<2>(50, (0.0, 10.0), -1.0, 42).unwrap(); assert_eq!(points.len(), 50); // Should get exactly the requested number } @@ -2421,40 +2396,40 @@ mod tests { #[test] fn test_generate_random_points_invalid_range() { // Test invalid range (non-increasing bounds) - let result = try_generate_random_points::(100, (10.0, 5.0)); + let result = try_generate_random_points::<2>(100, (10.0, 5.0)); assert_matches!( result, Err(RandomPointGenerationError::InvalidCoordinateRange { .. }) ); // Test equal min and max - let result = try_generate_random_points::(100, (5.0, 5.0)); + let result = try_generate_random_points::<2>(100, (5.0, 5.0)); assert_matches!( result, Err(RandomPointGenerationError::InvalidCoordinateRange { .. }) ); // Test valid range - let points = try_generate_random_points::(10, (0.0, 1.0)).unwrap(); + let points = try_generate_random_points::<2>(10, (0.0, 1.0)).unwrap(); assert_eq!(points.len(), 10); } #[test] fn test_generate_random_points_seeded_invalid_range() { // Test invalid range with seed - let result = try_generate_random_points_seeded::(50, (100.0, 10.0), 42); + let result = try_generate_random_points_seeded::<3>(50, (100.0, 10.0), 42); assert_matches!( result, Err(RandomPointGenerationError::InvalidCoordinateRange { .. }) ); // Test valid range produces consistent results - let points1 = try_generate_random_points_seeded::(5, (0.0, 1.0), 42).unwrap(); - let points2 = try_generate_random_points_seeded::(5, (0.0, 1.0), 42).unwrap(); + let points1 = try_generate_random_points_seeded::<3>(5, (0.0, 1.0), 42).unwrap(); + let points2 = try_generate_random_points_seeded::<3>(5, (0.0, 1.0), 42).unwrap(); assert_eq!(points1, points2); // Different seeds produce different results - let points3 = try_generate_random_points_seeded::(5, (0.0, 1.0), 123).unwrap(); + let points3 = try_generate_random_points_seeded::<3>(5, (0.0, 1.0), 123).unwrap(); assert_ne!(points1, points3); } @@ -2462,11 +2437,11 @@ mod tests { fn test_generate_grid_points_overflow_detection_edge_cases() { // Test cases that would cause potential memory issues in grid point calculation // Generate small grid to test the function works - let points = generate_grid_points::(nonzero(10), 0.1, [0.0, 0.0]).unwrap(); + let points = generate_grid_points::<2>(nonzero(10), 0.1, [0.0, 0.0]).unwrap(); assert!(!points.is_empty()); // Test very fine spacing which would generate lots of points - let result = generate_grid_points::(nonzero(1000), 0.0001, [0.0, 0.0]); + let result = generate_grid_points::<2>(nonzero(1000), 0.0001, [0.0, 0.0]); // Should either succeed or fail gracefully if let Ok(points) = result { assert!(!points.is_empty()); @@ -2477,21 +2452,21 @@ mod tests { #[test] fn test_generate_poisson_points_edge_cases() { // Test very small spacing with valid number of points - let result = try_generate_poisson_points::(100, (0.0, 1.0), 0.001, 42); + let result = try_generate_poisson_points::<2>(100, (0.0, 1.0), 0.001, 42); if let Ok(points) = result { assert!(!points.is_empty()); } // May fail due to too many points // Test with zero points (should succeed with empty result) - let result = try_generate_poisson_points::(0, (0.0, 1.0), 0.1, 42); + let result = try_generate_poisson_points::<2>(0, (0.0, 1.0), 0.1, 42); if let Ok(points) = result { assert!(points.is_empty()); } // Also acceptable if Err // Test very large spacing (should work but produce fewer points) - let result = try_generate_poisson_points::(10, (0.0, 1.0), 2.0, 42); + let result = try_generate_poisson_points::<2>(10, (0.0, 1.0), 2.0, 42); if let Ok(points) = result { assert!(points.len() <= 10); } diff --git a/src/geometry/util/triangulation_generation.rs b/src/geometry/util/triangulation_generation.rs index b2f29c21..61029ebf 100644 --- a/src/geometry/util/triangulation_generation.rs +++ b/src/geometry/util/triangulation_generation.rs @@ -21,13 +21,11 @@ use crate::core::vertex::Vertex; use crate::geometry::coordinate_range::{CoordinateRange, CoordinateRangeError}; use crate::geometry::kernel::{AdaptiveKernel, Kernel}; use crate::geometry::point::Point; -use crate::geometry::traits::coordinate::{CoordinateScalar, FiniteCheck}; use crate::triangulation::DelaunayTriangulation; use rand::SeedableRng; -use rand::distr::uniform::SampleUniform; use rand::rngs::StdRng; use rand::seq::SliceRandom; -use std::{fmt::Debug, num::NonZeroUsize}; +use std::num::NonZeroUsize; const RANDOM_TRIANGULATION_MAX_SHUFFLE_ATTEMPTS: usize = 6; const RANDOM_TRIANGULATION_MAX_POINTSET_ATTEMPTS: usize = 6; @@ -52,14 +50,11 @@ const fn random_point_generation_error( /// error boundary: generation failures become /// [`DelaunayConstructionFailure::RandomPointGeneration`] before construction /// or validation can reinterpret them as geometric failures. -fn random_points_with_seed( +fn random_points_with_seed( n_points: usize, - bounds: CoordinateRange, + bounds: CoordinateRange, seed: Option, -) -> Vec> -where - T: CoordinateScalar + SampleUniform, -{ +) -> Vec> { #[expect( clippy::option_if_let_else, reason = "explicit match keeps seeded and unseeded generator paths readable" @@ -79,7 +74,7 @@ fn validate_random_triangulation( dt: DelaunayTriangulation, ) -> Result, DelaunayTriangulationConstructionError> where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -102,7 +97,7 @@ fn random_triangulation_is_acceptable( min_vertices: usize, ) -> bool where - K: Kernel, + K: Kernel, U: DataType, V: DataType, { @@ -114,15 +109,14 @@ where /// This isolates the single-attempt contract used by the shuffled fallback /// loop: build with input order, validate topology, then enforce the minimum /// retained-vertex threshold. -fn random_triangulation_try_build( +fn random_triangulation_try_build( kernel: &K, - vertices: &[Vertex], + vertices: &[Vertex], min_vertices: usize, topology_guarantee: TopologyGuarantee, ) -> Result>, DelaunayTriangulationConstructionError> where - K: Kernel, - T: CoordinateScalar, + K: Kernel, U: DataType, V: DataType, { @@ -148,19 +142,22 @@ where /// Vertex construction from generated, already-validated points is infallible, /// so random-triangulation errors remain attributable to point generation, /// construction, or validation rather than wrapper allocation. -fn random_triangulation_build_vertices( - points: Vec>, +fn random_triangulation_build_vertices( + points: Vec>, vertex_data: Option, -) -> Vec> +) -> Vec> where U: Copy, { match vertex_data { Some(data) => points .into_iter() - .map(|point| Vertex::from_point_with_data(point, data)) + .map(|point| Vertex::from_validated_point_with_data(point, data)) + .collect(), + None => points + .into_iter() + .map(Vertex::from_validated_point) .collect(), - None => points.into_iter().map(Vertex::from_point).collect(), } } @@ -168,7 +165,7 @@ where /// /// All code paths that build a non-empty triangulation should use this factory /// so they share the same kernel type. -const fn make_adaptive_kernel() -> AdaptiveKernel { +const fn make_adaptive_kernel() -> AdaptiveKernel { AdaptiveKernel::new() } @@ -176,21 +173,20 @@ const fn make_adaptive_kernel() -> AdaptiveKernel { /// /// The public random-triangulation APIs call this after point generation so /// all retry attempts share the same [`AdaptiveKernel`] and topology guarantee. -fn random_triangulation_try_with_vertices( - vertices: &[Vertex], +fn random_triangulation_try_with_vertices( + vertices: &[Vertex], min_vertices: usize, shuffle_seed: Option, topology_guarantee: TopologyGuarantee, ) -> Result< - Option, U, V, D>>, + Option, U, V, D>>, DelaunayTriangulationConstructionError, > where - T: CoordinateScalar, U: DataType, V: DataType, { - let adaptive_kernel = make_adaptive_kernel::(); + let adaptive_kernel = make_adaptive_kernel(); let mut last_error = None; match random_triangulation_try_build( @@ -512,7 +508,7 @@ where /// DelaunayConstructionFailure::RandomPointGeneration { source: source.into() }, /// ) /// })?; -/// let dt = generate_random_triangulation_in_range::( +/// let dt = generate_random_triangulation_in_range::<(), (), 3>( /// twelve, /// range, /// None, @@ -522,14 +518,16 @@ where /// # Ok(()) /// # } /// ``` -pub fn generate_random_triangulation_in_range( +pub fn generate_random_triangulation_in_range( n_points: NonZeroUsize, - bounds: CoordinateRange, + bounds: CoordinateRange, vertex_data: Option, seed: Option, -) -> Result, U, V, D>, DelaunayTriangulationConstructionError> +) -> Result< + DelaunayTriangulation, U, V, D>, + DelaunayTriangulationConstructionError, +> where - T: CoordinateScalar + SampleUniform, U: DataType, V: DataType, { @@ -591,7 +589,7 @@ where /// DelaunayConstructionFailure::RandomPointGeneration { source: source.into() }, /// ) /// })?; -/// let dt = generate_random_triangulation_in_range_with_topology_guarantee::( +/// let dt = generate_random_triangulation_in_range_with_topology_guarantee::<(), (), 3>( /// twelve, /// range, /// None, @@ -602,15 +600,17 @@ where /// # Ok(()) /// # } /// ``` -pub fn generate_random_triangulation_in_range_with_topology_guarantee( +pub fn generate_random_triangulation_in_range_with_topology_guarantee( n_points: NonZeroUsize, - bounds: CoordinateRange, + bounds: CoordinateRange, vertex_data: Option, seed: Option, topology_guarantee: TopologyGuarantee, -) -> Result, U, V, D>, DelaunayTriangulationConstructionError> +) -> Result< + DelaunayTriangulation, U, V, D>, + DelaunayTriangulationConstructionError, +> where - T: CoordinateScalar + SampleUniform, U: DataType, V: DataType, { @@ -628,7 +628,7 @@ where .into()); } - let points: Vec> = random_points_with_seed(n_points, bounds, seed); + let points: Vec> = random_points_with_seed(n_points, bounds, seed); let min_vertices = (n_points / 6).max(D + 1); @@ -740,15 +740,15 @@ where /// # } /// ``` #[must_use] -pub struct RandomTriangulationBuilder { +pub struct RandomTriangulationBuilder { n_points: NonZeroUsize, - bounds: CoordinateRange, + bounds: CoordinateRange, seed: Option, topology_guarantee: TopologyGuarantee, construction_options: ConstructionOptions, } -impl RandomTriangulationBuilder { +impl RandomTriangulationBuilder { /// Creates a new builder with the specified number of points and raw coordinate bounds. /// /// # Arguments @@ -789,10 +789,10 @@ impl RandomTriangulationBuilder { /// # Ok(()) /// # } /// ``` - pub fn try_new(n_points: NonZeroUsize, bounds: (T, T)) -> Result> - where - T: Debug + FiniteCheck + PartialOrd, - { + pub fn try_new( + n_points: NonZeroUsize, + bounds: (f64, f64), + ) -> Result> { Ok(Self { n_points, bounds: CoordinateRange::try_from(bounds)?, @@ -831,7 +831,7 @@ impl RandomTriangulationBuilder { /// # Ok(()) /// # } /// ``` - pub fn new_in_range(n_points: NonZeroUsize, bounds: CoordinateRange) -> Self { + pub fn new_in_range(n_points: NonZeroUsize, bounds: CoordinateRange) -> Self { Self { n_points, bounds, @@ -961,12 +961,6 @@ impl RandomTriangulationBuilder { self.construction_options = options; self } -} - -impl RandomTriangulationBuilder -where - T: CoordinateScalar + SampleUniform, -{ /// Builds the random triangulation with the configured options. /// /// The builder stores a [`CoordinateRange`] after [`Self::try_new`] or @@ -1022,7 +1016,7 @@ where pub fn build( self, ) -> Result< - DelaunayTriangulation, U, V, D>, + DelaunayTriangulation, U, V, D>, DelaunayTriangulationConstructionError, > where @@ -1080,7 +1074,7 @@ where self, vertex_data: Option, ) -> Result< - DelaunayTriangulation, U, V, D>, + DelaunayTriangulation, U, V, D>, DelaunayTriangulationConstructionError, > where @@ -1101,7 +1095,7 @@ where .into()); } - let points: Vec> = random_points_with_seed(n_points, self.bounds, self.seed); + let points: Vec> = random_points_with_seed(n_points, self.bounds, self.seed); // Convert to vertices let vertices = random_triangulation_build_vertices(points, vertex_data); @@ -1119,7 +1113,7 @@ where ); } let dt = DelaunayTriangulation::with_topology_guarantee_and_options( - &make_adaptive_kernel::(), + &make_adaptive_kernel(), &vertices, self.topology_guarantee, self.construction_options, @@ -1134,7 +1128,6 @@ mod tests { use crate::geometry::coordinate_range::{ CoordinateRangeBound, CoordinateRangeOrdering, InvalidCoordinateValue, }; - use crate::vertex; use approx::assert_relative_eq; use std::assert_matches; @@ -1264,7 +1257,7 @@ mod tests { ); let Err(CoordinateRangeError::NonFiniteBound { bound, value }) = - RandomTriangulationBuilder::::try_new(nonzero(10), (0.0, f64::INFINITY)) + RandomTriangulationBuilder::try_new(nonzero(10), (0.0, f64::INFINITY)) else { panic!("expected invalid infinite bounds to fail"); }; @@ -1276,13 +1269,9 @@ mod tests { fn test_random_triangulation_range_apis_accept_validated_bounds() { let range = CoordinateRange::try_new(-1.0_f64, 1.0).unwrap(); - let triangulation = generate_random_triangulation_in_range::( - nonzero(10), - range, - None, - Some(42), - ) - .unwrap(); + let triangulation = + generate_random_triangulation_in_range::<(), (), 2>(nonzero(10), range, None, Some(42)) + .unwrap(); assert_eq!(triangulation.dim(), 2); triangulation.is_valid().unwrap(); @@ -1294,7 +1283,7 @@ mod tests { builder_triangulation.is_valid().unwrap(); let guaranteed_triangulation = - generate_random_triangulation_in_range_with_topology_guarantee::( + generate_random_triangulation_in_range_with_topology_guarantee::<(), (), 2>( nonzero(10), range, None, @@ -1311,7 +1300,7 @@ mod tests { #[test] fn test_random_triangulation_builder_success_and_error_paths() { - let triangulation = RandomTriangulationBuilder::::try_new(nonzero(10), (-5.0, 5.0)) + let triangulation = RandomTriangulationBuilder::try_new(nonzero(10), (-5.0, 5.0)) .unwrap() .seed(42) .build::<(), (), 2>() @@ -1320,12 +1309,11 @@ mod tests { assert!(triangulation.number_of_vertices() >= 3); triangulation.is_valid().unwrap(); - let triangulation_with_data = - RandomTriangulationBuilder::::try_new(nonzero(10), (-5.0, 5.0)) - .unwrap() - .seed(43) - .build_with_vertex_data::(Some(7)) - .unwrap(); + let triangulation_with_data = RandomTriangulationBuilder::try_new(nonzero(10), (-5.0, 5.0)) + .unwrap() + .seed(43) + .build_with_vertex_data::(Some(7)) + .unwrap(); let vertex_data: Vec<_> = triangulation_with_data .tds() .vertices() @@ -1338,7 +1326,7 @@ mod tests { assert!(vertex_data.iter().all(|&data| data == 7)); triangulation_with_data.is_valid().unwrap(); - let too_few_vertices = RandomTriangulationBuilder::::try_new(nonzero(2), (-1.0, 1.0)) + let too_few_vertices = RandomTriangulationBuilder::try_new(nonzero(2), (-1.0, 1.0)) .unwrap() .build::<(), (), 2>(); let Err(DelaunayTriangulationConstructionError::Triangulation( @@ -1357,7 +1345,7 @@ mod tests { } ); - let invalid_bounds = RandomTriangulationBuilder::::try_new(nonzero(10), (5.0, 1.0)); + let invalid_bounds = RandomTriangulationBuilder::try_new(nonzero(10), (5.0, 1.0)); let Err(CoordinateRangeError::NonIncreasing { ordering, min, max }) = invalid_bounds else { panic!("expected invalid bounds to fail"); }; @@ -1365,7 +1353,7 @@ mod tests { assert_relative_eq!(min, 5.0, epsilon = f64::EPSILON); assert_relative_eq!(max, 1.0, epsilon = f64::EPSILON); - let equal_bounds = RandomTriangulationBuilder::::try_new(nonzero(10), (2.0, 2.0)); + let equal_bounds = RandomTriangulationBuilder::try_new(nonzero(10), (2.0, 2.0)); let Err(CoordinateRangeError::NonIncreasing { ordering, min, max }) = equal_bounds else { panic!("expected equal bounds to fail"); }; @@ -1408,13 +1396,13 @@ mod tests { #[test] fn test_random_triangulation_try_with_vertices_exercises_fallbacks() { // Use a valid 2D simplex, but require more vertices than provided to force retries. - let vertices: Vec> = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + let vertices: Vec> = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; - let result = random_triangulation_try_with_vertices::( + let result = random_triangulation_try_with_vertices::<(), (), 2>( &vertices, vertices.len() + 1, Some(7), diff --git a/src/lib.rs b/src/lib.rs index b35e36e4..52e84c1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,16 +100,16 @@ //! //! ```rust //! use delaunay::prelude::construction::{ -//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, +//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, //! }; //! use delaunay::prelude::insertion::InsertionError; //! //! # fn main() -> Result<(), DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -133,16 +133,15 @@ //! ```rust //! use delaunay::prelude::construction::{ //! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee, -//! vertex, //! }; //! use delaunay::prelude::validation::ValidationPolicy; //! //! # fn main() -> Result<(), DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -162,15 +161,15 @@ //! //! ```rust //! use delaunay::prelude::construction::{ -//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, +//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, //! }; //! use delaunay::prelude::insertion::InsertionError; //! //! # fn main() -> Result<(), DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0]), -//! vertex!([1.0, 0.0]), -//! vertex!([0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -178,7 +177,7 @@ //! let before_simplices = dt.number_of_simplices(); //! //! // Duplicate coordinates are rejected. -//! let result = dt.insert(vertex!([0.0, 0.0])); +//! let result = dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates")); //! std::assert_matches!(result, Err(InsertionError::DuplicateCoordinates { .. })); //! //! // On error, the triangulation is unchanged. @@ -272,7 +271,7 @@ //! //! ```rust //! use delaunay::prelude::construction::{ -//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, +//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, //! }; //! use delaunay::prelude::insertion::InsertionError; //! use delaunay::prelude::validation::{ValidationConfigurationError, ValidationPolicy}; @@ -288,10 +287,10 @@ //! # } //! # fn main() -> Result<(), ExampleError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -300,7 +299,7 @@ //! dt.try_set_validation_policy(ValidationPolicy::ExplicitOnly)?; //! //! // Do incremental work... -//! dt.insert(vertex!([0.2, 0.2, 0.2]))?; +//! dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).expect("finite vertex coordinates")).expect("finite vertex coordinates"); //! //! // ...then explicitly validate the topology layer when you need a certificate. //! assert!(dt.as_triangulation().validate().is_ok()); @@ -333,15 +332,15 @@ //! //! ```rust //! use delaunay::prelude::construction::{ -//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, +//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, //! }; //! //! # fn main() -> Result<(), DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -354,15 +353,15 @@ //! //! ```rust //! use delaunay::prelude::construction::{ -//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, +//! DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, //! }; //! //! # fn main() -> Result<(), DelaunayTriangulationConstructionError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -734,7 +733,7 @@ pub mod validation; pub use crate::builder::DelaunayTriangulationBuilder; pub use crate::construction::{ ConstructionOptions, ConstructionSkipSample, ConstructionSlowInsertionSample, - ConstructionStatistics, DedupPolicy, DelaunayConstructionFailure, + ConstructionStatistics, DedupPolicy, DedupTolerance, DelaunayConstructionFailure, DelaunayConstructionRepairPhase, DelaunayTriangulationConstructionError, DelaunayTriangulationConstructionErrorWithStatistics, InitialSimplexStrategy, InsertionOrderStrategy, RetryPolicy, @@ -796,7 +795,7 @@ pub use crate::validation::DelaunayTriangulationValidationError; /// /// ```rust /// use delaunay::prelude::construction::{ -/// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, +/// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::topology::validation; /// @@ -809,10 +808,10 @@ pub use crate::validation::DelaunayTriangulationValidationError; /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -827,6 +826,7 @@ pub mod topology { pub mod traits { pub(crate) mod global_topology_model; pub mod topological_space; + pub use global_topology_model::GlobalTopologyModelError; pub use topological_space::*; } /// Topological invariants and their computation @@ -928,7 +928,7 @@ pub mod collections { /// ```rust /// use delaunay::tds::Tds; /// -/// let tds: Tds = Tds::empty(); +/// let tds: Tds<(), (), 2> = Tds::empty(); /// /// assert_eq!(tds.number_of_vertices(), 0); /// assert_eq!(tds.number_of_simplices(), 0); @@ -966,14 +966,17 @@ pub mod tds { /// use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate, Point}; /// use delaunay::tds::Tds; /// -/// let tds: Tds = Tds::empty(); +/// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { +/// let tds: Tds<(), (), 2> = Tds::empty(); /// let kernel = AdaptiveKernel::new(); -/// let point = Point::new([0.0, 0.0]); +/// let point = Point::try_from([0.0, 0.0])?; /// /// std::assert_matches!( /// locate(&tds, &kernel, &point, None), /// Err(LocateError::EmptyTriangulation) /// ); +/// # Ok(()) +/// # } /// ``` pub mod algorithms { #[cfg(any(feature = "diagnostics", all(test, debug_assertions)))] @@ -1028,8 +1031,8 @@ pub mod query { pub use crate::geometry::traits::coordinate::Coordinate; pub use crate::geometry::{insphere, insphere_distance, insphere_lifted}; pub use crate::tds::{ - AdjacencyIndex, AdjacencyIndexBuildError, EdgeKey, FacetView, Simplex, SimplexKey, Vertex, - VertexKey, + AdjacencyIndex, AdjacencyIndexBuildError, EdgeKey, EdgeKeyError, FacetView, Simplex, + SimplexKey, Vertex, VertexKey, }; pub use crate::{DelaunayTriangulation, Triangulation}; } @@ -1045,11 +1048,11 @@ pub mod prelude { pub use crate::tds::*; pub use crate::{ ConstructionOptions, ConstructionSkipSample, ConstructionSlowInsertionSample, - ConstructionStatistics, DedupPolicy, DelaunayCheckPolicy, DelaunayConstructionFailure, - DelaunayConstructionRepairPhase, DelaunayRepairHeuristicConfig, - DelaunayRepairHeuristicSeeds, DelaunayRepairOperation, DelaunayRepairOutcome, - DelaunayRepairPolicy, DelaunayTriangulation, DelaunayTriangulationBuilder, - DelaunayTriangulationConstructionError, + ConstructionStatistics, DedupPolicy, DedupTolerance, DelaunayCheckPolicy, + DelaunayConstructionFailure, DelaunayConstructionRepairPhase, + DelaunayRepairHeuristicConfig, DelaunayRepairHeuristicSeeds, DelaunayRepairOperation, + DelaunayRepairOutcome, DelaunayRepairPolicy, DelaunayTriangulation, + DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, DelaunayTriangulationConstructionErrorWithStatistics, DelaunayTriangulationValidationError, DuplicateDetectionMetrics, InitialSimplexStrategy, InsertionOrderStrategy, InsertionResult, PlManifoldRepairError, PlManifoldRepairStats, RepairDecision, RepairSkipReason, @@ -1107,6 +1110,7 @@ pub mod prelude { DelaunayRepairVerificationContext, FlipContextError, FlipEdgeAdjacencyError, FlipError, FlipMutationError, FlipNeighborWiringError, FlipPredicateError, FlipPredicateOperation, FlipTriangleAdjacencyError, FlipVertexAdjacencyError, RepairQueueOrder, + TriangleHandleError, }; // Re-export commonly used collection types from the public collections facade. @@ -1133,14 +1137,14 @@ pub mod prelude { /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, /// }; /// /// # fn main() -> Result<(), DelaunayTriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let triangulation = DelaunayTriangulationBuilder::new(&vertices) /// .build::<()>()?; @@ -1159,24 +1163,23 @@ pub mod prelude { }; pub use crate::construction::{ ConstructionOptions, ConstructionSkipSample, ConstructionSlowInsertionSample, - ConstructionStatistics, DedupPolicy, DelaunayConstructionFailure, + ConstructionStatistics, DedupPolicy, DedupTolerance, DelaunayConstructionFailure, DelaunayConstructionRepairPhase, DelaunayTriangulationConstructionError, DelaunayTriangulationConstructionErrorWithStatistics, InitialSimplexStrategy, InsertionOrderStrategy, RetryPolicy, }; + pub use crate::core::util::DeduplicationError; pub use crate::geometry::coordinate_range::{ CoordinateRangeBound, CoordinateRangeError, CoordinateRangeOrdering, InvalidCoordinateValue, }; + pub use crate::geometry::traits::coordinate::CoordinateValidationError; pub use crate::geometry::util::{InvalidPositiveScalar, RandomPointGenerationError}; pub use crate::repair::DelaunayRepairPolicy; - pub use crate::tds::{ - SimplexValidationError, Vertex, VertexBuilder, VertexBuilderError, - VertexValidationError, - }; + pub use crate::tds::{SimplexValidationError, Vertex, VertexValidationError}; pub use crate::topology::traits::{ - GlobalTopology, TopologyKind, ToroidalConstructionMode, ToroidalDomain, - ToroidalDomainError, + GlobalTopology, GlobalTopologyModelError, TopologyKind, ToroidalConstructionMode, + ToroidalDomain, ToroidalDomainError, }; pub use crate::validation::DelaunayTriangulationValidationError; pub use crate::{ @@ -1184,8 +1187,6 @@ pub mod prelude { SpatialIndexConstructionFailure, TopologyGuarantee, Triangulation, TriangulationConstructionError, }; - // Convenience macro (commonly used in docs/examples). - pub use crate::vertex; } /// Generic triangulation construction, validation, query, and local repair. @@ -1200,14 +1201,14 @@ pub mod prelude { /// /// ```rust /// use delaunay::prelude::triangulation::{ - /// FastKernel, Triangulation, TriangulationConstructionError, vertex, + /// FastKernel, Triangulation, TriangulationConstructionError, Vertex, /// }; /// /// # fn main() -> Result<(), TriangulationConstructionError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0]), - /// vertex!([1.0, 0.0]), - /// vertex!([0.0, 1.0]), + /// Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), + /// Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let tds = Triangulation::, (), (), 2>::build_initial_simplex(&vertices)?; /// @@ -1224,23 +1225,20 @@ pub mod prelude { pub use crate::geometry::point::Point; pub use crate::query::{ AdjacencyIndex, AdjacencyIndexBuildError, BoundaryAnalysis, DataCopy, DataDebug, - DataDeserialize, DataIdentity, DataSerde, DataSerialize, DataType, EdgeKey, FacetView, - QueryError, + DataDeserialize, DataIdentity, DataSerde, DataSerialize, DataType, EdgeKey, + EdgeKeyError, FacetView, QueryError, }; pub use crate::tds::{ FacetHandle, InvariantError, InvariantErrorSummary, InvariantErrorSummaryDetail, InvariantErrorSummaryKind, NeighborSlot, Simplex, SimplexKey, Tds, TdsConstructionError, TdsError, TdsErrorKind, TdsMutationError, - TriangulationValidationErrorKind, Vertex, VertexBuilder, VertexBuilderError, VertexKey, + TriangulationValidationErrorKind, Vertex, VertexKey, }; pub use crate::{ InsertionError, SpatialIndexConstructionFailure, TopologyGuarantee, Triangulation, TriangulationConstructionError, TriangulationValidationError, ValidationConfigurationError, ValidationPolicy, }; - - // Convenience macro for generic triangulation examples and tests. - pub use crate::vertex; } /// Bistellar (Pachner) flips for explicit triangulation editing. @@ -1260,12 +1258,10 @@ pub mod prelude { FlipEdgeAdjacencyError, FlipError, FlipInfo, FlipMutationError, FlipNeighborWiringError, FlipPredicateError, FlipPredicateOperation, FlipTriangleAdjacencyError, FlipVertexAdjacencyError, RidgeHandle, TriangleHandle, + TriangleHandleError, }; pub use crate::flips::{BistellarMove, ConstK}; - pub use crate::tds::{EdgeKey, FacetHandle, SimplexKey, VertexKey}; - - // Convenience macro (commonly used in docs/examples). - pub use crate::vertex; + pub use crate::tds::{EdgeKey, EdgeKeyError, FacetHandle, SimplexKey, VertexKey}; } /// Incremental insertion building blocks and diagnostics. @@ -1314,7 +1310,8 @@ pub mod prelude { DelaunayRepairVerificationContext, FlipContextError, FlipEdgeAdjacencyError, FlipError, FlipMutationError, FlipNeighborWiringError, FlipPredicateError, FlipPredicateOperation, FlipTriangleAdjacencyError, FlipVertexAdjacencyError, RepairQueueOrder, - verify_delaunay_for_triangulation, verify_delaunay_via_flip_predicates, + TriangleHandleError, verify_delaunay_for_triangulation, + verify_delaunay_via_flip_predicates, }; pub use crate::repair::{ DelaunayCheckPolicy, DelaunayRepairHeuristicConfig, DelaunayRepairHeuristicSeeds, @@ -1334,14 +1331,11 @@ pub mod prelude { /// /// Self-contained: a single `use delaunay::prelude::delaunayize::*` /// import brings in [`DelaunayTriangulationBuilder`], [`DelaunayTriangulation`], - /// [`vertex!`], and all delaunayize-specific types. + /// [`DelaunayTriangulation`], and all delaunayize-specific types. pub mod delaunayize { pub use crate::delaunayize::*; pub use crate::{DelaunayTriangulation, DelaunayTriangulationBuilder}; pub use crate::{PlManifoldRepairError, PlManifoldRepairStats}; - - // Convenience macro (commonly used in docs/examples). - pub use crate::vertex; } /// Validation scheduling helpers for construction diagnostics. @@ -1409,7 +1403,7 @@ pub mod prelude { /// ```rust /// use delaunay::prelude::tds::Tds; /// - /// let tds: Tds = Tds::empty(); + /// let tds: Tds<(), (), 2> = Tds::empty(); /// /// assert_eq!(tds.number_of_vertices(), 0); /// assert_eq!(tds.number_of_simplices(), 0); @@ -1445,9 +1439,9 @@ pub mod prelude { }, traits::coordinate::{ Coordinate, CoordinateConversionError, CoordinateConversionValue, - CoordinateIdentity, CoordinateRepresentation, CoordinateScalar, - CoordinateValidationError, CoordinateValues, DEFAULT_TOLERANCE_F64, - DegenerateSimplexReason, FiniteCheck, FiniteCoordinateValue, HashCoordinate, + CoordinateIdentity, CoordinateRepresentation, CoordinateValidationError, + CoordinateValues, DEFAULT_TOLERANCE_F64, DegenerateSimplexReason, + F64_MANTISSA_DIGITS, FiniteCheck, FiniteCoordinateValue, HashCoordinate, OrderedCmp, OrderedEq, }, util::{ @@ -1470,14 +1464,17 @@ pub mod prelude { /// use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate, Point}; /// use delaunay::prelude::tds::Tds; /// - /// let tds: Tds = Tds::empty(); + /// # fn main() -> Result<(), delaunay::prelude::geometry::CoordinateConversionError> { + /// let tds: Tds<(), (), 2> = Tds::empty(); /// let kernel = AdaptiveKernel::new(); - /// let point = Point::new([0.0, 0.0]); + /// let point = Point::try_from([0.0, 0.0])?; /// /// std::assert_matches!( /// locate(&tds, &kernel, &point, None), /// Err(LocateError::EmptyTriangulation) /// ); + /// # Ok(()) + /// # } /// ``` pub mod algorithms { pub use crate::algorithms::{ @@ -1550,7 +1547,7 @@ pub mod prelude { pub mod query { // Core read-only traversal / adjacency pub use crate::tds::{ - AdjacencyIndex, AdjacencyIndexBuildError, EdgeKey, SimplexKey, VertexKey, + AdjacencyIndex, AdjacencyIndexBuildError, EdgeKey, EdgeKeyError, SimplexKey, VertexKey, }; pub use crate::{DelaunayTriangulation, Triangulation}; @@ -1581,9 +1578,6 @@ pub mod prelude { // Instrumentation helpers (no-op unless features enable extra tracking) pub use crate::query::measure_with_result; - - // Fixture construction macro used by query doctests, examples, and benchmarks. - pub use crate::vertex; } /// Focused exports for generating fixture data in doctests, integration tests, @@ -1602,7 +1596,7 @@ pub mod prelude { /// /// # fn main() -> Result<(), CoordinateRangeError> { /// let range = CoordinateRange::try_new(0.0_f64, 1.0)?; - /// let points: Vec> = + /// let points: Vec> = /// generate_random_points_in_range_seeded(4, range, 42); /// /// assert_eq!(points.len(), 4); @@ -1672,8 +1666,8 @@ pub mod prelude { validate_ridge_links_for_simplices, validate_vertex_links, }; pub use crate::topology::traits::{ - GlobalTopology, TopologicalSpace, TopologyError, TopologyKind, - ToroidalConstructionMode, + GlobalTopology, GlobalTopologyModelError, TopologicalSpace, TopologyError, + TopologyKind, ToroidalConstructionMode, }; } @@ -1681,14 +1675,11 @@ pub mod prelude { pub mod spaces { pub use crate::topology::spaces::*; pub use crate::topology::traits::{ - GlobalTopology, TopologicalSpace, TopologyError, TopologyKind, - ToroidalConstructionMode, ToroidalDomain, ToroidalDomainError, + GlobalTopology, GlobalTopologyModelError, TopologicalSpace, TopologyError, + TopologyKind, ToroidalConstructionMode, ToroidalDomain, ToroidalDomainError, }; } } - - // Convenience macros - pub use crate::vertex; } /// The function `is_normal` checks that structs implement `auto` traits. @@ -1729,7 +1720,6 @@ mod tests { verify_delaunay_for_triangulation, verify_delaunay_via_flip_predicates, }, prelude::*, - vertex, }; use std::assert_matches; @@ -1742,22 +1732,22 @@ mod tests { #[test] fn normal_types() { - assert!(is_normal::>()); - assert!(is_normal::>()); - assert!(is_normal::>()); - assert!(is_normal::>()); + assert!(is_normal::>()); + assert!(is_normal::>()); + assert!(is_normal::>()); + assert!(is_normal::>()); assert!(is_normal::, (), (), 3>>()); assert!(is_normal::, (), (), 3>>()); - assert!(is_normal::, (), (), 3>>()); + assert!(is_normal::>()); assert!(is_normal::()); assert!(is_normal::()); assert!(is_normal::()); - assert!(is_normal::>()); + assert!(is_normal::>()); assert!(is_normal::()); assert!(is_normal::()); assert!(is_normal::()); assert!(is_normal::()); - assert!(is_normal::>()); + assert!(is_normal::>()); assert!(is_normal::()); assert!(is_normal::()); } @@ -1836,9 +1826,9 @@ mod tests { #[test] fn prelude_repair_exports() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: RepairDelaunayTriangulation<_, (), (), 2> = RepairDelaunayTriangulation::new(&vertices).unwrap(); @@ -1879,9 +1869,9 @@ mod tests { fn prelude_quality_exports() { // Test that quality functions are accessible from prelude let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1905,9 +1895,9 @@ mod tests { // Test 2D orientation predicate let triangle = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; let fast_orientation = fast_kernel.orientation(&triangle).unwrap(); @@ -1921,9 +1911,9 @@ mod tests { // Test collinear detection let collinear = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([2.0, 0.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([2.0, 0.0]), ]; assert_eq!( fast_kernel.orientation(&collinear).unwrap(), @@ -1932,11 +1922,11 @@ mod tests { ); // Test in_sphere predicate - let inside_point = Point::new([0.25, 0.25]); + let inside_point = Point::from_validated_coords([0.25, 0.25]); let result = fast_kernel.in_sphere(&triangle, &inside_point).unwrap(); assert_eq!(result, 1, "Point should be inside circumcircle"); - let outside_point = Point::new([2.0, 2.0]); + let outside_point = Point::from_validated_coords([2.0, 2.0]); let result = fast_kernel.in_sphere(&triangle, &outside_point).unwrap(); assert_eq!(result, -1, "Point should be outside circumcircle"); } @@ -1945,21 +1935,21 @@ mod tests { fn test_prelude_core_types() { // Test that core types are accessible and work from prelude // Point construction - let p1 = Point::new([0.0, 0.0, 0.0]); - let p2 = Point::new([1.0, 0.0, 0.0]); + let p1 = Point::from_validated_coords([0.0, 0.0, 0.0]); + let p2 = Point::from_validated_coords([1.0, 0.0, 0.0]); assert_ne!(p1, p2); - // Vertex construction via macro and builder - let v1: Vertex = vertex!([0.0, 0.0, 0.0]); - let v2: Vertex = vertex!([1.0, 0.0, 0.0]); + // Vertex construction via the fallible smart constructor. + let v1: Vertex<(), 3> = Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(); + let v2: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(); assert_ne!(v1.point(), v2.point()); // DelaunayTriangulation construction let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -1983,16 +1973,16 @@ mod tests { fn test_prelude_point_location() { // Test that point location algorithms are accessible let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); // Test locate function with kernel let kernel = FastKernel::::new(); - let query_point = Point::new([0.3, 0.3]); + let query_point = Point::from_validated_coords([0.3, 0.3]); let result = locate(dt.tds(), &kernel, &query_point, None); assert!(result.is_ok()); @@ -2006,7 +1996,7 @@ mod tests { } // Test outside point - let outside_point = Point::new([10.0, 10.0]); + let outside_point = Point::from_validated_coords([10.0, 10.0]); let result = locate(dt.tds(), &kernel, &outside_point, None); assert!(result.is_ok()); } @@ -2014,16 +2004,16 @@ mod tests { #[test] fn test_prelude_geometry_types() { // Test Point with Coordinate trait - let p = Point::new([1.0_f64, 2.0_f64, 3.0_f64]); + let p = Point::from_validated_coords([1.0_f64, 2.0_f64, 3.0_f64]); assert!((p.coords()[0] - 1.0_f64).abs() < f64::EPSILON); assert!((p.coords()[1] - 2.0_f64).abs() < f64::EPSILON); assert!((p.coords()[2] - 3.0_f64).abs() < f64::EPSILON); // Test predicates are accessible let triangle = [ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), + Point::from_validated_coords([0.0, 0.0]), + Point::from_validated_coords([1.0, 0.0]), + Point::from_validated_coords([0.0, 1.0]), ]; // simplex_orientation is exported from predicates @@ -2031,7 +2021,7 @@ mod tests { assert_ne!(orientation, Orientation::DEGENERATE); // Test insphere predicate - let test_point = Point::new([0.25, 0.25]); + let test_point = Point::from_validated_coords([0.25, 0.25]); let result = insphere(&triangle, test_point).unwrap(); assert_eq!(result, InSphere::INSIDE); } @@ -2040,10 +2030,10 @@ mod tests { fn test_prelude_convex_hull() { // Test that convex hull operations are accessible let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -2053,13 +2043,13 @@ mod tests { assert_eq!(hull.number_of_facets(), 4); // Tetrahedron has 4 faces // Test point visibility - let outside_point = Point::new([2.0, 2.0, 2.0]); + let outside_point = Point::from_validated_coords([2.0, 2.0, 2.0]); let is_outside = hull .is_point_outside(&outside_point, dt.as_triangulation()) .unwrap(); assert!(is_outside); - let inside_point = Point::new([0.25, 0.25, 0.25]); + let inside_point = Point::from_validated_coords([0.25, 0.25, 0.25]); let is_outside = hull .is_point_outside(&inside_point, dt.as_triangulation()) .unwrap(); diff --git a/src/topology/characteristics/euler.rs b/src/topology/characteristics/euler.rs index 3b5980f8..49cb6872 100644 --- a/src/topology/characteristics/euler.rs +++ b/src/topology/characteristics/euler.rs @@ -18,10 +18,10 @@ //! # } //! # fn main() -> Result<(), ExampleError> { //! let vertices = vec![ -//! vertex!([0.0, 0.0, 0.0]), -//! vertex!([1.0, 0.0, 0.0]), -//! vertex!([0.0, 1.0, 0.0]), -//! vertex!([0.0, 0.0, 1.0]), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +//! delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), //! ]; //! let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; //! @@ -214,9 +214,9 @@ pub enum TopologyClassification { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.5, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -231,9 +231,7 @@ pub enum TopologyClassification { /// # Errors /// /// Returns [`TopologyError::FacetMapBuild`] if simplex enumeration fails. -pub fn count_simplices( - tds: &Tds, -) -> Result { +pub fn count_simplices(tds: &Tds) -> Result { // Handle empty triangulation without building any facet map. let mut by_dim = vec![0usize; D + 1]; by_dim[0] = tds.number_of_vertices(); @@ -253,8 +251,8 @@ pub fn count_simplices( )) } -pub(crate) fn count_simplices_with_facet_to_simplices_map( - tds: &Tds, +pub(crate) fn count_simplices_with_facet_to_simplices_map( + tds: &Tds, facet_to_simplices: &FacetToSimplicesMap, ) -> FVector { let mut by_dim = vec![0usize; D + 1]; @@ -468,10 +466,10 @@ fn insert_simplices_of_size( /// # fn main() -> Result<(), ExampleError> { /// // 3D tetrahedron - boundary is S² (sphere) /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -486,8 +484,8 @@ fn insert_simplices_of_size( /// /// Returns [`TopologyError::BoundaryFacetEnumeration`] or /// [`TopologyError::BoundaryFacetSimplexAccess`] if boundary enumeration fails. -pub fn count_boundary_simplices( - tds: &Tds, +pub fn count_boundary_simplices( + tds: &Tds, ) -> Result { // Get boundary facets let boundary_facets: Vec<_> = tds @@ -643,9 +641,9 @@ pub(crate) fn triangulated_surface_euler_characteristic( vertices.insert(b); vertices.insert(c); - edges.insert(EdgeKey::new(a, b)); - edges.insert(EdgeKey::new(b, c)); - edges.insert(EdgeKey::new(c, a)); + edges.insert(EdgeKey::from_validated_endpoints(a, b)); + edges.insert(EdgeKey::from_validated_endpoints(b, c)); + edges.insert(EdgeKey::from_validated_endpoints(c, a)); } let counts = FVector { @@ -672,7 +670,11 @@ pub(crate) fn triangulated_surface_boundary_component_count( let b = tri[1]; let c = tri[2]; - for e in [EdgeKey::new(a, b), EdgeKey::new(b, c), EdgeKey::new(c, a)] { + for e in [ + EdgeKey::from_validated_endpoints(a, b), + EdgeKey::from_validated_endpoints(b, c), + EdgeKey::from_validated_endpoints(c, a), + ] { *edge_counts.entry(e).or_insert(0) += 1; } } @@ -755,10 +757,10 @@ pub(crate) fn triangulated_surface_boundary_component_count( /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -771,8 +773,8 @@ pub(crate) fn triangulated_surface_boundary_component_count( /// # Errors /// /// Returns [`TopologyError::BoundaryFacetCount`] if boundary detection fails. -pub fn classify_triangulation( - tds: &Tds, +pub fn classify_triangulation( + tds: &Tds, ) -> Result { let num_simplices = tds.number_of_simplices(); @@ -844,7 +846,6 @@ mod tests { use super::*; use crate::core::simplex::Simplex; - use crate::vertex; use slotmap::{KeyData, SlotMap}; #[test] @@ -1082,27 +1083,51 @@ mod tests { ); } - fn build_closed_2d_surface_tds() -> Tds { + fn build_closed_2d_surface_tds() -> Tds<(), (), 2> { // Build the boundary of a tetrahedron as a 2D simplicial complex (a closed S^2): // 4 triangles on 4 vertices, with every edge shared by exactly 2 triangles. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(), + ) .unwrap(); tds diff --git a/src/topology/characteristics/validation.rs b/src/topology/characteristics/validation.rs index 283e2785..601b37af 100644 --- a/src/topology/characteristics/validation.rs +++ b/src/topology/characteristics/validation.rs @@ -34,10 +34,10 @@ use crate::topology::{ /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -129,9 +129,9 @@ impl TopologyCheckResult { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.5, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0]).expect("finite vertex coordinates"), /// ]; /// let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?; /// @@ -148,8 +148,8 @@ impl TopologyCheckResult { /// # Errors /// /// Returns [`TopologyError`] if topology validation support data cannot be built. -pub fn validate_triangulation_euler( - tds: &Tds, +pub fn validate_triangulation_euler( + tds: &Tds, ) -> Result { // Precompute the facet map once and reuse it for both counting and classification. // @@ -167,8 +167,8 @@ pub fn validate_triangulation_euler( )) } -pub(crate) fn validate_triangulation_euler_with_facet_to_simplices_map( - tds: &Tds, +pub(crate) fn validate_triangulation_euler_with_facet_to_simplices_map( + tds: &Tds, facet_to_simplices: &FacetToSimplicesMap, ) -> TopologyCheckResult { let counts = count_simplices_with_facet_to_simplices_map(tds, facet_to_simplices); diff --git a/src/topology/manifold.rs b/src/topology/manifold.rs index af0aa905..440315ec 100644 --- a/src/topology/manifold.rs +++ b/src/topology/manifold.rs @@ -34,8 +34,7 @@ //! To certify that `K` is a **PL-manifold with boundary**, the canonical condition is: //! //! 4. **Vertex-link condition (canonical PL-manifold test)** -//! For every vertex `v`, the link `Lk(v)` is a (D−1)-sphere if `v` is an interior vertex, -//! or a (D−1)-ball if `v` lies on the boundary. +//! For every vertex `v`, the link `Lk(v)` is a (D−1)-sphere if `v` is an interior //! or a (D−1)-ball if `v` lies on the boundary. //! //! This condition is enforced by [`validate_vertex_links`]. //! @@ -190,7 +189,7 @@ fn periodic_simplex_key(lifted_vertices: &[LiftedVertexId]) -> u64 { /// Computes an exact lifted simplex key without quotient translation normalization. /// /// Vertex links already express every lifted vertex relative to the linked -/// anchor vertex, so applying an additional global translation quotient can +/// anchor so applying an additional global translation quotient can /// collapse distinct link simplices. fn anchored_lifted_simplex_key(lifted_vertices: &[LiftedVertexId]) -> u64 { if lifted_vertices.iter().all(LiftedVertexId::is_base) { @@ -209,7 +208,7 @@ fn anchored_lifted_simplex_key(lifted_vertices: &[LiftedVertexId]) -> u64 { } /// Normalizes lifted vertices by subtracting the offset of the first sorted -/// lifted vertex, making periodic simplex identities translation invariant. +/// lifted making periodic simplex identities translation invariant. fn normalize_lifted_vertices(lifted_vertices: &[LiftedVertexId]) -> LiftedVertexBuffer { let mut keys: LiftedVertexBuffer = lifted_vertices.iter().cloned().collect(); keys.sort_unstable(); @@ -376,7 +375,7 @@ pub enum RidgeVerticesError { /// /// ```rust /// use delaunay::prelude::construction::{ -/// DelaunayTriangulation, DelaunayTriangulationConstructionError, vertex, +/// DelaunayTriangulation, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::topology::validation::{RidgeVertices, RidgeVerticesError}; /// @@ -388,16 +387,16 @@ pub enum RidgeVerticesError { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let triangulation = DelaunayTriangulation::new(&vertices)?; /// let Some(v0) = triangulation.tds().vertex_keys().next() else { /// return Err(ExampleError::Empty); /// }; /// -/// // In 2D, a ridge is a vertex, so the validated ridge set has arity 1. +/// // In 2D, a ridge is a so the validated ridge set has arity 1. /// let ridge = RidgeVertices::<2>::try_from_vertices([v0])?; /// assert_eq!(ridge.as_slice(), &[v0]); /// assert_eq!(ridge.iter().collect::>(), vec![v0]); @@ -425,7 +424,7 @@ impl RidgeVertices { /// /// ```rust /// use delaunay::prelude::construction::{ - /// DelaunayTriangulation, DelaunayTriangulationConstructionError, vertex, + /// DelaunayTriangulation, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::topology::validation::{RidgeVertices, RidgeVerticesError}; /// @@ -437,10 +436,10 @@ impl RidgeVertices { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ - /// vertex!([0.0, 0.0, 0.0]), - /// vertex!([1.0, 0.0, 0.0]), - /// vertex!([0.0, 1.0, 0.0]), - /// vertex!([0.0, 0.0, 1.0]), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), + /// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let triangulation = DelaunayTriangulation::new(&vertices)?; /// let keys = triangulation.tds().vertex_keys().collect::>(); @@ -522,10 +521,10 @@ impl RidgeVertices { /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices)?; /// let facet_to_simplices = tds.build_facet_to_simplices_map()?; @@ -586,10 +585,10 @@ pub fn validate_facet_degree( /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices)?; /// let facet_to_simplices = tds.build_facet_to_simplices_map()?; @@ -598,8 +597,8 @@ pub fn validate_facet_degree( /// # Ok(()) /// # } /// ``` -pub fn validate_closed_boundary( - tds: &Tds, +pub fn validate_closed_boundary( + tds: &Tds, facet_to_simplices: &FacetToSimplicesMap, ) -> Result<(), ManifoldError> { // The boundary is a (D-1)-complex. Codimension-2 manifoldness is only meaningful for D>=2. @@ -714,8 +713,8 @@ pub fn validate_closed_boundary( /// incident-simplex star, then checks only boundary ridges incident to those /// touched facets. This keeps post-insertion checks local while preserving the /// same codimension-1 and codimension-2 invariants for the mutated region. -pub(crate) fn validate_local_pseudomanifold_for_simplices( - tds: &Tds, +pub(crate) fn validate_local_pseudomanifold_for_simplices( + tds: &Tds, simplices: &[SimplexKey], ) -> Result<(), ManifoldError> { if D == 0 || simplices.is_empty() { @@ -728,8 +727,8 @@ pub(crate) fn validate_local_pseudomanifold_for_simplices( - tds: &Tds, +fn build_local_facet_star_map( + tds: &Tds, simplices: &[SimplexKey], ) -> Result { let mut facet_to_simplices = FacetToSimplicesMap::default(); @@ -754,8 +753,8 @@ fn build_local_facet_star_map( } /// Returns lifted and bare vertices of one simplex facet by omitting `facet_index`. -fn simplex_facet_vertex_ids( - tds: &Tds, +fn simplex_facet_vertex_ids( + tds: &Tds, simplex_key: SimplexKey, facet_index: usize, ) -> Result<(LiftedVertexBuffer, VertexKeyBuffer), ManifoldError> { @@ -793,8 +792,8 @@ fn simplex_facet_vertex_ids( } /// Finds all simplex/facet handles whose facet has the requested key. -fn facet_incident_handles( - tds: &Tds, +fn facet_incident_handles( + tds: &Tds, facet_key: u64, facet_vertices_bare: &[VertexKey], ) -> Result, ManifoldError> { @@ -818,7 +817,7 @@ fn facet_incident_handles( } .into()); }; - handles.push(FacetHandle::new(simplex_key, facet_index)); + handles.push(FacetHandle::from_validated(simplex_key, facet_index)); } } @@ -826,8 +825,8 @@ fn facet_incident_handles( } /// Validates boundary closure for boundary facets present in a local facet map. -fn validate_closed_boundary_for_local_facets( - tds: &Tds, +fn validate_closed_boundary_for_local_facets( + tds: &Tds, facet_to_simplices: &FacetToSimplicesMap, ) -> Result<(), ManifoldError> { if D < 2 { @@ -905,8 +904,8 @@ fn ridge_vertices_for_facet( } /// Counts boundary facets in the full star of a ridge. -fn boundary_facet_count_for_ridge( - tds: &Tds, +fn boundary_facet_count_for_ridge( + tds: &Tds, ridge_vertices: &[LiftedVertexId], ridge_vertices_bare: &[VertexKey], ) -> Result { @@ -956,8 +955,8 @@ fn boundary_facet_count_for_ridge( /// Periodic quotient TDSs may encode a closed facet identification as a single /// facet occurrence whose neighbor slot points back to the owning simplex. That /// is valid closed-topology metadata, not a boundary facet. -fn is_boundary_facet_handle( - tds: &Tds, +fn is_boundary_facet_handle( + tds: &Tds, handle: FacetHandle, ) -> Result { let simplex_key = handle.simplex_key(); @@ -986,8 +985,8 @@ fn is_boundary_facet_handle( /// /// This helper does **not** call `tds.is_valid()`; it performs lightweight checks and /// returns [`ManifoldError::Tds`] if the underlying TDS is internally inconsistent. -fn simplex_star_simplices( - tds: &Tds, +fn simplex_star_simplices( + tds: &Tds, simplex_vertices: &[VertexKey], ) -> Result, ManifoldError> { if simplex_vertices.is_empty() { @@ -1036,8 +1035,8 @@ fn simplex_star_simplices( /// For each incident D-simplex, this returns the complementary vertex set (the vertices in the /// D-simplex that are not in `simplex_vertices`). This is the standard combinatorial definition of /// the link of a simplex in a pure simplicial complex. -fn simplex_link_simplices_from_star( - tds: &Tds, +fn simplex_link_simplices_from_star( + tds: &Tds, simplex_vertices: &[VertexKey], star_simplices: &[SimplexKey], ) -> Result { @@ -1130,7 +1129,7 @@ fn simplex_link_simplices_from_star( /// /// ```rust /// use delaunay::prelude::construction::{ -/// DelaunayTriangulation, DelaunayTriangulationConstructionError, vertex, +/// DelaunayTriangulation, DelaunayTriangulationConstructionError, /// }; /// use delaunay::prelude::topology::validation::{ /// ManifoldError, RidgeVertices, RidgeVerticesError, ridge_star_simplices, @@ -1145,9 +1144,9 @@ fn simplex_link_simplices_from_star( /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0]), -/// vertex!([1.0, 0.0]), -/// vertex!([0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let triangulation = DelaunayTriangulation::new(&vertices)?; /// let Some(v0) = triangulation.tds().vertex_keys().next() else { @@ -1160,15 +1159,15 @@ fn simplex_link_simplices_from_star( /// # Ok(()) /// # } /// ``` -pub fn ridge_star_simplices( - tds: &Tds, +pub fn ridge_star_simplices( + tds: &Tds, ridge_vertices: &RidgeVertices, ) -> Result, ManifoldError> { simplex_star_simplices(tds, ridge_vertices.as_slice()) } -fn ridge_link_edges_from_star( - tds: &Tds, +fn ridge_link_edges_from_star( + tds: &Tds, ridge_vertices: &[LiftedVertexId], star_simplices: &[SimplexKey], ) -> Result, ManifoldError> { @@ -1252,8 +1251,8 @@ struct RidgeStar { // // This is appropriate for Level 3 topology validation / debugging, but it can be expensive // for extremely large triangulations (e.g., millions of simplices) or higher-dimensional complexes. -fn build_ridge_star_map( - tds: &Tds, +fn build_ridge_star_map( + tds: &Tds, ) -> Result, ManifoldError> { let simplex_count = tds.number_of_simplices(); if simplex_count == 0 { @@ -1329,8 +1328,8 @@ fn build_ridge_star_map( Ok(ridge_to_star) } -fn build_ridge_star_map_for_simplices( - tds: &Tds, +fn build_ridge_star_map_for_simplices( + tds: &Tds, simplices: &[SimplexKey], ) -> Result, ManifoldError> { if D < 2 { @@ -1435,8 +1434,8 @@ fn build_ridge_star_map_for_simplices( Ok(ridge_to_star) } -fn normalized_simplex_vertices_for_lifted_target( - tds: &Tds, +fn normalized_simplex_vertices_for_lifted_target( + tds: &Tds, simplex_key: SimplexKey, target_vertices: &[LiftedVertexId], ) -> Result, ManifoldError> { @@ -1489,8 +1488,8 @@ fn normalized_simplex_vertices_for_lifted_target( /// - `simplex_vertices` fails for any candidate simplex. /// - Periodic offset filtering produces an empty star, indicating inconsistent /// offsets in the TDS. -fn periodic_aware_ridge_star( - tds: &Tds, +fn periodic_aware_ridge_star( + tds: &Tds, ridge_key: u64, lifted_vertices: &[LiftedVertexId], bare_vertices: &VertexKeyBuffer, @@ -1583,10 +1582,10 @@ fn periodic_aware_ridge_star( /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices)?; /// @@ -1594,9 +1593,7 @@ fn periodic_aware_ridge_star( /// # Ok(()) /// # } /// ``` -pub fn validate_ridge_links( - tds: &Tds, -) -> Result<(), ManifoldError> { +pub fn validate_ridge_links(tds: &Tds) -> Result<(), ManifoldError> { // Ridge links are only meaningful for D>=2. if D < 2 { return Ok(()); @@ -1667,10 +1664,10 @@ pub fn validate_ridge_links( /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices)?; /// let simplices: SimplexKeyBuffer = tds.simplices().map(|(k, _)| k).collect(); @@ -1679,8 +1676,8 @@ pub fn validate_ridge_links( /// # Ok(()) /// # } /// ``` -pub fn validate_ridge_links_for_simplices( - tds: &Tds, +pub fn validate_ridge_links_for_simplices( + tds: &Tds, simplices: &[SimplexKey], ) -> Result<(), ManifoldError> { // Ridge links are only meaningful for D>=2. @@ -1764,10 +1761,10 @@ pub fn validate_ridge_links_for_simplices( /// # } /// # fn main() -> Result<(), ExampleError> { /// let vertices = vec![ -/// vertex!([0.0, 0.0, 0.0]), -/// vertex!([1.0, 0.0, 0.0]), -/// vertex!([0.0, 1.0, 0.0]), -/// vertex!([0.0, 0.0, 1.0]), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).expect("finite vertex coordinates"), +/// delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).expect("finite vertex coordinates"), /// ]; /// let tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices)?; /// let facet_to_simplices = tds.build_facet_to_simplices_map()?; @@ -1778,8 +1775,8 @@ pub fn validate_ridge_links_for_simplices( /// # Ok(()) /// # } /// ``` -pub fn validate_vertex_links( - tds: &Tds, +pub fn validate_vertex_links( + tds: &Tds, facet_to_simplices: &FacetToSimplicesMap, ) -> Result<(), ManifoldError> { // Vertex links are only meaningful for D>=1. @@ -1801,8 +1798,8 @@ pub fn validate_vertex_links( Ok(()) } -fn build_boundary_vertex_set( - tds: &Tds, +fn build_boundary_vertex_set( + tds: &Tds, facet_to_simplices: &FacetToSimplicesMap, ) -> Result, ManifoldError> { // Single pass: collect all vertices that appear on a boundary facet (a facet incident to exactly 1 D-simplex). @@ -1938,8 +1935,8 @@ fn validate_vertex_link_d2( } } -fn validate_single_vertex_link( - tds: &Tds, +fn validate_single_vertex_link( + tds: &Tds, vertex_key: VertexKey, interior_vertex: bool, ) -> Result<(), ManifoldError> { @@ -2477,7 +2474,6 @@ mod tests { use crate::core::simplex::Simplex; use crate::core::triangulation::Triangulation; use crate::geometry::kernel::FastKernel; - use crate::vertex; use slotmap::KeyData; @@ -2491,18 +2487,34 @@ mod tests { s } - fn build_closed_surface_s2_tds_2d() -> (Tds, [VertexKey; 4]) { + fn build_closed_surface_s2_tds_2d() -> (Tds<(), (), 2>, [VertexKey; 4]) { // Closed 2D simplicial complex (topologically S²): boundary of a tetrahedron. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); for tri in [[v0, v1, v2], [v0, v1, v3], [v0, v2, v3], [v1, v2, v3]] { tds.insert_simplex_with_mapping( - Simplex::new(vec![tri[0], tri[1], tri[2]], None).unwrap(), + Simplex::try_new_with_data(vec![tri[0], tri[1], tri[2]], None).unwrap(), ) .unwrap(); } @@ -2510,39 +2522,59 @@ mod tests { (tds, [v0, v1, v2, v3]) } - fn build_non_manifold_boundary_ridge_tds_3d() -> (Tds, SimplexKey, u64) { + fn build_non_manifold_boundary_ridge_tds_3d() -> (Tds<(), (), 3>, SimplexKey, u64) { // Two tetrahedra that share an edge but not a facet create a non-manifold boundary: // the shared edge is incident to 4 boundary triangles. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let shared_edge_v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let shared_edge_v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let tet1_v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let tet1_v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let tet2_v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0, 0.0]).unwrap(), + ) .unwrap(); let tet2_v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, -1.0]).unwrap(), + ) .unwrap(); let touched_simplex = tds .insert_simplex_with_mapping( - Simplex::new(vec![shared_edge_v0, shared_edge_v1, tet1_v2, tet1_v3], None).unwrap(), + Simplex::try_new_with_data( + vec![shared_edge_v0, shared_edge_v1, tet1_v2, tet1_v3], + None, + ) + .unwrap(), ) .unwrap(); let _ = tds .insert_simplex_with_mapping( - Simplex::new(vec![shared_edge_v0, shared_edge_v1, tet2_v2, tet2_v3], None).unwrap(), + Simplex::try_new_with_data( + vec![shared_edge_v0, shared_edge_v1, tet2_v2, tet2_v3], + None, + ) + .unwrap(), ) .unwrap(); @@ -2553,10 +2585,10 @@ mod tests { #[test] fn test_validate_facet_degree_ok_for_single_tetrahedron() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = @@ -2569,32 +2601,46 @@ mod tests { #[test] fn test_validate_facet_degree_ok_for_two_tetrahedra_sharing_facet() { // Two tetrahedra share a facet => that facet has degree 2, all others degree 1. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); // Shared triangle. let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); // Apex points on opposite sides. let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, -1.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v4], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v4], None).unwrap(), + ) .unwrap(); let facet_to_simplices = tds.build_facet_to_simplices_map().unwrap(); @@ -2604,37 +2650,53 @@ mod tests { #[test] fn test_validate_facet_degree_errors_on_non_manifold_facet_multiplicity() { // Three tetrahedra share a single facet -> not a manifold-with-boundary. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 2.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), + ) .unwrap(); let v5 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 3.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 3.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v4], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v4], None).unwrap(), + ) .unwrap(); let _ = tds .insert_simplex_bypassing_topology_checks_for_test( - Simplex::new(vec![v0, v1, v2, v5], None).unwrap(), + Simplex::try_new_with_data(vec![v0, v1, v2, v5], None).unwrap(), ) .unwrap(); @@ -2656,10 +2718,10 @@ mod tests { #[test] fn test_validate_closed_boundary_ok_for_single_tetrahedron() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = @@ -2672,10 +2734,10 @@ mod tests { #[test] fn test_validate_closed_boundary_errors_on_out_of_bounds_facet_index() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = @@ -2685,7 +2747,7 @@ mod tests { // Synthesize an invalid boundary facet handle: facet indices must be < D+1. let mut facet_to_simplices: FacetToSimplicesMap = FacetToSimplicesMap::default(); let mut handles: SmallBuffer = SmallBuffer::new(); - handles.push(FacetHandle::new(simplex_key, u8::MAX)); + handles.push(FacetHandle::from_validated(simplex_key, u8::MAX)); facet_to_simplices.insert(0_u64, handles); match validate_closed_boundary(&tds, &facet_to_simplices) { @@ -2869,15 +2931,27 @@ mod tests { #[test] fn test_validate_single_vertex_link_d1_accepts_path_middle_and_endpoints() { // 0--1--2 : middle vertex is interior (2 neighbors), endpoints are boundary (1 neighbor). - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 1> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([2.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([2.0]).unwrap(), + ) + .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1], None).unwrap()) + tds.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v0, v1], None).unwrap()) .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v1, v2], None).unwrap()) + tds.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v1, v2], None).unwrap()) .unwrap(); validate_single_vertex_link(&tds, v0, false).unwrap(); @@ -2887,12 +2961,20 @@ mod tests { #[test] fn test_validate_single_vertex_link_d1_rejects_endpoint_classified_as_interior() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 1> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ) + .unwrap(); - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1], None).unwrap()) + tds.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v0, v1], None).unwrap()) .unwrap(); assert_matches!( @@ -2903,14 +2985,28 @@ mod tests { #[test] fn test_validate_single_vertex_link_d2_accepts_boundary_vertex_in_single_triangle() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - - tds.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + + tds.insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) + .unwrap(); validate_single_vertex_link(&tds, v0, false).unwrap(); assert_matches!( @@ -2936,13 +3032,33 @@ mod tests { // // Boundary vertices must have path links (degree-one vertices = 2), while the interior // vertex has a cycle link (degree-one vertices = 0). - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let va = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let vb = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let vc = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); - let vd = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let center = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap(); + let va = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let vb = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let vc = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); + let vd = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let center = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), + ) + .unwrap(); for tri in [ [center, va, vb], @@ -2951,7 +3067,7 @@ mod tests { [center, vd, va], ] { tds.insert_simplex_with_mapping( - Simplex::new(vec![tri[0], tri[1], tri[2]], None).unwrap(), + Simplex::try_new_with_data(vec![tri[0], tri[1], tri[2]], None).unwrap(), ) .unwrap(); } @@ -3010,7 +3126,10 @@ mod tests { #[test] fn test_validate_closed_boundary_noop_for_d_lt_2() { - let vertices = vec![vertex!([0.0]), vertex!([1.0])]; + let vertices = vec![ + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ]; let tds = Triangulation::, (), (), 1>::build_initial_simplex(&vertices).unwrap(); @@ -3049,7 +3168,7 @@ mod tests { #[test] fn test_simplex_star_simplices_errors_on_empty_simplex() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); match simplex_star_simplices(&tds, &[]) { Err(ManifoldError::Tds(TdsError::InconsistentDataStructure { ref message })) @@ -3060,9 +3179,13 @@ mod tests { #[test] fn test_simplex_star_simplices_returns_empty_for_isolated_vertex() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); let star = simplex_star_simplices(&tds, &[v0]).unwrap(); assert!(star.is_empty()); @@ -3070,7 +3193,7 @@ mod tests { #[test] fn test_simplex_link_simplices_from_star_errors_on_empty_simplex() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); match simplex_link_simplices_from_star(&tds, &[], &[]) { Err(ManifoldError::Tds(TdsError::InconsistentDataStructure { ref message })) @@ -3084,17 +3207,33 @@ mod tests { // Defensive: a simplex vertex that exists in the TDS but is not part of a star simplex should // trigger a link-size mismatch (this should not happen when star simplices are produced by // `simplex_star_simplices`, but is a robustness check for corrupted inputs). - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([10.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0]).unwrap(), + ) .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); match simplex_link_simplices_from_star(&tds, &[v0, v3], &[simplex_key]) { @@ -3121,7 +3260,7 @@ mod tests { #[test] fn test_ridge_link_edges_from_star_noop_for_d_lt_2() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 1> = Tds::empty(); let edges = ridge_link_edges_from_star(&tds, &[], &[]).unwrap(); assert!(edges.is_empty()); @@ -3144,23 +3283,33 @@ mod tests { #[test] fn test_ridge_link_edges_from_star_errors_on_wrong_vertex_count_in_3d() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); // In 3D, ridges are edges (2 vertices). Passing a single vertex is invalid. @@ -3177,24 +3326,48 @@ mod tests { #[test] fn test_ridge_star_simplices_returns_incident_simplices_for_vertex_ridge_in_2d() { // In 2D, a ridge is a vertex and its star is the set of incident triangles. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let c012 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let c013 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); let c023 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) .unwrap(); let _c123 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(), + ) .unwrap(); let ridge_vertices = RidgeVertices::<2>::try_from_vertices([v0]).unwrap(); @@ -3209,32 +3382,48 @@ mod tests { fn test_ridge_star_simplices_returns_full_edge_star_in_3d() { // In 3D, a ridge is an edge. This regression protects k=3 support // collection from using only the anchor simplex. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([0.0, -1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, -1.0, 0.0]).unwrap(), + ) .unwrap(); let c0123 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let c0134 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3, v4], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3, v4], None).unwrap(), + ) .unwrap(); let c0142 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v4, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v4, v2], None).unwrap(), + ) .unwrap(); let ridge_vertices = RidgeVertices::<3>::try_from_vertices([v0, v1]).unwrap(); @@ -3247,7 +3436,7 @@ mod tests { #[test] fn test_ridge_star_simplices_errors_on_missing_vertex_key() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); let missing = VertexKey::from(KeyData::from_ffi(u64::MAX)); let ridge_vertices = RidgeVertices::<2>::try_from_vertices([missing]).unwrap(); @@ -3261,14 +3450,28 @@ mod tests { #[test] fn test_ridge_link_edges_from_star_rejects_self_loop_edge() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Corrupt the simplex in-place: keep length == D+1 but introduce a duplicate link vertex. @@ -3314,14 +3517,28 @@ mod tests { fn test_validate_ridge_links_errors_on_corrupted_simplex_with_duplicate_vertices() { // This is a defensive robustness test: a corrupted simplex with duplicate vertices can // produce a malformed ridge link (wrong number of link vertices). - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Corrupt the simplex in-place: keep length == D+1 but introduce a duplicate vertex. @@ -3386,10 +3603,10 @@ mod tests { #[test] fn test_validate_local_pseudomanifold_for_simplices_errors_on_missing_scope_simplex() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut tds = Triangulation::, (), (), 3>::build_initial_simplex(&vertices).unwrap(); @@ -3408,10 +3625,10 @@ mod tests { #[test] fn test_validate_ridge_links_ok_for_single_tetrahedron() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = @@ -3422,7 +3639,7 @@ mod tests { #[test] fn test_validate_ridge_links_noop_for_empty_tds() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); assert!(validate_ridge_links(&tds).is_ok()); } @@ -3455,88 +3672,140 @@ mod tests { } fn build_two_tetrahedra_sharing_facet_tds_3d() - -> (Tds, [VertexKey; 5], [SimplexKey; 2]) { - let mut tds: Tds = Tds::empty(); + -> (Tds<(), (), 3>, [VertexKey; 5], [SimplexKey; 2]) { + let mut tds: Tds<(), (), 3> = Tds::empty(); // Shared triangle. let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); // Opposite vertices. let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let v4 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, -1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, -1.0]).unwrap(), + ) .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let c2 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v4], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v4], None).unwrap(), + ) .unwrap(); (tds, [v0, v1, v2, v3, v4], [c1, c2]) } fn build_wedge_two_spheres_share_vertex_tds_2d() - -> (Tds, VertexKey, SimplexKey, SimplexKey) { + -> (Tds<(), (), 2>, VertexKey, SimplexKey, SimplexKey) { // Two closed 2D spheres (boundaries of tetrahedra) that share a single vertex. // This is a pseudomanifold (every edge has degree 2), but not a PL 2-manifold: // the shared vertex has a disconnected link (two disjoint cycles). - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); // Shared vertex. - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); // First tetrahedron boundary (4 triangles on 4 vertices). - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); - let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); + let v3 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + ) + .unwrap(); let c012 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); let _c013 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(), + ) .unwrap(); let _c023 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(), + ) .unwrap(); let c123 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(), + ) .unwrap(); // Second tetrahedron boundary (shares only v0). let v4 = tds - .insert_vertex_with_mapping(vertex!([10.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 10.0]).unwrap(), + ) .unwrap(); let v5 = tds - .insert_vertex_with_mapping(vertex!([11.0, 10.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([11.0, 10.0]).unwrap(), + ) .unwrap(); let v6 = tds - .insert_vertex_with_mapping(vertex!([10.0, 11.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([10.0, 11.0]).unwrap(), + ) .unwrap(); let _c045 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v4, v5], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v4, v5], None).unwrap(), + ) .unwrap(); let _c046 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v4, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v4, v6], None).unwrap(), + ) .unwrap(); let _c056 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v5, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v5, v6], None).unwrap(), + ) .unwrap(); let _c456 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v4, v5, v6], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v4, v5, v6], None).unwrap(), + ) .unwrap(); (tds, v0, c012, c123) @@ -3544,7 +3813,7 @@ mod tests { #[test] fn test_build_ridge_star_map_empty_returns_empty() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 3> = Tds::empty(); let map = build_ridge_star_map(&tds).unwrap(); assert!(map.is_empty()); @@ -3552,14 +3821,28 @@ mod tests { #[test] fn test_build_ridge_star_map_errors_on_corrupted_simplex_vertex_count() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Corrupt the simplex in-place: change it to have only 2 vertices. @@ -3586,7 +3869,7 @@ mod tests { #[test] fn test_build_ridge_star_map_for_simplices_noop_for_d_lt_2() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 1> = Tds::empty(); let simplex_key = SimplexKey::from(KeyData::from_ffi(0)); let map = build_ridge_star_map_for_simplices(&tds, &[simplex_key]).unwrap(); @@ -3595,22 +3878,32 @@ mod tests { #[test] fn test_build_ridge_star_map_for_simplices_empty_returns_empty() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let _ = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); let map = build_ridge_star_map_for_simplices(&tds, &[]).unwrap(); @@ -3713,14 +4006,28 @@ mod tests { #[test] fn test_build_ridge_star_map_for_simplices_errors_on_corrupted_simplex_vertex_count() { // Corrupt a simplex's vertex list to violate the (D+1)-vertices invariant. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + ) + .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); // Corrupt the simplex in-place: change it to have only 2 vertices. @@ -3748,10 +4055,10 @@ mod tests { #[test] fn test_validate_ridge_links_for_simplices_ok_for_single_tetrahedron_in_3d() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = @@ -3767,7 +4074,7 @@ mod tests { #[test] fn test_validate_ridge_links_for_simplices_ok_for_missing_simplex_keys() { // Defensive: local validation should ignore missing simplex keys. - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 3> = Tds::empty(); let missing = SimplexKey::from(KeyData::from_ffi(u64::MAX)); assert!(validate_ridge_links_for_simplices(&tds, &[missing]).is_ok()); @@ -3775,13 +4082,21 @@ mod tests { #[test] fn test_validate_ridge_links_for_simplices_noop_for_d_lt_2() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 1> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0]).unwrap(), + ) + .unwrap(); let c01 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1], None).unwrap()) + .insert_simplex_with_mapping(Simplex::try_new_with_data(vec![v0, v1], None).unwrap()) .unwrap(); assert!(validate_ridge_links_for_simplices(&tds, &[c01]).is_ok()); @@ -3823,7 +4138,7 @@ mod tests { assert!(validate_ridge_links_for_simplices(&tds, &[nonincident]).is_ok()); } - fn build_cone_on_torus_tds() -> (Tds, VertexKey) { + fn build_cone_on_torus_tds() -> (Tds<(), (), 3>, VertexKey) { // Construct a 3D simplicial complex that is a cone over a triangulated 2-torus. // // This is a pseudomanifold and passes ridge-link validation, but is NOT a PL 3-manifold: @@ -3832,7 +4147,7 @@ mod tests { const N: usize = 3; const M: usize = 3; - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); // Build a small triangulated torus using a periodic 3x3 grid. let mut v: [[VertexKey; M]; N] = [[VertexKey::from(KeyData::from_ffi(0)); M]; N]; @@ -3841,14 +4156,18 @@ mod tests { let i_f = >::from(u32::try_from(i).unwrap()); let j_f = >::from(u32::try_from(j).unwrap()); *slot = tds - .insert_vertex_with_mapping(vertex!([i_f, j_f, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([i_f, j_f, 0.0]).unwrap(), + ) .unwrap(); } } // Apex of the cone (interior vertex; not on any boundary facet). let apex = tds - .insert_vertex_with_mapping(vertex!([0.5, 0.5, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 0.5, 1.0]).unwrap(), + ) .unwrap(); // Triangulate each periodic square into two triangles, then cone to the apex. @@ -3865,7 +4184,8 @@ mod tests { for tri in [[v00, v10, v01], [v10, v11, v01]] { let _ = tds .insert_simplex_with_mapping( - Simplex::new(vec![tri[0], tri[1], tri[2], apex], None).unwrap(), + Simplex::try_new_with_data(vec![tri[0], tri[1], tri[2], apex], None) + .unwrap(), ) .unwrap(); } @@ -3907,7 +4227,7 @@ mod tests { #[test] fn test_simplex_star_simplices_rejects_missing_vertex() { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); let stale_key = VertexKey::from(KeyData::from_ffi(0xDEAD)); match simplex_star_simplices(&tds, &[stale_key]) { Err(ManifoldError::Tds(TdsError::VertexNotFound { @@ -3966,23 +4286,33 @@ mod tests { fn test_validate_closed_boundary_dimension_mismatch_on_corrupted_simplex() { // Create a 3D TDS with a simplex that has too few vertices (corrupted state), // then trigger the DimensionMismatch path in validate_closed_boundary. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); let simplex_key = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2, v3], None).unwrap(), + ) .unwrap(); // Build a facet-to-simplices map with a synthetic boundary facet pointing at facet_index=0 @@ -3997,7 +4327,7 @@ mod tests { let mut facet_to_simplices: FacetToSimplicesMap = FacetToSimplicesMap::default(); let mut handles: SmallBuffer = SmallBuffer::new(); - handles.push(FacetHandle::new(simplex_key, 0)); + handles.push(FacetHandle::from_validated(simplex_key, 0)); facet_to_simplices.insert(0_u64, handles); match validate_closed_boundary(&tds, &facet_to_simplices) { @@ -4040,25 +4370,35 @@ mod tests { // Cone on the boundary of a tetrahedron (S^2). // The apex link is S^2, so this IS a valid PL 3-manifold (a 3-ball). - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); // Base tetrahedron vertices (triangulated S^2 boundary) let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let v3 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) .unwrap(); // Apex of the cone let apex = tds - .insert_vertex_with_mapping(vertex!([0.3, 0.3, 0.3])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.3, 0.3, 0.3]).unwrap(), + ) .unwrap(); // Each boundary triangle of the tetrahedron, coned to apex @@ -4072,7 +4412,7 @@ mod tests { for tri in sphere_faces { let mut verts = tri; verts.push(apex); - tds.insert_simplex_with_mapping(Simplex::new(verts, None).unwrap()) + tds.insert_simplex_with_mapping(Simplex::try_new_with_data(verts, None).unwrap()) .unwrap(); } @@ -4091,21 +4431,33 @@ mod tests { fn test_build_ridge_star_map_for_simplices_identifies_translated_periodic_images() { // Two 2D simplices share bare vertex keys but differ in periodic offsets. // The ridge map identifies globally translated quotient ridges. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.5, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + ) + .unwrap(); // c1: all vertices at base image [0,0]. - let mut simplex1 = Simplex::new(vec![v0, v1, v2], None).unwrap(); + let mut simplex1 = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); simplex1 .set_periodic_vertex_offsets(vec![[0, 0], [0, 0], [0, 0]]) .unwrap(); let c1 = tds.insert_simplex_with_mapping(simplex1).unwrap(); // c2: v0 at periodic image [1,0]; v1 and v2 at base image. - let mut simplex2 = Simplex::new(vec![v0, v1, v2], None).unwrap(); + let mut simplex2 = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); simplex2 .set_periodic_vertex_offsets(vec![[1, 0], [0, 0], [0, 0]]) .unwrap(); @@ -4125,16 +4477,22 @@ mod tests { #[test] fn test_anchored_lifted_simplex_key_preserves_vertex_link_offsets() { - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 3> = Tds::empty(); let v0 = tds - .insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v1 = tds - .insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) .unwrap(); let v2 = tds - .insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0])) + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) .unwrap(); let first_link_triangle: LiftedVertexBuffer = [ @@ -4168,14 +4526,28 @@ mod tests { fn test_periodic_aware_ridge_star_empty_star_returns_error() { // Call periodic_aware_ridge_star with lifted vertices that don't match // any simplex's offsets, forcing an empty star after filtering. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.5, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + ) + .unwrap(); let c1 = tds - .insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap()) + .insert_simplex_with_mapping( + Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(), + ) .unwrap(); tds.simplex_mut(c1) .unwrap() @@ -4203,19 +4575,31 @@ mod tests { fn test_validate_ridge_links_for_simplices_rejects_split_periodic_link() { // These two lifted triangles share quotient ridge vertices but leave a split // periodic link, so quotient-aware ridge-link validation must reject them. - let mut tds: Tds = Tds::empty(); + let mut tds: Tds<(), (), 2> = Tds::empty(); - let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap(); - let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap(); - let v2 = tds.insert_vertex_with_mapping(vertex!([0.5, 1.0])).unwrap(); + let v0 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + ) + .unwrap(); + let v1 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ) + .unwrap(); + let v2 = tds + .insert_vertex_with_mapping( + crate::core::vertex::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + ) + .unwrap(); - let mut simplex1 = Simplex::new(vec![v0, v1, v2], None).unwrap(); + let mut simplex1 = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); simplex1 .set_periodic_vertex_offsets(vec![[0, 0], [0, 0], [0, 0]]) .unwrap(); let c1 = tds.insert_simplex_with_mapping(simplex1).unwrap(); - let mut simplex2 = Simplex::new(vec![v0, v1, v2], None).unwrap(); + let mut simplex2 = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(); simplex2 .set_periodic_vertex_offsets(vec![[1, 0], [0, 0], [0, 0]]) .unwrap(); @@ -4233,10 +4617,10 @@ mod tests { // Each boundary vertex has a link homeomorphic to a 2-ball. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + crate::core::vertex::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let tds = diff --git a/src/topology/spaces/toroidal.rs b/src/topology/spaces/toroidal.rs index 77a291c2..7b180181 100644 --- a/src/topology/spaces/toroidal.rs +++ b/src/topology/spaces/toroidal.rs @@ -5,11 +5,9 @@ #![forbid(unsafe_code)] -use crate::geometry::traits::coordinate::CoordinateScalar; use crate::topology::traits::topological_space::{ TopologicalSpace, TopologyKind, ToroidalDomain, ToroidalDomainError, }; -use num_traits::NumCast; /// Represents toroidal topological space with periodic boundaries. /// @@ -145,25 +143,23 @@ impl ToroidalSpace { /// let space = ToroidalSpace::<2>::try_new([1.0, 2.0])?; /// /// // Positive out-of-range - /// assert_eq!(space.wrap_coord::(0, 1.7), Some(0.7)); + /// assert_eq!(space.wrap_coord(0, 1.7), Some(0.7)); /// /// // Negative wraps to positive - /// assert_eq!(space.wrap_coord::(1, -0.5), Some(1.5)); + /// assert_eq!(space.wrap_coord(1, -0.5), Some(1.5)); /// /// // Out-of-range axis returns None - /// assert_eq!(space.wrap_coord::(5, 0.3), None); + /// assert_eq!(space.wrap_coord(5, 0.3), None); /// # Ok(()) /// # } /// ``` #[must_use] - pub fn wrap_coord(&self, axis: usize, value: T) -> Option { + pub fn wrap_coord(&self, axis: usize, value: f64) -> Option { let period = self.domain.period(axis)?; - let v_f64 = value.to_f64()?; - if !v_f64.is_finite() { + if !value.is_finite() { return None; } - let wrapped = v_f64.rem_euclid(period); - ::from(wrapped) + Some(value.rem_euclid(period)) } } @@ -323,7 +319,7 @@ mod tests { #[test] fn test_wrap_coord_positive_out_of_range() { let space = ToroidalSpace::<2>::try_new([1.0, 2.0]).unwrap(); - let wrapped = space.wrap_coord::(0, 1.7); + let wrapped = space.wrap_coord(0, 1.7); assert!(wrapped.is_some()); assert_relative_eq!(wrapped.unwrap(), 0.7); } @@ -332,7 +328,7 @@ mod tests { fn test_wrap_coord_negative() { let space = ToroidalSpace::<2>::try_new([1.0, 2.0]).unwrap(); // -0.5 rem_euclid 2.0 = 1.5 - let wrapped = space.wrap_coord::(1, -0.5); + let wrapped = space.wrap_coord(1, -0.5); assert!(wrapped.is_some()); assert_relative_eq!(wrapped.unwrap(), 1.5); } @@ -340,7 +336,7 @@ mod tests { #[test] fn test_wrap_coord_in_range_unchanged() { let space = ToroidalSpace::<2>::unit(); - let wrapped = space.wrap_coord::(0, 0.3); + let wrapped = space.wrap_coord(0, 0.3); assert!(wrapped.is_some()); assert_relative_eq!(wrapped.unwrap(), 0.3); } @@ -349,7 +345,7 @@ mod tests { fn test_wrap_coord_boundary() { let space = ToroidalSpace::<2>::unit(); // Exactly at period boundary wraps to 0 - let wrapped = space.wrap_coord::(0, 1.0); + let wrapped = space.wrap_coord(0, 1.0); assert!(wrapped.is_some()); assert_relative_eq!(wrapped.unwrap(), 0.0); } @@ -357,13 +353,13 @@ mod tests { #[test] fn test_wrap_coord_out_of_range_axis() { let space = ToroidalSpace::<2>::unit(); - assert!(space.wrap_coord::(5, 0.3).is_none()); + assert!(space.wrap_coord(5, 0.3).is_none()); } #[test] fn test_wrap_coord_f64() { let space = ToroidalSpace::<2>::unit(); - let wrapped = space.wrap_coord::(0, 1.5); + let wrapped = space.wrap_coord(0, 1.5); assert!(wrapped.is_some()); assert_relative_eq!(wrapped.unwrap(), 0.5, epsilon = 1e-12); } @@ -371,8 +367,8 @@ mod tests { #[test] fn test_wrap_coord_non_finite() { let space = ToroidalSpace::<2>::unit(); - assert!(space.wrap_coord::(0, f64::NAN).is_none()); - assert!(space.wrap_coord::(0, f64::INFINITY).is_none()); + assert!(space.wrap_coord(0, f64::NAN).is_none()); + assert!(space.wrap_coord(0, f64::INFINITY).is_none()); } #[test] diff --git a/src/topology/traits/global_topology_model.rs b/src/topology/traits/global_topology_model.rs index a36642ce..12a1a023 100644 --- a/src/topology/traits/global_topology_model.rs +++ b/src/topology/traits/global_topology_model.rs @@ -1,8 +1,7 @@ //! Internal behavior models for [`GlobalTopology`] metadata. //! //! Public APIs continue to expose [`GlobalTopology`] as runtime metadata. This module provides -//! scalar-generic behavior models used by core triangulation/build paths to avoid ad-hoc topology -//! branching. +//! behavior models used by core triangulation/build paths to avoid ad-hoc topology branching. //! //! # Overview //! @@ -20,14 +19,12 @@ #![forbid(unsafe_code)] -use crate::geometry::traits::coordinate::CoordinateScalar; use crate::topology::traits::topological_space::{ GlobalTopology, TopologyKind, ToroidalConstructionMode, ToroidalDomain, ToroidalDomainError, }; -use num_traits::NumCast; use thiserror::Error; -/// Errors emitted by [`GlobalTopologyModel`] behavior calls. +/// Errors emitted by topology model behavior calls. #[derive(Clone, Debug, Error, PartialEq)] #[non_exhaustive] pub enum GlobalTopologyModelError { @@ -40,15 +37,6 @@ pub enum GlobalTopologyModelError { period: f64, }, - /// A coordinate could not be converted to/from `f64` during canonicalization/lifting. - #[error("Failed scalar conversion while processing axis {axis} with value {value:?}")] - ScalarConversion { - /// Axis index where conversion failed. - axis: usize, - /// Source value that failed to convert. - value: f64, - }, - /// A coordinate is non-finite and cannot be canonicalized. #[error("Non-finite coordinate encountered while processing axis {axis}: {value:?}")] NonFiniteCoordinate { @@ -104,14 +92,10 @@ pub trait GlobalTopologyModel { /// domain periods are invalid. /// - [`GlobalTopologyModelError::NonFiniteCoordinate`] when a coordinate is /// `NaN` or infinite. - /// - [`GlobalTopologyModelError::ScalarConversion`] when scalar - /// conversion to/from `f64` fails. - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - coords: &mut [T; D], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar; + coords: &mut [f64; D], + ) -> Result<(), GlobalTopologyModelError>; /// Lifts coordinates for orientation predicates. /// @@ -125,15 +109,13 @@ pub trait GlobalTopologyModel { /// are supplied for non-periodic models. /// - [`GlobalTopologyModelError::InvalidToroidalPeriod`] when toroidal /// domain periods are invalid. - /// - [`GlobalTopologyModelError::ScalarConversion`] when scalar - /// conversion fails during lifting. - fn lift_for_orientation( + /// - [`GlobalTopologyModelError::NonFiniteCoordinate`] when an input or + /// lifted coordinate is `NaN` or infinite. + fn lift_for_orientation( &self, - coords: [T; D], + coords: [f64; D], periodic_offset: Option<[i8; D]>, - ) -> Result<[T; D], GlobalTopologyModelError> - where - T: CoordinateScalar; + ) -> Result<[f64; D], GlobalTopologyModelError>; /// Returns the periodic domain when relevant. /// @@ -177,24 +159,18 @@ impl GlobalTopologyModel for EuclideanModel { true } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - _coords: &mut [T; D], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + _coords: &mut [f64; D], + ) -> Result<(), GlobalTopologyModelError> { Ok(()) } - fn lift_for_orientation( + fn lift_for_orientation( &self, - coords: [T; D], + coords: [f64; D], periodic_offset: Option<[i8; D]>, - ) -> Result<[T; D], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; D], GlobalTopologyModelError> { if periodic_offset.is_some() { return Err(GlobalTopologyModelError::PeriodicOffsetsUnsupported { kind: TopologyKind::Euclidean, @@ -245,51 +221,26 @@ impl GlobalTopologyModel for ToroidalModel { false } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - coords: &mut [T; D], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + coords: &mut [f64; D], + ) -> Result<(), GlobalTopologyModelError> { for (axis, coord_ref) in coords.iter_mut().enumerate() { let period = self.domain.periods()[axis]; - let Some(coord) = coord_ref.to_f64() else { - let value = if coord_ref.is_nan() { - f64::NAN - } else if coord_ref.is_infinite() { - if coord_ref.is_sign_negative() { - f64::NEG_INFINITY - } else { - f64::INFINITY - } - } else { - f64::NAN - }; - return Err(GlobalTopologyModelError::ScalarConversion { axis, value }); - }; + let coord = *coord_ref; if !coord.is_finite() { return Err(GlobalTopologyModelError::NonFiniteCoordinate { axis, value: coord }); } - let wrapped = coord.rem_euclid(period); - *coord_ref = ::from(wrapped).ok_or( - GlobalTopologyModelError::ScalarConversion { - axis, - value: wrapped, - }, - )?; + *coord_ref = coord.rem_euclid(period); } Ok(()) } - fn lift_for_orientation( + fn lift_for_orientation( &self, - mut coords: [T; D], + mut coords: [f64; D], periodic_offset: Option<[i8; D]>, - ) -> Result<[T; D], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; D], GlobalTopologyModelError> { // Canonicalized toroidal mode intentionally accepts optional periodic offsets but // does not apply them. This differs from `EuclideanModel`, which treats any // provided periodic offset as unsupported and returns an error. @@ -301,35 +252,21 @@ impl GlobalTopologyModel for ToroidalModel { }; // Validate finiteness before performing arithmetic - for (axis, coord_ref) in coords.iter().enumerate() { - let Some(coord_f64) = coord_ref.to_f64() else { - return Err(GlobalTopologyModelError::ScalarConversion { - axis, - value: f64::NAN, - }); - }; - if !coord_f64.is_finite() { - return Err(GlobalTopologyModelError::NonFiniteCoordinate { - axis, - value: coord_f64, - }); + for (axis, coord) in coords.iter().copied().enumerate() { + if !coord.is_finite() { + return Err(GlobalTopologyModelError::NonFiniteCoordinate { axis, value: coord }); } } for axis in 0..D { let period = self.domain.periods()[axis]; - let period_scalar = - ::from(period).ok_or(GlobalTopologyModelError::ScalarConversion { - axis, - value: period, - })?; - let offset_value = >::from(offset[axis]); - let offset_scalar = ::from(offset[axis]).ok_or( - GlobalTopologyModelError::ScalarConversion { + let lifted = f64::from(offset[axis]).mul_add(period, coords[axis]); + if !lifted.is_finite() { + return Err(GlobalTopologyModelError::NonFiniteCoordinate { axis, - value: offset_value, - }, - )?; - coords[axis] += offset_scalar * period_scalar; + value: lifted, + }); + } + coords[axis] = lifted; } Ok(coords) } @@ -360,25 +297,19 @@ impl GlobalTopologyModel for SphericalModel { false } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - _coords: &mut [T; D], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + _coords: &mut [f64; D], + ) -> Result<(), GlobalTopologyModelError> { // Scaffold: full sphere-constrained projection will be expanded in #188. Ok(()) } - fn lift_for_orientation( + fn lift_for_orientation( &self, - coords: [T; D], + coords: [f64; D], periodic_offset: Option<[i8; D]>, - ) -> Result<[T; D], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; D], GlobalTopologyModelError> { if periodic_offset.is_some() { return Err(GlobalTopologyModelError::PeriodicOffsetsUnsupported { kind: TopologyKind::Spherical, @@ -401,25 +332,19 @@ impl GlobalTopologyModel for HyperbolicModel { false } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - _coords: &mut [T; D], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + _coords: &mut [f64; D], + ) -> Result<(), GlobalTopologyModelError> { // Scaffold: full model-constrained projection is future work. Ok(()) } - fn lift_for_orientation( + fn lift_for_orientation( &self, - coords: [T; D], + coords: [f64; D], periodic_offset: Option<[i8; D]>, - ) -> Result<[T; D], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; D], GlobalTopologyModelError> { if periodic_offset.is_some() { return Err(GlobalTopologyModelError::PeriodicOffsetsUnsupported { kind: TopologyKind::Hyperbolic, @@ -506,13 +431,10 @@ impl GlobalTopologyModel for GlobalTopologyModelAdapter { } } - fn canonicalize_point_in_place( + fn canonicalize_point_in_place( &self, - coords: &mut [T; D], - ) -> Result<(), GlobalTopologyModelError> - where - T: CoordinateScalar, - { + coords: &mut [f64; D], + ) -> Result<(), GlobalTopologyModelError> { match self { Self::Euclidean(model) => { GlobalTopologyModel::::canonicalize_point_in_place(model, coords) @@ -529,14 +451,11 @@ impl GlobalTopologyModel for GlobalTopologyModelAdapter { } } - fn lift_for_orientation( + fn lift_for_orientation( &self, - coords: [T; D], + coords: [f64; D], periodic_offset: Option<[i8; D]>, - ) -> Result<[T; D], GlobalTopologyModelError> - where - T: CoordinateScalar, - { + ) -> Result<[f64; D], GlobalTopologyModelError> { match self { Self::Euclidean(model) => { GlobalTopologyModel::::lift_for_orientation(model, coords, periodic_offset) @@ -682,6 +601,22 @@ mod tests { ); } + #[test] + fn toroidal_model_lift_rejects_overflowed_lifted_coordinates() { + let model = toroidal_model::<2>( + [f64::MAX, 3.0], + ToroidalConstructionMode::PeriodicImagePoint, + ); + let err = model + .lift_for_orientation([f64::MAX, 0.5_f64], Some([1, 0])) + .unwrap_err(); + assert_matches!( + err, + GlobalTopologyModelError::NonFiniteCoordinate { axis: 0, value } + if value.is_infinite() && value.is_sign_positive() + ); + } + #[test] fn toroidal_model_lift_ignores_offset_when_orientation_offsets_unsupported() { let model = toroidal_model::<2>([2.0, 3.0], ToroidalConstructionMode::Canonicalized); @@ -1369,13 +1304,10 @@ mod tests { } #[test] - fn scalar_conversion_error_includes_axis_context() { - // This test verifies the error structure for scalar conversion failures. - // In practice, conversion failures are rare with f64, but the error - // variant exists for completeness and custom scalar types. - let err = GlobalTopologyModelError::ScalarConversion { + fn non_finite_coordinate_error_includes_axis_context() { + let err = GlobalTopologyModelError::NonFiniteCoordinate { axis: 2, - value: 1.5e308_f64, + value: f64::INFINITY, }; let err_str = err.to_string(); @@ -1384,13 +1316,13 @@ mod tests { "Error should mention axis: {err_str}" ); assert!( - err_str.contains("1.5e308") || err_str.contains("value"), + err_str.contains("inf") || err_str.contains("value"), "Error should include problematic value: {err_str}" ); } // ========================================================================= - // Trait bounds and scalar tests + // f64 coordinate behavior tests // ========================================================================= #[test] diff --git a/src/topology/traits/topological_space.rs b/src/topology/traits/topological_space.rs index d1a2e449..3729d0a7 100644 --- a/src/topology/traits/topological_space.rs +++ b/src/topology/traits/topological_space.rs @@ -129,8 +129,8 @@ pub enum TopologyKind { /// Construction mode metadata for toroidal triangulations. /// /// This distinguishes between: -/// - canonicalized builds (`.canonicalized_toroidal(...)`) and -/// - true periodic quotient builds (`.toroidal(...)`). +/// - canonicalized builds (`.try_canonicalized_toroidal(...)`) and +/// - true periodic quotient builds (`.try_toroidal(...)`). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ToroidalConstructionMode { /// Canonicalized toroidal mode: coordinates are wrapped into the fundamental domain @@ -445,7 +445,7 @@ impl GlobalTopology { /// toroidal, hyperbolic) to enable topology-aware triangulation algorithms. /// /// The dimension is specified via the associated constant `DIM`, which must -/// match the dimension of the associated `Tds`. This ensures +/// match the dimension of the associated `Tds`. This ensures /// type safety and prevents dimension mismatches. /// /// # Future Use @@ -472,7 +472,7 @@ pub trait TopologicalSpace { /// The dimension of this topological space. /// /// This must match the dimension `D` of the associated triangulation - /// `Tds` to ensure geometric consistency. + /// `Tds` to ensure geometric consistency. const DIM: usize; /// Returns the kind of topological space. diff --git a/tests/README.md b/tests/README.md index c1c0db1e..b6792338 100644 --- a/tests/README.md +++ b/tests/README.md @@ -587,7 +587,7 @@ Canonical set extraction functions for comparing triangulation topology: ```rust use delaunay::prelude::query::{ - extract_vertex_coordinate_set, // HashSet> + extract_vertex_coordinate_set, // HashSet> extract_edge_set, // HashSet<(u128, u128)> extract_facet_identifier_set, // Result, FacetError> extract_hull_facet_set, // HashSet diff --git a/tests/benchmark_flip_fixtures.rs b/tests/benchmark_flip_fixtures.rs index 397e72c3..dcd7c931 100644 --- a/tests/benchmark_flip_fixtures.rs +++ b/tests/benchmark_flip_fixtures.rs @@ -17,11 +17,13 @@ mod flip_fixtures; #[path = "../benches/common/flip_workflows.rs"] mod flip_workflows; +use std::assert_matches; + use delaunay::prelude::construction::{ DelaunayConstructionFailure, DelaunayTriangulationConstructionError, }; use delaunay::prelude::flips::{FacetHandle, FlipError, RidgeHandle}; -use delaunay::prelude::tds::SimplexKey; +use delaunay::prelude::tds::{FacetError, SimplexKey}; use slotmap::KeyData; use flip_fixtures::{ @@ -29,7 +31,7 @@ use flip_fixtures::{ STABLE_POINTS_2D, STABLE_POINTS_3D, STABLE_POINTS_4D, STABLE_POINTS_5D, }; use flip_workflows::{ - CandidateFilter, FlipMoveKind, FlipWorkflowError, assert_same_topology, build_flip_dt, + CandidateFilter, FlipWorkflowError, assert_same_topology, build_flip_dt, facet_support_touches_adversarial_feature, flippable_k2_facet, flippable_k3_ridge, forward_k2, forward_k3, largest_volume_simplex, ridge_support_touches_adversarial_feature, roundtrip_k1, simplex_touches_adversarial_feature, snapshot_topology, verify_k1_roundtrip, @@ -162,24 +164,15 @@ fn invalid_facet_support_returns_specific_error() { .simplices() .next() .expect("stable 2D fixture should contain a simplex"); - let invalid_facet = FacetHandle::new(simplex_key, u8::MAX); - - let err = facet_support_touches_adversarial_feature(&base_dt, invalid_facet) - .expect_err("invalid facet support should be reported as an error"); - match err { - FlipWorkflowError::InvalidFacetSupportIndex { - facet, - facet_index, - vertex_count, - simplex_key: observed_simplex, - } => { - assert_eq!(facet, invalid_facet); - assert_eq!(facet_index, u8::MAX); - assert_eq!(vertex_count, 3); - assert_eq!(observed_simplex, simplex_key); + let err = FacetHandle::try_new(base_dt.tds(), simplex_key, u8::MAX) + .expect_err("invalid facet index should be rejected at construction"); + assert_eq!( + err, + FacetError::InvalidFacetIndex { + index: u8::MAX, + facet_count: 3, } - other => panic!("unexpected invalid facet error: {other}"), - } + ); } /// Verifies that invalid ridge support inspection reports a specific error. @@ -190,82 +183,48 @@ fn invalid_ridge_support_returns_specific_error() { .simplices() .next() .expect("stable 3D fixture should contain a simplex"); - let invalid_ridge = RidgeHandle::new(simplex_key, u8::MAX, u8::MAX); - - let err = ridge_support_touches_adversarial_feature(&base_dt, invalid_ridge) - .expect_err("invalid ridge support should be reported as an error"); - match err { - FlipWorkflowError::InvalidRidgeSupportIndex { - ridge, - omit_a, - omit_b, - vertex_count, + let err = RidgeHandle::try_new(base_dt.tds(), simplex_key, u8::MAX, u8::MAX) + .expect_err("invalid ridge indices should be rejected at construction"); + assert_matches!( + err, + FlipError::InvalidRidgeIndex { simplex_key: observed_simplex, - } => { - assert_eq!(ridge, invalid_ridge); - assert_eq!(omit_a, u8::MAX); - assert_eq!(omit_b, u8::MAX); - assert_eq!(vertex_count, 4); - assert_eq!(observed_simplex, simplex_key); - } - other => panic!("unexpected invalid ridge error: {other}"), - } + omit_a: u8::MAX, + omit_b: u8::MAX, + vertex_count: 4, + } if observed_simplex == simplex_key + ); - let duplicate_ridge = RidgeHandle::new(simplex_key, 0, 0); - let err = ridge_support_touches_adversarial_feature(&base_dt, duplicate_ridge) - .expect_err("duplicate ridge support indices should be reported as an error"); - match err { - FlipWorkflowError::DuplicateRidgeSupportIndex { - ridge, - omit_a, - omit_b, - vertex_count, + let err = RidgeHandle::try_new(base_dt.tds(), simplex_key, 0, 0) + .expect_err("duplicate ridge indices should be rejected at construction"); + assert_matches!( + err, + FlipError::InvalidRidgeIndex { simplex_key: observed_simplex, - } => { - assert_eq!(ridge, duplicate_ridge); - assert_eq!(omit_a, 0); - assert_eq!(omit_b, 0); - assert_eq!(vertex_count, 4); - assert_eq!(observed_simplex, simplex_key); - } - other => panic!("unexpected duplicate ridge error: {other}"), - } + omit_a: 0, + omit_b: 0, + vertex_count: 4, + } if observed_simplex == simplex_key + ); } /// Verifies that failed public flips preserve the typed [`FlipError`] source. #[test] fn forward_flip_failure_preserves_typed_source() { - let mut base_dt = build_flip_dt(STABLE_POINTS_2D).expect("stable 2D fixture should build"); + let base_dt = build_flip_dt(STABLE_POINTS_2D).expect("stable 2D fixture should build"); let (simplex_key, _) = base_dt .simplices() .next() .expect("stable 2D fixture should contain a simplex"); - let invalid_facet = FacetHandle::new(simplex_key, u8::MAX); - - let err = forward_k2(&mut base_dt, invalid_facet) - .expect_err("invalid k=2 facet should be reported as a flip failure"); - match err { - FlipWorkflowError::FlipFailed { - dimension, - move_kind, - source, - } => { - assert_eq!(dimension, 2); - assert_eq!(move_kind, FlipMoveKind::K2); - match *source { - FlipError::InvalidFacetIndex { - simplex_key: observed_simplex, - facet_index, - .. - } => { - assert_eq!(observed_simplex, simplex_key); - assert_eq!(facet_index, u8::MAX); - } - other => panic!("unexpected flip source: {other}"), - } + let err = FacetHandle::try_new(base_dt.tds(), simplex_key, u8::MAX) + .expect_err("invalid k=2 facet should fail before flip execution"); + assert_eq!( + err, + FacetError::InvalidFacetIndex { + index: u8::MAX, + facet_count: 3, } - other => panic!("unexpected forward flip error: {other}"), - } + ); } /// Verifies that topology mismatches include Jaccard diagnostics in the fail path. diff --git a/tests/circumsphere_debug_tools.rs b/tests/circumsphere_debug_tools.rs index dbcbbb31..61a34793 100644 --- a/tests/circumsphere_debug_tools.rs +++ b/tests/circumsphere_debug_tools.rs @@ -15,7 +15,7 @@ use delaunay::geometry::matrix::{Matrix, determinant}; use delaunay::geometry::util::hypot; -use delaunay::prelude::construction::{Vertex, vertex}; +use delaunay::prelude::construction::Vertex; use delaunay::prelude::geometry::*; use serde::{Deserialize, Serialize}; @@ -173,9 +173,9 @@ fn test_everything_debug() { /// Test 2D circumsphere methods with a triangle fn test_2d_circumsphere() { let vertices = vec![ - vertex!([0.0, 0.0], 0), - vertex!([1.0, 0.0], 1), - vertex!([0.0, 1.0], 2), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 2).unwrap(), ]; let test_points = test_points!( @@ -195,7 +195,7 @@ fn test_2d_circumsphere() { /// Test a single 2D point against all circumsphere methods fn test_2d_point( - vertices: &[Vertex], + vertices: &[Vertex], coords: [f64; 2], description: &str, center: &[f64; 2], @@ -207,7 +207,7 @@ fn test_2d_point( /// Generic function to test circumsphere methods for any dimension fn test_circumsphere_generic( dimension_name: &str, - vertices: &[Vertex], + vertices: &[Vertex], test_points: Vec<([f64; D], &str)>, ) where [f64; D]: Copy + Sized + Serialize + for<'de> Deserialize<'de>, @@ -224,7 +224,7 @@ fn test_circumsphere_generic( println!(); // Calculate circumcenter and circumradius - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); match (circumcenter(&vertex_points), circumradius(&vertex_points)) { (Ok(center), Ok(radius)) => { println!("Circumcenter: {:?}", center.to_array()); @@ -244,7 +244,7 @@ fn test_circumsphere_generic( /// Generic function to test a single point against circumsphere methods fn test_point_generic( - vertices: &[Vertex], + vertices: &[Vertex], coords: [f64; D], description: &str, center: &[f64; D], @@ -252,9 +252,10 @@ fn test_point_generic( ) where [f64; D]: Copy + Sized + Serialize + for<'de> Deserialize<'de>, { - let test_vertex: Vertex = vertex!(coords, 99); + let test_vertex: Vertex = + delaunay::prelude::Vertex::<_, _>::try_new_with_data(coords, 99).unwrap(); - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); let result_insphere = insphere(&vertex_points, Point::from(&test_vertex)); let result_distance = insphere_distance(&vertex_points, Point::from(&test_vertex)); let result_lifted = insphere_lifted(&vertex_points, Point::from(&test_vertex)); @@ -289,10 +290,10 @@ fn test_point_generic( fn test_3d_circumsphere() { // Create a unit tetrahedron: (0,0,0), (1,0,0), (0,1,0), (0,0,1) let vertices = vec![ - vertex!([0.0, 0.0, 0.0], 0), - vertex!([1.0, 0.0, 0.0], 1), - vertex!([0.0, 1.0, 0.0], 2), - vertex!([0.0, 0.0, 1.0], 3), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 2).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 3).unwrap(), ]; let test_points = test_points!( @@ -310,7 +311,7 @@ fn test_3d_circumsphere() { /// Test a single 3D point against all circumsphere methods fn test_3d_point( - vertices: &[Vertex], + vertices: &[Vertex], coords: [f64; 3], description: &str, center: &[f64; 3], @@ -323,11 +324,11 @@ fn test_3d_point( fn test_4d_circumsphere() { // Create a unit 4-simplex: vertices at origin and unit vectors along each axis let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0], 0), - vertex!([1.0, 0.0, 0.0, 0.0], 1), - vertex!([0.0, 1.0, 0.0, 0.0], 2), - vertex!([0.0, 0.0, 1.0, 0.0], 3), - vertex!([0.0, 0.0, 0.0, 1.0], 4), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0, 0.0], 2).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0, 0.0], 3).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 1.0], 4).unwrap(), ]; let test_points = test_points!( @@ -345,7 +346,7 @@ fn test_4d_circumsphere() { /// Test a single 4D point against all circumsphere methods fn test_4d_point( - vertices: &[Vertex], + vertices: &[Vertex], coords: [f64; 4], description: &str, center: &[f64; 4], @@ -371,16 +372,16 @@ fn test_4d_circumsphere_methods() { println!("============================================="); // Create a unit 4-simplex: vertices at origin and unit vectors along each axis - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0, 0.0], 1), - vertex!([1.0, 0.0, 0.0, 0.0], 1), - vertex!([0.0, 1.0, 0.0, 0.0], 1), - vertex!([0.0, 0.0, 1.0, 0.0], 1), - vertex!([0.0, 0.0, 0.0, 1.0], 2), + let vertices: Vec> = vec![ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 1.0], 2).unwrap(), ]; // Calculate circumcenter and circumradius for reference - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); match circumcenter(&vertex_points) { Ok(center) => { println!("Circumcenter: {:?}", center.to_array()); @@ -432,12 +433,12 @@ fn test_circumsphere_containment() { // This creates a 4D simplex with vertices at the origin and unit vectors // along each coordinate axis. The circumcenter is at [0.5, 0.5, 0.5, 0.5] and // the circumradius is √4/2 = 1.0. - let vertices: [Vertex; 5] = [ - vertex!([0.0, 0.0, 0.0, 0.0], 0), // Origin - vertex!([1.0, 0.0, 0.0, 0.0], 1), // Unit vector along x-axis - vertex!([0.0, 1.0, 0.0, 0.0], 2), // Unit vector along y-axis - vertex!([0.0, 0.0, 1.0, 0.0], 3), // Unit vector along z-axis - vertex!([0.0, 0.0, 0.0, 1.0], 4), // Unit vector along w-axis + let vertices: [Vertex; 5] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 0).unwrap(), // Origin + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0, 0.0], 1).unwrap(), // Unit vector along x-axis + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0, 0.0], 2).unwrap(), // Unit vector along y-axis + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0, 0.0], 3).unwrap(), // Unit vector along z-axis + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 1.0], 4).unwrap(), // Unit vector along w-axis ]; println!("4D simplex vertices:"); @@ -453,16 +454,16 @@ fn test_circumsphere_containment() { // Test points that should be inside the circumsphere // These are points with small coordinates that should be well within // the circumsphere radius of √4/2 = 1.0 - let test_points_inside: [Vertex; 5] = [ - vertex!([0.25, 0.25, 0.25, 0.25], 10), - vertex!([0.1, 0.1, 0.1, 0.1], 11), - vertex!([0.2, 0.2, 0.2, 0.2], 12), - vertex!([0.3, 0.2, 0.1, 0.0], 13), - vertex!([0.0, 0.0, 0.0, 0.0], 14), // Origin should be inside + let test_points_inside: [Vertex; 5] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.25, 0.25, 0.25, 0.25], 10).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.1, 0.1, 0.1, 0.1], 11).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.2, 0.2, 0.2, 0.2], 12).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.3, 0.2, 0.1, 0.0], 13).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 14).unwrap(), // Origin should be inside ]; // Calculate circumcenter and circumradius for testing - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); let (Ok(center), Ok(radius)) = (circumcenter(&vertex_points), circumradius(&vertex_points)) else { println!("Error calculating circumcenter or circumradius"); @@ -479,18 +480,18 @@ fn test_circumsphere_containment() { // Test points that should be outside (or on the boundary) of the circumsphere // These include points with large coordinates and points along the axes // that extend beyond the simplex vertices - let test_points_outside: [Vertex; 6] = [ - vertex!([2.0, 2.0, 2.0, 2.0], 20), - vertex!([1.0, 1.0, 1.0, 1.0], 21), // boundary - vertex!([0.8, 0.8, 0.8, 0.8], 22), - vertex!([1.5, 0.0, 0.0, 0.0], 23), - vertex!([0.0, 1.5, 0.0, 0.0], 24), - vertex!([0.0, 0.0, 1.5, 0.0], 25), + let test_points_outside: [Vertex; 6] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([2.0, 2.0, 2.0, 2.0], 20).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 1.0, 1.0, 1.0], 21).unwrap(), // boundary + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.8, 0.8, 0.8, 0.8], 22).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.5, 0.0, 0.0, 0.0], 23).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.5, 0.0, 0.0], 24).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.5, 0.0], 25).unwrap(), ]; println!("Testing points that should be OUTSIDE (or on boundary) the circumsphere:"); for (i, point) in test_points_outside.iter().enumerate() { - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); let result_determinant = insphere(&vertex_points, Point::from(point)); let result_distance = insphere_distance(&vertex_points, Point::from(point)); let coords = *point.point().coords(); @@ -521,7 +522,7 @@ fn test_circumsphere_containment() { // These should be on the boundary of the circumsphere (distance = radius) println!("Testing the simplex vertices themselves:"); for (i, vertex) in vertices.iter().enumerate() { - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); let result = insphere(&vertex_points, Point::from(vertex)); let coords = *vertex.point().coords(); println!( @@ -544,17 +545,17 @@ fn test_circumsphere_containment() { // Additional boundary testing with points on edges and faces of the tetrahedron // These points lie on the boundary of the 4D simplex and test // numerical stability near the boundary - let boundary_points: [Vertex; 5] = [ - vertex!([0.5, 0.5, 0.0, 0.0], 30), - vertex!([0.5, 0.0, 0.5, 0.0], 31), - vertex!([0.0, 0.5, 0.5, 0.0], 32), - vertex!([0.33, 0.33, 0.33, 0.01], 33), - vertex!([0.25, 0.25, 0.25, 0.25], 34), + let boundary_points: [Vertex; 5] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.5, 0.5, 0.0, 0.0], 30).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.5, 0.0, 0.5, 0.0], 31).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.5, 0.5, 0.0], 32).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.33, 0.33, 0.33, 0.01], 33).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.25, 0.25, 0.25, 0.25], 34).unwrap(), ]; println!("Testing boundary/edge points:"); for (i, point) in boundary_points.iter().enumerate() { - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); let result = insphere(&vertex_points, Point::from(point)); let coords = *point.point().coords(); println!( @@ -583,7 +584,7 @@ fn test_simplex_orientation() { let vertices = create_unit_4d_simplex(); // Test the original 4D simplex's orientation - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); let orientation_original = simplex_orientation(&vertex_points); println!( "Original 4D simplex orientation: {}", @@ -598,8 +599,7 @@ fn test_simplex_orientation() { // Create a negatively oriented 4D simplex by swapping two vertices let vertices_negative = create_negative_4d_simplex(); - let vertex_points_negative: Vec> = - vertices_negative.iter().map(Point::from).collect(); + let vertex_points_negative: Vec> = vertices_negative.iter().map(Point::from).collect(); let orientation_negative = simplex_orientation(&vertex_points_negative); println!( "Negatively oriented 4D simplex: {}", @@ -612,15 +612,14 @@ fn test_simplex_orientation() { ); // Test 3D orientation (tetrahedron) for comparison - let tetrahedron_vertices: [Vertex; 4] = [ - vertex!([0.0, 0.0, 0.0], 0), // Origin - vertex!([1.0, 0.0, 0.0], 1), // Unit vector along x-axis - vertex!([0.0, 1.0, 0.0], 2), // Unit vector along y-axis - vertex!([0.0, 0.0, 1.0], 3), // Unit vector along z-axis + let tetrahedron_vertices: [Vertex; 4] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 0).unwrap(), // Origin + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(), // Unit vector along x-axis + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 2).unwrap(), // Unit vector along y-axis + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 3).unwrap(), // Unit vector along z-axis ]; - let tetrahedron_points: Vec> = - tetrahedron_vertices.iter().map(Point::from).collect(); + let tetrahedron_points: Vec> = tetrahedron_vertices.iter().map(Point::from).collect(); let orientation_3d = simplex_orientation(&tetrahedron_points); println!( "3D tetrahedron orientation: {}", @@ -633,13 +632,13 @@ fn test_simplex_orientation() { ); // Test 2D orientation (triangle) - let triangle_vertices: [Vertex; 3] = [ - vertex!([0.0, 0.0], 0), - vertex!([1.0, 0.0], 1), - vertex!([0.0, 1.0], 2), + let triangle_vertices: [Vertex; 3] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 2).unwrap(), ]; - let triangle_points: Vec> = triangle_vertices.iter().map(Point::from).collect(); + let triangle_points: Vec> = triangle_vertices.iter().map(Point::from).collect(); let orientation_2d = simplex_orientation(&triangle_points); println!( "2D triangle orientation: {}", @@ -652,13 +651,13 @@ fn test_simplex_orientation() { ); // Test 2D orientation with reversed vertex order - let triangle_vertices_reversed: [Vertex; 3] = [ - vertex!([0.0, 0.0], 0), - vertex!([0.0, 1.0], 2), // Swapped order - vertex!([1.0, 0.0], 1), // Swapped order + let triangle_vertices_reversed: [Vertex; 3] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 2).unwrap(), // Swapped order + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 1).unwrap(), // Swapped order ]; - let triangle_points_reversed: Vec> = + let triangle_points_reversed: Vec> = triangle_vertices_reversed.iter().map(Point::from).collect(); let orientation_2d_reversed = simplex_orientation(&triangle_points_reversed); println!( @@ -672,13 +671,13 @@ fn test_simplex_orientation() { ); // Test degenerate case (collinear points in 2D) - let collinear_vertices: [Vertex; 3] = [ - vertex!([0.0, 0.0], 0), - vertex!([1.0, 0.0], 1), - vertex!([2.0, 0.0], 2), // Collinear point + let collinear_vertices: [Vertex; 3] = [ + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([2.0, 0.0], 2).unwrap(), // Collinear point ]; - let collinear_points: Vec> = collinear_vertices.iter().map(Point::from).collect(); + let collinear_points: Vec> = collinear_vertices.iter().map(Point::from).collect(); let orientation_collinear = simplex_orientation(&collinear_points); println!( "Collinear 2D points: {}", @@ -691,23 +690,23 @@ fn test_simplex_orientation() { ); } -fn create_unit_4d_simplex() -> [Vertex; 5] { +fn create_unit_4d_simplex() -> [Vertex; 5] { [ - vertex!([0.0, 0.0, 0.0, 0.0], 0), - vertex!([1.0, 0.0, 0.0, 0.0], 1), - vertex!([0.0, 1.0, 0.0, 0.0], 2), - vertex!([0.0, 0.0, 1.0, 0.0], 3), - vertex!([0.0, 0.0, 0.0, 1.0], 4), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0, 0.0], 2).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0, 0.0], 3).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 1.0], 4).unwrap(), ] } -fn create_negative_4d_simplex() -> [Vertex; 5] { +fn create_negative_4d_simplex() -> [Vertex; 5] { [ - vertex!([0.0, 0.0, 0.0, 0.0], 0), - vertex!([0.0, 1.0, 0.0, 0.0], 2), - vertex!([1.0, 0.0, 0.0, 0.0], 1), - vertex!([0.0, 0.0, 1.0, 0.0], 3), - vertex!([0.0, 0.0, 0.0, 1.0], 4), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0, 0.0], 2).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0, 0.0], 3).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 1.0], 4).unwrap(), ] } @@ -718,11 +717,12 @@ fn demonstrate_orientation_impact_on_circumsphere() { let vertices = create_unit_4d_simplex(); let vertices_negative = create_negative_4d_simplex(); - let test_point: Vertex = vertex!([0.25, 0.25, 0.25, 0.25], 100); + let test_point: Vertex = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.25, 0.25, 0.25, 0.25], 100) + .unwrap(); - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); - let vertex_points_negative: Vec> = - vertices_negative.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points_negative: Vec> = vertices_negative.iter().map(Point::from).collect(); let inside_positive = insphere(&vertex_points, Point::from(&test_point)); let inside_negative = insphere(&vertex_points_negative, Point::from(&test_point)); @@ -759,8 +759,7 @@ fn test_3d_simplex_analysis() { let simplex_vertices_3d = create_3d_test_simplex(); - let simplex_points_3d: Vec> = - simplex_vertices_3d.iter().map(Point::from).collect(); + let simplex_points_3d: Vec> = simplex_vertices_3d.iter().map(Point::from).collect(); match circumcenter(&simplex_points_3d) { Ok(circumcenter_3d) => match circumradius(&simplex_points_3d) { Ok(circumradius_3d) => { @@ -777,15 +776,19 @@ fn test_3d_simplex_analysis() { } } -fn create_3d_test_simplex() -> Vec> { - let vertex1_3d: Vertex = vertex!([0.0, 0.0, 0.0], 1); - let vertex2_3d = vertex!([1.0, 0.0, 0.0], 1); - let vertex3_3d = vertex!([0.0, 1.0, 0.0], 1); - let vertex4_3d = vertex!([0.0, 0.0, 1.0], 2); +fn create_3d_test_simplex() -> Vec> { + let vertex1_3d: Vertex = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1).unwrap(); + let vertex2_3d = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(); + let vertex3_3d = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(); + let vertex4_3d = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 2).unwrap(); vec![vertex1_3d, vertex2_3d, vertex3_3d, vertex4_3d] } -fn print_3d_simplex_info(circumcenter_3d: &Point, circumradius_3d: f64) { +fn print_3d_simplex_info(circumcenter_3d: &Point<3>, circumradius_3d: f64) { println!("3D Simplex vertices:"); println!(" v1: (0, 0, 0)"); println!(" v2: (1, 0, 0)"); @@ -798,12 +801,13 @@ fn print_3d_simplex_info(circumcenter_3d: &Point, circumradius_3d: f64) } fn test_point_against_3d_simplex( - simplex_vertices_3d: &[Vertex], - circumcenter_3d: &Point, + simplex_vertices_3d: &[Vertex], + circumcenter_3d: &Point<3>, circumradius_3d: f64, ) { // Test the point [0.9, 0.9, 0.9] - let test_vertex_3d = vertex!([0.9, 0.9, 0.9], 3); + let test_vertex_3d = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.9, 0.9, 0.9], 3).unwrap(); // Calculate distance from circumcenter to test point let distance_to_test_3d = distance(&circumcenter_3d.to_array(), &[0.9, 0.9, 0.9]); @@ -823,11 +827,8 @@ fn test_point_against_3d_simplex( test_boundary_vertex_case(simplex_vertices_3d); } -fn test_circumsphere_methods( - simplex_vertices: &[Vertex], - test_vertex: Vertex, -) { - let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); +fn test_circumsphere_methods(simplex_vertices: &[Vertex], test_vertex: Vertex) { + let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); match insphere(&simplex_points, Point::from(&test_vertex)) { Ok(standard_method_3d) => match insphere_lifted(&simplex_points, Point::from(&test_vertex)) { @@ -841,11 +842,11 @@ fn test_circumsphere_methods( } } -fn test_boundary_vertex_case(simplex_vertices: &[Vertex]) { +fn test_boundary_vertex_case(simplex_vertices: &[Vertex]) { println!(); println!("Testing boundary vertex (vertex1):"); let vertex1 = simplex_vertices[0]; - let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); + let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); match insphere(&simplex_points, Point::from(&vertex1)) { Ok(standard_vertex) => match insphere_lifted(&simplex_points, Point::from(&vertex1)) { Ok(matrix_vertex) => { @@ -861,7 +862,7 @@ fn test_boundary_vertex_case(simplex_vertices: &[Vertex]) { } // Type alias to simplify complex return type -type Setup3DResult = (Vec>, [f64; 3], Vertex); +type Setup3DResult = (Vec>, [f64; 3], Vertex); /// Set up the 3D test matrix data fn setup_3d_matrix_test() -> Setup3DResult { @@ -870,10 +871,11 @@ fn setup_3d_matrix_test() -> Setup3DResult { println!("============================================="); // Create the 3D simplex: vertices at (0,0,0), (1,0,0), (0,1,0), (0,0,1) - let vertex1: Vertex = vertex!([0.0, 0.0, 0.0], 1); - let vertex2 = vertex!([1.0, 0.0, 0.0], 1); - let vertex3 = vertex!([0.0, 1.0, 0.0], 1); - let vertex4 = vertex!([0.0, 0.0, 1.0], 2); + let vertex1: Vertex = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1).unwrap(); + let vertex2 = delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(); + let vertex3 = delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(); + let vertex4 = delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 2).unwrap(); let simplex_vertices = vec![vertex1, vertex2, vertex3, vertex4]; println!("3D Simplex vertices:"); @@ -885,7 +887,7 @@ fn setup_3d_matrix_test() -> Setup3DResult { // Test point let test_point = [0.9, 0.9, 0.9]; - let test_vertex = vertex!(test_point, 3); + let test_vertex = delaunay::prelude::Vertex::<_, _>::try_new_with_data(test_point, 3).unwrap(); // Get reference vertex (first vertex) let ref_coords = [0.0, 0.0, 0.0]; @@ -896,7 +898,7 @@ fn setup_3d_matrix_test() -> Setup3DResult { } /// Build and analyze the matrix for the 3D test -fn build_and_analyze_matrix(simplex_vertices: &[Vertex]) -> (f64, bool) { +fn build_and_analyze_matrix(simplex_vertices: &[Vertex]) -> (f64, bool) { // Manually build the matrix as in the matrix method let mut matrix = Matrix::<4>::zero(); // D+1 x D+1 for D=3 @@ -967,7 +969,7 @@ fn build_and_analyze_matrix(simplex_vertices: &[Vertex]) -> (f64, b println!("Determinant: {det:.6}"); // Check simplex orientation and return determinant and matrix result - let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); + let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); match simplex_orientation(&simplex_points) { Ok(is_positive_orientation) => { let is_positive = matches!(is_positive_orientation, Orientation::POSITIVE); @@ -1009,11 +1011,11 @@ fn build_and_analyze_matrix(simplex_vertices: &[Vertex]) -> (f64, b /// Compare methods with geometry for the 3D test fn compare_methods_with_geometry( - simplex_vertices: &[Vertex], + simplex_vertices: &[Vertex], test_point: [f64; 3], - test_vertex: Vertex, + test_vertex: Vertex, ) -> Option<(f64, f64, bool, InSphere, InSphere)> { - let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); + let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); match circumcenter(&simplex_points) { Ok(circumcenter) => { match circumradius(&simplex_points) { @@ -1105,7 +1107,7 @@ fn print_method_comparison_results( println!("✓ Both methods agree with each other"); } else { println!("⚠ Methods disagree (expected for this matrix formulation)"); - println!(" The matrix method uses coordinates relative to the first vertex,"); + println!(" The matrix method uses coordinates relative to the first "); println!(" which produces an inverted sign convention compared to the standard method."); println!(" Both methods are mathematically correct but use different interpretations."); } @@ -1133,13 +1135,14 @@ fn debug_3d_circumsphere_properties() { println!("=== 3D Unit Tetrahedron Analysis ==="); // Unit tetrahedron: vertices at (0,0,0), (1,0,0), (0,1,0), (0,0,1) - let vertex1: Vertex = vertex!([0.0, 0.0, 0.0], 1); - let vertex2 = vertex!([1.0, 0.0, 0.0], 1); - let vertex3 = vertex!([0.0, 1.0, 0.0], 1); - let vertex4 = vertex!([0.0, 0.0, 1.0], 2); + let vertex1: Vertex = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 1).unwrap(); + let vertex2 = delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(); + let vertex3 = delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 1).unwrap(); + let vertex4 = delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 2).unwrap(); let simplex_vertices = [vertex1, vertex2, vertex3, vertex4]; - let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); + let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); let center = circumcenter(&simplex_points).unwrap(); let radius = circumradius(&simplex_points).unwrap(); @@ -1154,7 +1157,8 @@ fn debug_3d_circumsphere_properties() { distance_to_center < radius ); - let test_vertex: Vertex = vertex!([0.9, 0.9, 0.9], 4); + let test_vertex: Vertex = + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.9, 0.9, 0.9], 4).unwrap(); let standard_result = insphere_distance(&simplex_points, Point::from(&test_vertex)).unwrap(); let matrix_result = insphere_lifted(&simplex_points, Point::from(&test_vertex)).unwrap(); @@ -1168,15 +1172,15 @@ fn debug_4d_circumsphere_properties() { println!("\n=== 4D Symmetric Simplex Analysis ==="); // Regular 4D simplex with vertices forming a specific pattern - let vertex1: Vertex = vertex!([1.0, 1.0, 1.0, 1.0]); - let vertex2 = vertex!([1.0, -1.0, -1.0, -1.0]); - let vertex3 = vertex!([-1.0, 1.0, -1.0, -1.0]); - let vertex4 = vertex!([-1.0, -1.0, 1.0, -1.0]); - let vertex5 = vertex!([-1.0, -1.0, -1.0, 1.0]); + let vertex1: Vertex<(), 4> = + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 1.0, 1.0]).unwrap(); + let vertex2 = delaunay::prelude::Vertex::<(), _>::try_new([1.0, -1.0, -1.0, -1.0]).unwrap(); + let vertex3 = delaunay::prelude::Vertex::<(), _>::try_new([-1.0, 1.0, -1.0, -1.0]).unwrap(); + let vertex4 = delaunay::prelude::Vertex::<(), _>::try_new([-1.0, -1.0, 1.0, -1.0]).unwrap(); + let vertex5 = delaunay::prelude::Vertex::<(), _>::try_new([-1.0, -1.0, -1.0, 1.0]).unwrap(); let simplex_vertices_4d = [vertex1, vertex2, vertex3, vertex4, vertex5]; - let simplex_points_4d: Vec> = - simplex_vertices_4d.iter().map(Point::from).collect(); + let simplex_points_4d: Vec> = simplex_vertices_4d.iter().map(Point::from).collect(); let center_4d = circumcenter(&simplex_points_4d).unwrap(); let radius_4d = circumradius(&simplex_points_4d).unwrap(); @@ -1191,7 +1195,8 @@ fn debug_4d_circumsphere_properties() { distance_to_center_4d < radius_4d ); - let origin_vertex: Vertex = vertex!([0.0, 0.0, 0.0, 0.0]); + let origin_vertex: Vertex<(), 4> = + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(); let standard_result_4d = insphere_distance(&simplex_points_4d, Point::from(&origin_vertex)).unwrap(); @@ -1207,23 +1212,24 @@ fn compare_circumsphere_methods() { println!("\n=== Comparing Circumsphere Methods ==="); // Compare results between standard and matrix methods - let vertex1: Vertex = vertex!([0.0, 0.0]); - let vertex2 = vertex!([1.0, 0.0]); - let vertex3 = vertex!([0.0, 1.0]); + let vertex1: Vertex<(), 2> = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(); + let vertex2 = delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(); + let vertex3 = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(); let simplex_vertices = [vertex1, vertex2, vertex3]; // Test various points let test_points = [ - Point::new([0.1, 0.1]), // Should be inside - Point::new([0.5, 0.5]), // Circumcenter region - Point::new([10.0, 10.0]), // Far outside - Point::new([0.25, 0.25]), // Inside - Point::new([2.0, 2.0]), // Outside + Point::try_new([0.1, 0.1]).expect("finite point coordinates"), // Should be inside + Point::try_new([0.5, 0.5]).expect("finite point coordinates"), // Circumcenter region + Point::try_new([10.0, 10.0]).expect("finite point coordinates"), // Far outside + Point::try_new([0.25, 0.25]).expect("finite point coordinates"), // Inside + Point::try_new([2.0, 2.0]).expect("finite point coordinates"), // Outside ]; for (i, point) in test_points.iter().enumerate() { - let test_vertex: Vertex = vertex!(point.to_array()); - let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); + let test_vertex: Vertex<(), 2> = + delaunay::prelude::Vertex::<(), _>::try_new(point.to_array()).unwrap(); + let simplex_points: Vec> = simplex_vertices.iter().map(Point::from).collect(); let standard_result = insphere_distance(&simplex_points, Point::from(&test_vertex)).unwrap(); @@ -1300,13 +1306,13 @@ fn test_single_2d_point() { // Create a unit triangle: (0,0), (1,0), (0,1) let vertices = vec![ - vertex!([0.0, 0.0], 0), - vertex!([1.0, 0.0], 1), - vertex!([0.0, 1.0], 2), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0], 2).unwrap(), ]; // Calculate circumcenter and circumradius - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); match (circumcenter(&vertex_points), circumradius(&vertex_points)) { (Ok(center), Ok(radius)) => { println!("Triangle vertices:"); @@ -1342,14 +1348,14 @@ fn test_single_3d_point() { // Create a unit tetrahedron: (0,0,0), (1,0,0), (0,1,0), (0,0,1) let vertices = vec![ - vertex!([0.0, 0.0, 0.0], 0), - vertex!([1.0, 0.0, 0.0], 1), - vertex!([0.0, 1.0, 0.0], 2), - vertex!([0.0, 0.0, 1.0], 3), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0], 2).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0], 3).unwrap(), ]; // Calculate circumcenter and circumradius - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); match (circumcenter(&vertex_points), circumradius(&vertex_points)) { (Ok(center), Ok(radius)) => { println!("Tetrahedron vertices:"); @@ -1385,15 +1391,15 @@ fn test_single_4d_point() { // Create a unit 4-simplex: vertices at origin and unit vectors along each axis let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0], 0), - vertex!([1.0, 0.0, 0.0, 0.0], 1), - vertex!([0.0, 1.0, 0.0, 0.0], 2), - vertex!([0.0, 0.0, 1.0, 0.0], 3), - vertex!([0.0, 0.0, 0.0, 1.0], 4), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 0.0], 0).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([1.0, 0.0, 0.0, 0.0], 1).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 1.0, 0.0, 0.0], 2).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 1.0, 0.0], 3).unwrap(), + delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0, 0.0, 1.0], 4).unwrap(), ]; // Calculate circumcenter and circumradius - let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); + let vertex_points: Vec> = vertices.iter().map(Point::from).collect(); match (circumcenter(&vertex_points), circumradius(&vertex_points)) { (Ok(center), Ok(radius)) => { println!("4D simplex vertices:"); diff --git a/tests/coordinate_conversion_errors.rs b/tests/coordinate_conversion_errors.rs index 771fc25b..8d35f81b 100644 --- a/tests/coordinate_conversion_errors.rs +++ b/tests/coordinate_conversion_errors.rs @@ -3,140 +3,41 @@ //! This module tests that functions properly handle coordinate conversion errors //! when dealing with extreme values, NaN, infinity, etc. -use std::assert_matches; - use delaunay::prelude::geometry::*; -// ============================================================================= -// GEOMETRIC PREDICATES ERROR TESTS -// ============================================================================= - -#[test] -fn test_insphere_with_nan_coordinates() { - // Test 3D insphere with NaN coordinates - let points = vec![ - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), - Point::new([f64::NAN, 0.5, 0.5]), // Point with NaN - ]; - let test_point = Point::new([0.5, 0.5, 0.5]); - - // The function should return an error due to NaN coordinate - let result = insphere(&points, test_point); - - match result { - Err(CoordinateConversionError::NonFiniteValue { .. }) => { - // Expected error type - } - other => panic!("Expected CoordinateConversionError::NonFiniteValue, got: {other:?}"), - } -} - -#[test] -fn test_insphere_with_infinity_coordinates() { - // Test 3D insphere with infinity coordinates - let points = vec![ - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), - Point::new([f64::INFINITY, 0.5, 0.5]), // Point with positive infinity - ]; - let test_point = Point::new([0.5, 0.5, 0.5]); - - // The function should return an error due to infinity coordinate - let result = insphere(&points, test_point); - - match result { - Err(CoordinateConversionError::NonFiniteValue { .. }) => { - // Expected error type - } - other => panic!("Expected CoordinateConversionError::NonFiniteValue, got: {other:?}"), - } -} - -#[test] -fn test_insphere_2d_with_nan_coordinates() { - // Test 2D insphere with NaN coordinates (using 2D triangle) - let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([f64::NAN, 1.0]), // Point with NaN - ]; - let test_point = Point::new([0.5, 0.5]); - - // The function should return an error due to NaN coordinate - let result = insphere(&points, test_point); - - match result { - Err(CoordinateConversionError::NonFiniteValue { .. }) => { - // Expected error type - } - other => panic!("Expected CoordinateConversionError::NonFiniteValue, got: {other:?}"), - } -} - -#[test] -fn test_simplex_orientation_with_infinity_coordinates() { - // Test simplex orientation with infinity coordinates - let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([f64::NEG_INFINITY, 1.0]), // Point with negative infinity - ]; - - // The function should return an error due to infinity coordinate - let result = simplex_orientation(&points); - - match result { - Err(CoordinateConversionError::NonFiniteValue { .. }) => { - // Expected error type - } - other => panic!("Expected CoordinateConversionError::NonFiniteValue, got: {other:?}"), - } -} - -// ============================================================================= -// UTILITY FUNCTIONS ERROR TESTS -// ============================================================================= - -#[test] -fn test_circumcenter_with_nan_coordinates() { - // Test circumcenter with NaN coordinates - let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([f64::NAN, 1.0]), // Point with NaN - ]; - - // The function should return an error due to NaN coordinate - let result = circumcenter(&points); - - assert_matches!( - result, - Err(CircumcenterError::CoordinateConversion { - source: CoordinateConversionError::NonFiniteValue { .. }, +fn assert_invalid_coordinate( + coords: [f64; D], + coordinate_index: usize, + coordinate_value: InvalidCoordinateValue, +) { + assert_eq!( + Point::::try_new(coords), + Err(CoordinateValidationError::InvalidCoordinate { + coordinate_index, + coordinate_value, + dimension: D, }) ); } #[test] -fn test_circumradius_with_infinity_coordinates() { - // Test circumradius with infinity coordinates - let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, f64::INFINITY]), // Point with positive infinity - ]; - - // The function should return an error due to infinity coordinate - let result = circumradius(&points); - - assert_matches!( - result, - Err(CircumcenterError::CoordinateConversion { - source: CoordinateConversionError::NonFiniteValue { .. }, - }) +fn test_point_try_new_rejects_non_finite_coordinates() { + assert_invalid_coordinate([f64::NAN, 0.5, 0.5], 0, InvalidCoordinateValue::Nan); + assert_invalid_coordinate( + [f64::INFINITY, 0.5, 0.5], + 0, + InvalidCoordinateValue::PositiveInfinity, + ); + assert_invalid_coordinate([f64::NAN, 1.0], 0, InvalidCoordinateValue::Nan); + assert_invalid_coordinate( + [f64::NEG_INFINITY, 1.0], + 0, + InvalidCoordinateValue::NegativeInfinity, + ); + assert_invalid_coordinate( + [0.0, f64::INFINITY], + 1, + InvalidCoordinateValue::PositiveInfinity, ); } @@ -188,52 +89,6 @@ fn test_hypot_with_infinity_values() { ); } -// ============================================================================= -// ROBUST PREDICATES ERROR TESTS -// ============================================================================= - -#[test] -fn test_robust_insphere_with_nan() { - // Test robust insphere with NaN coordinates - let points = vec![ - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), - Point::new([f64::NAN, 0.5, 0.5]), // Point with NaN - ]; - let test_point = Point::new([0.5, 0.5, 0.5]); - - let result = robust_insphere(&points, &test_point); - assert!( - matches!( - result, - Err(CoordinateConversionError::NonFiniteValue { .. }) - ), - "Expected CoordinateConversionError::NonFiniteValue, got: {result:?}" - ); -} - -#[test] -fn test_robust_insphere_with_infinity() { - // Test robust insphere with infinity coordinates - let points = vec![ - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), - Point::new([f64::NEG_INFINITY, 0.5, 0.5]), // Point with negative infinity - ]; - let test_point = Point::new([0.5, 0.5, 0.5]); - - let result = robust_insphere(&points, &test_point); - assert!( - matches!( - result, - Err(CoordinateConversionError::NonFiniteValue { .. }) - ), - "Expected CoordinateConversionError::NonFiniteValue, got: {result:?}" - ); -} - // ============================================================================= // ERROR MESSAGE VERIFICATION TESTS // ============================================================================= @@ -241,53 +96,31 @@ fn test_robust_insphere_with_infinity() { #[test] fn test_error_message_contains_context() { // Test that error messages contain useful context information - let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([f64::NAN, 1.0]), // Point with NaN at coordinate index 0 - ]; - - let result = circumcenter(&points); - - if let Err(error) = result { - let error_msg = error.to_string(); + let Err(error) = Point::<2>::try_new([f64::NAN, 1.0]) else { + panic!("Expected an error, but got Ok"); + }; + let error_msg = error.to_string(); - // Error message should contain useful context - assert!(error_msg.contains("NaN") || error_msg.contains("non-finite")); + // Error message should contain useful context + assert!(error_msg.contains("NaN") || error_msg.contains("non-finite")); - // Should identify the problematic value type - assert!(error_msg.contains("coordinate") || error_msg.contains("value")); - } else { - panic!("Expected an error, but got Ok"); - } + // Should identify the problematic value type + assert!(error_msg.contains("coordinate") || error_msg.contains("value")); } #[test] fn test_infinity_error_message_contains_context() { - // Test that infinity error messages contain useful context using insphere_distance - // which uses hypot internally and should handle problematic coordinates - let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([0.0, 1.0]), // Valid triangle - ]; - let test_point = Point::new([f64::INFINITY, 1.0]); // Point with infinity - - let result = insphere_distance(&points, test_point); - - if let Err(error) = result { - let error_msg = error.to_string(); + let Err(error) = Point::<2>::try_new([f64::INFINITY, 1.0]) else { + panic!("Expected an error, but got Ok"); + }; + let error_msg = error.to_string(); - // Error message should contain useful context about infinity or non-finite values - assert!( - error_msg.contains("inf") - || error_msg.contains("infinite") - || error_msg.contains("non-finite") - ); - } else { - // For this test, we might get Ok since hypot can handle infinity - // In that case, we just verify the function doesn't crash - } + // Error message should contain useful context about infinity or non-finite values + assert!( + error_msg.contains("inf") + || error_msg.contains("infinite") + || error_msg.contains("non-finite") + ); } // ============================================================================= @@ -299,9 +132,9 @@ fn test_subnormal_values_handling() { // Test that subnormal values are handled correctly (should not error) let subnormal = f64::MIN_POSITIVE / 2.0; let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([subnormal, 1.0]), // Subnormal value + Point::try_new([0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([subnormal, 1.0]).expect("finite point coordinates"), // Subnormal value ]; // Subnormal values should be handled without error (they're finite) @@ -319,9 +152,9 @@ fn test_subnormal_values_handling() { fn test_zero_and_negative_zero() { // Test that positive and negative zero are handled correctly let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([-0.0, 1.0]), // Negative zero + Point::try_new([0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([-0.0, 1.0]).expect("finite point coordinates"), // Negative zero ]; // Both positive and negative zero should be processed normally @@ -341,9 +174,9 @@ fn test_very_large_finite_values() { // Use a large value that won't overflow when squared (f64::MAX would become infinity when squared) let large_value = 1e100; // Large but safe value let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([large_value, 1.0]), // Very large but finite value + Point::try_new([0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([large_value, 1.0]).expect("finite point coordinates"), // Very large but finite value ]; // Large finite values should be handled without error as long as they don't overflow in calculations @@ -364,9 +197,9 @@ fn test_very_large_finite_values() { fn test_very_small_finite_values() { // Test that very small but finite values are handled correctly let points = vec![ - Point::new([0.0, 0.0]), - Point::new([1.0, 0.0]), - Point::new([f64::MIN_POSITIVE, 1.0]), // Very small but finite value + Point::try_new([0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([f64::MIN_POSITIVE, 1.0]).expect("finite point coordinates"), // Very small but finite value ]; // Small finite values should be handled without error diff --git a/tests/dedup_batch_construction.rs b/tests/dedup_batch_construction.rs index 8d04e3d5..82e69afd 100644 --- a/tests/dedup_batch_construction.rs +++ b/tests/dedup_batch_construction.rs @@ -11,7 +11,7 @@ use delaunay::prelude::construction::{ ConstructionOptions, DedupPolicy, DelaunayConstructionFailure, DelaunayTriangulation, - DelaunayTriangulationConstructionError, InsertionOrderStrategy, Vertex, vertex, + DelaunayTriangulationConstructionError, InsertionOrderStrategy, Vertex, }; // ============================================================================= @@ -31,13 +31,13 @@ fn init_tracing() { } /// Build D+1 standard simplex vertices: origin + D unit vectors. -fn simplex_vertices() -> Vec> { +fn simplex_vertices() -> Vec> { let mut verts = Vec::with_capacity(D + 1); - verts.push(vertex!([0.0; D])); + verts.push(delaunay::prelude::Vertex::<(), _>::try_new([0.0; D]).unwrap()); for i in 0..D { let mut coords = [0.0; D]; coords[i] = 1.0; - verts.push(vertex!(coords)); + verts.push(delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()); } verts } @@ -48,22 +48,24 @@ fn simplex_vertices() -> Vec> { clippy::cast_precision_loss, reason = "D ≤ 5 in practice; no precision loss" )] -fn simplex_with_interior_and_duplicates() -> (Vec>, usize) { +fn simplex_with_interior_and_duplicates() -> (Vec>, usize) { let mut verts = simplex_vertices::(); // Interior point let interior = [0.25 / (D as f64); D]; - verts.push(vertex!(interior)); + verts.push(delaunay::prelude::Vertex::<(), _>::try_new(interior).unwrap()); let distinct = verts.len(); // D+2 // Duplicates: origin + interior again - verts.push(vertex!([0.0; D])); - verts.push(vertex!(interior)); + verts.push(delaunay::prelude::Vertex::<(), _>::try_new([0.0; D]).unwrap()); + verts.push(delaunay::prelude::Vertex::<(), _>::try_new(interior).unwrap()); (verts, distinct) } /// Build `count` copies of the same all-identical vertex. -fn all_identical_vertices(count: usize) -> Vec> { - (0..count).map(|_| vertex!([1.0; D])).collect() +fn all_identical_vertices(count: usize) -> Vec> { + (0..count) + .map(|_| delaunay::prelude::Vertex::<(), _>::try_new([1.0; D]).unwrap()) + .collect() } /// Select exact preprocessing dedup so tests opt into duplicate collapse explicitly. @@ -77,14 +79,14 @@ fn exact_dedup_options() -> ConstructionOptions { clippy::cast_precision_loss, reason = "D ≤ 5 in practice; no precision loss" )] -fn simplex_with_one_duplicate() -> (Vec>, usize) { +fn simplex_with_one_duplicate() -> (Vec>, usize) { let mut verts = simplex_vertices::(); // Extra non-vertex interior point to make the triangulation interesting let interior = [0.5 / (D as f64); D]; - verts.push(vertex!(interior)); + verts.push(delaunay::prelude::Vertex::<(), _>::try_new(interior).unwrap()); let distinct = verts.len(); // One duplicate of the origin - verts.push(vertex!([0.0; D])); + verts.push(delaunay::prelude::Vertex::<(), _>::try_new([0.0; D]).unwrap()); (verts, distinct) } @@ -204,11 +206,11 @@ macro_rules! gen_dedup_batch_tests { // D+2 distinct vertices, each repeated 5× = 5(D+2) total let (base, distinct_count_raw) = simplex_with_interior_and_duplicates::<$dim>(); // Take only the distinct portion using the helper's reported count. - let distinct: Vec> = + let distinct: Vec> = base.into_iter().take(distinct_count_raw).collect(); let distinct_count = distinct.len(); - let vertices: Vec> = distinct + let vertices: Vec> = distinct .iter() .cycle() .take(distinct_count * 5) @@ -252,8 +254,8 @@ gen_dedup_batch_tests!(5); fn test_hilbert_dedup_quantized_collision_2d() { init_tracing(); let mut vertices = simplex_vertices::<2>(); - vertices.push(vertex!([0.5, 0.5])); - vertices.push(vertex!([0.5 + 1e-10, 0.5])); // quantizes to same simplex + vertices.push(delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap()); + vertices.push(delaunay::prelude::Vertex::<(), _>::try_new([0.5 + 1e-10, 0.5]).unwrap()); // quantizes to same simplex let total = vertices.len(); let dt: DelaunayTriangulation<_, (), (), 2> = diff --git a/tests/delaunay_edge_cases.rs b/tests/delaunay_edge_cases.rs index 3dc94f01..6b19c4a0 100644 --- a/tests/delaunay_edge_cases.rs +++ b/tests/delaunay_edge_cases.rs @@ -10,7 +10,7 @@ use delaunay::prelude::construction::{ DelaunayConstructionFailure, DelaunayTriangulation, DelaunayTriangulationConstructionError, - TopologyGuarantee, Vertex, vertex, + TopologyGuarantee, Vertex, }; #[cfg(feature = "diagnostics")] use delaunay::prelude::diagnostics::debug_print_first_delaunay_violation; @@ -111,11 +111,11 @@ test_regression_config!( regression_2d_canonical, 2, vec![ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([0.0, 2.0]), - vertex!([0.8, 0.7]), // interior - vertex!([-0.5, -0.4]), // exterior + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.7]).unwrap(), // interior + delaunay::prelude::Vertex::<(), _>::try_new([-0.5, -0.4]).unwrap(), // exterior ] ); @@ -135,55 +135,62 @@ test_regression_config!( fn debug_issue_120_empty_circumsphere_5d() { init_tracing(); let vertices = vec![ - vertex!([ + delaunay::prelude::Vertex::<(), _>::try_new([ 18.781125710207355, 85.19556603270544, -35.577425948458725, -78.4710254162274, - 25.721771703577573 - ]), - vertex!([ + 25.721771703577573, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -21.95447633622051, 83.05734190480365, 96.97214006048821, -48.80161083192332, - -21.250997394474208 - ]), - vertex!([ + -21.250997394474208, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 50.96615929339812, -12.888014856181814, 64.35842847516192, 81.20742517692801, - -67.85330902948604 - ]), - vertex!([ + -67.85330902948604, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -74.19363960080797, -87.39864134220277, -31.002590635557322, -73.43717909637807, - 38.224369898650814 - ]), - vertex!([ + 38.224369898650814, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -15.305719099426401, 37.44773385928881, -31.57846617007415, -11.413274473891796, - 32.32927241254111 - ]), - vertex!([ + 32.32927241254111, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -58.19271902837987, -50.763430349360824, 72.37200252994022, 66.28041332398725, - 53.51398806010464 - ]), - vertex!([ + 53.51398806010464, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 96.64514856949884, 69.99880120063219, 29.117126126375382, 88.1850558085571, - 95.34623469752856 - ]), + 95.34623469752856, + ]) + .unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 5> = @@ -274,11 +281,11 @@ test_regression_config!( regression_3d_canonical, 3, vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([0.0, 2.0, 0.0]), - vertex!([0.0, 0.0, 2.0]), - vertex!([0.4, 0.4, 0.3]), // interior + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.4, 0.4, 0.3]).unwrap(), // interior ] ); @@ -287,12 +294,12 @@ test_regression_config!( regression_4d_canonical, 4, vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 2.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 2.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 2.0]), - vertex!([0.4, 0.3, 0.3, 0.3]), // interior + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 2.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.4, 0.3, 0.3, 0.3]).unwrap(), // interior ] ); @@ -309,60 +316,69 @@ test_regression_config!( #[test] fn test_regression_proptest_insertion_order_4d_euler_mismatch() { let vertices = vec![ - vertex!([ + delaunay::prelude::Vertex::<(), _>::try_new([ -65.070_532_013_377_94, 72.223_880_592_145_24, -97.837_333_303_337_39, 35.700_988_360_396_63, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 27.011_682_298_030_294, 52.054_594_213_988_53, -4.067_357_689_604_181, 59.253_713_038_817_09, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 64.848_601_083_080_47, 84.907_367_805_317_42, -98.659_828_418_664_1, 70.056_498_821_543_85, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -23.543_852_823_069_876, 96.741_963_200_207_22, -50.539_503_136_092_634, -49.616_262_314_856_67, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 24.886_830_567_772_98, -81.708_725_314_824, 50.775_700_870_880_27, 20.281_603_779_436_033, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 78.030_479_318_165_25, -82.763_788_627_520_88, 94.075_337_487_756_27, 44.637_774_779_142_73, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -5.175_491_150_708_228, 97.527_582_084_288_54, 95.344_552_027_220_42, 84.908_292_808_161_85, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 71.994_788_686_588_11, 4.833_973_465_666_131, -80.802_685_728_835_39, 64.010_634_775_159_37, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 23.390_079_186_814_17, -2.157_395_632_040_824_7, -6.601_766_119_999_574, 32.062_796_044_560_2, - ]), + ]) + .unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 4> = @@ -392,55 +408,62 @@ test_regression_config!( regression_5d_known_config, 5, vec![ - vertex!([ + delaunay::prelude::Vertex::<(), _>::try_new([ 61.994_906_139_357_86, 66.880_064_158_234_8, 62.542_871_273_730_91, -27.857_784_980_103_375, -78.369_282_526_711_23, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -31.430_765_957_270_268, 50.418_208_939_604_746, 88.657_219_404_750_96, 47.248_786_623_931_88, -81.163_199_600_681_14, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -89.902_834_998_758_96, 93.719_989_121_636_87, 64.524_277_928_893_98, 40.001_314_184_454_05, 14.196_053_554_411_321, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 2.625_958_385_925_883, 48.251_155_688_054_36, 3.491_542_746_106_750_5, 97.241_732_043_079_37, -27.107_939_334_194_757, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ 62.628_856_831_188_11, -18.181_728_263_486_345, -32.153_141_689_537_584, 25.692_809_519_458_7, 26.369_541_091_117_114, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -41.886_149_523_644_406, -54.537_563_736_672_65, -54.555_379_092_740_964, 75.499_924_758_912_23, 16.127_546_041_675_355, - ]), - vertex!([ + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ -77.161_459_173_963_2, -59.065_517_574_769_37, -19.652_689_679_369_03, -51.622_382_706_243_18, -26.000_263_271_298_543, - ]), + ]) + .unwrap(), ] ); @@ -572,9 +595,9 @@ fn test_regression_non_manifold_nearby_seeds() { fn test_exact_minimum_vertices_2d() { // Exactly D+1 = 3 vertices for 2D let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -592,10 +615,10 @@ fn test_exact_minimum_vertices_2d() { fn test_exact_minimum_vertices_3d() { // Exactly D+1 = 4 vertices for 3D let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -613,11 +636,11 @@ fn test_exact_minimum_vertices_3d() { fn test_exact_minimum_vertices_4d() { // Exactly D+1 = 5 vertices for 4D let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 4> = @@ -638,12 +661,12 @@ fn test_exact_minimum_vertices_4d() { #[test] fn test_multiple_interior_points_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([2.0, 4.0]), - vertex!([1.5, 1.5]), // interior - vertex!([2.0, 1.5]), // interior - vertex!([2.5, 1.5]), // interior + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 4.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.5, 1.5]).unwrap(), // interior + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 1.5]).unwrap(), // interior + delaunay::prelude::Vertex::<(), _>::try_new([2.5, 1.5]).unwrap(), // interior ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -660,13 +683,13 @@ fn test_multiple_interior_points_2d() { #[test] fn test_multiple_interior_points_3d() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([0.0, 2.0, 0.0]), - vertex!([0.0, 0.0, 2.0]), - vertex!([0.5, 0.5, 0.5]), // interior - vertex!([0.6, 0.5, 0.5]), // interior - vertex!([0.5, 0.6, 0.5]), // interior + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), // interior + delaunay::prelude::Vertex::<(), _>::try_new([0.6, 0.5, 0.5]).unwrap(), // interior + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.6, 0.5]).unwrap(), // interior ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -689,11 +712,11 @@ fn test_square_with_center_2d() { init_tracing(); // Square vertices with center point let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([2.0, 2.0]), - vertex!([0.0, 2.0]), - vertex!([1.0, 1.0]), // center + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), // center ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -711,14 +734,14 @@ fn test_square_with_center_2d() { fn test_cube_vertices_3d() { // 8 corners of a unit cube let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([1.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 0.0, 1.0]), - vertex!([0.0, 1.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -758,10 +781,10 @@ fn test_cube_vertices_3d() { #[test] fn test_large_coordinates_2d() { let vertices = vec![ - vertex!([1000.0, 1000.0]), - vertex!([2000.0, 1000.0]), - vertex!([1500.0, 2000.0]), - vertex!([1500.0, 1500.0]), + delaunay::prelude::Vertex::<(), _>::try_new([1000.0, 1000.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2000.0, 1000.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1500.0, 2000.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1500.0, 1500.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -778,11 +801,11 @@ fn test_large_coordinates_2d() { #[test] fn test_small_coordinates_3d() { let vertices = vec![ - vertex!([0.001, 0.001, 0.001]), - vertex!([0.002, 0.001, 0.001]), - vertex!([0.001, 0.002, 0.001]), - vertex!([0.001, 0.001, 0.002]), - vertex!([0.0015, 0.0015, 0.0015]), + delaunay::prelude::Vertex::<(), _>::try_new([0.001, 0.001, 0.001]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.002, 0.001, 0.001]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.001, 0.002, 0.001]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.001, 0.001, 0.002]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0015, 0.0015, 0.0015]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -799,10 +822,10 @@ fn test_small_coordinates_3d() { #[test] fn test_negative_coordinates_2d() { let vertices = vec![ - vertex!([-1.0, -1.0]), - vertex!([1.0, -1.0]), - vertex!([0.0, 1.0]), - vertex!([0.0, 0.0]), + delaunay::prelude::Vertex::<(), _>::try_new([-1.0, -1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, -1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -824,10 +847,10 @@ fn test_negative_coordinates_2d() { fn test_robust_kernel_with_edge_case() { // Configuration that might benefit from robust predicates let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025_403_784_438_7]), // ~sqrt(3)/2 - near-equilateral - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.866_025_403_784_438_7]).unwrap(), // ~sqrt(3)/2 - near-equilateral + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let dt: DelaunayTriangulation, (), (), 2> = @@ -852,10 +875,10 @@ fn test_collinear_points_2d() { // AdaptiveKernel uses exact orientation (no SoS), so collinear points // are correctly detected as degenerate. let collinear = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([3.0, 0.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 0.0]).unwrap(), ]; let result: Result, _> = @@ -894,9 +917,12 @@ fn test_collinear_points_2d() { #[test] fn regression_issue_228_exact_predicate_paths_3d_fast() { let seed: u64 = 0x0228_FA53_0003; - let points = generate_random_points_in_ball_seeded::(16, 100.0, seed) + let points = generate_random_points_in_ball_seeded::<3>(16, 100.0, seed) .expect("point generation should succeed"); - let vertices: Vec> = points.into_iter().map(|p| vertex!(p)).collect(); + let vertices: Vec> = points + .into_iter() + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p.into()).unwrap()) + .collect(); let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new_with_topology_guarantee( @@ -920,13 +946,13 @@ fn regression_issue_228_exact_predicate_paths_3d_fast() { #[test] fn test_5d_simplex_plus_interior() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2, 0.2]), // interior + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, 0.2]).unwrap(), // interior ]; let dt: DelaunayTriangulation<_, (), (), 5> = diff --git a/tests/delaunay_incremental_insertion.rs b/tests/delaunay_incremental_insertion.rs index 9255f351..d8ed8b95 100644 --- a/tests/delaunay_incremental_insertion.rs +++ b/tests/delaunay_incremental_insertion.rs @@ -14,19 +14,16 @@ use delaunay::geometry::kernel::RobustKernel; use delaunay::prelude::algorithms::{LocateResult, find_conflict_region, locate}; use delaunay::prelude::collections::MAX_PRACTICAL_DIMENSION_SIZE; use delaunay::prelude::construction::{ - ConstructionOptions, DedupPolicy, DelaunayTriangulation, TopologyGuarantee, Vertex, vertex, + ConstructionOptions, DedupPolicy, DelaunayTriangulation, TopologyGuarantee, Vertex, }; -use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate, Point}; +use delaunay::prelude::geometry::{AdaptiveKernel, Point}; use delaunay::prelude::tds::{ Simplex, SimplexKey, SmallBuffer, VertexKey, facet_key_from_vertices, }; use uuid::Uuid; /// Build the canonical facet key used to compare neighbor mirror facets in tests. -fn facet_key_for_simplex( - simplex: &Simplex, - facet_idx: usize, -) -> u64 { +fn facet_key_for_simplex(simplex: &Simplex<(), D>, facet_idx: usize) -> u64 { let mut facet_vertices = SmallBuffer::::new(); for (idx, &vertex_key) in simplex.vertices().iter().enumerate() { if idx != facet_idx { @@ -72,7 +69,7 @@ fn assert_neighbors_valid_and_symmetric( /// Return a query point strictly inside the first simplex so locate traversal has a stable target. fn centroid_of_first_simplex( dt: &DelaunayTriangulation, (), (), D>, -) -> (SimplexKey, Point) { +) -> (SimplexKey, Point) { let (simplex_key, simplex) = dt .simplices() .next() @@ -95,7 +92,10 @@ fn centroid_of_first_simplex( for coord in &mut coords { *coord *= scale; } - (simplex_key, Point::new(coords)) + ( + simplex_key, + Point::try_new(coords).expect("finite point coordinates"), + ) } /// Verify repaired neighbor pointers support hinted locate and conflict-region traversal. @@ -137,7 +137,7 @@ macro_rules! test_insert_single_point { #[test] fn []() { let vertices = vec![ - $(vertex!($simplex)),+ + $(delaunay::prelude::Vertex::<(), _>::try_new($simplex).unwrap()),+ ]; let mut dt: DelaunayTriangulation<_, (), (), $dim> = @@ -150,7 +150,7 @@ macro_rules! test_insert_single_point { let initial_vertices = vertices.len(); assert_eq!(dt.number_of_simplices(), 1); - dt.insert(vertex!($point)).unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new($point).unwrap()).unwrap(); assert_eq!(dt.number_of_vertices(), initial_vertices + 1); assert_eq!(dt.number_of_simplices(), $expected_simplices); @@ -205,7 +205,7 @@ macro_rules! test_insert_5_points { #[test] fn []() { let vertices = vec![ - $(vertex!($simplex)),+ + $(delaunay::prelude::Vertex::<(), _>::try_new($simplex).unwrap()),+ ]; let mut dt: DelaunayTriangulation<_, (), (), $dim> = @@ -219,7 +219,7 @@ macro_rules! test_insert_5_points { assert_eq!(dt.number_of_simplices(), 1); // Insert 5 well-spaced interior points - let points = vec![$(vertex!($point)),+]; + let points = vec![$(delaunay::prelude::Vertex::<(), _>::try_new($point).unwrap()),+]; for (i, point) in points.iter().enumerate() { dt.insert(*point).unwrap(); assert_eq!(dt.number_of_vertices(), initial_vertices + i + 1); @@ -299,7 +299,7 @@ macro_rules! test_local_neighbor_repair_guardrails { #[test] fn []() { let vertices = vec![ - $(vertex!($simplex)),+ + $(delaunay::prelude::Vertex::<(), _>::try_new($simplex).unwrap()),+ ]; let mut dt: DelaunayTriangulation, (), (), $dim> = DelaunayTriangulation::with_topology_guarantee( @@ -309,7 +309,7 @@ macro_rules! test_local_neighbor_repair_guardrails { ) .unwrap(); - for point in [$(vertex!($point)),+] { + for point in [$(delaunay::prelude::Vertex::<(), _>::try_new($point).unwrap()),+] { dt.insert(point).unwrap(); assert_neighbors_valid_and_symmetric(&dt); assert_locate_and_conflict_traversal(&dt); @@ -392,10 +392,10 @@ test_local_neighbor_repair_guardrails!( #[test] fn test_adaptive_kernel_vs_robust_kernel_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let dt_adaptive: DelaunayTriangulation, (), (), 2> = @@ -429,9 +429,9 @@ fn test_adaptive_kernel_vs_robust_kernel_2d() { #[test] fn test_robust_kernel_incremental_insertion() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation, (), (), 2> = @@ -443,9 +443,12 @@ fn test_robust_kernel_incremental_insertion() { .unwrap(); // Insert points with robust kernel - dt.insert(vertex!([0.3, 0.3])).unwrap(); - dt.insert(vertex!([0.5, 0.3])).unwrap(); - dt.insert(vertex!([0.4, 0.5])).unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.3]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.4, 0.5]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 6); assert!(dt.number_of_simplices() > 1); @@ -458,9 +461,9 @@ fn test_robust_kernel_incremental_insertion() { #[test] fn test_clustered_points_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([10.0, 0.0]), - vertex!([5.0, 10.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([10.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([5.0, 10.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -471,11 +474,16 @@ fn test_clustered_points_2d() { .unwrap(); // Insert 5 points clustered around (3.0, 3.0) - dt.insert(vertex!([3.0, 3.0])).unwrap(); - dt.insert(vertex!([3.1, 3.0])).unwrap(); - dt.insert(vertex!([3.0, 3.1])).unwrap(); - dt.insert(vertex!([3.1, 3.1])).unwrap(); - dt.insert(vertex!([3.05, 3.05])).unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([3.0, 3.0]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([3.1, 3.0]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([3.0, 3.1]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([3.1, 3.1]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([3.05, 3.05]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 8); assert!(dt.number_of_simplices() > 3); @@ -484,9 +492,9 @@ fn test_clustered_points_2d() { #[test] fn test_grid_pattern_2d() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([3.0, 0.0]), - vertex!([1.5, 3.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.5, 3.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -497,10 +505,14 @@ fn test_grid_pattern_2d() { .unwrap(); // Insert 4 points in a grid - dt.insert(vertex!([1.0, 1.0])).unwrap(); - dt.insert(vertex!([2.0, 1.0])).unwrap(); - dt.insert(vertex!([1.0, 1.5])).unwrap(); - dt.insert(vertex!([2.0, 1.5])).unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([2.0, 1.0]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.5]).unwrap()) + .unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([2.0, 1.5]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 7); assert!(dt.number_of_simplices() > 4); @@ -513,12 +525,12 @@ fn test_grid_pattern_2d() { #[test] fn test_batch_vs_incremental_same_vertex_count() { let all_vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([2.0, 4.0]), - vertex!([1.0, 1.0]), - vertex!([2.0, 1.0]), - vertex!([3.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 4.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 1.0]).unwrap(), ]; // Batch construction @@ -531,17 +543,23 @@ fn test_batch_vs_incremental_same_vertex_count() { // Incremental construction let initial = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([2.0, 4.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 4.0]).unwrap(), ]; let mut dt_incremental: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new_with_topology_guarantee(&initial, TopologyGuarantee::PLManifold) .unwrap(); - dt_incremental.insert(vertex!([1.0, 1.0])).unwrap(); - dt_incremental.insert(vertex!([2.0, 1.0])).unwrap(); - dt_incremental.insert(vertex!([3.0, 1.0])).unwrap(); + dt_incremental + .insert(delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap()) + .unwrap(); + dt_incremental + .insert(delaunay::prelude::Vertex::<(), _>::try_new([2.0, 1.0]).unwrap()) + .unwrap(); + dt_incremental + .insert(delaunay::prelude::Vertex::<(), _>::try_new([3.0, 1.0]).unwrap()) + .unwrap(); // Both should have same vertex count assert_eq!( @@ -559,16 +577,16 @@ fn test_batch_vs_incremental_same_vertex_count() { fn test_bulk_construction_skips_near_duplicate_3d() { // Use bulk construction with a near-duplicate inside the tolerance (1e-10). let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2]), - vertex!([0.2 + 1e-11, 0.2, 0.2]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.2 + 1e-11, 0.2, 0.2]).unwrap(), ]; let opts = - ConstructionOptions::default().with_dedup_policy(DedupPolicy::Epsilon { tolerance: 1e-10 }); + ConstructionOptions::default().with_dedup_policy(DedupPolicy::try_epsilon(1e-10).unwrap()); let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new_with_options(&vertices, opts).unwrap(); @@ -583,9 +601,9 @@ fn test_bulk_construction_skips_near_duplicate_3d() { #[test] fn test_insert_at_centroid() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([3.0, 0.0]), - vertex!([1.5, 3.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([3.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.5, 3.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -596,7 +614,8 @@ fn test_insert_at_centroid() { .unwrap(); // Insert point at approximate centroid - dt.insert(vertex!([1.5, 1.0])).unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([1.5, 1.0]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 4); assert_eq!(dt.number_of_simplices(), 3); @@ -606,10 +625,10 @@ fn test_insert_at_centroid() { fn test_minimal_simplex_then_insert() { // Test with exactly D+1 vertices initially let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = @@ -623,7 +642,8 @@ fn test_minimal_simplex_then_insert() { assert_eq!(dt.number_of_simplices(), 1); // Insert one point - dt.insert(vertex!([0.25, 0.25, 0.25])).unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 5); assert!(dt.number_of_simplices() > 1); @@ -636,9 +656,9 @@ fn test_minimal_simplex_then_insert() { #[test] fn test_f64_coordinates() { let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation, (), (), 2> = @@ -649,7 +669,8 @@ fn test_f64_coordinates() { ) .unwrap(); - dt.insert(vertex!([0.3, 0.3])).unwrap(); + dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.3, 0.3]).unwrap()) + .unwrap(); assert_eq!(dt.number_of_vertices(), 4); assert_eq!(dt.number_of_simplices(), 3); @@ -675,7 +696,7 @@ macro_rules! test_bootstrap_key_stability { ); // Collect the test points - let points = vec![$(vertex!($point)),+]; + let points = vec![$(delaunay::prelude::Vertex::<(), _>::try_new($point).unwrap()),+]; assert_eq!(points.len(), $dim + 1, "Must provide exactly D+1 points"); // Insert vertices incrementally and collect keys @@ -770,9 +791,9 @@ fn test_bootstrap_returns_valid_key_after_tds_rebuild() { let uuid2 = Uuid::new_v4(); let uuid3 = Uuid::new_v4(); - let v1 = Vertex::new_with_uuid(Point::try_from([0.0, 0.0]).unwrap(), uuid1, None); - let v2 = Vertex::new_with_uuid(Point::try_from([1.0, 0.0]).unwrap(), uuid2, None); - let v3 = Vertex::new_with_uuid(Point::try_from([0.0, 1.0]).unwrap(), uuid3, None); + let v1 = Vertex::try_new_with_uuid(Point::try_from([0.0, 0.0]).unwrap(), uuid1, None).unwrap(); + let v2 = Vertex::try_new_with_uuid(Point::try_from([1.0, 0.0]).unwrap(), uuid2, None).unwrap(); + let v3 = Vertex::try_new_with_uuid(Point::try_from([0.0, 1.0]).unwrap(), uuid3, None).unwrap(); // Bootstrap insertion: vertices inserted in order v1, v2, v3 let mut dt: DelaunayTriangulation<_, (), (), 2> = diff --git a/tests/delaunay_repair_fallback.rs b/tests/delaunay_repair_fallback.rs index 6f447ee5..f96685eb 100644 --- a/tests/delaunay_repair_fallback.rs +++ b/tests/delaunay_repair_fallback.rs @@ -6,7 +6,7 @@ use delaunay::flips::FacetHandle; use delaunay::prelude::construction::{ - DelaunayRepairPolicy, DelaunayTriangulation, TopologyGuarantee, vertex, + DelaunayRepairPolicy, DelaunayTriangulation, TopologyGuarantee, }; use delaunay::prelude::flips::BistellarFlips; use delaunay::prelude::repair::DelaunayRepairHeuristicConfig; @@ -46,10 +46,10 @@ macro_rules! test_debug_info { fn repair_fallback_produces_valid_triangulation() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([4.0, 2.0]), - vertex!([1.0, 2.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new_with_topology_guarantee( @@ -64,7 +64,10 @@ fn repair_fallback_produces_valid_triangulation() { for (index, neighbor) in neighbors.enumerate() { if neighbor.is_some() { let facet_index = u8::try_from(index).expect("2D facet index fits in u8"); - candidate_facets.push(FacetHandle::new(simplex_key, facet_index)); + candidate_facets.push( + FacetHandle::try_new(dt.tds(), simplex_key, facet_index) + .expect("interior facet index should be valid"), + ); } } } @@ -114,13 +117,13 @@ fn incremental_insertion_with_repair_fallback() { // Insert vertices that might trigger repair challenges let test_vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), - vertex!([1.0, 0.5, 1.5]), - vertex!([1.0, 0.5, 0.5]), // Interior point - vertex!([0.8, 0.8, 0.8]), // Another interior point - vertex!([1.2, 0.6, 0.7]), // Close to existing + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5, 1.5]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5, 0.5]).unwrap(), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.8, 0.8]).unwrap(), // Another interior point + delaunay::prelude::Vertex::<(), _>::try_new([1.2, 0.6, 0.7]).unwrap(), // Close to existing ]; for (i, vertex) in test_vertices.into_iter().enumerate() { @@ -158,14 +161,14 @@ fn incremental_insertion_with_repair_fallback() { fn repair_fallback_2d() { // Use non-collinear points to avoid degeneracy let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([2.0, 3.5]), // Non-collinear with first two - vertex!([0.5, 2.0]), - vertex!([3.5, 2.0]), - vertex!([1.5, 1.0]), - vertex!([2.5, 2.5]), - vertex!([1.0, 3.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 3.5]).unwrap(), // Non-collinear with first two + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([3.5, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.5, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.5, 2.5]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 3.0]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -188,11 +191,11 @@ fn explicit_repair_call_validates_result() { init_tracing(); // Build a triangulation let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = @@ -225,10 +228,10 @@ fn explicit_repair_call_validates_result() { #[test] fn repair_policy_configuration_with_fallback() { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; // Test with different repair policies diff --git a/tests/delaunayize_workflow.rs b/tests/delaunayize_workflow.rs index 1100797c..f637a249 100644 --- a/tests/delaunayize_workflow.rs +++ b/tests/delaunayize_workflow.rs @@ -45,11 +45,11 @@ fn test_delaunayize_config_default_values() { fn test_non_delaunay_pl_manifold_repaired_2d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([4.0, 0.0]), - vertex!([0.0, 4.0]), - vertex!([4.0, 4.0]), - vertex!([2.0, 2.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 4.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([4.0, 4.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 2.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -66,12 +66,12 @@ fn test_non_delaunay_pl_manifold_repaired_2d() { fn test_non_delaunay_pl_manifold_repaired_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -90,10 +90,10 @@ fn test_non_delaunay_pl_manifold_repaired_3d() { fn test_fallback_off_does_not_rebuild() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -110,10 +110,10 @@ fn test_fallback_off_does_not_rebuild() { fn test_fallback_on_does_not_trigger_on_valid() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -136,11 +136,11 @@ fn test_fallback_on_does_not_trigger_on_valid() { fn test_outcome_stats_populated_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -163,11 +163,11 @@ fn test_outcome_stats_populated_3d() { fn test_repeat_run_determinism_2d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let config = DelaunayizeConfig::default(); @@ -203,12 +203,12 @@ fn test_repeat_run_determinism_2d() { fn test_repeat_run_determinism_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let config = DelaunayizeConfig::default(); @@ -239,11 +239,11 @@ fn test_repeat_run_determinism_3d() { fn test_vertex_count_preserved_after_delaunayize() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -267,11 +267,11 @@ fn test_flip_breaks_delaunay_then_delaunayize_restores() { init_tracing(); // 5 points in 3D — produces multiple simplices with interior facets. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -283,7 +283,10 @@ fn test_flip_breaks_delaunay_then_delaunayize_restores() { if let Some(neighbors) = simplex.neighbors() { for (i, n) in neighbors.enumerate() { if let (Some(_), Ok(idx)) = (n, u8::try_from(i)) { - candidate_facets.push(FacetHandle::new(ck, idx)); + candidate_facets.push( + FacetHandle::try_new(dt.tds(), ck, idx) + .expect("interior facet index should be valid"), + ); } } } @@ -454,7 +457,7 @@ fn test_prelude_exports_error_payloads() { const _: usize = size_of::(); const _: usize = size_of::(); const _: usize = size_of::(); - const _: usize = size_of::>(); + const _: usize = size_of::>(); const _: usize = size_of::(); const _: usize = size_of::(); } @@ -470,11 +473,11 @@ fn test_prelude_exports_error_payloads() { fn test_delaunayize_with_explicit_flip_budget_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -495,11 +498,11 @@ fn test_delaunayize_with_explicit_flip_budget_3d() { fn test_delaunayize_with_flip_budget_and_fallback_2d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -522,11 +525,11 @@ fn test_delaunayize_with_flip_budget_and_fallback_2d() { fn test_flip_breaks_then_delaunayize_with_budget_restores_3d() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices).unwrap(); @@ -538,7 +541,10 @@ fn test_flip_breaks_then_delaunayize_with_budget_restores_3d() { if let Some(neighbors) = simplex.neighbors() { for (i, n) in neighbors.enumerate() { if let (Some(_), Ok(idx)) = (n, u8::try_from(i)) { - candidate_facets.push(FacetHandle::new(ck, idx)); + candidate_facets.push( + FacetHandle::try_new(dt.tds(), ck, idx) + .expect("interior facet index should be valid"), + ); } } } @@ -573,12 +579,12 @@ fn test_flip_breaks_then_delaunayize_with_budget_restores_3d() { fn test_full_validation_passes_after_delaunayize() { init_tracing(); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, 0.5]), - vertex!([0.25, 0.75]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.75]).unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::new(&vertices).unwrap(); diff --git a/tests/euler_characteristic.rs b/tests/euler_characteristic.rs index 2ae84171..31bfeb50 100644 --- a/tests/euler_characteristic.rs +++ b/tests/euler_characteristic.rs @@ -17,7 +17,7 @@ use delaunay::builder::DelaunayTriangulationBuilder; use delaunay::prelude::construction::{ DelaunayTriangulation, DelaunayTriangulationConstructionError, ExplicitConstructionError, - TopologyGuarantee, vertex, + TopologyGuarantee, }; use delaunay::prelude::geometry::AdaptiveKernel; use delaunay::prelude::query::BoundaryAnalysis; @@ -34,7 +34,7 @@ use delaunay::topology::traits::topological_space::{ #[test] fn test_empty_triangulation_euler() { // Empty triangulation should have χ = 0 - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 3> = Tds::empty(); let counts = euler::count_simplices(&tds).unwrap(); assert_eq!(counts.count(0), 0); // No vertices @@ -54,9 +54,9 @@ fn test_empty_triangulation_euler() { fn test_2d_single_triangle() { // Single triangle: V=3, E=3, F=1 → χ = 3-3+1 = 1 let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new_with_topology_guarantee( @@ -81,10 +81,10 @@ fn test_2d_single_triangle() { fn test_2d_multiple_triangles() { // Four points forming multiple triangles let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 1.0]), - vertex!([0.5, 0.3]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.3]).unwrap(), // Interior point ]; let dt = DelaunayTriangulation::new_with_topology_guarantee( @@ -110,10 +110,10 @@ fn test_2d_multiple_triangles() { fn test_3d_single_tetrahedron() { // Single tetrahedron: V=4, E=6, F=4, C=1 → χ = 4-6+4-1 = 1 let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new_with_topology_guarantee( @@ -139,11 +139,11 @@ fn test_3d_single_tetrahedron() { fn test_3d_with_interior_vertex() { // Tetrahedron with one interior vertex let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), // Interior point ]; let dt = DelaunayTriangulation::new_with_topology_guarantee( @@ -170,11 +170,11 @@ fn test_4d_single_simplex() { // Single 4-simplex: V=5, E=10, F=10, T=5, C=1 // χ = 5 - 10 + 10 - 5 + 1 = 1 let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new_with_topology_guarantee( @@ -201,12 +201,12 @@ fn test_4d_single_simplex() { fn test_5d_single_simplex() { // Single 5-simplex: χ = 1 let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::new_with_topology_guarantee( @@ -250,7 +250,10 @@ fn test_2d_toroidal_explicit_construction_rejected() { [0.333, 0.667], [0.667, 0.667], ]; - let vertices: Vec<_> = coords.iter().map(|c| vertex!([c[0], c[1]])).collect(); + let vertices: Vec<_> = coords + .iter() + .map(|c| delaunay::prelude::Vertex::<(), _>::try_new([c[0], c[1]]).unwrap()) + .collect(); let v = |i: usize, j: usize| -> usize { (i % N) * N + (j % N) }; let mut simplices = Vec::with_capacity(2 * N * N); for i in 0..N { @@ -304,7 +307,7 @@ fn test_3d_toroidal_explicit_construction_rejected() { for &x in &grid { for &y in &grid { for &z in &grid { - vertices.push(vertex!([x, y, z])); + vertices.push(delaunay::prelude::Vertex::<(), _>::try_new([x, y, z]).unwrap()); } } } @@ -436,10 +439,10 @@ test_complex_with_interior!( test_2d_complex_with_interior, 2, &[ - vertex!([0.0, 0.0]), - vertex!([2.0, 0.0]), - vertex!([1.0, 2.0]), - vertex!([1.0, 0.5]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5]).unwrap(), // Interior point ], 0 // S¹ has χ = 0 ); @@ -449,11 +452,11 @@ test_complex_with_interior!( test_3d_complex_with_interior, 3, &[ - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), - vertex!([1.0, 0.5, 2.0]), - vertex!([1.0, 0.5, 0.5]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5, 2.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.5, 0.5]).unwrap(), // Interior point ], 2 // S² has χ = 2 ); @@ -463,12 +466,12 @@ test_complex_with_interior!( test_4d_complex_with_interior, 4, &[ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25, 0.25]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25, 0.25]).unwrap(), // Interior point ], 0 // S³ has χ = 0 ); @@ -478,13 +481,13 @@ test_complex_with_interior!( test_5d_complex_with_interior, 5, &[ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2, 0.2]), // Interior point + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2, 0.2]).unwrap(), // Interior point ], 2 // S⁴ has χ = 2 ); diff --git a/tests/example_workflows.rs b/tests/example_workflows.rs index fc1c2af2..608cf526 100644 --- a/tests/example_workflows.rs +++ b/tests/example_workflows.rs @@ -3,19 +3,19 @@ //! Integration tests for workflows demonstrated by runnable examples. use delaunay::prelude::construction::{ - DelaunayTriangulation, DelaunayTriangulationConstructionError, vertex, + DelaunayTriangulation, DelaunayTriangulationConstructionError, }; -use delaunay::prelude::query::{ConvexHull, Coordinate, Point, QueryError}; +use delaunay::prelude::query::{ConvexHull, Point, QueryError}; #[test] fn triangulation_and_hull_workflow_remains_valid() -> Result<(), WorkflowTestError> { let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.35, 0.25, 0.20]), - vertex!([0.20, 0.60, 0.25]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.35, 0.25, 0.20]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.20, 0.60, 0.25]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::new(&vertices)?; @@ -31,8 +31,8 @@ fn triangulation_and_hull_workflow_remains_valid() -> Result<(), WorkflowTestErr hull.validate(dt.as_triangulation())?; assert_eq!(hull.number_of_facets(), boundary_facets.len()); - let inside = Point::new([0.25, 0.25, 0.25]); - let outside = Point::new([2.0, 2.0, 2.0]); + let inside = Point::try_new([0.25, 0.25, 0.25]).expect("finite point coordinates"); + let outside = Point::try_new([2.0, 2.0, 2.0]).expect("finite point coordinates"); assert!(!hull.is_point_outside(&inside, dt.as_triangulation())?); assert!(hull.is_point_outside(&outside, dt.as_triangulation())?); diff --git a/tests/insert_with_statistics.rs b/tests/insert_with_statistics.rs index a00cd6a8..6cf6fdc4 100644 --- a/tests/insert_with_statistics.rs +++ b/tests/insert_with_statistics.rs @@ -17,7 +17,7 @@ use std::assert_matches; -use delaunay::prelude::construction::{DelaunayTriangulation, TopologyGuarantee, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulation, TopologyGuarantee}; use delaunay::prelude::geometry::CoordinateValues; use delaunay::prelude::insertion::{InsertionError, InsertionOutcome}; @@ -32,7 +32,7 @@ fn delaunay_insert_with_statistics_basic_2d() { // Insert first vertex let (outcome, stats) = dt - .insert_with_statistics(vertex!([0.0, 0.0])) + .insert_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap()) .expect("insertion should succeed"); assert_matches!(outcome, InsertionOutcome::Inserted { .. }); @@ -46,7 +46,7 @@ fn delaunay_insert_with_statistics_basic_2d() { // Insert second vertex let (outcome, stats) = dt - .insert_with_statistics(vertex!([1.0, 0.0])) + .insert_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap()) .expect("insertion should succeed"); assert_matches!(outcome, InsertionOutcome::Inserted { .. }); @@ -55,7 +55,7 @@ fn delaunay_insert_with_statistics_basic_2d() { // Insert third vertex (completes simplex) let (outcome, stats) = dt - .insert_with_statistics(vertex!([0.5, 1.0])) + .insert_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([0.5, 1.0]).unwrap()) .expect("insertion should succeed"); assert_matches!(outcome, InsertionOutcome::Inserted { hint, .. } if hint.is_some()); @@ -71,17 +71,32 @@ fn delaunay_insert_with_statistics_hint_caching_3d() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); // Build initial simplex - dt.insert_with_statistics(vertex!([0.0, 0.0, 0.0])).unwrap(); - dt.insert_with_statistics(vertex!([1.0, 0.0, 0.0])).unwrap(); - dt.insert_with_statistics(vertex!([0.0, 1.0, 0.0])).unwrap(); - let (outcome, _) = dt.insert_with_statistics(vertex!([0.0, 0.0, 1.0])).unwrap(); + dt.insert_with_statistics( + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + ) + .unwrap(); + dt.insert_with_statistics( + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + ) + .unwrap(); + dt.insert_with_statistics( + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + ) + .unwrap(); + let (outcome, _) = dt + .insert_with_statistics( + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + ) + .unwrap(); // After simplex creation, hint should be available assert_matches!(outcome, InsertionOutcome::Inserted { hint: Some(_), .. }); // Insert interior point - should benefit from hint let (outcome, stats) = dt - .insert_with_statistics(vertex!([0.25, 0.25, 0.25])) + .insert_with_statistics( + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), + ) .unwrap(); assert_matches!(outcome, InsertionOutcome::Inserted { hint: Some(_), .. }); @@ -97,13 +112,13 @@ fn delaunay_insert_with_statistics_multiple_vertices_4d() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), - vertex!([0.8, 0.1, 0.1, 0.1]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.1, 0.1, 0.1]).unwrap(), ]; let input_count = vertices.len(); @@ -141,13 +156,13 @@ fn delaunay_insert_with_statistics_handles_degenerate_k2_flips_4d() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), - vertex!([0.8, 0.1, 0.1, 0.1]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.1, 0.1, 0.1]).unwrap(), ]; for v in vertices { @@ -165,12 +180,13 @@ fn delaunay_insert_with_statistics_duplicate_coordinates_2d() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); // Insert first vertex - dt.insert_with_statistics(vertex!([1.0, 2.0])) + dt.insert_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap()) .expect("first insertion should succeed"); // The strict statistics API reports skipped insertions as errors so callers // using `?` cannot miss them. - let result = dt.insert_with_statistics(vertex!([1.0, 2.0])); + let result = + dt.insert_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap()); let duplicate_coordinates = CoordinateValues::from([1.0, 2.0]); assert!( matches!( @@ -182,7 +198,9 @@ fn delaunay_insert_with_statistics_duplicate_coordinates_2d() { ); // The explicitly best-effort API preserves the skipped outcome plus telemetry. - let result = dt.insert_best_effort_with_statistics(vertex!([1.0, 2.0])); + let result = dt.insert_best_effort_with_statistics( + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), + ); match result { Ok(( @@ -213,10 +231,10 @@ fn delaunay_insert_with_statistics_bootstrap_happy_path_3d() { // Build simplex with well-separated points let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; for v in vertices { @@ -240,7 +258,9 @@ fn delaunay_insert_with_statistics_statistics_fields_3d() { coords[i - 1] = 1.0; } - let (outcome, stats) = dt.insert_with_statistics(vertex!(coords)).unwrap(); + let (outcome, stats) = dt + .insert_with_statistics(delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()) + .unwrap(); // Verify all statistics fields assert_matches!(outcome, InsertionOutcome::Inserted { .. }); @@ -268,11 +288,11 @@ fn statistics_invariants() { // Build simplex let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.25, 0.25, 0.25]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25]).unwrap(), ]; for v in vertices { @@ -326,10 +346,10 @@ fn insert_with_statistics_2d_coverage() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5]).unwrap(), ]; for v in vertices { @@ -346,11 +366,11 @@ fn insert_with_statistics_3d_coverage() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([0.5, 0.5, 0.5]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5]).unwrap(), ]; for v in vertices { @@ -367,12 +387,12 @@ fn insert_with_statistics_4d_coverage() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0]), - vertex!([0.2, 0.2, 0.2, 0.2]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2]).unwrap(), ]; for v in vertices { @@ -389,12 +409,12 @@ fn insert_with_statistics_5d_coverage() { DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); let vertices = vec![ - vertex!([0.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 1.0, 0.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(), ]; for v in vertices { diff --git a/tests/large_scale_debug.rs b/tests/large_scale_debug.rs index 8ad19adb..1518901a 100644 --- a/tests/large_scale_debug.rs +++ b/tests/large_scale_debug.rs @@ -60,7 +60,7 @@ //! # Debug mode: //! # - "cadenced" (default): PLManifold, ridge-link validation during insertion, //! # vertex-link validation at completion -//! # - "strict": PLManifoldStrict, vertex-link validation after every insertion +//! # - "strict": PLManifoldStrict-link validation after every insertion //! DELAUNAY_LARGE_DEBUG_DEBUG_MODE=cadenced \ //! # Deterministically shuffle insertion order (incremental mode only) //! DELAUNAY_LARGE_DEBUG_SHUFFLE_SEED=123 \ @@ -104,7 +104,7 @@ use delaunay::geometry::util::safe_usize_to_scalar; use delaunay::prelude::construction::{ ConstructionOptions, ConstructionStatistics, DelaunayRepairPolicy, DelaunayTriangulation, DelaunayTriangulationConstructionErrorWithStatistics, InitialSimplexStrategy, - TopologyGuarantee, Vertex, vertex, + TopologyGuarantee, Vertex, }; use delaunay::prelude::diagnostics::ConstructionTelemetry; use delaunay::prelude::generators::{ @@ -536,10 +536,10 @@ fn skip_percentage(skipped: usize, total: usize) -> f64 { return 0.0; } - let skipped = safe_usize_to_scalar::(skipped) + let skipped = safe_usize_to_scalar(skipped) .expect("skipped-vertex count should fit in f64 for debug reporting"); - let total = safe_usize_to_scalar::(total) - .expect("point count should fit in f64 for debug reporting"); + let total = + safe_usize_to_scalar(total).expect("point count should fit in f64 for debug reporting"); (skipped / total) * 100.0 } @@ -1257,25 +1257,28 @@ where println!("Generating points..."); let t_gen = Instant::now(); let points = match distribution { - PointDistribution::Ball => { - generate_random_points_in_ball_seeded::(n_points, ball_radius, seed) - .unwrap_or_else(|e| { - panic!( - "failed to generate deterministic ball points (radius={ball_radius}): {e}" - ) - }) - } + PointDistribution::Ball => generate_random_points_in_ball_seeded::( + n_points, + ball_radius, + seed, + ) + .unwrap_or_else(|e| { + panic!("failed to generate deterministic ball points (radius={ball_radius}): {e}") + }), PointDistribution::Box => { let range = CoordinateRange::try_new(-box_half_width, box_half_width) .expect("box half-width should define a finite non-empty coordinate range"); - generate_random_points_in_range_seeded::(n_points, range, seed) + generate_random_points_in_range_seeded::(n_points, range, seed) } }; println!("Generated {} points in {:?}", points.len(), t_gen.elapsed()); println!("Building vertices..."); let t_vertices = Instant::now(); - let mut vertices: Vec> = points.into_iter().map(|p| vertex!(p)).collect(); + let mut vertices: Vec> = points + .into_iter() + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p.into()).unwrap()) + .collect(); println!( "Built {} vertices in {:?}", vertices.len(), @@ -1687,9 +1690,12 @@ fn test_repair_policy_from_repair_every_maps_cadence() { #[test] fn regression_issue_228_3d_1000_flip_repair_convergence() { let seed = seed_for_case::<3>(42, 1000); - let points = generate_random_points_in_ball_seeded::(1000, 100.0, seed) + let points = generate_random_points_in_ball_seeded::<3>(1000, 100.0, seed) .expect("point generation should succeed"); - let vertices: Vec> = points.into_iter().map(|p| vertex!(p)).collect(); + let vertices: Vec> = points + .into_iter() + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p.into()).unwrap()) + .collect(); // Use the default kernel (AdaptiveKernel — exact+SoS predicates) to match the // actual regression scenario. PLManifold enables full orientation validation @@ -1727,9 +1733,12 @@ fn regression_issue_228_3d_1000_flip_repair_convergence() { #[test] fn regression_issue_230_4d_100_orientation() { let seed = seed_for_case::<4>(42, 100); - let points = generate_random_points_in_ball_seeded::(100, 100.0, seed) + let points = generate_random_points_in_ball_seeded::<4>(100, 100.0, seed) .expect("point generation should succeed"); - let vertices: Vec> = points.into_iter().map(|p| vertex!(p)).collect(); + let vertices: Vec> = points + .into_iter() + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p.into()).unwrap()) + .collect(); let kernel = RobustKernel::::new(); let (dt, stats) = diff --git a/tests/pachner_roundtrip.rs b/tests/pachner_roundtrip.rs index 0c356595..51b4646e 100644 --- a/tests/pachner_roundtrip.rs +++ b/tests/pachner_roundtrip.rs @@ -3,7 +3,7 @@ //! Public API roundtrip tests for Pachner/bistellar flips. use delaunay::prelude::construction::{ - ConstructionOptions, DelaunayTriangulation, InsertionOrderStrategy, TopologyGuarantee, vertex, + ConstructionOptions, DelaunayTriangulation, InsertionOrderStrategy, TopologyGuarantee, }; use delaunay::prelude::flips::{ BistellarFlips, EdgeKey, FacetHandle, RidgeHandle, SimplexKey, TriangleHandle, VertexKey, @@ -61,7 +61,7 @@ fn public_pachner_roundtrips_preserve_stable_4d_topology() { fn build_stable_dt_4d() -> Dt4 { let vertices = STABLE_POINTS_4D .iter() - .map(|coords| vertex!(*coords)) + .map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(*coords).unwrap()) .collect::>(); let options = ConstructionOptions::default().with_insertion_order(InsertionOrderStrategy::Input); @@ -138,7 +138,8 @@ fn simplex_centroid(dt: &Dt4, simplex_key: SimplexKey) -> [f64; 4] { fn roundtrip_k1(dt: &mut Dt4) { let simplex_key = first_simplex(dt); - let new_vertex = vertex!(simplex_centroid(dt, simplex_key)); + let new_vertex = + delaunay::prelude::Vertex::<(), _>::try_new(simplex_centroid(dt, simplex_key)).unwrap(); let new_uuid = new_vertex.uuid(); dt.flip_k1_insert(simplex_key, new_vertex) .expect("k=1 insert should succeed on stable 4D fixture"); @@ -159,10 +160,14 @@ fn interior_facets(dt: &Dt4) -> Vec { }; for (facet_index, neighbor) in neighbors.enumerate() { if neighbor.is_some() { - facets.push(FacetHandle::new( - simplex_key, - u8::try_from(facet_index).expect("facet index should fit in u8"), - )); + facets.push( + FacetHandle::try_new( + dt.tds(), + simplex_key, + u8::try_from(facet_index).expect("facet index should fit in u8"), + ) + .expect("interior facet index should be valid"), + ); } } } @@ -173,7 +178,7 @@ fn flippable_k2_facet(dt: &Dt4) -> FacetHandle { for facet in interior_facets(dt) { let mut trial = dt.clone(); if let Ok(info) = trial.flip_k2(facet) { - let edge = inserted_edge(&info.inserted_face_vertices); + let edge = inserted_edge(&trial, &info.inserted_face_vertices); if trial.flip_k2_inverse_from_edge(edge).is_ok() && trial.validate().is_ok() { return facet; } @@ -186,13 +191,16 @@ fn roundtrip_k2(dt: &mut Dt4, facet: FacetHandle) { let info = dt .flip_k2(facet) .expect("k=2 flip should succeed on selected stable 4D facet"); - dt.flip_k2_inverse_from_edge(inserted_edge(&info.inserted_face_vertices)) + let edge = inserted_edge(dt, &info.inserted_face_vertices); + dt.flip_k2_inverse_from_edge(edge) .expect("k=2 inverse should succeed after k=2 flip"); } -fn inserted_edge(vertices: &[VertexKey]) -> EdgeKey { +fn inserted_edge(dt: &Dt4, vertices: &[VertexKey]) -> EdgeKey { match vertices { - [a, b] => EdgeKey::new(*a, *b), + [a, b] => { + EdgeKey::try_new(dt.tds(), *a, *b).expect("k=2 flip should report a real inserted edge") + } _ => panic!("k=2 flip should report an inserted edge"), } } @@ -202,11 +210,15 @@ fn ridges(dt: &Dt4) -> Vec { for (simplex_key, simplex) in dt.simplices() { for i in 0..simplex.number_of_vertices() { for j in (i + 1)..simplex.number_of_vertices() { - ridges.push(RidgeHandle::new( - simplex_key, - u8::try_from(i).expect("ridge index should fit in u8"), - u8::try_from(j).expect("ridge index should fit in u8"), - )); + ridges.push( + RidgeHandle::try_new( + dt.tds(), + simplex_key, + u8::try_from(i).expect("ridge index should fit in u8"), + u8::try_from(j).expect("ridge index should fit in u8"), + ) + .expect("ridge indices should be valid"), + ); } } } @@ -236,7 +248,8 @@ fn roundtrip_k3(dt: &mut Dt4, ridge: RidgeHandle) { fn inserted_triangle(vertices: &[VertexKey]) -> TriangleHandle { match vertices { - [a, b, c] => TriangleHandle::new(*a, *b, *c), + [a, b, c] => TriangleHandle::try_new(*a, *b, *c) + .expect("k=3 flip should report a valid inserted triangle"), _ => panic!("k=3 flip should report an inserted triangle"), } } diff --git a/tests/prelude_exports.rs b/tests/prelude_exports.rs index e6b8cbc6..c26e3e74 100644 --- a/tests/prelude_exports.rs +++ b/tests/prelude_exports.rs @@ -24,18 +24,21 @@ use delaunay::prelude::collections::{ use delaunay::prelude::construction::{ CavityFillingError, CavityRepairStage, ConstructionOptions, ConstructionSkipSample, ConstructionSlowInsertionSample, CoordinateRangeError as ConstructionCoordinateRangeError, - CoordinateRangeOrdering as ConstructionCoordinateRangeOrdering, DelaunayConstructionFailure, - DelaunayRepairPolicy, DelaunayTriangulation, DelaunayTriangulationConstructionError, + CoordinateRangeOrdering as ConstructionCoordinateRangeOrdering, + CoordinateValidationError as ConstructionCoordinateValidationError, DedupPolicy, + DedupTolerance, DeduplicationError, DelaunayConstructionFailure, DelaunayRepairPolicy, + DelaunayTriangulation, DelaunayTriangulationConstructionError, DelaunayTriangulationValidationError as ConstructionDelaunayTriangulationValidationError, ExplicitConstructionError, ExplicitDelaunayValidationError, ExplicitDelaunayValidationErrorKind, ExplicitDelaunayValidationSourceKind, ExplicitInsertionError, ExplicitInsertionErrorKind, ExplicitInvariantError, - ExplicitInvariantErrorKind, ExplicitTdsError, ExplicitTdsErrorKind, InsertionOrderStrategy, + ExplicitInvariantErrorKind, ExplicitTdsError, ExplicitTdsErrorKind, + GlobalTopologyModelError as ConstructionGlobalTopologyModelError, InsertionOrderStrategy, InvalidCoordinateValue as ConstructionInvalidCoordinateValue, InvalidPositiveScalar as ConstructionInvalidPositiveScalar, RandomPointGenerationError, SimplexValidationError, SpatialIndexConstructionFailure as ConstructionSpatialIndexConstructionFailure, - TopologyGuarantee, ToroidalDomain as ConstructionToroidalDomain, Vertex, vertex, + TopologyGuarantee, ToroidalDomain as ConstructionToroidalDomain, Vertex, }; use delaunay::prelude::delaunayize::{ DelaunayTriangulationBuilder as DelaunayizeDelaunayTriangulationBuilder, DelaunayizeConfig, @@ -55,7 +58,7 @@ use delaunay::prelude::generators::{ try_generate_random_points_seeded, }; #[cfg(feature = "diagnostics")] -use delaunay::prelude::geometry::{AdaptiveKernel, Coordinate}; +use delaunay::prelude::geometry::AdaptiveKernel; use delaunay::prelude::geometry::{ ArrayConversionFailureReason, CircumcenterError, CircumcenterFailureReason, CoordinateConversionError, CoordinateConversionValue, DegenerateGeometry, DegenerateMeasure, @@ -88,7 +91,8 @@ use delaunay::prelude::tds::{ FacetError, InvariantErrorSummaryDetail, NeighborSlot, TdsError, TdsErrorKind, VertexKey, }; use delaunay::prelude::topology::spaces::{ - GlobalTopology, ToroidalConstructionMode, ToroidalDomain, ToroidalDomainError, + GlobalTopology, GlobalTopologyModelError, TopologyKind, ToroidalConstructionMode, + ToroidalDomain, ToroidalDomainError, }; use delaunay::prelude::topology::validation::{ GlobalTopology as TopologyValidationGlobalTopology, ManifoldError, RidgeVertices, @@ -102,7 +106,7 @@ use delaunay::prelude::triangulation::{ Triangulation as GenericTriangulation, TriangulationConstructionError as GenericTriangulationConstructionError, ValidationConfigurationError as TriangulationValidationConfigurationError, - ValidationPolicy as TriangulationValidationPolicy, vertex as triangulation_vertex, + ValidationPolicy as TriangulationValidationPolicy, }; use delaunay::prelude::validation::{ TopologyGuarantee as FocusedValidationTopologyGuarantee, ValidationCadence, @@ -118,6 +122,8 @@ enum RootApiExportTestError { #[error(transparent)] Construction(#[from] delaunay::DelaunayTriangulationConstructionError), #[error(transparent)] + CoordinateConversion(#[from] delaunay::geometry::CoordinateConversionError), + #[error(transparent)] Validation(#[from] delaunay::DelaunayTriangulationValidationError), #[error(transparent)] DelaunayRepair(#[from] delaunay::flips::DelaunayRepairError), @@ -130,6 +136,8 @@ enum PreludeExportTestError { #[error(transparent)] CoordinateRange(#[from] CoordinateRangeError), #[error(transparent)] + CoordinateConversion(#[from] CoordinateConversionError), + #[error(transparent)] RandomPointGeneration(#[from] RandomPointGenerationError), #[error(transparent)] Construction(#[from] DelaunayTriangulationConstructionError), @@ -152,16 +160,106 @@ enum PreludeExportTestError { } /// Proves the focused flips prelude exports the trait bound expected by benchmarks. -const fn assert_bistellar_flips(_: &impl BistellarFlips<3, Scalar = f64, VertexData = ()>) {} +const fn assert_bistellar_flips(_: &impl BistellarFlips<3, VertexData = ()>) {} /// Proves the root flips module exports the same public trait bound. -const fn assert_root_bistellar_flips( - _: &impl delaunay::flips::BistellarFlips<3, Scalar = f64, VertexData = ()>, -) { +const fn assert_root_bistellar_flips(_: &impl delaunay::flips::BistellarFlips<3, VertexData = ()>) { } const fn assert_send_sync_unpin() {} +#[test] +fn construction_prelude_covers_dedup_policy() { + assert_eq!( + DedupTolerance::try_new(-1.0), + Err(DeduplicationError::NegativeEpsilon) + ); + let dedup_policy = DedupPolicy::epsilon(DedupTolerance::try_new(0.0).unwrap()); + assert_matches!(dedup_policy, DedupPolicy::Epsilon { .. }); +} + +#[test] +fn construction_prelude_covers_typed_construction_failure_variants() { + assert_matches!( + DelaunayConstructionFailure::GeometricDegeneracy { + message: "synthetic".to_string(), + }, + DelaunayConstructionFailure::GeometricDegeneracy { .. } + ); + let unsupported_periodic_dimension = + DelaunayConstructionFailure::UnsupportedPeriodicDimension { + dimension: 4, + max_validated_dimension: 3, + tracking_issue: 416, + }; + assert_matches!( + unsupported_periodic_dimension, + DelaunayConstructionFailure::UnsupportedPeriodicDimension { + dimension: 4, + max_validated_dimension: 3, + tracking_issue: 416, + } + ); + let topology_model_failure = DelaunayConstructionFailure::TopologyModelConfiguration { + source: ConstructionGlobalTopologyModelError::PeriodicOffsetsUnsupported { + kind: TopologyKind::Euclidean, + }, + }; + assert_matches!( + topology_model_failure, + DelaunayConstructionFailure::TopologyModelConfiguration { + source: ConstructionGlobalTopologyModelError::PeriodicOffsetsUnsupported { + kind: TopologyKind::Euclidean, + }, + } + ); + let vertex_canonicalization_failure = DelaunayConstructionFailure::VertexCanonicalization { + vertex_index: 2, + source: GlobalTopologyModelError::NonFiniteCoordinate { + axis: 1, + value: f64::INFINITY, + }, + }; + assert_matches!( + vertex_canonicalization_failure, + DelaunayConstructionFailure::VertexCanonicalization { + vertex_index: 2, + source: GlobalTopologyModelError::NonFiniteCoordinate { + axis: 1, + value, + }, + } if value.is_infinite() + ); + let canonicalized_point_failure = DelaunayConstructionFailure::CanonicalizedPointValidation { + vertex_index: 3, + source: ConstructionCoordinateValidationError::InvalidCoordinate { + dimension: 2, + coordinate_index: 0, + coordinate_value: ConstructionInvalidCoordinateValue::Nan, + }, + }; + assert_matches!( + canonicalized_point_failure, + DelaunayConstructionFailure::CanonicalizedPointValidation { + vertex_index: 3, + source: ConstructionCoordinateValidationError::InvalidCoordinate { + dimension: 2, + coordinate_index: 0, + coordinate_value: ConstructionInvalidCoordinateValue::Nan, + }, + } + ); + let cavity_failure = DelaunayConstructionFailure::InsertionCavityFilling { + source: CavityFillingError::EmptyFanTriangulation, + }; + assert_matches!( + cavity_failure, + DelaunayConstructionFailure::InsertionCavityFilling { + source: CavityFillingError::EmptyFanTriangulation, + } + ); +} + #[test] fn root_exports_cover_flattened_public_api() -> Result<(), RootApiExportTestError> { use delaunay::builder::DelaunayTriangulationBuilder as BuilderModuleBuilder; @@ -180,15 +278,15 @@ fn root_exports_cover_flattened_public_api() -> Result<(), RootApiExportTestErro }; let vertices = vec![ - delaunay::vertex!([0.0, 0.0, 0.0]), - delaunay::vertex!([1.0, 0.0, 0.0]), - delaunay::vertex!([0.0, 1.0, 0.0]), - delaunay::vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let options: ConstructionOptions = ConstructionModuleOptions::default().with_insertion_order(InsertionOrderStrategy::Input); - let builder: BuilderModuleBuilder<'_, f64, (), 3> = + let builder: BuilderModuleBuilder<'_, (), 3> = DelaunayTriangulationBuilder::new(&vertices).construction_options(options); let mut dt: DelaunayTriangulation<_, (), (), 3> = builder.build::<()>()?; @@ -227,14 +325,13 @@ fn root_exports_cover_flattened_public_api() -> Result<(), RootApiExportTestErro #[test] fn preludes_cover_bench_apis() -> Result<(), PreludeExportTestError> { - let _generated_points: Vec> = - try_generate_random_points_seeded(3, (0.0, 1.0), 42)?; - - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + let _generated_points: Vec> = try_generate_random_points_seeded(3, (0.0, 1.0), 42)?; + + let vertices: Vec> = vec![ + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let options = ConstructionOptions::default().with_insertion_order(InsertionOrderStrategy::Input); @@ -250,40 +347,11 @@ fn preludes_cover_bench_apis() -> Result<(), PreludeExportTestError> { dt.validate().unwrap(); assert_bistellar_flips(&dt); - let mut empty_tds: InsertionTds = InsertionTds::empty(); + let mut empty_tds: InsertionTds<(), (), 2> = InsertionTds::empty(); assert_eq!( repair_neighbor_pointers_local(&mut empty_tds, &[], None)?, 0 ); - assert_matches!( - DelaunayConstructionFailure::GeometricDegeneracy { - message: "synthetic".to_string(), - }, - DelaunayConstructionFailure::GeometricDegeneracy { .. } - ); - let unsupported_periodic_dimension = - DelaunayConstructionFailure::UnsupportedPeriodicDimension { - dimension: 4, - max_validated_dimension: 3, - tracking_issue: 416, - }; - assert_matches!( - unsupported_periodic_dimension, - DelaunayConstructionFailure::UnsupportedPeriodicDimension { - dimension: 4, - max_validated_dimension: 3, - tracking_issue: 416, - } - ); - let cavity_failure = DelaunayConstructionFailure::InsertionCavityFilling { - source: CavityFillingError::EmptyFanTriangulation, - }; - assert_matches!( - cavity_failure, - DelaunayConstructionFailure::InsertionCavityFilling { - source: CavityFillingError::EmptyFanTriangulation, - } - ); assert_eq!( CavityRepairStage::PrimaryInsertion.to_string(), "primary insertion" @@ -441,9 +509,9 @@ fn geometry_prelude_covers_typed_error_variants() { #[test] fn generator_prelude_covers_validated_coordinate_ranges() -> Result<(), PreludeExportTestError> { let generated_range = CoordinateRange::try_new(0.0_f64, 1.0)?; - let range_points: Vec> = + let range_points: Vec> = generate_random_points_in_range_seeded(3, generated_range, 42); - let grid_points: Vec> = generate_grid_points(NonZeroUsize::MIN, 1.0, [0.0, 0.0])?; + let grid_points: Vec> = generate_grid_points(NonZeroUsize::MIN, 1.0, [0.0, 0.0])?; let builder = RandomTriangulationBuilder::try_new(NonZeroUsize::MIN, (-1.0_f64, 1.0))? .seed(7) .insertion_order(InsertionOrderStrategy::Input); @@ -553,53 +621,71 @@ fn validation_prelude_covers_configuration_error() { ); } -fn simplex_prelude_vertices(origin: f64, scale: f64) -> Vec> { +fn simplex_prelude_vertices(origin: f64, scale: f64) -> Vec> { let mut vertices = Vec::with_capacity(D + 1); - vertices.push(vertex!([origin; D])); + vertices.push( + delaunay::prelude::Vertex::<(), _>::try_new([origin; D]) + .expect("finite simplex origin should construct"), + ); for axis in 0..D { let mut coords = [origin; D]; coords[axis] = origin + scale; - vertices.push(vertex!(coords)); + vertices.push( + delaunay::prelude::Vertex::<(), _>::try_new(coords) + .expect("finite simplex axis vertex should construct"), + ); } vertices } -fn cospherical_prelude_vertices() -> Vec> { +fn cospherical_prelude_vertices() -> Vec> { let mut vertices = Vec::with_capacity(D + 2); for axis in 0..D { let mut coords = [0.0; D]; coords[axis] = 1.0; - vertices.push(vertex!(coords)); + vertices.push( + delaunay::prelude::Vertex::<(), _>::try_new(coords) + .expect("finite cospherical axis vertex should construct"), + ); } let mut negative_first_axis = [0.0; D]; negative_first_axis[0] = -1.0; - vertices.push(vertex!(negative_first_axis)); + vertices.push( + delaunay::prelude::Vertex::<(), _>::try_new(negative_first_axis) + .expect("finite cospherical negative first-axis vertex should construct"), + ); let mut negative_second_axis = [0.0; D]; negative_second_axis[1] = -1.0; - vertices.push(vertex!(negative_second_axis)); + vertices.push( + delaunay::prelude::Vertex::<(), _>::try_new(negative_second_axis) + .expect("finite cospherical negative second-axis vertex should construct"), + ); vertices } -fn degenerate_prelude_vertices() -> Vec> { +fn degenerate_prelude_vertices() -> Vec> { let mut vertices = Vec::with_capacity(D + 1); let mut coordinate = 0.0; for _ in 0..=D { let mut coords = [0.0; D]; coords[0] = coordinate; - vertices.push(vertex!(coords)); + vertices.push( + delaunay::prelude::Vertex::<(), _>::try_new(coords) + .expect("finite degenerate fixture vertex should construct"), + ); coordinate += 1.0; } vertices } fn assert_single_simplex_ridge_star( - vertices: &[Vertex], + vertices: &[Vertex<(), D>], ) -> Result<(), PreludeExportTestError> { let dt = DelaunayTriangulation::new(vertices)?; let ridge = RidgeVertices::::try_from_vertices(dt.tds().vertex_keys().take(D - 1))?; @@ -694,9 +780,12 @@ fn topology_spaces_prelude_covers_toroidal_domain_api() -> Result<(), PreludeExp fn triangulation_prelude_covers_generic_layer() -> Result<(), GenericTriangulationConstructionError> { let vertices = vec![ - triangulation_vertex!([0.0, 0.0]), - triangulation_vertex!([1.0, 0.0]), - triangulation_vertex!([0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]) + .expect("finite 2D vertex should construct"), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0]) + .expect("finite 2D vertex should construct"), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0]) + .expect("finite 2D vertex should construct"), ]; let tds = GenericTriangulation::, (), (), 2>::build_initial_simplex( @@ -791,11 +880,11 @@ fn construction_prelude_covers_random_point_generation_failure_variant() { #[test] fn diagnostic_preludes_cover_repair_apis() -> Result<(), PreludeExportTestError> { - let vertices: Vec> = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + let vertices: Vec> = vec![ + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let mut dt = DelaunayizeDelaunayTriangulationBuilder::new(&vertices).build::<()>()?; @@ -844,7 +933,7 @@ fn diagnostic_preludes_cover_repair_apis() -> Result<(), PreludeExportTestError> let outcome = delaunayize_by_flips(&mut dt, DelaunayizeConfig::default())?; assert!(!outcome.used_fallback_rebuild); - let _typed_outcome: DelaunayizeOutcome = outcome; + let _typed_outcome: DelaunayizeOutcome<(), (), 3> = outcome; let _typed_error: Option = None; Ok(()) } @@ -852,7 +941,7 @@ fn diagnostic_preludes_cover_repair_apis() -> Result<(), PreludeExportTestError> #[cfg(feature = "diagnostics")] #[test] fn diagnostics_prelude_covers_opt_in_helpers() -> Result<(), PreludeExportTestError> { - let tds: Tds = Tds::empty(); + let tds: Tds<(), (), 2> = Tds::empty(); debug_print_first_delaunay_violation(&tds, None); let report = delaunay_violation_report(&tds, None)?; let _typed_report: DelaunayViolationReport = report; @@ -860,7 +949,7 @@ fn diagnostics_prelude_covers_opt_in_helpers() -> Result<(), PreludeExportTestEr assert!(DiagnosticNeighborSlot::Boundary.is_boundary()); let kernel = AdaptiveKernel::new(); - let point = Point::new([0.0, 0.0]); + let point = Point::try_new([0.0, 0.0]).expect("finite point coordinates"); let conflict_simplices = SimplexKeyBuffer::new(); assert_eq!( verify_conflict_region_completeness(&tds, &kernel, &point, &conflict_simplices), diff --git a/tests/proptest_convex_hull.rs b/tests/proptest_convex_hull.rs index 79ab48ee..78a3b30d 100644 --- a/tests/proptest_convex_hull.rs +++ b/tests/proptest_convex_hull.rs @@ -42,13 +42,13 @@ macro_rules! test_minimal_simplex_hull { let mut points = Vec::new(); // Origin - points.push(Point::new([0.0f64; $dim])); + points.push(Point::try_new([0.0f64; $dim]).expect("finite point coordinates")); // D more points along coordinate axes for i in 0..$dim { let mut coords = [0.0f64; $dim]; coords[i] = base_scale; - points.push(Point::new(coords)); + points.push(Point::try_new(coords).expect("finite point coordinates")); } let vertices = Vertex::from_points(&points); @@ -84,7 +84,7 @@ macro_rules! test_convex_hull_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -129,7 +129,7 @@ macro_rules! test_convex_hull_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -179,10 +179,10 @@ macro_rules! test_convex_hull_properties { #[test] fn []( initial_vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)), - new_point in prop::array::[](finite_coordinate()).prop_map(Point::new) + new_point in prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) ) { if let Ok(mut dt) = DelaunayTriangulation::<_, (), (), $dim>::new_with_topology_guarantee( &initial_vertices, @@ -239,7 +239,7 @@ macro_rules! test_convex_hull_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -281,7 +281,7 @@ macro_rules! test_convex_hull_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { diff --git a/tests/proptest_delaunay_triangulation.rs b/tests/proptest_delaunay_triangulation.rs index cd67fd5d..212199c9 100644 --- a/tests/proptest_delaunay_triangulation.rs +++ b/tests/proptest_delaunay_triangulation.rs @@ -34,11 +34,10 @@ use delaunay::geometry::kernel::{AdaptiveKernel, RobustKernel}; use delaunay::prelude::construction::{ ConstructionOptions, DedupPolicy, DelaunayRepairPolicy, DelaunayTriangulation, - TopologyGuarantee, Vertex, vertex, + TopologyGuarantee, Vertex, }; use delaunay::prelude::geometry::*; -use delaunay::prelude::insertion::{InsertionError, InsertionOutcome}; -use delaunay::prelude::tds::TdsError; +use delaunay::prelude::insertion::InsertionOutcome; use delaunay::prelude::validation::ValidationPolicy; use proptest::prelude::*; use proptest::test_runner::{Config, TestCaseError, TestRunner}; @@ -100,30 +99,32 @@ fn non_finite_coordinate() -> impl Strategy { } /// Strategy for generating 2D vertices -fn vertex_2d() -> impl Strategy> { - prop::array::uniform2(finite_coordinate()).prop_map(Point::new) +fn vertex_2d() -> impl Strategy> { + prop::array::uniform2(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 3D vertices -fn vertex_3d() -> impl Strategy> { - prop::array::uniform3(finite_coordinate()).prop_map(Point::new) +fn vertex_3d() -> impl Strategy> { + prop::array::uniform3(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 4D vertices -fn vertex_4d() -> impl Strategy> { - prop::array::uniform4(finite_coordinate()).prop_map(Point::new) +fn vertex_4d() -> impl Strategy> { + prop::array::uniform4(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 5D vertices -fn vertex_5d() -> impl Strategy> { - prop::array::uniform5(finite_coordinate()).prop_map(Point::new) +fn vertex_5d() -> impl Strategy> { + prop::array::uniform5(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /* Deduplicate helpers to avoid pathological degeneracies in property tests */ -fn dedup_vertices_by_coords( - vertices: Vec>, -) -> Vec> { - let mut unique: Vec> = Vec::with_capacity(vertices.len()); +fn dedup_vertices_by_coords(vertices: Vec>) -> Vec> { + let mut unique: Vec> = Vec::with_capacity(vertices.len()); 'outer: for v in vertices { let vc = *v.point().coords(); for u in &unique { @@ -149,9 +150,7 @@ fn dedup_vertices_by_coords( /// keep it as defense-in-depth in case the generators change. /// /// Returns `true` iff **no** coordinate hyperplane contains `>= D` input vertices. -fn has_no_coordinate_hyperplane_degeneracy( - vertices: &[Vertex], -) -> bool { +fn has_no_coordinate_hyperplane_degeneracy(vertices: &[Vertex<(), D>]) -> bool { let mut axis_counts = [0usize; D]; for v in vertices { @@ -176,7 +175,7 @@ fn has_no_coordinate_hyperplane_degeneracy( /// - This property is specifically scoped to the “standard incremental path” where we want /// insertion-order invariance for **validation Levels 1–3** (elements + structure + topology), /// leaving degenerate/perturbation behavior to dedicated suites. -fn has_no_nearly_coplanar_tetrahedra_3d(vertices: &[Vertex]) -> bool { +fn has_no_nearly_coplanar_tetrahedra_3d(vertices: &[Vertex<(), 3>]) -> bool { // Relative threshold: volume6 scales with L^3, so compare against scale^3. // Intentionally loose: we only want to drop “shrink-pathological” nearly-coplanar cases, // not typical random inputs. @@ -252,7 +251,7 @@ fn has_no_nearly_coplanar_tetrahedra_3d(vertices: &[Vertex]) -> bool /// the chosen triangulation. /// - This property is intentionally scoped to a slice of 3D space where we expect insertion-order /// invariance for **validation Levels 1–3** without depending on perturbation heuristics. -fn has_no_cospherical_5_tuples_3d(vertices: &[Vertex]) -> bool { +fn has_no_cospherical_5_tuples_3d(vertices: &[Vertex<(), 3>]) -> bool { const MAX_N: usize = 15; let n = vertices.len(); @@ -367,17 +366,17 @@ macro_rules! prop_assert_levels_1_to_3_valid { /// Rectangle corners are exactly cocircular, so randomized insertion orders /// exercise the `OnSuspicion` validation cadence on an adversarial boundary /// between valid Delaunay triangulations. -fn rectangle_corners_2d(origin: [i32; 2], side_lengths: [i32; 2]) -> Vec> { +fn rectangle_corners_2d(origin: [i32; 2], side_lengths: [i32; 2]) -> Vec> { let x0 = f64::from(origin[0]); let y0 = f64::from(origin[1]); let x1 = x0 + f64::from(side_lengths[0]); let y1 = y0 + f64::from(side_lengths[1]); vec![ - Point::new([x0, y0]), - Point::new([x1, y0]), - Point::new([x0, y1]), - Point::new([x1, y1]), + Point::try_new([x0, y0]).expect("finite point coordinates"), + Point::try_new([x1, y0]).expect("finite point coordinates"), + Point::try_new([x0, y1]).expect("finite point coordinates"), + Point::try_new([x1, y1]).expect("finite point coordinates"), ] } @@ -385,7 +384,7 @@ fn rectangle_corners_2d(origin: [i32; 2], side_lengths: [i32; 2]) -> Vec Vec> { +fn cuboid_corners_3d(origin: [i32; 3], side_lengths: [i32; 3]) -> Vec> { let x0 = f64::from(origin[0]); let y0 = f64::from(origin[1]); let z0 = f64::from(origin[2]); @@ -394,19 +393,19 @@ fn cuboid_corners_3d(origin: [i32; 3], side_lengths: [i32; 3]) -> Vec(mut points: Vec>, seed: u64) -> Vec> { +fn shuffle_points(mut points: Vec>, seed: u64) -> Vec> { let mut rng = rand::rngs::StdRng::seed_from_u64(seed); points.shuffle(&mut rng); points @@ -416,14 +415,16 @@ fn shuffle_points(mut points: Vec>, seed: u64) -> /// validation succeeds after each attempted insertion and at the end of the /// sequence. fn assert_on_suspicion_sequence_valid( - points: Vec>, + points: Vec>, ) -> Result<(), TestCaseError> { let mut dt: DelaunayTriangulation, (), (), D> = DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); dt.set_validation_policy(ValidationPolicy::OnSuspicion); for (idx, point) in points.into_iter().enumerate() { - let result = dt.insert_best_effort_with_statistics(vertex!(point)); + let result = dt.insert_best_effort_with_statistics( + delaunay::prelude::Vertex::<(), _>::try_new(point.into()).unwrap(), + ); if dt.number_of_simplices() > 0 { let validation = dt.validate(); @@ -450,32 +451,22 @@ fn assert_on_suspicion_sequence_valid( Ok(()) } -/// Return `true` when insertion rejected a vertex through the typed invalid-vertex path. -const fn rejected_invalid_vertex(result: &Result) -> bool { - matches!( - result, - Err(InsertionError::TopologyValidation( - TdsError::InvalidVertex { .. } - )) - ) -} - /// Build a fixed non-degenerate unit simplex for non-finite insertion tests. -fn unit_simplex_vertices() -> Vec> { +fn unit_simplex_vertices() -> Vec> { let mut points = Vec::with_capacity(D + 1); - points.push(Point::new([0.0; D])); + points.push(Point::try_new([0.0; D]).expect("finite point coordinates")); for axis in 0..D { let mut coords = [0.0; D]; coords[axis] = 1.0; - points.push(Point::new(coords)); + points.push(Point::try_new(coords).expect("finite point coordinates")); } Vertex::from_points(&points) } -/// Build a point with one non-finite coordinate and finite filler elsewhere. -fn non_finite_point(axis: usize, value: f64) -> Point { +/// Build coordinate input with one non-finite coordinate and finite filler elsewhere. +fn non_finite_coords(axis: usize, value: f64) -> [f64; D] { let mut coords = [0.25; D]; for (idx, coord) in coords.iter_mut().enumerate() { if idx % 2 == 1 { @@ -483,22 +474,38 @@ fn non_finite_point(axis: usize, value: f64) -> Point { } } coords[axis] = value; - Point::new(coords) + coords } -/// Assert non-finite insertions fail explicitly and leave topology fully valid. -fn assert_non_finite_insert_rejected_and_preserves_validity( - initial_vertices: &[Vertex], - point: Point, +const fn invalid_coordinate_value(value: f64) -> InvalidCoordinateValue { + if value.is_nan() { + InvalidCoordinateValue::Nan + } else if value.is_sign_positive() { + InvalidCoordinateValue::PositiveInfinity + } else { + InvalidCoordinateValue::NegativeInfinity + } +} + +/// Assert non-finite public coordinates are rejected before insertion can mutate topology. +fn assert_non_finite_point_rejected_and_preserves_validity( + initial_vertices: &[Vertex<(), D>], + coords: [f64; D], + axis: usize, + value: f64, ) -> Result<(), TestCaseError> { - let mut empty: DelaunayTriangulation, (), (), D> = + prop_assert_eq!( + Point::::try_new(coords), + Err(CoordinateValidationError::InvalidCoordinate { + coordinate_index: axis, + coordinate_value: invalid_coordinate_value(value), + dimension: D, + }) + ); + + let empty: DelaunayTriangulation, (), (), D> = DelaunayTriangulation::empty_with_topology_guarantee(TopologyGuarantee::PLManifold); - let empty_result = empty.insert_with_statistics(vertex!(point)); - prop_assert!( - rejected_invalid_vertex(&empty_result), - "{D}D empty insertion should reject non-finite coordinates as an invalid vertex, got {empty_result:?}" - ); prop_assert_eq!(empty.number_of_vertices(), 0); prop_assert_eq!(empty.number_of_simplices(), 0); let empty_validation = empty.validate(); @@ -508,24 +515,18 @@ fn assert_non_finite_insert_rejected_and_preserves_validity( empty_validation.err() ); - let mut dt = - DelaunayTriangulation::, (), (), D>::new_with_topology_guarantee( - initial_vertices, - TopologyGuarantee::PLManifold, - ) - .map_err(|err| { - TestCaseError::fail(format!( - "{D}D finite initial simplex construction failed: {err:?}" - )) - })?; + let dt = DelaunayTriangulation::, (), (), D>::new_with_topology_guarantee( + initial_vertices, + TopologyGuarantee::PLManifold, + ) + .map_err(|err| { + TestCaseError::fail(format!( + "{D}D finite initial simplex construction failed: {err:?}" + )) + })?; let vertex_count = dt.number_of_vertices(); let simplex_count = dt.number_of_simplices(); - let result = dt.insert_with_statistics(vertex!(point)); - prop_assert!( - rejected_invalid_vertex(&result), - "{D}D constructed insertion should reject non-finite coordinates as an invalid vertex, got {result:?}" - ); prop_assert_eq!(dt.number_of_vertices(), vertex_count); prop_assert_eq!(dt.number_of_simplices(), simplex_count); @@ -554,7 +555,7 @@ enum InsertionOrder3dRunStatus { fn insert_vertices_3d_no_retry_or_skip( dt: &mut DelaunayTriangulation, (), (), 3>, - vertices: &[Vertex], + vertices: &[Vertex<(), 3>], ) -> InsertionOrder3dRunStatus { for (idx, v) in vertices.iter().enumerate() { let result = dt.insert_best_effort_with_statistics(*v); @@ -585,51 +586,60 @@ fn insert_vertices_3d_no_retry_or_skip( #[test] fn regression_insertion_order_3d_case_001() { let points = vec![ - Point::new([ + Point::try_new([ -44.945_005_120_296_535, 99.756_651_886_055_48, -42.335_053_700_056_505, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ 73.700_964_567_587_9, -63.612_148_894_577_96, 74.105_780_529_266_97, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ 17.617_609_442_986_858, 82.105_819_037_825_37, 96.704_584_226_450_41, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ -84.890_821_270_170_35, -81.870_190_740_455_77, -15.370_417_803_763_095, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ 91.801_310_819_317_69, -59.048_589_230_347_44, 32.595_036_426_462_01, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ 71.577_209_178_042_41, 67.758_811_346_527_74, -13.549_441_576_893_503, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ 13.014_109_789_682_385, -44.307_776_868_547_734, 12.429_638_278_179_98, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ 56.289_801_257_128_175, 8.701_429_811_815_13, -74.980_558_102_554_71, - ]), - Point::new([ + ]) + .expect("finite point coordinates"), + Point::try_new([ -94.434_209_076_643_74, -18.606_000_110_184_25, 4.127_801_687_310_058, - ]), + ]) + .expect("finite point coordinates"), ]; let vertices = Vertex::from_points(&points); let mut dt: DelaunayTriangulation<_, (), (), 3> = @@ -676,7 +686,9 @@ macro_rules! gen_incremental_insertion_validity { // generator changes. prop_assume!(has_no_coordinate_hyperplane_degeneracy(&initial_vertices)); - let additional_vertex = vertex!(additional_point); + let additional_vertex = + delaunay::prelude::Vertex::<(), _>::try_new(additional_point.into()) + .unwrap(); // Avoid duplicate insertion cases here; duplicate-handling is tested in dedicated suites. let add_coords = *additional_vertex.point().coords(); @@ -743,7 +755,8 @@ proptest! { prop_assume!(has_no_nearly_coplanar_tetrahedra_3d(&initial_vertices)); prop_assume!(has_no_cospherical_5_tuples_3d(&initial_vertices)); - let additional_vertex = vertex!(additional_point); + let additional_vertex = + delaunay::prelude::Vertex::<(), _>::try_new(additional_point.into()).unwrap(); // Avoid duplicate insertion cases here; duplicate-handling is tested in dedicated suites. let add_coords = *additional_vertex.point().coords(); @@ -832,15 +845,20 @@ macro_rules! gen_non_finite_insert_rejection_tests { proptest! { $( #[test] - fn []( + fn []( value in non_finite_coordinate(), axis in 0usize..$dim, ) { init_tracing(); let vertices = unit_simplex_vertices::<$dim>(); - let point = non_finite_point::<$dim>(axis, value); - - assert_non_finite_insert_rejected_and_preserves_validity(&vertices, point)?; + let coords = non_finite_coords::<$dim>(axis, value); + + assert_non_finite_point_rejected_and_preserves_validity( + &vertices, + coords, + axis, + value, + )?; } )+ } @@ -865,7 +883,7 @@ macro_rules! gen_duplicate_coords_test { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min..=$max ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -1093,7 +1111,7 @@ macro_rules! gen_high_dim_delaunay_smoke { $dim ); - let mut cloud_points: Vec> = + let mut cloud_points: Vec> = vertices.iter().map(|v| *v.point()).collect(); if cloud_points.len() >= 2 { cloud_points.push(cloud_points[0]); @@ -1101,12 +1119,12 @@ macro_rules! gen_high_dim_delaunay_smoke { for coord in &mut jittered { *coord += 1e-7; } - cloud_points.push(Point::new(jittered)); + cloud_points.push(Point::try_new(jittered).expect("finite point coordinates")); } let cloud_vertices = Vertex::from_points(&cloud_points); let options = ConstructionOptions::default() - .with_dedup_policy(DedupPolicy::Epsilon { tolerance: 1e-6 }); + .with_dedup_policy(DedupPolicy::try_epsilon(1e-6).unwrap()); let cloud_dt = match DelaunayTriangulation::<_, (), (), $dim>::new_with_options( &cloud_vertices, options, @@ -1247,7 +1265,7 @@ macro_rules! gen_insertion_order_robustness_test { #[test] fn []( points in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|pts| dedup_vertices_by_coords::<$dim>(Vertex::from_points(&pts))) ) { @@ -1396,7 +1414,8 @@ fn prop_insertion_order_robustness_3d() { let mut runner = TestRunner::new(config); let strategy = prop::collection::vec( - prop::array::uniform3(finite_coordinate()).prop_map(Point::new), + prop::array::uniform3(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), 6..=10, ) .prop_map(|pts| dedup_vertices_by_coords::<3>(Vertex::from_points(&pts))); @@ -1889,7 +1908,7 @@ gen_insertion_order_robustness_high_dim!(5, 7, 12, #[cfg(feature = "slow-tests") /// Count unique coordinate tuples using bitwise equality. /// Used to ensure at least D+1 distinct points before attempting triangulation. -fn count_unique_coords_by_bits(pts: &[Point]) -> usize { +fn count_unique_coords_by_bits(pts: &[Point]) -> usize { let mut set: HashSet> = HashSet::with_capacity(pts.len()); for p in pts { let coords = *p.coords(); @@ -1905,9 +1924,9 @@ macro_rules! gen_duplicate_cloud_test { /// Generate random point cloud with exact duplicates and near-duplicates (1e-7 jitter). /// Tests full construction pipeline with realistic messy inputs. $(#[$attr])* - fn []() -> impl Strategy>> { + fn []() -> impl Strategy>> { prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), 6..=12, ) .prop_map(|mut pts| { @@ -1921,7 +1940,7 @@ macro_rules! gen_duplicate_cloud_test { for c in &mut coords { *c += 1e-7; } - pts.push(Point::new(coords)); + pts.push(Point::try_new(coords).expect("finite point coordinates")); } pts }) @@ -1949,11 +1968,11 @@ macro_rules! gen_duplicate_cloud_test { let unique = count_unique_coords_by_bits(&points); prop_assume!(unique > $min_vertices); - let vertices: Vec> = Vertex::from_points(&points); + let vertices: Vec> = Vertex::from_points(&points); let build_start = std::time::Instant::now(); let options = ConstructionOptions::default() - .with_dedup_policy(DedupPolicy::Epsilon { tolerance: 1e-6 }); + .with_dedup_policy(DedupPolicy::try_epsilon(1e-6).unwrap()); let dt = DelaunayTriangulation::<_, (), (), $dim>::new_with_options( &vertices, options, diff --git a/tests/proptest_euler_characteristic.rs b/tests/proptest_euler_characteristic.rs index cf9d592d..d7ebf25b 100644 --- a/tests/proptest_euler_characteristic.rs +++ b/tests/proptest_euler_characteristic.rs @@ -16,7 +16,7 @@ //! //! For deterministic tests with known configurations, see `euler_characteristic.rs`. -use delaunay::prelude::construction::{DelaunayTriangulation, TopologyGuarantee, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulation, TopologyGuarantee}; use delaunay::prelude::generators::try_generate_random_triangulation_with_topology_guarantee; use delaunay::topology::characteristics::{euler, validation}; use proptest::prelude::*; @@ -67,7 +67,7 @@ macro_rules! test_euler_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(|coords| vertex!(coords)), + prop::array::[](finite_coordinate()).prop_map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()), $min_vertices..$max_vertices ) ) { @@ -100,7 +100,7 @@ macro_rules! test_euler_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(|coords| vertex!(coords)), + prop::array::[](finite_coordinate()).prop_map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()), $min_vertices..$max_vertices ) ) { @@ -140,7 +140,7 @@ macro_rules! test_euler_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(|coords| vertex!(coords)), + prop::array::[](finite_coordinate()).prop_map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()), $min_vertices..$max_vertices ) ) { diff --git a/tests/proptest_facet.rs b/tests/proptest_facet.rs index 7bf5cd63..a0befd86 100644 --- a/tests/proptest_facet.rs +++ b/tests/proptest_facet.rs @@ -37,7 +37,7 @@ macro_rules! test_facet_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -49,7 +49,7 @@ macro_rules! test_facet_properties { for simplex_key in tds.simplex_keys() { // Each simplex has D+1 facets (one opposite each vertex) for facet_index in 0..=($dim as u8) { - if let Ok(facet) = FacetView::new(&tds, simplex_key, facet_index) { + if let Ok(facet) = FacetView::try_new(&tds, simplex_key, facet_index) { if let Ok(facet_vertices) = facet.vertices() { let vertex_count = facet_vertices.count(); prop_assert_eq!( @@ -71,7 +71,7 @@ macro_rules! test_facet_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -89,7 +89,7 @@ macro_rules! test_facet_properties { let simplex_vertex_count = simplex.vertices().len(); for facet_index in 0..=($dim as u8) { - if let Ok(facet) = FacetView::new(&tds, simplex_key, facet_index) { + if let Ok(facet) = FacetView::try_new(&tds, simplex_key, facet_index) { if let Ok(facet_vertices) = facet.vertices() { let facet_vertex_count = facet_vertices.count(); prop_assert_eq!( @@ -110,7 +110,7 @@ macro_rules! test_facet_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -124,7 +124,7 @@ macro_rules! test_facet_properties { for facet_index in 0..=($dim as u8) { // Each facet should be constructible prop_assert!( - FacetView::new(&tds, simplex_key, facet_index).is_ok(), + FacetView::try_new(&tds, simplex_key, facet_index).is_ok(), "{}D facet {} of simplex should be valid", $dim, facet_index @@ -139,7 +139,7 @@ macro_rules! test_facet_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -151,7 +151,7 @@ macro_rules! test_facet_properties { for simplex_key in tds.simplex_keys() { let mut facet_count = 0; for facet_index in 0..=($dim as u8) { - if FacetView::new(&tds, simplex_key, facet_index).is_ok() { + if FacetView::try_new(&tds, simplex_key, facet_index).is_ok() { facet_count += 1; } } @@ -186,7 +186,7 @@ macro_rules! test_facet_multiplicity { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { diff --git a/tests/proptest_flips.rs b/tests/proptest_flips.rs index aefb43a9..54b0d010 100644 --- a/tests/proptest_flips.rs +++ b/tests/proptest_flips.rs @@ -13,9 +13,7 @@ use delaunay::prelude::construction::{ DelaunayTriangulation, TopologyGuarantee, Triangulation, Vertex, }; use delaunay::prelude::flips::BistellarFlips; -use delaunay::prelude::geometry::{ - AdaptiveKernel, Coordinate, FastKernel, Kernel, Point, RobustKernel, -}; +use delaunay::prelude::geometry::{AdaptiveKernel, FastKernel, Kernel, Point, RobustKernel}; use proptest::prelude::*; use std::collections::{BTreeSet, HashMap}; @@ -54,14 +52,14 @@ fn near_degenerate_edge_length() -> impl Strategy { fn axis_aligned_simplex_vertices( origin: [f64; D], edge_lengths: [f64; D], -) -> Vec> { +) -> Vec> { let mut points = Vec::with_capacity(D + 1); - points.push(Point::new(origin)); + points.push(Point::try_new(origin).expect("finite point coordinates")); for (axis, edge_length) in edge_lengths.iter().copied().enumerate() { let mut coordinates = origin; coordinates[axis] += edge_length; - points.push(Point::new(coordinates)); + points.push(Point::try_new(coordinates).expect("finite point coordinates")); } Vertex::from_points(&points) @@ -71,13 +69,13 @@ fn axis_aligned_simplex_vertices( fn interior_simplex_vertex( origin: [f64; D], edge_lengths: [f64; D], -) -> Vertex { +) -> Vertex<(), D> { let denominator = f64::from(u32::try_from(D + 1).expect("test dimension fits in u32")); let mut coordinates = origin; for (axis, edge_length) in edge_lengths.iter().copied().enumerate() { coordinates[axis] += edge_length / denominator; } - Vertex::from_point(Point::new(coordinates)) + Vertex::try_new(coordinates).expect("finite point coordinates") } /// Captures the public vertex/simplex incidence needed to check round-trips. diff --git a/tests/proptest_geometry.rs b/tests/proptest_geometry.rs index d36f1066..85b4f5e0 100644 --- a/tests/proptest_geometry.rs +++ b/tests/proptest_geometry.rs @@ -32,17 +32,14 @@ fn nonzero_scale() -> impl Strategy { (-90.0_f64..90.0).prop_map(|exponent| 10.0_f64.powf(exponent)) } -fn axis_aligned_simplex( - base: [f64; D], - side_lengths: [f64; D], -) -> Vec> { +fn axis_aligned_simplex(base: [f64; D], side_lengths: [f64; D]) -> Vec> { let mut simplex = Vec::with_capacity(D + 1); - simplex.push(Point::new(base)); + simplex.push(Point::try_new(base).expect("finite point coordinates")); for (axis, side_length) in side_lengths.iter().copied().enumerate() { let mut coords = base; coords[axis] += side_length; - simplex.push(Point::new(coords)); + simplex.push(Point::try_new(coords).expect("finite point coordinates")); } simplex @@ -70,7 +67,7 @@ macro_rules! test_geometry_properties { #[test] fn []( simplex_points in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $num_points ) ) { @@ -99,7 +96,7 @@ macro_rules! test_geometry_properties { #[test] fn []( simplex_points in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $num_points ) ) { @@ -154,7 +151,7 @@ macro_rules! test_geometry_properties { #[test] fn []( simplex_points in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $num_points ) ) { @@ -172,7 +169,7 @@ macro_rules! test_geometry_properties { #[test] fn []( simplex_points in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $num_points ) ) { @@ -252,23 +249,23 @@ proptest! { /// of magnitude. This guards against fixed absolute degeneracy thresholds. #[test] fn prop_low_dimensional_simplex_volume_accepts_scaled_valid_simplices(scale in nonzero_scale()) { - let segment = vec![Point::new([0.0]), Point::new([scale])]; + let segment = vec![Point::try_new([0.0]).expect("finite point coordinates"), Point::try_new([scale]).expect("finite point coordinates")]; let length = simplex_volume(&segment).unwrap(); prop_assert_relative_close(length, scale)?; let triangle = vec![ - Point::new([0.0, 0.0]), - Point::new([scale, 0.0]), - Point::new([0.0, scale]), + Point::try_new([0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([scale, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, scale]).expect("finite point coordinates"), ]; let area = simplex_volume(&triangle).unwrap(); prop_assert_relative_close(area, scale * scale / 2.0)?; let tetrahedron = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([scale, 0.0, 0.0]), - Point::new([0.0, scale, 0.0]), - Point::new([0.0, 0.0, scale]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([scale, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, scale, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 0.0, scale]).expect("finite point coordinates"), ]; let volume = simplex_volume(&tetrahedron).unwrap(); prop_assert_relative_close(volume, scale * scale * scale / 6.0)?; @@ -277,14 +274,14 @@ proptest! { /// Property: Low-dimensional facet measure remains scale-aware for valid facets. #[test] fn prop_low_dimensional_facet_measure_accepts_scaled_valid_facets(scale in nonzero_scale()) { - let segment = vec![Point::new([0.0, 0.0]), Point::new([scale, 0.0])]; + let segment = vec![Point::try_new([0.0, 0.0]).expect("finite point coordinates"), Point::try_new([scale, 0.0]).expect("finite point coordinates")]; let length = facet_measure(&segment).unwrap(); prop_assert_relative_close(length, scale)?; let triangle = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([scale, 0.0, 0.0]), - Point::new([0.0, scale, 0.0]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([scale, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, scale, 0.0]).expect("finite point coordinates"), ]; let area = facet_measure(&triangle).unwrap(); prop_assert_relative_close(area, scale * scale / 2.0)?; diff --git a/tests/proptest_orientation.rs b/tests/proptest_orientation.rs index abbb5b34..dc4a5389 100644 --- a/tests/proptest_orientation.rs +++ b/tests/proptest_orientation.rs @@ -30,7 +30,7 @@ macro_rules! gen_orientation_construction_and_tamper_props { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|points| Vertex::from_points(&points)) ) { @@ -54,7 +54,7 @@ macro_rules! gen_orientation_construction_and_tamper_props { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|points| Vertex::from_points(&points)) ) { @@ -79,22 +79,30 @@ macro_rules! gen_orientation_construction_and_tamper_props { first_simplex_vertices.swap(0, 1); let tampered_json = serde_json::to_string(&serialized).unwrap(); - let tampered_tds: Tds = - serde_json::from_str(&tampered_json).unwrap(); - - prop_assert!( - !tampered_tds.is_coherently_oriented(), - "{}D: tampered triangulation should not remain coherently oriented", - $dim - ); - prop_assert!( - matches!( - tampered_tds.is_valid(), - Err(TdsError::OrientationViolation { .. }) - ), - "{}D: tampered triangulation should fail with OrientationViolation", - $dim - ); + match serde_json::from_str::>(&tampered_json) { + Ok(tampered_tds) => { + prop_assert!( + !tampered_tds.is_coherently_oriented(), + "{}D: tampered triangulation should not remain coherently oriented", + $dim + ); + prop_assert!( + matches!( + tampered_tds.is_valid(), + Err(TdsError::OrientationViolation { .. }) + ), + "{}D: tampered triangulation should fail with OrientationViolation", + $dim + ); + } + Err(error) => { + prop_assert!( + error.to_string().contains("Orientation invariant violated"), + "{}D: tampered triangulation should be rejected for orientation, got {error}", + $dim + ); + } + } } } } @@ -111,7 +119,7 @@ macro_rules! gen_orientation_incremental_props { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|points| Vertex::from_points(&points)) ) { diff --git a/tests/proptest_point.rs b/tests/proptest_point.rs index 79eb00b7..bd6c406d 100644 --- a/tests/proptest_point.rs +++ b/tests/proptest_point.rs @@ -6,7 +6,7 @@ //! - Equality reflexivity, symmetry, and transitivity //! - Serialization/deserialization roundtrips //! - Coordinate extraction consistency -//! - NaN handling determinism +//! - Non-finite coordinate rejection and scalar special-value determinism use delaunay::prelude::geometry::*; use proptest::prelude::*; @@ -25,23 +25,27 @@ fn finite_f64() -> impl Strategy { } /// Strategy for generating 2D points with finite coordinates -fn point_2d() -> impl Strategy> { - prop::array::uniform2(finite_f64()).prop_map(Point::new) +fn point_2d() -> impl Strategy> { + prop::array::uniform2(finite_f64()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 3D points with finite coordinates -fn point_3d() -> impl Strategy> { - prop::array::uniform3(finite_f64()).prop_map(Point::new) +fn point_3d() -> impl Strategy> { + prop::array::uniform3(finite_f64()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 4D points with finite coordinates -fn point_4d() -> impl Strategy> { - prop::array::uniform4(finite_f64()).prop_map(Point::new) +fn point_4d() -> impl Strategy> { + prop::array::uniform4(finite_f64()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 5D points with finite coordinates -fn point_5d() -> impl Strategy> { - prop::array::uniform5(finite_f64()).prop_map(Point::new) +fn point_5d() -> impl Strategy> { + prop::array::uniform5(finite_f64()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Helper function to compute hash of a point @@ -60,8 +64,8 @@ macro_rules! gen_equal_points_equal_hashes { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let p1 = Point::new(coords); - let p2 = Point::new(coords); + let p1 = Point::try_new(coords).expect("finite point coordinates"); + let p2 = Point::try_new(coords).expect("finite point coordinates"); prop_assert_eq!(p1, p2); prop_assert_eq!(hash_point(&p1), hash_point(&p2)); } @@ -89,8 +93,8 @@ macro_rules! gen_equality_symmetric { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let p1 = Point::new(coords); - let p2 = Point::new(coords); + let p1 = Point::try_new(coords).expect("finite point coordinates"); + let p2 = Point::try_new(coords).expect("finite point coordinates"); prop_assert_eq!(p1, p2); prop_assert_eq!(p2, p1); } @@ -105,9 +109,9 @@ macro_rules! gen_equality_transitive { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let p1 = Point::new(coords); - let p2 = Point::new(coords); - let p3 = Point::new(coords); + let p1 = Point::try_new(coords).expect("finite point coordinates"); + let p2 = Point::try_new(coords).expect("finite point coordinates"); + let p3 = Point::try_new(coords).expect("finite point coordinates"); prop_assert_eq!(p1, p2); prop_assert_eq!(p2, p3); prop_assert_eq!(p1, p3); @@ -152,7 +156,7 @@ macro_rules! gen_coordinate_roundtrip { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let point = Point::new(coords); + let point = Point::try_new(coords).expect("finite point coordinates"); for (i, &coord) in coords.iter().enumerate() { prop_assert!(approx::relative_eq!(point.coords()[i], coord, epsilon = 1e-10)); } @@ -168,7 +172,7 @@ macro_rules! gen_into_conversion_matches_coords { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let point = Point::new(coords); + let point = Point::try_new(coords).expect("finite point coordinates"); let extracted: [f64; $dim] = point.into(); for i in 0..$dim { prop_assert!(approx::relative_eq!(extracted[i], point.coords()[i], epsilon = 1e-10)); @@ -186,7 +190,7 @@ macro_rules! gen_get_method_consistency { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let point = Point::new(coords); + let point = Point::try_new(coords).expect("finite point coordinates"); for (i, &coord) in coords.iter().enumerate() { prop_assert_eq!(point.get(i), Some(coord)); } @@ -204,7 +208,7 @@ macro_rules! gen_finite_coordinates_validate { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let point = Point::new(coords); + let point = Point::try_new(coords).expect("finite point coordinates"); prop_assert!(point.validate().is_ok()); } } @@ -220,12 +224,24 @@ macro_rules! gen_infinite_coordinates_fail_validation { fn [](coords in prop::array::[](finite_f64())) { let mut arr = coords; arr[0] = f64::INFINITY; - let point = Point::new(arr); - prop_assert!(point.validate().is_err()); + prop_assert_eq!( + Point::<$dim>::try_new(arr), + Err(CoordinateValidationError::InvalidCoordinate { + coordinate_index: 0, + coordinate_value: InvalidCoordinateValue::PositiveInfinity, + dimension: $dim, + }) + ); let mut arr2 = coords; arr2[0] = f64::NEG_INFINITY; - let point_neg = Point::new(arr2); - prop_assert!(point_neg.validate().is_err()); + prop_assert_eq!( + Point::<$dim>::try_new(arr2), + Err(CoordinateValidationError::InvalidCoordinate { + coordinate_index: 0, + coordinate_value: InvalidCoordinateValue::NegativeInfinity, + dimension: $dim, + }) + ); } } } @@ -240,8 +256,14 @@ macro_rules! gen_nan_coordinates_fail_validation { fn [](coords in prop::array::[](finite_f64())) { let mut arr = coords; arr[0] = f64::NAN; - let point = Point::new(arr); - prop_assert!(point.validate().is_err()); + prop_assert_eq!( + Point::<$dim>::try_new(arr), + Err(CoordinateValidationError::InvalidCoordinate { + coordinate_index: 0, + coordinate_value: InvalidCoordinateValue::Nan, + dimension: $dim, + }) + ); } } } @@ -254,8 +276,8 @@ macro_rules! gen_ordering_equal_consistency { proptest! { #[test] fn [](coords in prop::array::[](finite_f64())) { - let p1 = Point::new(coords); - let p2 = Point::new(coords); + let p1 = Point::try_new(coords).expect("finite point coordinates"); + let p2 = Point::try_new(coords).expect("finite point coordinates"); if p1 == p2 { prop_assert_eq!(p1.partial_cmp(&p2), Some(std::cmp::Ordering::Equal)); } @@ -306,19 +328,20 @@ macro_rules! gen_nan_handling_consistency { fn [](finite_coord in finite_f64()) { let mut arr = [finite_coord; $dim]; arr[0] = f64::NAN; - let p = Point::new(arr); - prop_assert_eq!(p, p); - let h1 = hash_point(&p); let h2 = hash_point(&p); - prop_assert_eq!(h1, h2); + prop_assert!(Point::<$dim>::try_new(arr).is_err()); + prop_assert!(f64::NAN.ordered_eq(&f64::NAN)); + + let mut hasher1 = FxHasher::default(); + let mut hasher2 = FxHasher::default(); + f64::NAN.hash_scalar(&mut hasher1); + f64::NAN.hash_scalar(&mut hasher2); + prop_assert_eq!(hasher1.finish(), hasher2.finish()); } #[test] fn [](finite_coord in finite_f64()) { let mut arr = [finite_coord; $dim]; arr[0] = f64::NAN; - let p1 = Point::new(arr); - let p2 = Point::new(arr); - prop_assert_eq!(p1, p2); - prop_assert_eq!(hash_point(&p1), hash_point(&p2)); + prop_assert_eq!(Point::<$dim>::try_new(arr), Point::<$dim>::try_new(arr)); } } } diff --git a/tests/proptest_predicates.rs b/tests/proptest_predicates.rs index 4f28da0b..14dd4087 100644 --- a/tests/proptest_predicates.rs +++ b/tests/proptest_predicates.rs @@ -20,23 +20,27 @@ fn finite_coordinate() -> impl Strategy { } /// Strategy for generating 2D points -fn point_2d() -> impl Strategy> { - prop::array::uniform2(finite_coordinate()).prop_map(Point::new) +fn point_2d() -> impl Strategy> { + prop::array::uniform2(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 3D points -fn point_3d() -> impl Strategy> { - prop::array::uniform3(finite_coordinate()).prop_map(Point::new) +fn point_3d() -> impl Strategy> { + prop::array::uniform3(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 4D points -fn point_4d() -> impl Strategy> { - prop::array::uniform4(finite_coordinate()).prop_map(Point::new) +fn point_4d() -> impl Strategy> { + prop::array::uniform4(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 5D points -fn point_5d() -> impl Strategy> { - prop::array::uniform5(finite_coordinate()).prop_map(Point::new) +fn point_5d() -> impl Strategy> { + prop::array::uniform5(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } // ============================================================================= @@ -156,7 +160,7 @@ macro_rules! gen_insphere_inward_scaling_inside { for i in 0..$dim { centroid[i] *= inv_len; } // Use centroid, which is guaranteed to lie inside the simplex - let interior_point = Point::new(centroid); + let interior_point = Point::try_new(centroid).expect("finite point coordinates"); if let Ok(result) = insphere(&simplex, interior_point) { // Check separation to avoid degenerate case @@ -206,7 +210,7 @@ macro_rules! gen_insphere_distant_point_outside_by_radius { for i in 0..$dim { far[i] = dir[i].mul_add(distance, center_coords[i]); } - let far_point = Point::new(far); + let far_point = Point::try_new(far).expect("finite point coordinates"); if let Ok(result) = insphere(&simplex, far_point) { prop_assert!(result == InSphere::OUTSIDE, "Point at 10x circumradius from center should be OUTSIDE, got {:?}", result); } @@ -231,7 +235,7 @@ macro_rules! gen_insphere_scale_center_outside { let scale = 10000.0; let mut far = [0.0_f64; $dim]; for i in 0..$dim { far[i] = center_coords[i] * scale; } - let far_point = Point::new(far); + let far_point = Point::try_new(far).expect("finite point coordinates"); if let Ok(result) = insphere(&simplex, far_point) { if center_coords.iter().any(|&c| c.abs() > 0.01) { prop_assert!(result == InSphere::OUTSIDE, "Point scaled away from circumcenter should be OUTSIDE, got {:?}", result); @@ -293,24 +297,24 @@ proptest! { prop_assume!((base + delta).to_bits() != base.to_bits()); let local_simplex = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([delta, 0.0, 0.0]), - Point::new([0.0, delta, 0.0]), - Point::new([0.0, 0.0, delta]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([delta, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, delta, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 0.0, delta]).expect("finite point coordinates"), ]; - let local_inside = Point::new([0.25 * delta, 0.25 * delta, 0.25 * delta]); - let local_outside = Point::new([2.0 * delta, 2.0 * delta, 2.0 * delta]); + let local_inside = Point::try_new([0.25 * delta, 0.25 * delta, 0.25 * delta]).expect("finite point coordinates"); + let local_outside = Point::try_new([2.0 * delta, 2.0 * delta, 2.0 * delta]).expect("finite point coordinates"); let translated_simplex = vec![ - Point::new([base, base, base]), - Point::new([base + delta, base, base]), - Point::new([base, base + delta, base]), - Point::new([base, base, base + delta]), + Point::try_new([base, base, base]).expect("finite point coordinates"), + Point::try_new([base + delta, base, base]).expect("finite point coordinates"), + Point::try_new([base, base + delta, base]).expect("finite point coordinates"), + Point::try_new([base, base, base + delta]).expect("finite point coordinates"), ]; let inside_coord = 0.25_f64.mul_add(delta, base); let outside_coord = 2.0_f64.mul_add(delta, base); - let translated_inside = Point::new([inside_coord, inside_coord, inside_coord]); - let translated_outside = Point::new([outside_coord, outside_coord, outside_coord]); + let translated_inside = Point::try_new([inside_coord, inside_coord, inside_coord]).expect("finite point coordinates"); + let translated_outside = Point::try_new([outside_coord, outside_coord, outside_coord]).expect("finite point coordinates"); let local_inside_result = robust_insphere(&local_simplex, &local_inside) .map_err(|error| TestCaseError::fail(format!("{error:?}")))?; @@ -345,8 +349,8 @@ proptest! { ) { // Create a nearly degenerate simplex (three nearly collinear points) let coords0 = *p0.coords(); - let p1 = Point::new([coords0[0] + 1.0, coords0[1]]); - let p2 = Point::new([coords0[0] + 2.0, f64::mul_add(scale, 0.01, coords0[1])]); // Very small y offset + let p1 = Point::try_new([coords0[0] + 1.0, coords0[1]]).expect("finite point coordinates"); + let p2 = Point::try_new([coords0[0] + 2.0, f64::mul_add(scale, 0.01, coords0[1])]).expect("finite point coordinates"); // Very small y offset let simplex = [p0, p1, p2]; diff --git a/tests/proptest_serialization.rs b/tests/proptest_serialization.rs index 1d703543..57319c55 100644 --- a/tests/proptest_serialization.rs +++ b/tests/proptest_serialization.rs @@ -19,7 +19,7 @@ type DefaultDt = DelaunayTriangulation, (), /// Check if two points are approximately equal (coordinate-wise) /// Uses relative epsilon comparison suitable for JSON serialization roundtrips -fn points_approx_equal(p1: &Point, p2: &Point) -> bool { +fn points_approx_equal(p1: &Point, p2: &Point) -> bool { p1.coords() .iter() .zip(p2.coords().iter()) @@ -49,7 +49,7 @@ macro_rules! test_serialization_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -61,7 +61,7 @@ macro_rules! test_serialization_properties { let json = serde_json::to_string(&dt).expect("Serialization failed"); // Deserialize from JSON via try_from_tds - let tds: Tds = + let tds: Tds<(), (), $dim> = serde_json::from_str(&json).expect("Deserialization failed"); let deserialized: DefaultDt<$dim> = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new()) @@ -94,7 +94,7 @@ macro_rules! test_serialization_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -105,7 +105,7 @@ macro_rules! test_serialization_properties { if dt.tds().validate().is_ok() { // Serialize and deserialize via try_from_tds let json = serde_json::to_string(&dt).expect("Serialization failed"); - let tds: Tds = + let tds: Tds<(), (), $dim> = serde_json::from_str(&json).expect("Deserialization failed"); let deserialized: DefaultDt<$dim> = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new()) @@ -127,7 +127,7 @@ macro_rules! test_serialization_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -148,7 +148,7 @@ macro_rules! test_serialization_properties { // Serialize and deserialize via try_from_tds let json = serde_json::to_string(&dt).expect("Serialization failed"); - let tds: Tds = + let tds: Tds<(), (), $dim> = serde_json::from_str(&json).expect("Deserialization failed"); let deserialized: DefaultDt<$dim> = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new()) @@ -188,7 +188,7 @@ macro_rules! test_serialization_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -206,7 +206,7 @@ macro_rules! test_serialization_properties { // Serialize and deserialize via try_from_tds let json = serde_json::to_string(&dt).expect("Serialization failed"); - let tds: Tds = + let tds: Tds<(), (), $dim> = serde_json::from_str(&json).expect("Deserialization failed"); let deserialized: DefaultDt<$dim> = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new()) diff --git a/tests/proptest_simplex.rs b/tests/proptest_simplex.rs index bfa8a43c..4e29eacb 100644 --- a/tests/proptest_simplex.rs +++ b/tests/proptest_simplex.rs @@ -37,7 +37,7 @@ macro_rules! test_simplex_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -58,7 +58,7 @@ macro_rules! test_simplex_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -77,7 +77,7 @@ macro_rules! test_simplex_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -98,7 +98,7 @@ macro_rules! test_simplex_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -118,7 +118,7 @@ macro_rules! test_simplex_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { diff --git a/tests/proptest_sos.rs b/tests/proptest_sos.rs index b752abe7..81556f28 100644 --- a/tests/proptest_sos.rs +++ b/tests/proptest_sos.rs @@ -24,7 +24,6 @@ use delaunay::geometry::point::Point; use delaunay::geometry::sos::{sos_insphere_sign, sos_orientation_sign}; -use delaunay::geometry::traits::coordinate::Coordinate; use proptest::prelude::*; // ============================================================================= @@ -39,7 +38,7 @@ use proptest::prelude::*; clippy::float_cmp, reason = "coordinates are integer-derived f64 values, so exact equality is intentional" )] -fn points_all_distinct(points: &[Point]) -> bool { +fn points_all_distinct(points: &[Point]) -> bool { (0..points.len()) .all(|i| ((i + 1)..points.len()).all(|j| points[i].coords() != points[j].coords())) } @@ -89,14 +88,14 @@ macro_rules! gen_sos_tests { ($dim + 1)..=($dim + 1), ), ) { - let points: Vec> = raw + let points: Vec> = raw .iter() .map(|arr| { // Last coordinate forced to 0 → co-hyperplanar. let coords: [f64; $dim] = std::array::from_fn(|i| { if i < $dim - 1 { f64::from(arr[i]) } else { 0.0 } }); - Point::new(coords) + Point::try_new(coords).expect("finite point coordinates") }) .collect(); // In 2D only one coordinate varies (the last is forced @@ -117,13 +116,13 @@ macro_rules! gen_sos_tests { ($dim + 1)..=($dim + 1), ), ) { - let points: Vec> = raw + let points: Vec> = raw .iter() .map(|arr| { let coords: [f64; $dim] = std::array::from_fn(|i| { if i < $dim - 1 { f64::from(arr[i]) } else { 0.0 } }); - Point::new(coords) + Point::try_new(coords).expect("finite point coordinates") }) .collect(); prop_assume!(points_all_distinct(&points)); @@ -149,25 +148,25 @@ macro_rules! gen_sos_tests { ), offset in $uniform(small_int()), ) { - let points: Vec> = raw + let points: Vec> = raw .iter() .map(|arr| { let coords: [f64; $dim] = std::array::from_fn(|i| { if i < $dim - 1 { f64::from(arr[i]) } else { 0.0 } }); - Point::new(coords) + Point::try_new(coords).expect("finite point coordinates") }) .collect(); prop_assume!(points_all_distinct(&points)); let s1 = sos_orientation_sign(&points).unwrap(); - let translated: Vec> = points + let translated: Vec> = points .iter() .map(|p| { let coords: [f64; $dim] = std::array::from_fn(|i| { p.coords()[i] + f64::from(offset[i]) }); - Point::new(coords) + Point::try_new(coords).expect("finite point coordinates") }) .collect(); let s2 = sos_orientation_sign(&translated).unwrap(); @@ -191,18 +190,18 @@ macro_rules! gen_sos_tests { let sides_f64: [f64; $dim] = std::array::from_fn(|i| f64::from(sides[i])); // Simplex: origin corner + D axis-aligned neighbours. - let mut simplex: Vec> = Vec::with_capacity($dim + 1); - simplex.push(Point::new(base_f64)); + let mut simplex: Vec> = Vec::with_capacity($dim + 1); + simplex.push(Point::try_new(base_f64).expect("finite point coordinates")); for axis in 0..$dim { let mut p = base_f64; p[axis] += sides_f64[axis]; - simplex.push(Point::new(p)); + simplex.push(Point::try_new(p).expect("finite point coordinates")); } // Test point: diagonally opposite corner (on the circumsphere). - let test = Point::new( + let test = Point::try_new( std::array::from_fn(|i| base_f64[i] + sides_f64[i]), - ); + ).expect("finite point coordinates"); let sign = sos_insphere_sign(&simplex, &test).unwrap(); prop_assert!(sign == 1 || sign == -1, "SoS insphere must return ±1 in {}D, got {}", $dim, sign); @@ -217,17 +216,17 @@ macro_rules! gen_sos_tests { let base_f64: [f64; $dim] = std::array::from_fn(|i| f64::from(base[i])); let sides_f64: [f64; $dim] = std::array::from_fn(|i| f64::from(sides[i])); - let mut simplex: Vec> = Vec::with_capacity($dim + 1); - simplex.push(Point::new(base_f64)); + let mut simplex: Vec> = Vec::with_capacity($dim + 1); + simplex.push(Point::try_new(base_f64).expect("finite point coordinates")); for axis in 0..$dim { let mut p = base_f64; p[axis] += sides_f64[axis]; - simplex.push(Point::new(p)); + simplex.push(Point::try_new(p).expect("finite point coordinates")); } - let test = Point::new( + let test = Point::try_new( std::array::from_fn(|i| base_f64[i] + sides_f64[i]), - ); + ).expect("finite point coordinates"); let s1 = sos_insphere_sign(&simplex, &test).unwrap(); let s2 = sos_insphere_sign(&simplex, &test).unwrap(); prop_assert_eq!(s1, s2, @@ -244,7 +243,7 @@ macro_rules! gen_sos_tests { #[test] fn []( points in prop::collection::vec( - $uniform(finite_coord()).prop_map(Point::new), + $uniform(finite_coord()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), ($dim + 1)..=($dim + 1), ), ) { @@ -256,10 +255,10 @@ macro_rules! gen_sos_tests { #[test] fn []( simplex in prop::collection::vec( - $uniform(finite_coord()).prop_map(Point::new), + $uniform(finite_coord()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), ($dim + 1)..=($dim + 1), ), - test in $uniform(finite_coord()).prop_map(Point::new), + test in $uniform(finite_coord()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), ) { let sign = sos_insphere_sign(&simplex, &test).unwrap(); prop_assert!(sign == 1 || sign == -1); @@ -283,7 +282,7 @@ proptest! { #[test] fn prop_sos_orientation_rejects_wrong_count( points in prop::collection::vec( - prop::array::uniform2(finite_coord()).prop_map(Point::new), + prop::array::uniform2(finite_coord()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), 0..2_usize, ), ) { diff --git a/tests/proptest_tds.rs b/tests/proptest_tds.rs index c448f949..ff2d4291 100644 --- a/tests/proptest_tds.rs +++ b/tests/proptest_tds.rs @@ -34,7 +34,7 @@ fn tds_empty_does_not_require_coordinate_scalar() { #[derive(Clone, Copy, Debug)] struct NotAScalar; - let tds: Tds = Tds::empty(); + let tds: Tds = Tds::empty(); assert_eq!(tds.number_of_vertices(), 0); assert_eq!(tds.number_of_simplices(), 0); @@ -54,42 +54,46 @@ fn finite_coordinate() -> impl Strategy { } /// Strategy for generating 2D vertices -fn vertex_2d() -> impl Strategy> { - prop::array::uniform2(finite_coordinate()).prop_map(Point::new) +fn vertex_2d() -> impl Strategy> { + prop::array::uniform2(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 3D vertices -fn vertex_3d() -> impl Strategy> { - prop::array::uniform3(finite_coordinate()).prop_map(Point::new) +fn vertex_3d() -> impl Strategy> { + prop::array::uniform3(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 4D vertices -fn vertex_4d() -> impl Strategy> { - prop::array::uniform4(finite_coordinate()).prop_map(Point::new) +fn vertex_4d() -> impl Strategy> { + prop::array::uniform4(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating 5D vertices -fn vertex_5d() -> impl Strategy> { - prop::array::uniform5(finite_coordinate()).prop_map(Point::new) +fn vertex_5d() -> impl Strategy> { + prop::array::uniform5(finite_coordinate()) + .prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")) } /// Strategy for generating a small collection of 2D vertices (4-10 vertices) -fn small_vertex_set_2d() -> impl Strategy>> { +fn small_vertex_set_2d() -> impl Strategy>> { prop::collection::vec(vertex_2d(), 4..=10).prop_map(|v| Vertex::from_points(&v)) } /// Strategy for generating a small collection of 3D vertices (5-12 vertices) -fn small_vertex_set_3d() -> impl Strategy>> { +fn small_vertex_set_3d() -> impl Strategy>> { prop::collection::vec(vertex_3d(), 5..=12).prop_map(|v| Vertex::from_points(&v)) } /// Strategy for generating a small collection of 4D vertices (6-14 vertices) -fn small_vertex_set_4d() -> impl Strategy>> { +fn small_vertex_set_4d() -> impl Strategy>> { prop::collection::vec(vertex_4d(), 6..=14).prop_map(|v| Vertex::from_points(&v)) } /// Strategy for generating a small collection of 5D vertices (7-16 vertices) -fn small_vertex_set_5d() -> impl Strategy>> { +fn small_vertex_set_5d() -> impl Strategy>> { prop::collection::vec(vertex_5d(), 7..=16).prop_map(|v| Vertex::from_points(&v)) } diff --git a/tests/proptest_triangulation.rs b/tests/proptest_triangulation.rs index d5577c3e..c16f4885 100644 --- a/tests/proptest_triangulation.rs +++ b/tests/proptest_triangulation.rs @@ -35,7 +35,7 @@ //! Tests are generated for dimensions 2D-5D using macros to reduce duplication. use ::uuid::Uuid; -use delaunay::prelude::construction::{DelaunayTriangulation, TopologyGuarantee, Vertex, vertex}; +use delaunay::prelude::construction::{DelaunayTriangulation, TopologyGuarantee, Vertex}; use delaunay::prelude::geometry::*; use delaunay::prelude::tds::SimplexKey; use proptest::prelude::*; @@ -163,7 +163,7 @@ macro_rules! test_simplex_quality_properties { #[test] fn []( simplex_points in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $num_points ) ) { @@ -188,7 +188,7 @@ macro_rules! test_simplex_quality_properties { #[test] fn []( simplex_points in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $num_points ), scale in 0.1f64..10.0f64 @@ -199,7 +199,7 @@ macro_rules! test_simplex_quality_properties { let ratio1 = r1 / r_inner_1; // Scale all points - let scaled_points: Vec> = simplex_points + let scaled_points: Vec> = simplex_points .iter() .map(|p| { let coords = *p.coords(); @@ -207,7 +207,7 @@ macro_rules! test_simplex_quality_properties { for i in 0..$dim { scaled[i] = coords[i] * scale; } - Point::new(scaled) + Point::try_new(scaled).expect("finite point coordinates") }) .collect(); @@ -239,26 +239,26 @@ macro_rules! test_simplex_quality_properties { // First vertex at origin (scaled) let origin = [0.0f64; $dim]; - regular_points.push(Point::new(origin)); + regular_points.push(Point::try_new(origin).expect("finite point coordinates")); // D more vertices with one coordinate = base_scale for i in 0..$dim { let mut coords = [0.0f64; $dim]; coords[i] = base_scale; - regular_points.push(Point::new(coords)); + regular_points.push(Point::try_new(coords).expect("finite point coordinates")); } // Create a flatter simplex (still valid but lower quality) // Make it elongated in one direction with small extent in others let mut degenerate_points = Vec::with_capacity($dim + 1); - degenerate_points.push(Point::new([0.0f64; $dim])); + degenerate_points.push(Point::try_new([0.0f64; $dim]).expect("finite point coordinates")); for i in 0..$dim { let mut coords = [0.0f64; $dim]; let i_f64: f64 = safe_usize_to_scalar(i).unwrap(); coords[0] = (10.0 + i_f64) * base_scale; let axis = (i + 1) % $dim; coords[axis] = 0.05_f64.mul_add(base_scale, coords[axis]); - degenerate_points.push(Point::new(coords)); + degenerate_points.push(Point::try_new(coords).expect("finite point coordinates")); } // Try to compute quality metrics, but skip if degenerate @@ -295,11 +295,11 @@ macro_rules! test_simplex_quality_properties { ) { // Create a regular simplex let mut regular_points = Vec::new(); - regular_points.push(Point::new([0.0f64; $dim])); + regular_points.push(Point::try_new([0.0f64; $dim]).expect("finite point coordinates")); for i in 0..$dim { let mut coords = [0.0f64; $dim]; coords[i] = base_scale; - regular_points.push(Point::new(coords)); + regular_points.push(Point::try_new(coords).expect("finite point coordinates")); } // Create a nearly-degenerate version (collapse last vertex toward origin) @@ -311,7 +311,7 @@ macro_rules! test_simplex_quality_properties { for i in 0..$dim { collapsed_coords[i] *= 0.01; // Move 99% toward origin } - *last = Point::new(collapsed_coords); + *last = Point::try_new(collapsed_coords).expect("finite point coordinates"); } // Compare quality metrics - collapsed should be much worse @@ -351,7 +351,7 @@ macro_rules! test_quality_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -381,7 +381,7 @@ macro_rules! test_quality_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)), translation in prop::array::[](finite_coordinate()) @@ -400,7 +400,7 @@ macro_rules! test_quality_properties { for i in 0..$dim { translated[i] = coords[i] + translation[i]; } - Point::new(translated) + Point::try_new(translated).expect("finite point coordinates") }) .collect::>(); @@ -455,7 +455,7 @@ macro_rules! test_quality_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)), translation in prop::array::[](finite_coordinate()) @@ -474,7 +474,7 @@ macro_rules! test_quality_properties { for i in 0..$dim { translated[i] = coords[i] + translation[i]; } - Point::new(translated) + Point::try_new(translated).expect("finite point coordinates") }) .collect::>(); @@ -529,7 +529,7 @@ macro_rules! test_quality_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)), scale in 0.1f64..10.0f64 @@ -548,7 +548,7 @@ macro_rules! test_quality_properties { for i in 0..$dim { scaled[i] = coords[i] * scale; } - Point::new(scaled) + Point::try_new(scaled).expect("finite point coordinates") }) .collect::>(); @@ -603,7 +603,7 @@ macro_rules! test_quality_properties { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(Point::new), + prop::array::[](finite_coordinate()).prop_map(|coords| Point::try_new(coords).expect("finite point coordinates")), $min_vertices..=$max_vertices ).prop_map(|v| Vertex::from_points(&v)) ) { @@ -684,7 +684,7 @@ macro_rules! test_facet_topology_invariant { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(|coords| vertex!(coords)), + prop::array::[](finite_coordinate()).prop_map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()), $min_vertices..$max_vertices ) ) { @@ -714,7 +714,7 @@ macro_rules! test_facet_topology_invariant { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(|coords| vertex!(coords)), + prop::array::[](finite_coordinate()).prop_map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()), $min_vertices..$max_vertices ) ) { @@ -750,7 +750,7 @@ macro_rules! test_facet_topology_invariant { #[test] fn []( vertices in prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(|coords| vertex!(coords)), + prop::array::[](finite_coordinate()).prop_map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()), $min_vertices..$max_vertices ) ) { @@ -806,7 +806,7 @@ macro_rules! gen_high_dim_facet_topology_smoke { let target_cases = config.cases; let mut runner = TestRunner::new(config); let strategy = prop::collection::vec( - prop::array::[](finite_coordinate()).prop_map(|coords| vertex!(coords)), + prop::array::[](finite_coordinate()).prop_map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()), $min_vertices..=$max_vertices, ); let stats = RefCell::new(SmokeStats::default()); diff --git a/tests/proptest_vertex.rs b/tests/proptest_vertex.rs index 6782a530..ad451817 100644 --- a/tests/proptest_vertex.rs +++ b/tests/proptest_vertex.rs @@ -5,18 +5,20 @@ //! - Vertex equality is reflexive, symmetric, and transitive //! - Vertex hashing consistency with equality (Eq/Hash contract) //! - UUID uniqueness across generated vertices -//! - Point coordinate validation (rejects NaN/Infinity) +//! - Vertex coordinate validation (rejects NaN/Infinity) //! - Vertex ordering consistency (lexicographic by coordinates) //! //! Tests are generated for dimensions 2D-5D using macros to reduce duplication. #![allow(unused_imports)] // Imports used in macro expansion -use delaunay::prelude::construction::{Vertex, vertex}; -use delaunay::prelude::geometry::Point; +use delaunay::prelude::construction::{Vertex, VertexValidationError}; +use delaunay::prelude::geometry::{CoordinateConversionError, Point}; +use delaunay::prelude::tds::UuidValidationError; use proptest::prelude::*; use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; +use uuid::Uuid; // ============================================================================= // TEST CONFIGURATION @@ -55,15 +57,15 @@ macro_rules! test_vertex_properties { /// Property: Vertex equality is reflexive (v == v) #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let vertex: Vertex = vertex!(coords); + let vertex: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); prop_assert_eq!(vertex, vertex, "{}D: Vertex should equal itself", $dim); } /// Property: Vertex equality is symmetric (v1 == v2 implies v2 == v1) #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let v1: Vertex = vertex!(coords); - let v2: Vertex = vertex!(coords); + let v1: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); + let v2: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); prop_assert_eq!(v1, v2, "{}D: Vertices with same coords should be equal", $dim); prop_assert_eq!(v2, v1, "{}D: Equality should be symmetric", $dim); } @@ -71,9 +73,9 @@ macro_rules! test_vertex_properties { /// Property: Vertex equality is transitive (v1 == v2 and v2 == v3 implies v1 == v3) #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let v1: Vertex = vertex!(coords); - let v2: Vertex = vertex!(coords); - let v3: Vertex = vertex!(coords); + let v1: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); + let v2: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); + let v3: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); prop_assert_eq!(v1, v2, "{}D: v1 should equal v2", $dim); prop_assert_eq!(v2, v3, "{}D: v2 should equal v3", $dim); @@ -83,8 +85,8 @@ macro_rules! test_vertex_properties { /// Property: Equal vertices have equal hashes (Eq/Hash contract) #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let v1: Vertex = vertex!(coords); - let v2: Vertex = vertex!(coords); + let v1: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); + let v2: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); prop_assert_eq!(v1, v2, "{}D: Vertices should be equal", $dim); @@ -99,8 +101,8 @@ macro_rules! test_vertex_properties { /// Property: Vertices with different coordinates have different equality #[test] fn [](coords1 in prop::array::[](finite_coordinate()), coords2 in prop::array::[](finite_coordinate())) { - let v1: Vertex = vertex!(coords1); - let v2: Vertex = vertex!(coords2); + let v1: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords1).unwrap(); + let v2: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords2).unwrap(); // If coordinates differ (component-wise), vertices should not be equal if coords1.iter().zip(coords2.iter()).any(|(a, b)| (a - b).abs() > 1e-12) { @@ -111,9 +113,9 @@ macro_rules! test_vertex_properties { /// Property: UUIDs are unique across generated vertices #[test] fn [](coords_list in prop::collection::vec(prop::array::[](finite_coordinate()), 1..=20_usize)) { - let vertices: Vec> = coords_list + let vertices: Vec> = coords_list .into_iter() - .map(|coords| vertex!(coords)) + .map(|coords| delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap()) .collect(); let mut seen_uuids = HashSet::new(); @@ -136,17 +138,17 @@ macro_rules! test_vertex_properties { /// Property: Vertices can be stored in HashMap with consistent lookup #[test] fn [](coords_list in prop::collection::vec(prop::array::[](finite_coordinate()), 1..=10_usize)) { - let mut map: HashMap, usize> = HashMap::new(); + let mut map: HashMap, usize> = HashMap::new(); // Insert vertices with their indices for (i, coords) in coords_list.iter().enumerate() { - let vertex: Vertex = vertex!(*coords); + let vertex: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(*coords).unwrap(); map.insert(vertex, i); } // Lookup using new vertices with same coordinates for (expected_index, coords) in coords_list.iter().enumerate() { - let lookup_vertex: Vertex = vertex!(*coords); + let lookup_vertex: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(*coords).unwrap(); if let Some(&actual_index) = map.get(&lookup_vertex) { prop_assert_eq!( actual_index, @@ -166,14 +168,14 @@ macro_rules! test_vertex_properties { /// Property: Vertex dimension matches const parameter #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let vertex: Vertex = vertex!(coords); + let vertex: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); prop_assert_eq!(vertex.dim(), $dim, "{}D: Vertex dimension should match D", $dim); } /// Property: Valid vertices pass validation #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let vertex: Vertex = vertex!(coords); + let vertex: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); prop_assert!( vertex.is_valid().is_ok(), "{}D: Vertex with finite coordinates should be valid", @@ -184,8 +186,8 @@ macro_rules! test_vertex_properties { /// Property: Vertex ordering is consistent with lexicographic coordinate order #[test] fn [](coords1 in prop::array::[](finite_coordinate()), coords2 in prop::array::[](finite_coordinate())) { - let v1: Vertex = vertex!(coords1); - let v2: Vertex = vertex!(coords2); + let v1: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords1).unwrap(); + let v2: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords2).unwrap(); // Compare vertices let vertex_cmp = v1.partial_cmp(&v2); @@ -203,7 +205,7 @@ macro_rules! test_vertex_properties { /// Property: Vertex data is preserved #[test] fn [](coords in prop::array::[](finite_coordinate()), data in prop::num::i32::ANY) { - let vertex: Vertex = vertex!(coords, data); + let vertex: Vertex = delaunay::prelude::Vertex::<_, _>::try_new_with_data(coords, data).unwrap(); prop_assert_eq!( vertex.data().copied(), Some(data), @@ -215,7 +217,7 @@ macro_rules! test_vertex_properties { /// Property: Vertex point() returns correct coordinates #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let vertex: Vertex = vertex!(coords); + let vertex: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); let point = vertex.point(); let point_coords = *point.coords(); @@ -231,7 +233,7 @@ macro_rules! test_vertex_properties { /// Property: Vertices can be converted to coordinate arrays #[test] fn [](coords in prop::array::[](finite_coordinate())) { - let vertex: Vertex = vertex!(coords); + let vertex: Vertex<(), $dim> = delaunay::prelude::Vertex::<(), _>::try_new(coords).unwrap(); let array: [f64; $dim] = vertex.into(); for (i, (&original, &converted)) in coords.iter().zip(array.iter()).enumerate() { @@ -258,30 +260,56 @@ test_vertex_properties!(5); // ============================================================================= proptest! { - /// Property: Vertices with NaN coordinates should be detected as invalid + /// Property: Vertex constructors reject non-finite coordinates before storage. #[test] - fn prop_vertex_rejects_nan_2d(valid_coord in finite_coordinate(), nan_coord in non_finite_coordinate(), nan_index in 0..2_usize) { + fn prop_vertex_rejects_non_finite_2d(valid_coord in finite_coordinate(), non_finite_coord in non_finite_coordinate(), non_finite_index in 0..2_usize) { let mut coords = [valid_coord; 2]; - coords[nan_index] = nan_coord; + coords[non_finite_index] = non_finite_coord; - // Point validation should catch non-finite values during construction - let point_result: Result, _> = Point::try_from(coords); + let vertex_result = Vertex::<(), 2>::try_new(coords); + prop_assert!( + matches!( + vertex_result, + Err(CoordinateConversionError::NonFiniteValue { + coordinate_index, + .. + }) if coordinate_index == non_finite_index + ), + "Vertex::try_new should reject non-finite coordinates at index {non_finite_index}" + ); + + let vertex_with_data_result = Vertex::::try_new_with_data(coords, 7); + prop_assert!( + matches!( + vertex_with_data_result, + Err(CoordinateConversionError::NonFiniteValue { + coordinate_index, + .. + }) if coordinate_index == non_finite_index + ), + "Vertex::try_new_with_data should reject non-finite coordinates at index {non_finite_index}" + ); + + // The lower-level point boundary should reject the same coordinate set. + let point_result: Result, _> = Point::try_from(coords); prop_assert!( point_result.is_err(), - "Point construction should reject non-finite coordinates" + "Point construction should reject the same non-finite coordinates" ); } } -/// Property: Empty vertex (with nil UUID) should fail validation +/// Property: nil UUIDs are rejected before a vertex can be stored. #[test] -fn prop_empty_vertex_invalid_3d() { - let vertex: Vertex = Vertex::empty(); - let validation_result = vertex.is_valid(); - assert!( - validation_result.is_err(), - "Empty vertex with nil UUID should fail validation" +fn prop_try_new_with_uuid_rejects_nil_uuid_3d() { + let point = Point::try_from([0.0, 0.0, 0.0]).unwrap(); + let validation_result = Vertex::<(), 3>::try_new_with_uuid(point, Uuid::nil(), None); + assert_eq!( + validation_result.unwrap_err(), + VertexValidationError::InvalidUuid { + source: UuidValidationError::NilUuid, + } ); } diff --git a/tests/public_topology_api.rs b/tests/public_topology_api.rs index a40bc960..dc6b7a91 100644 --- a/tests/public_topology_api.rs +++ b/tests/public_topology_api.rs @@ -6,9 +6,10 @@ //! - Simplex neighborhood traversal via [`DelaunayTriangulation::simplex_neighbors`] //! - Building and validating the opt-in [`AdjacencyIndex`] +use delaunay::prelude::DelaunayTriangulationConstructionError; use delaunay::prelude::TopologyGuarantee; +use delaunay::prelude::geometry::CoordinateConversionError; use delaunay::prelude::query::*; -use delaunay::prelude::{DelaunayTriangulationConstructionError, construction::vertex}; use std::collections::HashSet; #[derive(Debug, thiserror::Error)] @@ -16,6 +17,8 @@ enum PublicTopologyApiTestError { #[error(transparent)] Construction(#[from] DelaunayTriangulationConstructionError), #[error(transparent)] + CoordinateConversion(#[from] CoordinateConversionError), + #[error(transparent)] AdjacencyIndex(#[from] AdjacencyIndexBuildError), #[error("single tetrahedron triangulation has no vertices")] EmptySingleTetrahedronVertices, @@ -31,10 +34,10 @@ enum PublicTopologyApiTestError { fn edges_and_incident_edges_on_single_tetrahedron() -> Result<(), PublicTopologyApiTestError> { // Single tetrahedron: 4 vertices, 1 simplex, 6 unique edges. let vertices = vec![ - vertex!([0.0, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?, ]; let dt: DelaunayTriangulation<_, (), (), 3> = @@ -104,12 +107,12 @@ fn adjacency_index_on_double_tetrahedron() -> Result<(), PublicTopologyApiTestEr // Two tetrahedra sharing a triangular facet. let vertices: Vec<_> = vec![ // Shared triangle - vertex!([0.0, 0.0, 0.0]), - vertex!([2.0, 0.0, 0.0]), - vertex!([1.0, 2.0, 0.0]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([2.0, 0.0, 0.0])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 2.0, 0.0])?, // Two apices - vertex!([1.0, 0.7, 1.5]), - vertex!([1.0, 0.7, -1.5]), + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, 1.5])?, + delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.7, -1.5])?, ]; let dt: DelaunayTriangulation<_, (), (), 3> = diff --git a/tests/regressions.rs b/tests/regressions.rs index 2b1a99af..6d2323a3 100644 --- a/tests/regressions.rs +++ b/tests/regressions.rs @@ -6,12 +6,12 @@ use delaunay::prelude::construction::{ ConstructionOptions, DelaunayTriangulation, DelaunayTriangulationBuilder, - InsertionOrderStrategy, RetryPolicy, TopologyGuarantee, Vertex, vertex, + InsertionOrderStrategy, RetryPolicy, TopologyGuarantee, Vertex, }; #[cfg(feature = "diagnostics")] use delaunay::prelude::diagnostics::debug_print_first_delaunay_violation; use delaunay::prelude::generators::generate_random_points_in_ball_seeded; -use delaunay::prelude::geometry::{Coordinate, CoordinateRange, Point, RobustKernel}; +use delaunay::prelude::geometry::{CoordinateRange, Point, RobustKernel}; use delaunay::prelude::insertion::{ HullExtensionReason, InsertionError, InsertionErrorKind, InsertionErrorSummary, }; @@ -23,9 +23,9 @@ use delaunay::prelude::ordering::{ /// Replays a full Hilbert ordering while keeping only the prefix that first /// exposed issue #307, so the regression stays fast and deterministic. fn hilbert_ordered_prefix( - points: Vec>, + points: Vec>, prefix_len: usize, -) -> Vec> { +) -> Vec> { let bounds = coordinate_bounds(&points); let bits_per_coord = HilbertBitDepth::try_new(31).expect("test bit depth must be valid"); let quantized: Vec<[u32; D]> = points @@ -38,7 +38,7 @@ fn hilbert_ordered_prefix( let indices = hilbert_indices_prequantized(&quantized, bits_per_coord) .expect("4D Hilbert indices should fit in u128"); - let mut keyed: Vec<(u128, [u32; D], Point, usize)> = points + let mut keyed: Vec<(u128, [u32; D], Point, usize)> = points .into_iter() .enumerate() .map(|(input_index, point)| { @@ -68,13 +68,13 @@ fn hilbert_ordered_prefix( keyed .into_iter() .take(prefix_len) - .map(|(_, _, point, _)| vertex!(point)) + .map(|(_, _, point, _)| delaunay::prelude::Vertex::<(), _>::try_new(point.into()).unwrap()) .collect() } /// Computes the scalar range used by batch Hilbert ordering so regression /// prefixes match the original full construction order. -fn coordinate_bounds(points: &[Point]) -> CoordinateRange { +fn coordinate_bounds(points: &[Point]) -> CoordinateRange { let (min, max) = points .iter() .flat_map(Point::coords) @@ -96,7 +96,7 @@ fn coordinate_bounds(points: &[Point]) -> CoordinateRang /// insertion order — across representative dimensions and adversarial inputs. #[test] fn regression_hilbert_batch_quantize_matches_two_step_path() { - fn assert_paths_match(points: &[Point]) { + fn assert_paths_match(points: &[Point]) { let bounds = coordinate_bounds(points); // Mirror `order_vertices_hilbert`'s per-dimension precision so the // `D * bits <= 128` index-width invariant holds for every D. @@ -132,35 +132,53 @@ fn regression_hilbert_batch_quantize_matches_two_step_path() { // Adversarial mixes: negative/asymmetric ranges, clamping at both ends, // duplicate cells, and exact endpoints. assert_paths_match::<2>(&[ - Point::new([-2.0, -1.0]), - Point::new([-1.5, 0.25]), - Point::new([0.1, -0.7]), - Point::new([3.0, 3.0]), - Point::new([3.0, 3.0]), + Point::try_new([-2.0, -1.0]).expect("finite point coordinates"), + Point::try_new([-1.5, 0.25]).expect("finite point coordinates"), + Point::try_new([0.1, -0.7]).expect("finite point coordinates"), + Point::try_new([3.0, 3.0]).expect("finite point coordinates"), + Point::try_new([3.0, 3.0]).expect("finite point coordinates"), ]); assert_paths_match::<3>(&[ - Point::new([-2.0, -1.0, 0.0]), - Point::new([-1.5, 0.25, 1.75]), - Point::new([0.1, -0.7, 2.2]), - Point::new([3.0, 3.0, -2.0]), + Point::try_new([-2.0, -1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([-1.5, 0.25, 1.75]).expect("finite point coordinates"), + Point::try_new([0.1, -0.7, 2.2]).expect("finite point coordinates"), + Point::try_new([3.0, 3.0, -2.0]).expect("finite point coordinates"), ]); assert_paths_match::<5>(&[ - Point::new([-2.0, -1.0, 0.0, 1.0, 2.0]), - Point::new([-1.5, 0.25, 1.75, 2.5, -0.5]), - Point::new([0.1, -0.7, 2.2, -1.8, 1.4]), - Point::new([3.0, 3.0, -2.0, -2.0, 0.5]), + Point::try_new([-2.0, -1.0, 0.0, 1.0, 2.0]).expect("finite point coordinates"), + Point::try_new([-1.5, 0.25, 1.75, 2.5, -0.5]).expect("finite point coordinates"), + Point::try_new([0.1, -0.7, 2.2, -1.8, 1.4]).expect("finite point coordinates"), + Point::try_new([3.0, 3.0, -2.0, -2.0, 0.5]).expect("finite point coordinates"), ]); } #[test] fn regression_empty_circumsphere_2d_minimal_case() { let vertices = vec![ - vertex!([48.564_246_621_452_234, 23.481_505_128_710_488]), - vertex!([-9.807_184_344_740_996, -36.451_902_443_093_33]), - vertex!([75.784_620_110_257_45, 25.382_048_382_678_306]), - vertex!([50.330_335_525_698_53, 25.294_356_716_784_847]), - vertex!([77.411_339_748_608_4, -86.531_849_594_875_54]), - vertex!([-93.661_180_847_043, 1.562_430_007_326_195_9]), + delaunay::prelude::Vertex::<(), _>::try_new([ + 48.564_246_621_452_234, + 23.481_505_128_710_488, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ + -9.807_184_344_740_996, + -36.451_902_443_093_33, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ + 75.784_620_110_257_45, + 25.382_048_382_678_306, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([ + 50.330_335_525_698_53, + 25.294_356_716_784_847, + ]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([77.411_339_748_608_4, -86.531_849_594_875_54]) + .unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([-93.661_180_847_043, 1.562_430_007_326_195_9]) + .unwrap(), ]; let mut dt: DelaunayTriangulation<_, (), (), 2> = @@ -187,10 +205,10 @@ fn regression_empty_circumsphere_2d_minimal_case() { fn regression_issue_120_minimal_failing_input_2d() { // From docs/archive/issue_120_investigation.md (Example Failure Case (2D)). let vertices = vec![ - vertex!([0.0, 0.0]), - vertex!([-54.687, 0.0]), - vertex!([-85.026, 36.185]), - vertex!([0.0, 38.424]), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([-54.687, 0.0]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([-85.026, 36.185]).unwrap(), + delaunay::prelude::Vertex::<(), _>::try_new([0.0, 38.424]).unwrap(), ]; let dt: DelaunayTriangulation<_, (), (), 2> = @@ -229,19 +247,21 @@ fn regression_insertion_error_summary_preserves_top_level_retryability() { #[test] fn regression_periodic_neighbor_validation_uses_lifted_vertex_offsets() { - let vertices: Vec> = (0..7) + let vertices: Vec> = (0..7) .map(|index| { let index_f64 = f64::from(u32::try_from(index).expect("test index fits in u32")); - vertex!([ + delaunay::prelude::Vertex::<(), _>::try_new([ 0.9_f64.mul_add(((index_f64 + 1.0) * 0.618_033_988_749_894_8).fract(), 0.05), 0.9_f64.mul_add(((index_f64 + 1.0) * 0.414_213_562_373_095_03).fract(), 0.05), ]) + .unwrap() }) .collect(); let kernel = RobustKernel::::new(); let dt = DelaunayTriangulationBuilder::new(&vertices) - .toroidal([1.0_f64; 2]) + .try_toroidal([1.0_f64; 2]) + .unwrap() .build_with_kernel::<_, ()>(&kernel) .expect("periodic 2D build should succeed"); @@ -252,7 +272,7 @@ fn regression_periodic_neighbor_validation_uses_lifted_vertex_offsets() { ); assert!( dt.tds().is_valid().is_ok(), - "neighbor validation must compare lifted (vertex, offset) identities" + "neighbor validation must compare lifted (offset) identities" ); } @@ -270,9 +290,12 @@ fn regression_periodic_neighbor_validation_uses_lifted_vertex_offsets() { #[test] fn regression_issue_306_3d_construction_succeeds() { let seed: u64 = 0xE30C_7858_2376_677C; - let points = generate_random_points_in_ball_seeded::(35, 100.0, seed) + let points = generate_random_points_in_ball_seeded::<3>(35, 100.0, seed) .expect("point generation should succeed"); - let vertices: Vec> = points.into_iter().map(|p| vertex!(p)).collect(); + let vertices: Vec> = points + .into_iter() + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p.into()).unwrap()) + .collect(); let dt: Result, _> = DelaunayTriangulation::new(&vertices); assert!( @@ -288,7 +311,7 @@ fn regression_issue_306_3d_construction_succeeds() { #[test] fn regression_issue_307_4d_bulk_repair_keeps_positive_orientation() { let seed: u64 = 0x9B77_86C9_99C5_6A16; - let points = generate_random_points_in_ball_seeded::(100, 100.0, seed) + let points = generate_random_points_in_ball_seeded::<4>(100, 100.0, seed) .expect("point generation should succeed"); let vertices = hilbert_ordered_prefix(points, 14); @@ -353,9 +376,12 @@ fn regression_issue_204_4d_500_local_repair_budget() { let ball_radius = 100.0; let n_points: usize = 500; - let points = generate_random_points_in_ball_seeded::(n_points, ball_radius, seed) + let points = generate_random_points_in_ball_seeded::<4>(n_points, ball_radius, seed) .expect("point generation should succeed"); - let vertices: Vec> = points.into_iter().map(|p| vertex!(p)).collect(); + let vertices: Vec> = points + .into_iter() + .map(|p| delaunay::prelude::Vertex::<(), _>::try_new(p.into()).unwrap()) + .collect(); let (dt, stats) = DelaunayTriangulation::<_, (), (), 4>::new_with_construction_statistics(&vertices) diff --git a/tests/semgrep/doctests/unwrap_expect.txt b/tests/semgrep/doctests/unwrap_expect.txt index 4d2de1d3..d48d88f9 100644 --- a/tests/semgrep/doctests/unwrap_expect.txt +++ b/tests/semgrep/doctests/unwrap_expect.txt @@ -13,6 +13,18 @@ // ok: delaunay.rust.no-unwrap-expect-in-doctests /// Prefer `?` to `.expect("message")` in public examples. +// ok: delaunay.rust.no-unwrap-expect-in-doctests +/// let vertex = delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0]).expect("finite vertex coordinates"); + +// ok: delaunay.rust.no-unwrap-expect-in-doctests +/// let vertex = delaunay::prelude::Vertex::<_, _>::try_new_with_data([0.0, 0.0], 1).expect("finite vertex coordinates"); + +// ruleid: delaunay.rust.no-unwrap-expect-in-doctests +/// let vertex: delaunay::prelude::Vertex<(), 2> = maybe_vertex.expect("finite vertex coordinates"); + +// ruleid: delaunay.rust.no-unwrap-expect-in-doctests +/// let vertex = Some(delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0])).expect("finite vertex coordinates"); + // ruleid: delaunay.rust.no-box-dyn-error-in-doctests /// # Ok::<(), Box>(()) diff --git a/tests/semgrep/src/project_rules/rust_style.rs b/tests/semgrep/src/project_rules/rust_style.rs index 7d7894f5..2ec30102 100644 --- a/tests/semgrep/src/project_rules/rust_style.rs +++ b/tests/semgrep/src/project_rules/rust_style.rs @@ -71,6 +71,312 @@ pub fn public_panic_bypass() { panic!("public APIs should return typed errors instead"); } +// ruleid: delaunay.rust.no-legacy-coordinate-generic-api +type LegacyPoint = Point; + +// ok: delaunay.rust.no-legacy-coordinate-generic-api +type CurrentPoint = Point<3>; + +// ruleid: delaunay.rust.no-legacy-coordinate-generic-api +type LegacyVertex = Vertex; + +// ok: delaunay.rust.no-legacy-coordinate-generic-api +type CurrentVertex = Vertex<(), 3>; + +// ok: delaunay.rust.no-legacy-coordinate-generic-api +type TuplePayloadVertex = Vertex<(i32, i32), 2>; + +// ruleid: delaunay.rust.no-legacy-coordinate-generic-api +type LegacyTds = Tds; + +// ok: delaunay.rust.no-legacy-coordinate-generic-api +type CurrentTds = Tds<(), (), 3>; + +// ruleid: delaunay.rust.no-legacy-coordinate-generic-api +type LegacyConvexHull = ConvexHull, (), (), 3>; + +// ok: delaunay.rust.no-legacy-coordinate-generic-api +type CurrentConvexHull = ConvexHull<(), (), 3>; + +// ruleid: delaunay.rust.no-legacy-coordinate-generic-api +type LegacyConvexHull3D = ConvexHull3D, (), ()>; + +// ok: delaunay.rust.no-legacy-coordinate-generic-api +type CurrentConvexHull3D = ConvexHull3D<(), ()>; + +// ruleid: delaunay.rust.no-legacy-coordinate-generic-api +type LegacyBuilder<'v> = DelaunayTriangulationBuilder<'v, f64, (), 3>; + +// ok: delaunay.rust.no-legacy-coordinate-generic-api +type CurrentBuilder<'v> = DelaunayTriangulationBuilder<'v, (), 3>; + +pub fn legacy_point_constructor() { + // ruleid: delaunay.rust.no-legacy-coordinate-generic-api + let _point = Point::new([1.0, 2.0, 3.0]); +} + +pub fn current_point_constructor() { + // ok: delaunay.rust.no-legacy-coordinate-generic-api + let _point = Point::try_new([1.0, 2.0, 3.0]); +} + +// ruleid: delaunay.rust.no-raw-point-coordinate-storage +pub struct Point { + coords: [f64; D], +} + +// ok: delaunay.rust.no-raw-point-coordinate-storage +pub struct StrongPoint { + coords: ValidatedCoordinates, +} + +// ruleid: delaunay.rust.no-coordinate-scalar-trait +pub trait CoordinateScalar: Copy {} + +// ok: delaunay.rust.no-coordinate-scalar-trait +pub struct ValidatedCoordinates { + values: [f64; D], +} + +pub fn vertex_macro_bad() { + // ruleid: delaunay.rust.no-vertex-macro + let _vertex = vertex![1.0, 2.0, 3.0]; +} + +pub fn vertex_try_new_ok() { + // ok: delaunay.rust.no-vertex-macro + let _vertex = Vertex::<(), 3>::try_new([1.0, 2.0, 3.0]); +} + +pub fn vertex_empty_bad() { + // ruleid: delaunay.rust.no-vertex-empty-constructor + let _vertex = Vertex::<(), 3>::empty(); +} + +pub fn vertex_try_new_replaces_empty_ok() { + // ok: delaunay.rust.no-vertex-empty-constructor + let _vertex = Vertex::<(), 3>::try_new([0.0, 0.0, 0.0]); +} + +pub fn deserialize_simplex_vertex_keys_bad(mut map: M) +where + M: MapAccess<'static>, +{ + // ruleid: delaunay.rust.no-slotmap-key-topology-deserialization + let _vertices = map.next_value::>(); +} + +pub fn deserialize_simplex_vertex_key_buffer_bad(mut map: M) +where + M: MapAccess<'static>, +{ + // ruleid: delaunay.rust.no-slotmap-key-topology-deserialization + let _vertices = map.next_value::(); +} + +pub fn deserialize_simplex_neighbor_keys_bad(mut map: M) +where + M: MapAccess<'static>, +{ + // ruleid: delaunay.rust.no-slotmap-key-topology-deserialization + let _neighbors = map.next_value::>>(); +} + +pub fn deserialize_simplex_vertex_uuids_ok(mut map: M) +where + M: MapAccess<'static>, +{ + // ok: delaunay.rust.no-slotmap-key-topology-deserialization + let _vertices = map.next_value::>(); +} + +pub fn deserialize_simplex_neighbor_uuids_ok(mut map: M) +where + M: MapAccess<'static>, +{ + // ok: delaunay.rust.no-slotmap-key-topology-deserialization + let _neighbors = map.next_value::>>(); +} + +pub fn serialize_simplex_vertex_keys_bad(mut state: S) +where + S: SerializeStruct, +{ + // ruleid: delaunay.rust.no-simplex-slotmap-key-serialization + let _ = state.serialize_field("vertices", &self.vertex_keys); +} + +pub fn serialize_simplex_neighbors_bad(mut state: S) +where + S: SerializeStruct, +{ + // ruleid: delaunay.rust.no-simplex-slotmap-key-serialization + let _ = state.serialize_field("neighbors", &self.neighbors); +} + +pub fn serialize_simplex_uuid_ok(mut state: S) +where + S: SerializeStruct, +{ + // ok: delaunay.rust.no-simplex-slotmap-key-serialization + let _ = state.serialize_field("uuid", &self.uuid); +} + +pub fn serialize_tds_uuid_relationships_ok(mut state: S) +where + S: SerializeStruct, +{ + // ok: delaunay.rust.no-simplex-slotmap-key-serialization + let _ = state.serialize_field("simplex_vertices", &simplex_vertices); +} + +pub fn serialize_tds_vertices_storage_bad(mut state: S, tds: TdsFixture) +where + S: SerializeStruct, +{ + // ruleid: delaunay.rust.no-tds-storage-map-serde + let _ = state.serialize_field("vertices", &tds.vertices); +} + +pub fn serialize_tds_simplices_storage_bad(mut state: S, tds: TdsFixture) +where + S: SerializeStruct, +{ + // ruleid: delaunay.rust.no-tds-storage-map-serde + let _ = state.serialize_field("simplices", &tds.simplices); +} + +pub fn deserialize_tds_vertices_storage_bad() { + // ruleid: delaunay.rust.no-tds-storage-map-serde + let _vertices: Option>> = None; +} + +pub fn deserialize_tds_simplices_storage_bad() { + // ruleid: delaunay.rust.no-tds-storage-map-serde + let _simplices: Option>> = None; +} + +pub fn rebuild_tds_simplices_storage_bad( + // ruleid: delaunay.rust.no-tds-storage-map-serde + serialized_simplices: StorageMap>, +) { + let _ = serialized_simplices; +} + +pub fn serialize_tds_uuid_records_ok(mut state: S) +where + S: SerializeStruct, +{ + let vertices: Vec<_> = self.vertices.values().collect(); + let simplices: Vec<_> = self.simplices.values().collect(); + + // ok: delaunay.rust.no-tds-storage-map-serde + let _ = state.serialize_field("vertices", &vertices); + // ok: delaunay.rust.no-tds-storage-map-serde + let _ = state.serialize_field("simplices", &simplices); +} + +pub fn simplex_new_constructor_bad(vertex_keys: Vec) { + // ruleid: delaunay.rust.no-simplex-new-constructor + let _simplex = Simplex::new(vertex_keys, None); +} + +pub fn simplex_try_new_constructor_ok(vertex_keys: Vec) { + // ok: delaunay.rust.no-simplex-new-constructor + let _simplex = Simplex::try_new(vertex_keys); +} + +pub fn simplex_try_new_with_data_constructor_ok(vertex_keys: Vec) { + // ok: delaunay.rust.no-simplex-new-constructor + let _simplex = Simplex::try_new_with_data(vertex_keys, Some(())); +} + +pub fn facet_new_constructors_bad( + tds: &TdsType, + simplex_key: SimplexKeyType, + facet_map: FacetToSimplicesMap, +) { + // ruleid: delaunay.rust.no-facet-new-constructors + let _facet = FacetView::new(tds, simplex_key, 0); + // ruleid: delaunay.rust.no-facet-new-constructors + let _all_facets = AllFacetsIter::new(tds); + // ruleid: delaunay.rust.no-facet-new-constructors + let _boundary_facets = BoundaryFacetsIter::new(tds, facet_map); +} + +pub fn facet_try_new_constructors_ok( + tds: &TdsType, + simplex_key: SimplexKeyType, + facet_map: FacetToSimplicesMap, +) { + // ok: delaunay.rust.no-facet-new-constructors + let _facet = FacetView::try_new(tds, simplex_key, 0); + // ok: delaunay.rust.no-facet-new-constructors + let _all_facets = AllFacetsIter::try_new(tds); + // ok: delaunay.rust.no-facet-new-constructors + let _boundary_facets = BoundaryFacetsIter::try_new(tds, facet_map); +} + +pub fn facet_handle_new_constructor_bad(simplex_key: SimplexKey) { + // ruleid: delaunay.rust.no-facet-new-constructors + let _handle = FacetHandle::new(simplex_key, 0); +} + +pub fn facet_handle_try_new_constructor_ok(tds: &TdsType, simplex_key: SimplexKey) { + // ok: delaunay.rust.no-facet-new-constructors + let _handle = FacetHandle::try_new(tds, simplex_key, 0); +} + +pub fn ridge_handle_new_constructor_bad(simplex_key: SimplexKey) { + // ruleid: delaunay.rust.no-ridgehandle-new-constructor + let _handle = RidgeHandle::new(simplex_key, 0, 1); +} + +pub fn ridge_handle_try_new_constructor_ok(tds: &TdsType, simplex_key: SimplexKey) { + // ok: delaunay.rust.no-ridgehandle-new-constructor + let _handle = RidgeHandle::try_new(tds, simplex_key, 0, 1); +} + +pub fn edgekey_new_constructor_bad(a: VertexKey, b: VertexKey) { + // ruleid: delaunay.rust.no-edgekey-new-constructor + let _edge = EdgeKey::new(a, b); +} + +pub fn edgekey_try_new_without_tds_bad(a: VertexKey, b: VertexKey) { + // ruleid: delaunay.rust.no-edgekey-try-new-without-tds + let _edge = EdgeKey::try_new(a, b); +} + +pub fn edgekey_try_new_constructor_ok(tds: &TdsType, a: VertexKey, b: VertexKey) { + // ok: delaunay.rust.no-edgekey-new-constructor + // ok: delaunay.rust.no-edgekey-try-new-without-tds + let _edge = EdgeKey::try_new(tds, a, b); +} + +impl PublicVertexUuidConstructorFixture { + // ruleid: delaunay.rust.no-public-vertex-new-with-uuid + pub const fn new_with_uuid(point: Point<3>, uuid: Uuid, data: Option<()>) -> Self { + Self { point, uuid, data } + } +} + +impl CratePrivateVertexUuidConstructorFixture { + // ok: delaunay.rust.no-public-vertex-new-with-uuid + pub(crate) const fn new_with_uuid(point: Point<3>, uuid: Uuid, data: Option<()>) -> Self { + Self { point, uuid, data } + } + + // ok: delaunay.rust.no-public-vertex-new-with-uuid + pub fn try_new_with_uuid( + point: Point<3>, + uuid: Uuid, + data: Option<()>, + ) -> Result { + validate_uuid(&uuid)?; + Ok(Self { point, uuid, data }) + } +} + fn private_documented_invariant(value: Option) -> u8 { // ok: delaunay.rust.no-production-unwrap-panic // ruleid: delaunay.rust.no-public-surface-unwrap-panic, delaunay.rust.no-unwrap-expect-in-benches-examples diff --git a/tests/serialization_vertex_preservation.rs b/tests/serialization_vertex_preservation.rs index 95c85185..2c6e3f82 100644 --- a/tests/serialization_vertex_preservation.rs +++ b/tests/serialization_vertex_preservation.rs @@ -23,14 +23,14 @@ use std::collections::HashSet; fn test_vertex_preservation_with_duplicates_3d() { // Create vertices with duplicate coordinates but different data let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"), // Duplicate coordinate - Point::new([0.0, 0.0, 0.0]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), ]; - let vertices = Vertex::::from_points(&points); + let vertices = Vertex::<(), 3>::from_points(&points); let input_coords: HashSet<_> = vertices.iter().map(|v| *v.point()).collect(); println!("Input vertices: {}", vertices.len()); @@ -64,8 +64,7 @@ fn test_vertex_preservation_with_duplicates_3d() { println!("JSON size: {} bytes", json.len()); // Deserialize - let deserialized: Tds = - serde_json::from_str(&json).expect("Deserialization failed"); + let deserialized: Tds<(), (), 3> = serde_json::from_str(&json).expect("Deserialization failed"); let deser_vertex_count = deserialized.vertices().count(); let deser_coords = extract_vertex_coordinate_set(&deserialized); @@ -89,13 +88,13 @@ fn test_vertex_preservation_with_duplicates_3d() { #[test] fn test_vertex_preservation_without_duplicates_3d() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), - Point::new([0.5, 0.5, 0.5]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"), + Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates"), ]; - let vertices = Vertex::::from_points(&points); + let vertices = Vertex::<(), 3>::from_points(&points); println!("Input vertices (no duplicates): {}", vertices.len()); @@ -112,8 +111,7 @@ fn test_vertex_preservation_without_duplicates_3d() { let before_coords = extract_vertex_coordinate_set(tds); let json = serde_json::to_string(&tds).expect("Serialization failed"); - let deserialized: Tds = - serde_json::from_str(&json).expect("Deserialization failed"); + let deserialized: Tds<(), (), 3> = serde_json::from_str(&json).expect("Deserialization failed"); let deser_vertex_count = deserialized.vertices().count(); let after_coords = extract_vertex_coordinate_set(&deserialized); @@ -139,12 +137,12 @@ fn test_vertex_preservation_many_duplicates_3d() { // Use a stable interior point for this stress test. The previous choice // ([0.5, 0.5, 0.5]) can trigger insertion-order retry logic where shuffled // attempts frequently pick duplicate coordinates for the initial simplex. - let base_point = Point::new([0.25, 0.25, 0.25]); + let base_point = Point::try_new([0.25, 0.25, 0.25]).expect("finite point coordinates"); let mut points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"), ]; // Add 10 vertices with the same coordinate @@ -152,7 +150,7 @@ fn test_vertex_preservation_many_duplicates_3d() { points.push(base_point); } - let vertices = Vertex::::from_points(&points); + let vertices = Vertex::<(), 3>::from_points(&points); println!("Input vertices (with many duplicates): {}", vertices.len()); let unique_coords: HashSet<_> = vertices.iter().map(|v| *v.point()).collect(); @@ -177,8 +175,7 @@ fn test_vertex_preservation_many_duplicates_3d() { let before_coords = extract_vertex_coordinate_set(tds); let json = serde_json::to_string(&tds).expect("Serialization failed"); - let deserialized: Tds = - serde_json::from_str(&json).expect("Deserialization failed"); + let deserialized: Tds<(), (), 3> = serde_json::from_str(&json).expect("Deserialization failed"); let deser_vertex_count = deserialized.vertices().count(); let after_coords = extract_vertex_coordinate_set(&deserialized); @@ -199,12 +196,12 @@ fn test_vertex_preservation_many_duplicates_3d() { #[test] fn test_vertex_coordinate_preservation_3d() { let points = vec![ - Point::new([0.0, 0.0, 0.0]), - Point::new([1.0, 0.0, 0.0]), - Point::new([0.0, 1.0, 0.0]), - Point::new([0.0, 0.0, 1.0]), + Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"), + Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"), ]; - let vertices = Vertex::::from_points(&points); + let vertices = Vertex::<(), 3>::from_points(&points); let dt = DelaunayTriangulation::<_, (), (), 3>::new_with_topology_guarantee( &vertices, @@ -217,8 +214,7 @@ fn test_vertex_coordinate_preservation_3d() { let original_coords = extract_vertex_coordinate_set(tds); let json = serde_json::to_string(&tds).expect("Serialization failed"); - let deserialized: Tds = - serde_json::from_str(&json).expect("Deserialization failed"); + let deserialized: Tds<(), (), 3> = serde_json::from_str(&json).expect("Deserialization failed"); // Extract deserialized vertex coordinates let deserialized_coords = extract_vertex_coordinate_set(&deserialized); diff --git a/tests/trait_bound_ergonomics.rs b/tests/trait_bound_ergonomics.rs index 4a64420c..56f9030c 100644 --- a/tests/trait_bound_ergonomics.rs +++ b/tests/trait_bound_ergonomics.rs @@ -1,12 +1,71 @@ //! Compile coverage for read-only APIs with non-`DataType` payloads. +use std::hash::Hasher; + +use delaunay::DelaunayTriangulation; use delaunay::prelude::Triangulation; -use delaunay::prelude::geometry::FastKernel; +use delaunay::prelude::geometry::{Coordinate, CoordinateValidationError, FastKernel}; use delaunay::prelude::query::BoundaryAnalysis; use delaunay::prelude::tds::{Simplex, SimplexKey, Tds, verify_facet_index_consistency}; use delaunay::prelude::topology::validation::validate_triangulation_euler; struct Payload; +struct NotAKernel; + +struct MinimalCoordinate { + coords: [f64; D], +} + +impl Coordinate for MinimalCoordinate { + fn try_new(coords: [f64; D]) -> Result { + Ok(Self { coords }) + } + + fn to_array(&self) -> [f64; D] { + self.coords + } + + fn get(&self, index: usize) -> Option { + self.coords.get(index).copied() + } + + fn validate(&self) -> Result<(), CoordinateValidationError> { + Ok(()) + } + + fn hash_coordinate(&self, state: &mut H) { + for coord in self.coords { + state.write_u64(coord.to_bits()); + } + } + + fn ordered_equals(&self, other: &Self) -> bool { + self.coords + .iter() + .zip(other.coords.iter()) + .all(|(left, right)| left.to_bits() == right.to_bits()) + } +} + +#[test] +fn coordinate_trait_has_minimal_bounds() { + let coordinate = MinimalCoordinate::<2>::try_new([1.0, 2.0]).unwrap(); + + assert_eq!( + coordinate.to_array().map(f64::to_bits), + [1.0_f64.to_bits(), 2.0_f64.to_bits()] + ); + assert_eq!(coordinate.get(1), Some(2.0)); +} + +#[test] +fn triangulation_types_do_not_require_kernel_bounds() { + let generic: Option> = None; + let delaunay: Option> = None; + + assert!(generic.is_none()); + assert!(delaunay.is_none()); +} #[test] fn read_only_topology_apis_accept_non_datatype_payloads() { @@ -22,7 +81,7 @@ fn read_only_topology_apis_accept_non_datatype_payloads() { assert!(index.simplex_to_neighbors.is_empty()); assert!(index.vertex_to_edges.is_empty()); - let tds: Tds = Tds::empty(); + let tds: Tds = Tds::empty(); assert!(tds.build_facet_to_simplices_map().unwrap().is_empty()); assert_eq!(tds.number_of_boundary_facets().unwrap(), 0); @@ -32,7 +91,7 @@ fn read_only_topology_apis_accept_non_datatype_payloads() { #[test] fn facet_index_consistency_accepts_non_datatype_payloads() { - let tds: Tds = Tds::empty(); + let tds: Tds = Tds::empty(); assert!( verify_facet_index_consistency(&tds, SimplexKey::default(), SimplexKey::default(), 0) @@ -42,7 +101,7 @@ fn facet_index_consistency_accepts_non_datatype_payloads() { #[test] fn facet_views_accept_non_datatype_payloads() { - let tds: Tds = Tds::empty(); + let tds: Tds = Tds::empty(); assert!(Simplex::facet_views_from_tds(&tds, SimplexKey::default()).is_err()); assert!(Simplex::facet_view_iter(&tds, SimplexKey::default()).is_err()); diff --git a/tests/triangulation_builder.rs b/tests/triangulation_builder.rs index 9986a3da..f088eadb 100644 --- a/tests/triangulation_builder.rs +++ b/tests/triangulation_builder.rs @@ -13,9 +13,9 @@ use delaunay::prelude::construction::{ DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, ExplicitConstructionError, ExplicitInsertionError, ExplicitInsertionErrorKind, ExplicitInvariantError, ExplicitInvariantErrorKind, ExplicitTdsErrorKind, - InsertionOrderStrategy, TopologyGuarantee, Vertex, VertexBuilder, vertex, + InsertionOrderStrategy, TopologyGuarantee, Vertex, }; -use delaunay::prelude::geometry::{Coordinate, Point, RobustKernel}; +use delaunay::prelude::geometry::RobustKernel; use delaunay::prelude::insertion::InsertionErrorSourceKind; use delaunay::prelude::tds::{InvariantErrorSummaryDetail, TriangulationValidationErrorKind}; use delaunay::prelude::topology::spaces::{GlobalTopology, TopologyKind, ToroidalConstructionMode}; @@ -30,9 +30,9 @@ use delaunay::prelude::validation::ValidationPolicy; #[test] fn test_builder_euclidean_matches_new_2d() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt_new = DelaunayTriangulation::new(&vertices).expect("new() should succeed"); @@ -52,10 +52,10 @@ fn test_builder_euclidean_matches_new_2d() { #[test] fn test_builder_euclidean_3d() { let vertices = vec![ - vertex!([0.0_f64, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() @@ -69,9 +69,9 @@ fn test_builder_euclidean_3d() { #[test] fn test_builder_topology_guarantee_propagated() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) .topology_guarantee(TopologyGuarantee::Pseudomanifold) @@ -85,9 +85,9 @@ fn test_builder_topology_guarantee_propagated() { #[test] fn test_builder_validation_policy_derived_from_topology_guarantee() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -103,9 +103,9 @@ fn test_builder_validation_policy_derived_from_topology_guarantee() { #[test] fn test_builder_custom_construction_options() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let opts = ConstructionOptions::default().with_insertion_order(InsertionOrderStrategy::Input); let dt = DelaunayTriangulationBuilder::new(&vertices) @@ -124,9 +124,9 @@ fn test_builder_custom_construction_options() { #[test] fn test_builder_convenience() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let dt = DelaunayTriangulation::builder(&vertices) .build::<()>() @@ -139,13 +139,14 @@ fn test_builder_convenience() { #[test] fn test_builder_canonicalized_toroidal_convenience() { let vertices = vec![ - vertex!([0.2_f64, 0.3]), - vertex!([1.8, 0.1]), // → (0.8, 0.1) - vertex!([0.5, 0.7]), - vertex!([-0.4, 0.9]), // → (0.6, 0.9) + Vertex::<(), _>::try_new([0.2_f64, 0.3]).unwrap(), + Vertex::<(), _>::try_new([1.8, 0.1]).unwrap(), // → (0.8, 0.1) + Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), + Vertex::<(), _>::try_new([-0.4, 0.9]).unwrap(), // → (0.6, 0.9) ]; let dt = DelaunayTriangulation::builder(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("canonicalized toroidal builder should succeed"); assert_eq!(dt.number_of_vertices(), 4); @@ -162,26 +163,28 @@ fn test_builder_canonicalized_toroidal_convenience() { fn test_builder_canonicalized_toroidal_canonicalizes_coordinates() { // Canonical (already-wrapped) coordinates let canonical_vertices = vec![ - vertex!([0.2_f64, 0.3]), - vertex!([0.8, 0.1]), - vertex!([0.5, 0.7]), - vertex!([0.6, 0.9]), + Vertex::<(), _>::try_new([0.2_f64, 0.3]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.1]).unwrap(), + Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), + Vertex::<(), _>::try_new([0.6, 0.9]).unwrap(), ]; // Out-of-domain equivalents let shifted_vertices = vec![ - vertex!([2.2_f64, 3.3]), // → (0.2, 0.3) - vertex!([-0.2, 1.1]), // → (0.8, 0.1) - vertex!([1.5, 0.7]), // → (0.5, 0.7) - vertex!([-0.4, 2.9]), // → (0.6, 0.9) + Vertex::<(), _>::try_new([2.2_f64, 3.3]).unwrap(), // → (0.2, 0.3) + Vertex::<(), _>::try_new([-0.2, 1.1]).unwrap(), // → (0.8, 0.1) + Vertex::<(), _>::try_new([1.5, 0.7]).unwrap(), // → (0.5, 0.7) + Vertex::<(), _>::try_new([-0.4, 2.9]).unwrap(), // → (0.6, 0.9) ]; let dt_canonical = DelaunayTriangulationBuilder::new(&canonical_vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("canonical build should succeed"); let dt_shifted = DelaunayTriangulationBuilder::new(&shifted_vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("shifted build should succeed"); @@ -201,13 +204,14 @@ fn test_builder_canonicalized_toroidal_canonicalizes_coordinates() { #[test] fn test_builder_canonicalized_toroidal_validates_2d() { let vertices = vec![ - vertex!([0.2_f64, 0.3]), - vertex!([0.8, 0.1]), - vertex!([0.5, 0.7]), - vertex!([0.1, 0.9]), + Vertex::<(), _>::try_new([0.2_f64, 0.3]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.1]).unwrap(), + Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), + Vertex::<(), _>::try_new([0.1, 0.9]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("canonicalized toroidal build should succeed"); @@ -221,13 +225,14 @@ fn test_builder_canonicalized_toroidal_validates_2d() { #[test] fn test_builder_canonicalized_toroidal_delaunay_property_valid_2d() { let vertices = vec![ - vertex!([0.2_f64, 0.3]), - vertex!([0.8, 0.1]), - vertex!([0.5, 0.7]), - vertex!([0.1, 0.9]), + Vertex::<(), _>::try_new([0.2_f64, 0.3]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.1]).unwrap(), + Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), + Vertex::<(), _>::try_new([0.1, 0.9]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("canonicalized toroidal build should succeed"); @@ -241,13 +246,14 @@ fn test_builder_canonicalized_toroidal_delaunay_property_valid_2d() { #[test] fn test_builder_canonicalized_toroidal_2d_euler_dimension() { let vertices = vec![ - vertex!([0.2_f64, 0.3]), - vertex!([0.8, 0.1]), - vertex!([0.5, 0.7]), - vertex!([0.1, 0.9]), + Vertex::<(), _>::try_new([0.2_f64, 0.3]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.1]).unwrap(), + Vertex::<(), _>::try_new([0.5, 0.7]).unwrap(), + Vertex::<(), _>::try_new([0.1, 0.9]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("build should succeed"); @@ -262,15 +268,16 @@ fn test_builder_canonicalized_toroidal_2d_euler_dimension() { #[test] fn test_builder_canonicalized_toroidal_matches_euclidean_on_canonical_input() { let vertices = vec![ - vertex!([0.1_f64, 0.2]), - vertex!([0.8, 0.3]), - vertex!([0.4, 0.9]), + Vertex::<(), _>::try_new([0.1_f64, 0.2]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.3]).unwrap(), + Vertex::<(), _>::try_new([0.4, 0.9]).unwrap(), ]; let dt_euclidean = DelaunayTriangulationBuilder::new(&vertices) .build::<()>() .expect("euclidean build should succeed"); let dt_toroidal = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("canonicalized toroidal build should succeed"); @@ -292,17 +299,18 @@ fn test_builder_canonicalized_toroidal_matches_euclidean_on_canonical_input() { #[test] fn test_builder_canonicalized_toroidal_larger_point_set_2d() { let vertices = vec![ - vertex!([0.1_f64, 0.2]), - vertex!([0.6, 0.1]), - vertex!([0.9, 0.4]), - vertex!([0.7, 0.8]), - vertex!([0.3, 0.7]), - vertex!([0.48, 0.52]), - vertex!([0.15, 0.85]), - vertex!([0.8, 0.65]), + Vertex::<(), _>::try_new([0.1_f64, 0.2]).unwrap(), + Vertex::<(), _>::try_new([0.6, 0.1]).unwrap(), + Vertex::<(), _>::try_new([0.9, 0.4]).unwrap(), + Vertex::<(), _>::try_new([0.7, 0.8]).unwrap(), + Vertex::<(), _>::try_new([0.3, 0.7]).unwrap(), + Vertex::<(), _>::try_new([0.48, 0.52]).unwrap(), + Vertex::<(), _>::try_new([0.15, 0.85]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.65]).unwrap(), ]; let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .expect("larger canonicalized toroidal build should succeed"); @@ -318,15 +326,16 @@ fn test_builder_canonicalized_toroidal_larger_point_set_2d() { #[test] fn test_builder_canonicalized_toroidal_robust_kernel_3d() { let vertices = vec![ - vertex!([0.2_f64, 0.3, 0.4]), - vertex!([0.8, 0.1, 0.2]), - vertex!([0.5, 0.7, 0.6]), - vertex!([0.1, 0.9, 0.3]), - vertex!([0.6, 0.4, 0.8]), + Vertex::<(), _>::try_new([0.2_f64, 0.3, 0.4]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.1, 0.2]).unwrap(), + Vertex::<(), _>::try_new([0.5, 0.7, 0.6]).unwrap(), + Vertex::<(), _>::try_new([0.1, 0.9, 0.3]).unwrap(), + Vertex::<(), _>::try_new([0.6, 0.4, 0.8]).unwrap(), ]; let kernel = RobustKernel::new(); let dt = DelaunayTriangulationBuilder::new(&vertices) - .canonicalized_toroidal([1.0, 1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0, 1.0]) + .unwrap() .build_with_kernel::<_, ()>(&kernel) .expect("canonicalized toroidal robust kernel 3D build should succeed"); @@ -338,7 +347,7 @@ fn test_builder_canonicalized_toroidal_robust_kernel_3d() { // Periodic (image-point method) path // ============================================================================= -fn toroidal_vertices() -> Vec> { +fn toroidal_vertices() -> Vec> { assert!((2..=5).contains(&D)); let multipliers = [ @@ -358,7 +367,7 @@ fn toroidal_vertices() -> Vec> { let phase = (index_f64 + 1.0) * stride; coords[axis] = 0.9_f64.mul_add(phase.fract(), 0.05); } - vertex!(coords) + Vertex::<(), _>::try_new(coords).unwrap() }) .collect() } @@ -369,7 +378,8 @@ fn build_toroidal_triangulation() let expected_vertices = vertices.len(); let kernel = RobustKernel::new(); let dt = DelaunayTriangulationBuilder::new(&vertices) - .toroidal([1.0_f64; D]) + .try_toroidal([1.0_f64; D]) + .unwrap() .build_with_kernel::<_, ()>(&kernel) .expect("periodic build should succeed"); @@ -406,13 +416,14 @@ fn test_builder_toroidal_chi_zero_2d() { ); } -/// `DelaunayTriangulation::builder()` uses `.toroidal()` for the periodic quotient path. +/// `DelaunayTriangulation::builder()` uses `.try_toroidal()` for the periodic quotient path. #[test] fn test_builder_toroidal_convenience() { let vertices = toroidal_vertices::<2>(); let kernel = RobustKernel::new(); let dt = DelaunayTriangulation::builder(&vertices) - .toroidal([1.0_f64; 2]) + .try_toroidal([1.0_f64; 2]) + .unwrap() .build_with_kernel::<_, ()>(&kernel) .expect("periodic toroidal builder should succeed"); @@ -457,17 +468,18 @@ gen_toroidal_validation_test!(2, levels_1_to_4, true); #[test] fn test_builder_toroidal_3d_validates_level_1_to_4() { let vertices = vec![ - vertex!([0.2_f64, 0.3, 0.4]), - vertex!([0.8, 0.1, 0.2]), - vertex!([0.5, 0.7, 0.6]), - vertex!([0.1, 0.9, 0.3]), - vertex!([0.6, 0.4, 0.8]), - vertex!([0.3, 0.5, 0.9]), - vertex!([0.9, 0.2, 0.6]), + Vertex::<(), _>::try_new([0.2_f64, 0.3, 0.4]).unwrap(), + Vertex::<(), _>::try_new([0.8, 0.1, 0.2]).unwrap(), + Vertex::<(), _>::try_new([0.5, 0.7, 0.6]).unwrap(), + Vertex::<(), _>::try_new([0.1, 0.9, 0.3]).unwrap(), + Vertex::<(), _>::try_new([0.6, 0.4, 0.8]).unwrap(), + Vertex::<(), _>::try_new([0.3, 0.5, 0.9]).unwrap(), + Vertex::<(), _>::try_new([0.9, 0.2, 0.6]).unwrap(), ]; let kernel = RobustKernel::new(); let dt = DelaunayTriangulationBuilder::new(&vertices) - .toroidal([1.0_f64; 3]) + .try_toroidal([1.0_f64; 3]) + .unwrap() .build_with_kernel::<_, ()>(&kernel) .expect("compact periodic 3D quotient should validate after #413"); @@ -498,7 +510,8 @@ macro_rules! gen_toroidal_high_dim_guardrail_test { let vertices = toroidal_vertices::<$dim>(); let kernel = RobustKernel::new(); let err = DelaunayTriangulationBuilder::new(&vertices) - .toroidal([1.0_f64; $dim]) + .try_toroidal([1.0_f64; $dim]) + .unwrap() .build_with_kernel::<_, ()>(&kernel) .expect_err(concat!( stringify!($dim), @@ -538,7 +551,7 @@ fn test_explicit_toroidal_heawood_torus_rejected() { let vertices: Vec<_> = (0..7) .map(|i| { let angle = TAU * f64::from(i) / 7.0; - vertex!([angle.cos(), angle.sin()]) + Vertex::<(), _>::try_new([angle.cos(), angle.sin()]).unwrap() }) .collect(); @@ -575,7 +588,7 @@ fn test_explicit_toroidal_torus_euler_mismatch_without_override() { let vertices: Vec<_> = (0..7) .map(|i| { let angle = TAU * f64::from(i) / 7.0; - vertex!([angle.cos(), angle.sin()]) + Vertex::<(), _>::try_new([angle.cos(), angle.sin()]).unwrap() }) .collect(); @@ -630,10 +643,10 @@ fn test_explicit_toroidal_torus_euler_mismatch_without_override() { #[test] fn test_explicit_2d_two_triangle_quad() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([1.0, 1.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2], vec![0, 2, 3]]; @@ -675,10 +688,10 @@ fn test_explicit_2d_two_triangle_quad() { #[test] fn test_explicit_normalizes_incoherent_simplex_order() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([1.0, 1.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let mut simplices = vec![vec![0, 1, 2], vec![0, 2, 3]]; simplices[1].swap(0, 1); @@ -697,11 +710,11 @@ fn test_explicit_normalizes_incoherent_simplex_order() { #[test] fn test_explicit_3d_two_tetrahedra() { let vertices = vec![ - vertex!([0.0_f64, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, -1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 1.0, -1.0]).unwrap(), ]; // Two tetrahedra sharing face (0, 1, 2) let simplices = vec![vec![0, 1, 2, 3], vec![0, 1, 2, 4]]; @@ -722,11 +735,11 @@ fn test_explicit_3d_two_tetrahedra() { #[test] fn test_explicit_round_trip_3d() { let vertices = vec![ - vertex!([0.0_f64, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), - vertex!([1.0, 1.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 1.0, 1.0]).unwrap(), ]; let dt_original = DelaunayTriangulation::new(&vertices).expect("Delaunay build should succeed"); @@ -778,10 +791,10 @@ fn test_explicit_round_trip_3d() { #[test] fn test_explicit_round_trip_2d() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), ]; // Build via standard Delaunay. @@ -835,7 +848,10 @@ fn test_explicit_round_trip_2d() { /// Error: empty simplices should fail. #[test] fn test_explicit_error_empty_simplices() { - let vertices = vec![vertex!([0.0_f64, 0.0]), vertex!([1.0, 0.0])]; + let vertices = vec![ + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + ]; let simplices: Vec> = vec![]; let result = DelaunayTriangulationBuilder::from_vertices_and_simplices(&vertices, &simplices) @@ -848,9 +864,9 @@ fn test_explicit_error_empty_simplices() { #[test] fn test_explicit_error_wrong_arity() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; // 2D expects 3 vertices per simplex, but we provide 2. let simplices = vec![vec![0, 1]]; @@ -865,9 +881,9 @@ fn test_explicit_error_wrong_arity() { #[test] fn test_explicit_error_index_out_of_bounds() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 99]]; // 99 is out of bounds @@ -884,9 +900,9 @@ fn test_explicit_error_index_out_of_bounds() { #[test] fn test_explicit_error_duplicate_vertex_in_simplex() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 1]]; // Duplicate vertex 1 @@ -896,34 +912,13 @@ fn test_explicit_error_duplicate_vertex_in_simplex() { assert!(result.is_err(), "Duplicate vertex should produce an error"); } -/// Error: toroidal + explicit simplices is incompatible. -#[test] -fn test_explicit_error_toroidal_incompatible() { - let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - ]; - let simplices = vec![vec![0, 1, 2]]; - - // Construct with explicit simplices then add toroidal. - let result = DelaunayTriangulationBuilder::from_vertices_and_simplices(&vertices, &simplices) - .canonicalized_toroidal([1.0, 1.0]) - .build::<()>(); - - assert!( - result.is_err(), - "Toroidal + explicit simplices should produce an error" - ); -} - /// Minimal case: a single triangle in 2D. #[test] fn test_explicit_2d_single_triangle() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2]]; @@ -940,10 +935,10 @@ fn test_explicit_2d_single_triangle() { #[test] fn test_explicit_3d_single_tetrahedron() { let vertices = vec![ - vertex!([0.0_f64, 0.0, 0.0]), - vertex!([1.0, 0.0, 0.0]), - vertex!([0.0, 1.0, 0.0]), - vertex!([0.0, 0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2, 3]]; @@ -968,10 +963,10 @@ fn test_explicit_3d_single_tetrahedron() { #[test] fn test_explicit_non_delaunay_mesh() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([4.0, 0.0]), - vertex!([4.0, 2.0]), - vertex!([1.0, 2.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([4.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([4.0, 2.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 2.0]).unwrap(), ]; // Diagonal AC = (0,0)-(4,2): non-Delaunay because D=(1,2) is inside // the circumcircle of triangle ABC. @@ -1000,9 +995,9 @@ fn test_explicit_non_delaunay_mesh() { #[test] fn test_explicit_topology_guarantee_propagated() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2]]; @@ -1017,22 +1012,10 @@ fn test_explicit_topology_guarantee_propagated() { /// Explicit construction preserves vertex data (U ≠ ()). #[test] fn test_explicit_preserves_vertex_data() { - let vertices: Vec> = vec![ - VertexBuilder::default() - .point(Point::new([0.0, 0.0])) - .data(10_i32) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([1.0, 0.0])) - .data(20_i32) - .build() - .unwrap(), - VertexBuilder::default() - .point(Point::new([0.0, 1.0])) - .data(30_i32) - .build() - .unwrap(), + let vertices: Vec> = vec![ + Vertex::try_new_with_data([0.0, 0.0], 10_i32).unwrap(), + Vertex::try_new_with_data([1.0, 0.0], 20_i32).unwrap(), + Vertex::try_new_with_data([0.0, 1.0], 30_i32).unwrap(), ]; let simplices = vec![vec![0, 1, 2]]; @@ -1057,9 +1040,9 @@ fn test_explicit_preserves_vertex_data() { fn test_explicit_validate_delaunay_mesh() { // Use a known Delaunay configuration: the standard simplex. let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.5, 0.866_025_403_784_438_6]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.5, 0.866_025_403_784_438_6]).unwrap(), ]; let simplices = vec![vec![0, 1, 2]]; @@ -1077,10 +1060,10 @@ fn test_explicit_validate_delaunay_mesh() { #[test] fn test_explicit_unreferenced_vertices_rejected() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([5.0, 5.0]), // Not referenced by any simplex + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([5.0, 5.0]).unwrap(), // Not referenced by any simplex ]; let simplices = vec![vec![0, 1, 2]]; @@ -1102,7 +1085,7 @@ fn test_explicit_unreferenced_vertices_rejected() { /// Error variant: empty simplices returns ExplicitConstruction(EmptySimplices). #[test] fn test_explicit_error_variant_empty_simplices() { - let vertices = vec![vertex!([0.0_f64, 0.0])]; + let vertices = vec![Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap()]; let simplices: Vec> = vec![]; let err = DelaunayTriangulationBuilder::from_vertices_and_simplices(&vertices, &simplices) @@ -1124,9 +1107,9 @@ fn test_explicit_error_variant_empty_simplices() { #[test] fn test_explicit_error_variant_wrong_arity() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1]]; // 2D expects 3 vertices @@ -1155,11 +1138,11 @@ fn test_explicit_error_variant_non_manifold_facet() { // Three triangles sharing the same edge (0,1) — facet shared by 3 simplices // violates the 2-manifold property. let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), - vertex!([1.0, 1.0]), - vertex!([0.5, -1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 1.0]).unwrap(), + Vertex::<(), _>::try_new([0.5, -1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2], vec![0, 1, 3], vec![0, 1, 4]]; @@ -1183,9 +1166,9 @@ fn test_explicit_error_variant_non_manifold_facet() { #[test] fn test_explicit_error_variant_duplicate_vertex_in_simplex() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 1]]; // Duplicate vertex 1 @@ -1208,14 +1191,15 @@ fn test_explicit_error_variant_duplicate_vertex_in_simplex() { #[test] fn test_explicit_error_variant_incompatible_topology() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2]]; let err = DelaunayTriangulationBuilder::from_vertices_and_simplices(&vertices, &simplices) - .canonicalized_toroidal([1.0, 1.0]) + .try_canonicalized_toroidal([1.0, 1.0]) + .unwrap() .build::<()>() .unwrap_err(); @@ -1234,9 +1218,9 @@ fn test_explicit_error_variant_incompatible_topology() { #[test] fn test_explicit_error_variant_unsupported_construction_options() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2]]; @@ -1262,9 +1246,9 @@ fn test_explicit_error_variant_unsupported_construction_options() { #[test] fn test_explicit_error_variant_duplicate_simplices_structural_validation() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2], vec![0, 1, 2]]; @@ -1286,9 +1270,9 @@ fn test_explicit_error_variant_duplicate_simplices_structural_validation() { #[test] fn test_explicit_error_variant_geometric_nondegeneracy() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([2.0, 0.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([2.0, 0.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 2]]; @@ -1355,9 +1339,9 @@ fn test_explicit_error_variant_orientation_normalization_summary() { #[test] fn test_explicit_error_variant_index_out_of_bounds() { let vertices = vec![ - vertex!([0.0_f64, 0.0]), - vertex!([1.0, 0.0]), - vertex!([0.0, 1.0]), + Vertex::<(), _>::try_new([0.0_f64, 0.0]).unwrap(), + Vertex::<(), _>::try_new([1.0, 0.0]).unwrap(), + Vertex::<(), _>::try_new([0.0, 1.0]).unwrap(), ]; let simplices = vec![vec![0, 1, 99]];