#![forbid(unsafe_code)]
use crate::errors::{CdtError, CdtResult, GenerationParameterIssue};
pub use delaunay::TopologyGuarantee;
use delaunay::geometry::kernel::AdaptiveKernel;
use delaunay::geometry::point::Point;
use delaunay::geometry::traits::coordinate::Coordinate;
use delaunay::geometry::util::{generate_random_points, generate_random_points_seeded};
use delaunay::prelude::VertexBuilder;
pub use delaunay::topology::traits::{GlobalTopology, ToroidalConstructionMode};
use delaunay::{DelaunayTriangulation, DelaunayTriangulationBuilder};
pub type DelaunayTriangulation2D = DelaunayTriangulation<AdaptiveKernel<f64>, u32, i32, 2>;
fn generate_delaunay2_vertex_build_error(
number_of_vertices: u32,
underlying_error: String,
) -> CdtError {
CdtError::VertexBuildFailed {
context: format!("generate_delaunay2({number_of_vertices} vertices)"),
underlying_error,
}
}
fn invalid_generation_parameters(
issue: GenerationParameterIssue,
provided_value: String,
expected_range: &str,
) -> CdtError {
CdtError::InvalidGenerationParameters {
issue,
provided_value,
expected_range: expected_range.to_string(),
}
}
fn validate_coordinate_range(coordinate_range: (f64, f64)) -> CdtResult<()> {
let (min, max) = coordinate_range;
if min.is_finite() && max.is_finite() && min < max {
Ok(())
} else {
Err(invalid_generation_parameters(
GenerationParameterIssue::InvalidCoordinateRange,
format!("[{min}, {max}]"),
"finite min < max",
))
}
}
fn validate_explicit_coordinates(coords_with_data: &[([f64; 2], u32)]) -> CdtResult<()> {
for (vertex_index, (coord, _)) in coords_with_data.iter().enumerate() {
for (axis, value) in coord.iter().copied().enumerate() {
if !value.is_finite() {
return Err(invalid_generation_parameters(
GenerationParameterIssue::NonFiniteVertexCoordinate,
format!("vertex {vertex_index} axis {axis} = {value}"),
"finite coordinate values",
));
}
}
}
Ok(())
}
fn validate_toroidal_domain(domain: [f64; 2]) -> CdtResult<()> {
for (axis, period) in domain.into_iter().enumerate() {
if !period.is_finite() || period <= 0.0 {
return Err(invalid_generation_parameters(
GenerationParameterIssue::InvalidToroidalDomain,
format!("axis {axis} period {period}"),
"finite and positive periods",
));
}
}
Ok(())
}
pub fn generate_delaunay2(
number_of_vertices: u32,
coordinate_range: (f64, f64),
seed: Option<u64>,
) -> CdtResult<DelaunayTriangulation2D> {
if number_of_vertices < 3 {
return Err(invalid_generation_parameters(
GenerationParameterIssue::InsufficientVertexCount,
number_of_vertices.to_string(),
"โฅ 3",
));
}
validate_coordinate_range(coordinate_range)?;
let n = number_of_vertices as usize;
let points = seed
.map_or_else(
|| generate_random_points::<f64, 2>(n, coordinate_range),
|s| generate_random_points_seeded::<f64, 2>(n, coordinate_range, s),
)
.map_err(|e| CdtError::DelaunayGenerationFailed {
vertex_count: number_of_vertices,
coordinate_range,
attempt: 1,
underlying_error: format!("Point generation failed: {e}"),
})?;
let vertices: Vec<_> = points
.into_iter()
.map(|point| VertexBuilder::<f64, u32, 2>::default().point(point).build())
.collect::<Result<Vec<_>, _>>()
.map_err(|e| generate_delaunay2_vertex_build_error(number_of_vertices, e.to_string()))?;
let dt = DelaunayTriangulationBuilder::from_vertices(&vertices)
.build::<i32>()
.map_err(|e| CdtError::DelaunayGenerationFailed {
vertex_count: number_of_vertices,
coordinate_range,
attempt: 1,
underlying_error: e.to_string(),
})?;
Ok(dt)
}
pub fn build_delaunay2_with_data(
coords_with_data: &[([f64; 2], u32)],
) -> CdtResult<DelaunayTriangulation2D> {
validate_explicit_coordinates(coords_with_data)?;
let vertices: Vec<_> = coords_with_data
.iter()
.enumerate()
.map(|(i, (coord, data))| {
let point = Point::<f64, 2>::new(*coord);
VertexBuilder::<f64, u32, 2>::default()
.point(point)
.data(*data)
.build()
.map_err(|e| CdtError::VertexBuildFailed {
context: format!("vertex {i}"),
underlying_error: e.to_string(),
})
})
.collect::<CdtResult<Vec<_>>>()?;
let vertex_count = u32::try_from(vertices.len()).unwrap_or(u32::MAX);
let coordinate_range = coords_with_data
.iter()
.flat_map(|(c, _)| c.iter().copied())
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
(lo.min(v), hi.max(v))
});
DelaunayTriangulationBuilder::from_vertices(&vertices)
.build::<i32>()
.map_err(|e| CdtError::DelaunayGenerationFailed {
vertex_count,
coordinate_range,
attempt: 1,
underlying_error: e.to_string(),
})
}
pub fn build_delaunay2_from_simplices(
coords_with_data: &[([f64; 2], u32)],
simplices: &[Vec<usize>],
) -> CdtResult<DelaunayTriangulation2D> {
build_delaunay2_with_topology(
coords_with_data,
simplices,
TopologyGuarantee::DEFAULT,
GlobalTopology::Euclidean,
)
}
pub fn build_delaunay2_with_topology(
coords_with_data: &[([f64; 2], u32)],
simplices: &[Vec<usize>],
topology_guarantee: TopologyGuarantee,
global_topology: GlobalTopology<2>,
) -> CdtResult<DelaunayTriangulation2D> {
validate_explicit_coordinates(coords_with_data)?;
let vertices: Vec<_> = coords_with_data
.iter()
.enumerate()
.map(|(i, (coord, data))| {
let point = Point::<f64, 2>::new(*coord);
VertexBuilder::<f64, u32, 2>::default()
.point(point)
.data(*data)
.build()
.map_err(|e| CdtError::VertexBuildFailed {
context: format!("vertex {i}"),
underlying_error: e.to_string(),
})
})
.collect::<CdtResult<Vec<_>>>()?;
let vertex_count = u32::try_from(vertices.len()).unwrap_or(u32::MAX);
let coordinate_range = coords_with_data
.iter()
.flat_map(|(c, _)| c.iter().copied())
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
(lo.min(v), hi.max(v))
});
DelaunayTriangulationBuilder::from_vertices_and_simplices(&vertices, simplices)
.topology_guarantee(topology_guarantee)
.global_topology(global_topology)
.build::<i32>()
.map_err(|e| CdtError::DelaunayGenerationFailed {
vertex_count,
coordinate_range,
attempt: 1,
underlying_error: e.to_string(),
})
}
pub fn build_toroidal_delaunay2(
coords_with_data: &[([f64; 2], u32)],
simplices: &[Vec<usize>],
domain: [f64; 2],
) -> CdtResult<DelaunayTriangulation2D> {
validate_toroidal_domain(domain)?;
build_delaunay2_with_topology(
coords_with_data,
simplices,
TopologyGuarantee::Pseudomanifold,
GlobalTopology::Toroidal {
domain,
mode: ToroidalConstructionMode::Explicit,
},
)
}
pub fn build_periodic_toroidal_delaunay2(
coords_with_data: &[([f64; 2], u32)],
domain: [f64; 2],
) -> CdtResult<DelaunayTriangulation2D> {
validate_toroidal_domain(domain)?;
validate_explicit_coordinates(coords_with_data)?;
let vertices: Vec<_> = coords_with_data
.iter()
.enumerate()
.map(|(i, (coord, data))| {
let point = Point::<f64, 2>::new(*coord);
VertexBuilder::<f64, u32, 2>::default()
.point(point)
.data(*data)
.build()
.map_err(|e| CdtError::VertexBuildFailed {
context: format!("periodic toroidal vertex {i}"),
underlying_error: e.to_string(),
})
})
.collect::<CdtResult<Vec<_>>>()?;
let vertex_count = u32::try_from(vertices.len()).unwrap_or(u32::MAX);
let coordinate_range = coords_with_data
.iter()
.flat_map(|(c, _)| c.iter().copied())
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
(lo.min(v), hi.max(v))
});
DelaunayTriangulationBuilder::from_vertices(&vertices)
.toroidal_periodic(domain)
.topology_guarantee(TopologyGuarantee::PLManifold)
.build::<i32>()
.map_err(|e| CdtError::DelaunayGenerationFailed {
vertex_count,
coordinate_range,
attempt: 1,
underlying_error: e.to_string(),
})
}
#[cfg(test)]
#[must_use]
pub(crate) fn random_delaunay2(
number_of_vertices: u32,
coordinate_range: (f64, f64),
) -> DelaunayTriangulation2D {
generate_delaunay2(number_of_vertices, coordinate_range, None).unwrap_or_else(|_| {
panic!(
"Failed to generate random Delaunay triangulation with {number_of_vertices} vertices"
)
})
}
#[cfg(test)]
#[must_use]
pub(crate) fn seeded_delaunay2(
number_of_vertices: u32,
coordinate_range: (f64, f64),
seed: u64,
) -> DelaunayTriangulation2D {
generate_delaunay2(number_of_vertices, coordinate_range, Some(seed)).unwrap_or_else(
|_| {
panic!(
"Failed to generate seeded Delaunay triangulation with {number_of_vertices} vertices and seed {seed}"
)
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::CdtError;
use crate::geometry::DelaunayBackend2D;
use std::assert_matches;
use std::collections::HashMap;
fn triangulation_signature(dt: &DelaunayTriangulation2D) -> (Vec<String>, Vec<Vec<String>>) {
let mut vertex_coords: Vec<_> = dt
.vertices()
.map(|(_, vertex)| format!("{:?}", vertex.point().coords()))
.collect();
vertex_coords.sort();
let coord_by_key: HashMap<_, _> = dt
.vertices()
.map(|(key, vertex)| (key, format!("{:?}", vertex.point().coords())))
.collect();
let mut simplices: Vec<_> = dt
.simplices()
.map(|(_, simplex)| {
let mut vertices: Vec<_> = simplex
.vertices()
.iter()
.map(|key| {
coord_by_key
.get(key)
.cloned()
.expect("simplex vertices should refer to live vertices")
})
.collect();
vertices.sort();
vertices
})
.collect();
simplices.sort();
(vertex_coords, simplices)
}
#[test]
fn test_generate_delaunay2_vertex_build_error_context() {
let error = generate_delaunay2_vertex_build_error(5, "missing point".to_string());
assert_matches!(
error,
CdtError::VertexBuildFailed {
ref context,
ref underlying_error,
} if context == "generate_delaunay2(5 vertices)"
&& underlying_error == "missing point"
);
}
#[test]
fn test_build_delaunay2_from_simplices_single_triangle() {
let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
let simplices = vec![vec![0, 1, 2]];
let dt = build_delaunay2_from_simplices(&vertices, &simplices)
.expect("single-triangle explicit mesh should build with defaults");
assert_eq!(dt.number_of_vertices(), 3);
assert_eq!(dt.number_of_simplices(), 1);
}
#[test]
fn test_build_delaunay2_from_simplices_rejects_bad_index() {
let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
let simplices = vec![vec![0, 1, 3]];
let result = build_delaunay2_from_simplices(&vertices, &simplices);
assert_matches!(
result,
Err(CdtError::DelaunayGenerationFailed { .. }),
"explicit builder must reject out-of-bounds vertex indices"
);
}
#[test]
fn test_build_delaunay2_with_topology_euclidean() {
let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
let simplices = vec![vec![0, 1, 2]];
let dt = build_delaunay2_with_topology(
&vertices,
&simplices,
TopologyGuarantee::DEFAULT,
GlobalTopology::Euclidean,
)
.expect("single-triangle explicit mesh with explicit topology should build");
assert_eq!(dt.number_of_vertices(), 3);
assert_eq!(dt.number_of_simplices(), 1);
}
#[test]
fn test_explicit_toroidal_simplices_are_rejected() {
const N: usize = 3;
const T: usize = 3;
let mut vertices: Vec<([f64; 2], u32)> = Vec::with_capacity(N * T);
for t in 0..T {
for i in 0..N {
#[expect(
clippy::cast_precision_loss,
reason = "small deterministic test indices are converted to normalized f64 coordinates"
)]
let coord = [i as f64 / N as f64, t as f64 / T as f64];
let label = u32::try_from(t).expect("slice index fits in u32");
vertices.push((coord, label));
}
}
let mut simplices: Vec<Vec<usize>> = Vec::with_capacity(2 * N * T);
for t in 0..T {
let t_next = (t + 1) % T;
for i in 0..N {
let i_next = (i + 1) % N;
simplices.push(vec![i + t * N, i_next + t * N, i + t_next * N]);
simplices.push(vec![i_next + t * N, i_next + t_next * N, i + t_next * N]);
}
}
let error = build_toroidal_delaunay2(&vertices, &simplices, [1.0, 1.0])
.expect_err("explicit toroidal topology should report upstream limitation");
assert_matches!(
error,
CdtError::DelaunayGenerationFailed {
vertex_count: 9,
ref underlying_error,
..
} if underlying_error.contains(
"Explicit non-Euclidean connectivity is not supported for Toroidal"
),
"explicit toroidal mesh should fail with the upstream topology limitation"
);
}
#[test]
fn test_build_periodic_toroidal_delaunay2_3x3_validates_level_1_to_4() {
const N: usize = 3;
const T: usize = 3;
const DOMAIN: [f64; 2] = [3.0, 3.0];
let mut vertices: Vec<([f64; 2], u32)> = Vec::with_capacity(N * T);
for t in 0..T {
for i in 0..N {
#[expect(
clippy::cast_precision_loss,
reason = "small deterministic test indices are converted to f64 lattice coordinates"
)]
let coord = [i as f64, t as f64];
let label = u32::try_from(t).expect("slice index fits in u32");
vertices.push((coord, label));
}
}
let dt = build_periodic_toroidal_delaunay2(&vertices, DOMAIN)
.expect("periodic 3ร3 toroidal mesh should build");
assert_eq!(dt.number_of_vertices(), N * T);
assert_eq!(dt.number_of_simplices(), 2 * N * T);
let backend = DelaunayBackend2D::from_triangulation(dt)
.expect("periodic toroidal mesh should validate");
backend
.validate_delaunay()
.expect("periodic toroidal mesh must pass upstream Level 1-4 validation");
}
#[test]
fn test_build_periodic_toroidal_delaunay2_rejects_invalid_domain() {
let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.0, 1.0], 1)];
for (domain, expected_value) in [
([0.0, 3.0], "axis 0 period 0"),
([-1.0, 3.0], "axis 0 period -1"),
([3.0, f64::NAN], "axis 1 period NaN"),
([f64::INFINITY, 3.0], "axis 0 period inf"),
] {
let result = build_periodic_toroidal_delaunay2(&vertices, domain);
assert_matches!(
result,
Err(CdtError::InvalidGenerationParameters {
ref issue,
ref provided_value,
ref expected_range,
}) if *issue == GenerationParameterIssue::InvalidToroidalDomain
&& provided_value == expected_value
&& expected_range == "finite and positive periods",
"invalid periodic toroidal domain {domain:?} should be rejected"
);
}
}
#[test]
fn test_build_periodic_toroidal_delaunay2_rejects_non_finite_coordinate() {
let vertices = [
([0.0, 0.0], 0u32),
([1.0, f64::NEG_INFINITY], 0),
([0.0, 1.0], 1),
];
let result = build_periodic_toroidal_delaunay2(&vertices, [3.0, 3.0]);
assert_matches!(
result,
Err(CdtError::InvalidGenerationParameters {
ref issue,
ref provided_value,
ref expected_range,
}) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
&& provided_value == "vertex 1 axis 1 = -inf"
&& expected_range == "finite coordinate values",
"periodic toroidal non-finite coordinate should be rejected"
);
}
#[test]
fn test_generate_delaunay2_valid_parameters() {
let result = generate_delaunay2(4, (0.0, 10.0), None);
assert!(
result.is_ok(),
"Should successfully generate triangulation with valid parameters"
);
let dt = result.unwrap();
assert_eq!(dt.number_of_vertices(), 4, "Should have 4 vertices");
assert!(
dt.number_of_simplices() > 0,
"Should have at least one simplex"
);
}
#[test]
fn test_generate_delaunay2_with_seed() {
let seed = 12345;
let result1 = generate_delaunay2(4, (0.0, 10.0), Some(seed));
let result2 = generate_delaunay2(4, (0.0, 10.0), Some(seed));
assert!(result1.is_ok(), "First generation should succeed");
assert!(result2.is_ok(), "Second generation should succeed");
let dt1 = result1.unwrap();
let dt2 = result2.unwrap();
assert_eq!(
dt1.number_of_vertices(),
dt2.number_of_vertices(),
"Should have same vertex count"
);
assert_eq!(
dt1.number_of_simplices(),
dt2.number_of_simplices(),
"Should have same simplex count"
);
assert_eq!(
triangulation_signature(&dt1),
triangulation_signature(&dt2),
"Seeded generation should produce identical vertex coordinates and simplex connectivity"
);
}
#[test]
fn test_generate_delaunay2_insufficient_vertices() {
let result = generate_delaunay2(2, (0.0, 10.0), None);
assert!(result.is_err(), "Should fail with insufficient vertices");
match result.unwrap_err() {
CdtError::InvalidGenerationParameters {
issue,
provided_value,
expected_range,
} => {
assert_eq!(issue, GenerationParameterIssue::InsufficientVertexCount);
assert_eq!(provided_value, "2");
assert_eq!(expected_range, "โฅ 3");
}
_ => panic!("Expected InvalidGenerationParameters error"),
}
}
#[test]
fn test_generate_delaunay2_invalid_range() {
let result = generate_delaunay2(4, (10.0, 5.0), None);
assert!(result.is_err(), "Should fail with invalid coordinate range");
match result.unwrap_err() {
CdtError::InvalidGenerationParameters {
issue,
provided_value,
expected_range,
} => {
assert_eq!(issue, GenerationParameterIssue::InvalidCoordinateRange);
assert_eq!(provided_value, "[10, 5]");
assert_eq!(expected_range, "finite min < max");
}
_ => panic!("Expected InvalidGenerationParameters error"),
}
}
#[test]
fn test_generate_delaunay2_equal_range() {
let result = generate_delaunay2(4, (5.0, 5.0), None);
assert!(result.is_err(), "Should fail with equal coordinate range");
match result.unwrap_err() {
CdtError::InvalidGenerationParameters { issue, .. } => {
assert_eq!(issue, GenerationParameterIssue::InvalidCoordinateRange);
}
_ => panic!("Expected InvalidGenerationParameters error"),
}
}
#[test]
fn test_generate_delaunay2_rejects_non_finite_range() {
for range in [(f64::NAN, 1.0), (0.0, f64::INFINITY)] {
let result = generate_delaunay2(4, range, None);
assert_matches!(
result,
Err(CdtError::InvalidGenerationParameters {
ref issue,
ref expected_range,
..
}) if *issue == GenerationParameterIssue::InvalidCoordinateRange
&& expected_range == "finite min < max",
"non-finite range {range:?} should be rejected"
);
}
}
#[test]
fn test_build_delaunay2_with_data_rejects_non_finite_coordinate() {
let vertices = [([0.0, 0.0], 0u32), ([1.0, f64::NAN], 0), ([0.5, 1.0], 1)];
let result = build_delaunay2_with_data(&vertices);
assert_matches!(
result,
Err(CdtError::InvalidGenerationParameters {
ref issue,
ref provided_value,
ref expected_range,
}) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
&& provided_value == "vertex 1 axis 1 = NaN"
&& expected_range == "finite coordinate values",
"explicit non-finite coordinate should be rejected"
);
}
#[test]
fn test_build_delaunay2_from_simplices_rejects_non_finite_coordinate() {
let vertices = [
([0.0, 0.0], 0u32),
([1.0, 0.0], 0),
([0.5, f64::NEG_INFINITY], 1),
];
let simplices = vec![vec![0, 1, 2]];
let result = build_delaunay2_from_simplices(&vertices, &simplices);
assert_matches!(
result,
Err(CdtError::InvalidGenerationParameters {
ref issue,
ref provided_value,
ref expected_range,
}) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
&& provided_value == "vertex 2 axis 1 = -inf"
&& expected_range == "finite coordinate values",
"delegating explicit-simplex builder should reject non-finite coordinates"
);
}
#[test]
fn test_build_delaunay2_with_topology_rejects_non_finite_coordinate() {
let vertices = [
([0.0, 0.0], 0u32),
([f64::INFINITY, 0.0], 0),
([0.5, 1.0], 1),
];
let simplices = vec![vec![0, 1, 2]];
let result = build_delaunay2_with_topology(
&vertices,
&simplices,
TopologyGuarantee::DEFAULT,
GlobalTopology::Euclidean,
);
assert_matches!(
result,
Err(CdtError::InvalidGenerationParameters {
ref issue,
ref provided_value,
ref expected_range,
}) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
&& provided_value == "vertex 1 axis 0 = inf"
&& expected_range == "finite coordinate values",
"explicit non-finite topology coordinate should be rejected"
);
}
#[test]
fn test_build_toroidal_delaunay2_rejects_invalid_domain() {
let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
let simplices = vec![vec![0, 1, 2]];
for (domain, expected_value) in [
([0.0, 1.0], "axis 0 period 0"),
([-1.0, 1.0], "axis 0 period -1"),
([1.0, f64::NAN], "axis 1 period NaN"),
([f64::INFINITY, 1.0], "axis 0 period inf"),
] {
let result = build_toroidal_delaunay2(&vertices, &simplices, domain);
assert_matches!(
result,
Err(CdtError::InvalidGenerationParameters {
ref issue,
ref provided_value,
ref expected_range,
}) if *issue == GenerationParameterIssue::InvalidToroidalDomain
&& provided_value == expected_value
&& expected_range == "finite and positive periods",
"invalid domain {domain:?} should be rejected"
);
}
}
#[test]
fn test_invalid_toroidal_domain_display_is_actionable() {
let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
let simplices = vec![vec![0, 1, 2]];
let error = build_toroidal_delaunay2(&vertices, &simplices, [-1.0, 1.0])
.expect_err("negative toroidal period should be rejected");
assert_eq!(
error.to_string(),
"Invalid triangulation parameters: Invalid toroidal domain (got: axis 0 period -1, expected: finite and positive periods)"
);
}
#[test]
fn test_generate_delaunay2_various_sizes() {
let test_cases = [(3, "minimal"), (5, "small"), (10, "medium"), (20, "large")];
for (vertex_count, description) in test_cases {
let result = generate_delaunay2(vertex_count, (0.0, 100.0), None);
assert!(
result.is_ok(),
"Should generate {description} triangulation with {vertex_count} vertices"
);
let dt = result.unwrap();
assert_eq!(
dt.number_of_vertices(),
vertex_count as usize,
"Should have {vertex_count} vertices for {description} triangulation"
);
assert!(
dt.number_of_simplices() > 0,
"Should have at least one simplex for {description} triangulation"
);
}
}
#[test]
fn test_generate_delaunay2_different_ranges() {
let ranges = [(0.0, 1.0), (-10.0, 10.0), (100.0, 200.0), (-50.0, 0.0)];
for range in ranges {
let result = generate_delaunay2(4, range, None);
assert!(
result.is_ok(),
"Should generate triangulation with range {range:?}"
);
let dt = result.unwrap();
assert_eq!(dt.number_of_vertices(), 4, "Should have 4 vertices");
}
}
#[test]
fn test_random_delaunay2_success() {
let dt = random_delaunay2(5, (0.0, 10.0));
assert_eq!(dt.number_of_vertices(), 5, "Should have 5 vertices");
assert!(
dt.number_of_simplices() > 0,
"Should have at least one simplex"
);
}
#[test]
fn test_random_delaunay2_various_sizes() {
let sizes = [3, 4, 6, 8, 12];
for size in sizes {
let dt = random_delaunay2(size, (0.0, 50.0));
assert_eq!(
dt.number_of_vertices(),
size as usize,
"Should have {size} vertices"
);
assert!(
dt.number_of_simplices() > 0,
"Should have simplices for size {size}"
);
}
}
#[test]
#[should_panic(expected = "Failed to generate random Delaunay triangulation with 2 vertices")]
fn test_random_delaunay2_panic_insufficient_vertices() {
let _ = random_delaunay2(2, (0.0, 10.0));
}
#[test]
#[should_panic(expected = "Failed to generate random Delaunay triangulation with 4 vertices")]
fn test_random_delaunay2_panic_invalid_range() {
let _ = random_delaunay2(4, (10.0, 5.0));
}
#[test]
fn test_seeded_delaunay2_deterministic() {
let seed = 42;
let dt1 = seeded_delaunay2(6, (0.0, 20.0), seed);
let dt2 = seeded_delaunay2(6, (0.0, 20.0), seed);
assert_eq!(
dt1.number_of_vertices(),
dt2.number_of_vertices(),
"Should have same vertex count"
);
assert_eq!(
dt1.number_of_simplices(),
dt2.number_of_simplices(),
"Should have same simplex count"
);
assert_eq!(dt1.number_of_vertices(), 6, "Should have 6 vertices");
assert!(dt1.number_of_simplices() > 0, "Should have simplices");
}
#[test]
fn test_seeded_delaunay2_different_seeds() {
let dt1 = seeded_delaunay2(5, (0.0, 10.0), 123);
let dt2 = seeded_delaunay2(5, (0.0, 10.0), 456);
assert_eq!(dt1.number_of_vertices(), 5, "First should have 5 vertices");
assert_eq!(dt2.number_of_vertices(), 5, "Second should have 5 vertices");
}
#[test]
fn test_seeded_delaunay2_various_seeds() {
let seeds = [1, 100, 1000, u64::MAX];
for seed in seeds {
let dt = seeded_delaunay2(4, (-5.0, 5.0), seed);
assert_eq!(
dt.number_of_vertices(),
4,
"Should have 4 vertices with seed {seed}"
);
assert!(
dt.number_of_simplices() > 0,
"Should have simplices with seed {seed}"
);
}
}
#[test]
#[should_panic(
expected = "Failed to generate seeded Delaunay triangulation with 1 vertices and seed 42"
)]
fn test_seeded_delaunay2_panic_insufficient_vertices() {
let _ = seeded_delaunay2(1, (0.0, 10.0), 42);
}
#[test]
#[should_panic(
expected = "Failed to generate seeded Delaunay triangulation with 5 vertices and seed 123"
)]
fn test_seeded_delaunay2_panic_invalid_range() {
let _ = seeded_delaunay2(5, (15.0, 10.0), 123);
}
#[test]
fn test_euler_characteristic_properties() {
let dt = random_delaunay2(8, (0.0, 10.0));
#[expect(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
reason = "test triangulation sizes are tiny and fit in i32"
)]
let v = dt.number_of_vertices() as i32;
#[expect(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
reason = "test triangulation sizes are tiny and fit in i32"
)]
let c = dt.number_of_simplices() as i32;
assert!(v >= 3, "Should have at least 3 vertices");
assert!(c >= 1, "Should have at least 1 simplex/face");
}
#[test]
fn test_coordinate_range_bounds() {
let ranges = [
(-1.0e6, 1.0e6), (-1000.0, 1000.0),
(0.001, 0.002),
(-0.5, 0.5),
];
for range in ranges {
let result = generate_delaunay2(4, range, Some(789));
assert!(result.is_ok(), "Should handle coordinate range {range:?}");
}
}
#[test]
fn test_build_delaunay2_with_data_empty_input() {
let result = build_delaunay2_with_data(&[]);
match result {
Ok(dt) => assert_eq!(dt.number_of_vertices(), 0),
Err(e) => {
let msg = e.to_string();
assert!(
msg.contains("generation")
|| msg.contains("failed")
|| msg.contains("Delaunay"),
"Error should be descriptive: {msg}"
);
}
}
}
#[test]
fn test_build_delaunay2_with_data_single_point() {
let result = build_delaunay2_with_data(&[([0.0, 0.0], 0)]);
if let Ok(dt) = result {
assert_eq!(dt.number_of_vertices(), 1);
}
}
#[test]
fn test_build_delaunay2_with_data_valid_triangle() {
let coords = [([0.0, 0.0], 0), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
let dt = build_delaunay2_with_data(&coords)
.expect("Should build triangulation from 3 non-degenerate points");
assert_eq!(dt.number_of_vertices(), 3);
assert!(dt.number_of_simplices() >= 1);
}
#[test]
fn test_seeded_reproducibility_multiple_calls() {
let seed = 999;
let params = (7, (-10.0, 10.0));
let results: Vec<_> = (0..3)
.map(|_| seeded_delaunay2(params.0, params.1, seed))
.collect();
for (i, dt) in results.iter().enumerate() {
assert_eq!(
dt.number_of_vertices(),
7,
"Result {i} should have 7 vertices"
);
assert!(
dt.number_of_simplices() > 0,
"Result {i} should have simplices"
);
}
let first_vertex_count = results[0].number_of_vertices();
let first_simplex_count = results[0].number_of_simplices();
for (i, dt) in results.iter().enumerate().skip(1) {
assert_eq!(
dt.number_of_vertices(),
first_vertex_count,
"Result {i} vertex count should match first result"
);
assert_eq!(
dt.number_of_simplices(),
first_simplex_count,
"Result {i} simplex count should match first result"
);
}
}
}