#![forbid(unsafe_code)]
use super::point_generation::{
RandomPointGenerationError, generate_random_points_in_range,
generate_random_points_in_range_seeded,
};
use crate::builder::DelaunayTriangulationBuilder;
use crate::construction::{
ConstructionOptions, DelaunayConstructionFailure, DelaunayTriangulationConstructionError,
InsertionOrderStrategy, RetryPolicy,
};
use crate::core::construction::{FinalTopologyValidationContext, TriangulationConstructionError};
use crate::core::simplex::SimplexValidationError;
use crate::core::traits::data_type::DataType;
use crate::core::validation::TopologyGuarantee;
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::triangulation::DelaunayTriangulation;
use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
use std::{marker::PhantomData, num::NonZeroUsize};
const RANDOM_TRIANGULATION_MAX_SHUFFLE_ATTEMPTS: usize = 6;
const RANDOM_TRIANGULATION_MAX_POINTSET_ATTEMPTS: usize = 6;
const RANDOM_TRIANGULATION_POINTSET_SEED_MIX: u64 = 0x9E37_79B9_7F4A_7C15;
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum RandomPointCountError {
#[error(
"random triangulation in dimension {dimension} requires at least {expected} points, got {actual}"
)]
InsufficientPoints {
actual: usize,
expected: usize,
dimension: usize,
},
}
impl From<RandomPointCountError> for DelaunayTriangulationConstructionError {
fn from(error: RandomPointCountError) -> Self {
match error {
RandomPointCountError::InsufficientPoints {
actual,
expected,
dimension,
} => TriangulationConstructionError::InsufficientVertices {
dimension,
source: SimplexValidationError::InsufficientVertices {
actual,
expected,
dimension,
},
}
.into(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[must_use]
pub struct RandomPointCount<const D: usize>(NonZeroUsize);
impl<const D: usize> RandomPointCount<D> {
#[must_use]
pub const fn minimum() -> usize {
D + 1
}
pub const fn try_new(count: NonZeroUsize) -> Result<Self, RandomPointCountError> {
let actual = count.get();
let expected = Self::minimum();
if actual < expected {
return Err(RandomPointCountError::InsufficientPoints {
actual,
expected,
dimension: D,
});
}
Ok(Self(count))
}
#[must_use]
pub const fn get(self) -> usize {
self.0.get()
}
#[must_use]
pub const fn as_nonzero(self) -> NonZeroUsize {
self.0
}
}
impl<const D: usize> TryFrom<NonZeroUsize> for RandomPointCount<D> {
type Error = RandomPointCountError;
fn try_from(count: NonZeroUsize) -> Result<Self, Self::Error> {
Self::try_new(count)
}
}
impl<const D: usize> From<RandomPointCount<D>> for NonZeroUsize {
fn from(count: RandomPointCount<D>) -> Self {
count.as_nonzero()
}
}
#[derive(Clone, Debug, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum RandomTriangulationBuilderError {
#[error("{source}")]
PointCount {
#[from]
source: RandomPointCountError,
},
#[error("{source}")]
CoordinateRange {
#[from]
source: CoordinateRangeError<f64>,
},
}
impl From<RandomTriangulationBuilderError> for DelaunayTriangulationConstructionError {
fn from(error: RandomTriangulationBuilderError) -> Self {
match error {
RandomTriangulationBuilderError::PointCount { source } => source.into(),
RandomTriangulationBuilderError::CoordinateRange { source } => {
random_point_generation_error(source.into())
}
}
}
}
const fn random_point_generation_error(
source: RandomPointGenerationError<f64>,
) -> DelaunayTriangulationConstructionError {
DelaunayTriangulationConstructionError::Triangulation(
DelaunayConstructionFailure::RandomPointGeneration { source },
)
}
fn random_points_with_seed<const D: usize>(
n_points: usize,
bounds: CoordinateRange<f64>,
seed: Option<u64>,
) -> Result<Vec<Point<D>>, RandomPointGenerationError> {
#[expect(
clippy::option_if_let_else,
reason = "explicit match keeps seeded and unseeded generator paths readable"
)]
match seed {
Some(seed_value) => generate_random_points_in_range_seeded(n_points, bounds, seed_value),
None => generate_random_points_in_range(n_points, bounds),
}
}
fn validate_random_triangulation<K, U, V, const D: usize>(
dt: DelaunayTriangulation<K, U, V, D>,
) -> Result<DelaunayTriangulation<K, U, V, D>, DelaunayTriangulationConstructionError>
where
K: Kernel<D, Scalar = f64>,
U: DataType,
V: DataType,
{
dt.as_triangulation().validate().map_err(|e| {
TriangulationConstructionError::FinalTopologyValidation {
context: FinalTopologyValidationContext::RandomGeneration,
source: e.into(),
}
})?;
Ok(dt)
}
fn random_triangulation_is_acceptable<K, U, V, const D: usize>(
dt: &DelaunayTriangulation<K, U, V, D>,
min_vertices: usize,
) -> bool {
dt.number_of_vertices() >= min_vertices
}
fn random_triangulation_try_build<K, U, V, const D: usize>(
kernel: &K,
vertices: &[Vertex<U, D>],
min_vertices: usize,
topology_guarantee: TopologyGuarantee,
) -> Result<Option<DelaunayTriangulation<K, U, V, D>>, DelaunayTriangulationConstructionError>
where
K: Kernel<D, Scalar = f64>,
U: DataType,
V: DataType,
{
let options = ConstructionOptions::default()
.with_insertion_order(InsertionOrderStrategy::Input)
.with_retry_policy(RetryPolicy::Disabled);
let dt = DelaunayTriangulationBuilder::new(vertices)
.simplex_data_type::<V>()
.topology_guarantee(topology_guarantee)
.construction_options(options)
.build_with_kernel(kernel)?;
let dt = validate_random_triangulation(dt)?;
Ok(random_triangulation_is_acceptable(&dt, min_vertices).then_some(dt))
}
fn random_triangulation_build_vertices<U, const D: usize>(
points: Vec<Point<D>>,
vertex_data: Option<U>,
) -> Vec<Vertex<U, D>>
where
U: Copy,
{
points
.into_iter()
.map(|point| Vertex::from_validated_point(point, vertex_data))
.collect()
}
const fn make_adaptive_kernel() -> AdaptiveKernel<f64> {
AdaptiveKernel::new()
}
fn random_triangulation_try_with_vertices<U, V, const D: usize>(
vertices: &[Vertex<U, D>],
min_vertices: usize,
shuffle_seed: Option<u64>,
topology_guarantee: TopologyGuarantee,
) -> Result<
Option<DelaunayTriangulation<AdaptiveKernel<f64>, U, V, D>>,
DelaunayTriangulationConstructionError,
>
where
U: DataType,
V: DataType,
{
let adaptive_kernel = make_adaptive_kernel();
let mut last_error = None;
match random_triangulation_try_build(
&adaptive_kernel,
vertices,
min_vertices,
topology_guarantee,
) {
Ok(Some(dt)) => return Ok(Some(dt)),
Ok(None) => {}
Err(error) => last_error = Some(error),
}
for attempt in 0..RANDOM_TRIANGULATION_MAX_SHUFFLE_ATTEMPTS {
let mut shuffled = vertices.to_vec();
if let Some(seed_value) = shuffle_seed {
let mix = seed_value.wrapping_add(attempt as u64 + 1);
let mut rng = StdRng::seed_from_u64(mix);
shuffled.shuffle(&mut rng);
} else {
let mut rng = rand::rng();
shuffled.shuffle(&mut rng);
}
match random_triangulation_try_build(
&adaptive_kernel,
&shuffled,
min_vertices,
topology_guarantee,
) {
Ok(Some(dt)) => return Ok(Some(dt)),
Ok(None) => {}
Err(error) => last_error = Some(error),
}
}
last_error.map_or_else(|| Ok(None), Err)
}
pub fn try_generate_random_triangulation<U, V, const D: usize>(
n_points: NonZeroUsize,
bounds: (f64, f64),
vertex_data: Option<U>,
seed: Option<u64>,
) -> Result<
DelaunayTriangulation<AdaptiveKernel<f64>, U, V, D>,
DelaunayTriangulationConstructionError,
>
where
U: DataType,
V: DataType,
{
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() {
tracing::debug!(
n_points = n_points.get(),
dimension = D,
seed = ?seed,
"triangulation_generation::try_generate_random_triangulation called"
);
}
let bounds = CoordinateRange::try_from(bounds)
.map_err(RandomPointGenerationError::from)
.map_err(random_point_generation_error)?;
generate_random_triangulation_in_range_with_topology_guarantee(
n_points,
bounds,
vertex_data,
seed,
TopologyGuarantee::DEFAULT,
)
}
pub fn try_generate_random_triangulation_with_topology_guarantee<U, V, const D: usize>(
n_points: NonZeroUsize,
bounds: (f64, f64),
vertex_data: Option<U>,
seed: Option<u64>,
topology_guarantee: TopologyGuarantee,
) -> Result<
DelaunayTriangulation<AdaptiveKernel<f64>, U, V, D>,
DelaunayTriangulationConstructionError,
>
where
U: DataType,
V: DataType,
{
let bounds = CoordinateRange::try_from(bounds)
.map_err(RandomPointGenerationError::from)
.map_err(random_point_generation_error)?;
generate_random_triangulation_in_range_with_topology_guarantee(
n_points,
bounds,
vertex_data,
seed,
topology_guarantee,
)
}
pub fn generate_random_triangulation_in_range<U, V, const D: usize>(
n_points: NonZeroUsize,
bounds: CoordinateRange<f64>,
vertex_data: Option<U>,
seed: Option<u64>,
) -> Result<
DelaunayTriangulation<AdaptiveKernel<f64>, U, V, D>,
DelaunayTriangulationConstructionError,
>
where
U: DataType,
V: DataType,
{
generate_random_triangulation_in_range_with_topology_guarantee(
n_points,
bounds,
vertex_data,
seed,
TopologyGuarantee::DEFAULT,
)
}
pub fn generate_random_triangulation_in_range_with_topology_guarantee<U, V, const D: usize>(
n_points: NonZeroUsize,
bounds: CoordinateRange<f64>,
vertex_data: Option<U>,
seed: Option<u64>,
topology_guarantee: TopologyGuarantee,
) -> Result<
DelaunayTriangulation<AdaptiveKernel<f64>, U, V, D>,
DelaunayTriangulationConstructionError,
>
where
U: DataType,
V: DataType,
{
let n_points = RandomPointCount::<D>::try_new(n_points)?.get();
let points: Vec<Point<D>> =
random_points_with_seed(n_points, bounds, seed).map_err(random_point_generation_error)?;
let min_vertices = (n_points / 6).max(D + 1);
let mut initial_points = Some(points);
let mut last_error = None;
for attempt in 0..RANDOM_TRIANGULATION_MAX_POINTSET_ATTEMPTS {
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_RANDOM_POINTSET_RETRIES").is_some() {
tracing::debug!(
attempt,
max_attempts = RANDOM_TRIANGULATION_MAX_POINTSET_ATTEMPTS,
"random_triangulation: pointset attempt"
);
}
let point_seed = seed.map(|base| {
if attempt > 0 {
base ^ RANDOM_TRIANGULATION_POINTSET_SEED_MIX.wrapping_mul(attempt as u64)
} else {
base
}
});
let points = if attempt == 0 {
initial_points.take().ok_or_else(|| {
DelaunayTriangulationConstructionError::from(
TriangulationConstructionError::InternalInconsistency {
message: "initial points already consumed".to_owned(),
},
)
})?
} else {
random_points_with_seed(n_points, bounds, point_seed)
.map_err(random_point_generation_error)?
};
let vertices = random_triangulation_build_vertices(points, vertex_data);
match random_triangulation_try_with_vertices(
&vertices,
min_vertices,
point_seed,
topology_guarantee,
) {
Ok(Some(dt)) => return Ok(dt),
Ok(None) => {}
Err(error) => last_error = Some(error),
}
}
if let Some(error) = last_error {
return Err(error);
}
Err(TriangulationConstructionError::GeometricDegeneracy {
message: "Random triangulation failed validation after robust fallback".to_string(),
}
.into())
}
#[must_use]
pub struct RandomTriangulationBuilder<const D: usize, U = (), V = ()> {
n_points: RandomPointCount<D>,
bounds: CoordinateRange<f64>,
seed: Option<u64>,
topology_guarantee: TopologyGuarantee,
construction_options: ConstructionOptions,
vertex_data: Option<U>,
_simplex_data: PhantomData<V>,
}
impl<const D: usize> RandomTriangulationBuilder<D> {
pub fn try_new(
n_points: NonZeroUsize,
bounds: (f64, f64),
) -> Result<Self, RandomTriangulationBuilderError> {
Ok(Self {
n_points: RandomPointCount::try_new(n_points)?,
bounds: CoordinateRange::try_from(bounds)?,
seed: None,
topology_guarantee: TopologyGuarantee::DEFAULT,
construction_options: ConstructionOptions::default(),
vertex_data: None,
_simplex_data: PhantomData,
})
}
pub fn new_in_range(n_points: RandomPointCount<D>, bounds: CoordinateRange<f64>) -> Self {
Self {
n_points,
bounds,
seed: None,
topology_guarantee: TopologyGuarantee::DEFAULT,
construction_options: ConstructionOptions::default(),
vertex_data: None,
_simplex_data: PhantomData,
}
}
}
impl<const D: usize, U, V> RandomTriangulationBuilder<D, U, V> {
pub const fn seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
pub const fn topology_guarantee(mut self, topology_guarantee: TopologyGuarantee) -> Self {
self.topology_guarantee = topology_guarantee;
self
}
pub const fn insertion_order(mut self, strategy: InsertionOrderStrategy) -> Self {
self.construction_options = self.construction_options.with_insertion_order(strategy);
self
}
pub const fn construction_options(mut self, options: ConstructionOptions) -> Self {
self.construction_options = options;
self
}
pub fn vertex_data<W>(self, data: W) -> RandomTriangulationBuilder<D, W, V> {
let Self {
n_points,
bounds,
seed,
topology_guarantee,
construction_options,
vertex_data: _,
_simplex_data: _,
} = self;
RandomTriangulationBuilder {
n_points,
bounds,
seed,
topology_guarantee,
construction_options,
vertex_data: Some(data),
_simplex_data: PhantomData,
}
}
pub fn vertex_data_type<W>(self) -> RandomTriangulationBuilder<D, W, V> {
let Self {
n_points,
bounds,
seed,
topology_guarantee,
construction_options,
vertex_data: _,
_simplex_data: _,
} = self;
RandomTriangulationBuilder {
n_points,
bounds,
seed,
topology_guarantee,
construction_options,
vertex_data: None,
_simplex_data: PhantomData,
}
}
pub fn simplex_data_type<W>(self) -> RandomTriangulationBuilder<D, U, W> {
let Self {
n_points,
bounds,
seed,
topology_guarantee,
construction_options,
vertex_data,
_simplex_data: _,
} = self;
RandomTriangulationBuilder {
n_points,
bounds,
seed,
topology_guarantee,
construction_options,
vertex_data,
_simplex_data: PhantomData,
}
}
pub fn build(
self,
) -> Result<
DelaunayTriangulation<AdaptiveKernel<f64>, U, V, D>,
DelaunayTriangulationConstructionError,
>
where
U: DataType,
V: DataType,
{
let n_points = self.n_points.get();
let points: Vec<Point<D>> = random_points_with_seed(n_points, self.bounds, self.seed)
.map_err(random_point_generation_error)?;
let vertices = random_triangulation_build_vertices(points, self.vertex_data);
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_RANDOM_BUILDER").is_some() {
tracing::debug!(
n_points,
topology_guarantee = ?self.topology_guarantee,
insertion_order = ?self.construction_options.insertion_order(),
dedup_policy = ?self.construction_options.dedup_policy(),
retry_policy = ?self.construction_options.retry_policy(),
"random_triangulation_builder: single call through DelaunayTriangulationBuilder"
);
}
let kernel = make_adaptive_kernel();
let dt = DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<V>()
.topology_guarantee(self.topology_guarantee)
.construction_options(self.construction_options)
.build_with_kernel(&kernel)?;
validate_random_triangulation(dt)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::coordinate_range::{
CoordinateRangeBound, CoordinateRangeOrdering, InvalidCoordinateValue,
};
use crate::vertex;
use approx::assert_relative_eq;
use std::assert_matches;
const fn nonzero(value: usize) -> NonZeroUsize {
NonZeroUsize::new(value).expect("test point count must be non-zero")
}
#[test]
fn test_random_point_count_conversions_preserve_validated_nonzero_count() {
let count = RandomPointCount::<3>::try_new(nonzero(4)).unwrap();
assert_eq!(count.as_nonzero(), nonzero(4));
let parsed = RandomPointCount::<3>::try_from(nonzero(5)).unwrap();
let raw: NonZeroUsize = parsed.into();
assert_eq!(raw, nonzero(5));
let err = RandomPointCount::<3>::try_from(nonzero(3)).unwrap_err();
assert_eq!(
err,
RandomPointCountError::InsufficientPoints {
actual: 3,
expected: 4,
dimension: 3,
}
);
}
#[test]
fn test_random_triangulation_builder_error_coordinate_range_maps_to_construction_error() {
let Err(err) = RandomTriangulationBuilder::<2>::try_new(nonzero(10), (1.0, 0.0)) else {
panic!("expected invalid bounds to fail");
};
let err = DelaunayTriangulationConstructionError::from(err);
let DelaunayTriangulationConstructionError::Triangulation(
DelaunayConstructionFailure::RandomPointGeneration { source },
) = err
else {
panic!("expected coordinate-range builder error to map to random-point generation");
};
assert_eq!(
source,
RandomPointGenerationError::InvalidCoordinateRange {
source: CoordinateRangeError::NonIncreasing {
ordering: CoordinateRangeOrdering::Decreasing,
min: 1.0,
max: 0.0,
},
}
);
}
#[test]
fn test_random_triangulation_builder_type_state_selectors_build_typed_storage() {
let mut triangulation: DelaunayTriangulation<_, u32, usize, 2> =
RandomTriangulationBuilder::try_new(nonzero(12), (-2.0, 2.0))
.unwrap()
.seed(44)
.vertex_data_type::<u32>()
.simplex_data_type::<usize>()
.build()
.unwrap();
assert!(triangulation.number_of_simplices() > 0);
assert!(
triangulation
.tds()
.vertices()
.all(|(_, vertex)| vertex.data().is_none())
);
triangulation.fill_simplex_data(|_, simplex| simplex.number_of_vertices());
for (_, simplex) in triangulation.simplices() {
assert_eq!(simplex.data(), Some(&3));
}
assert!(triangulation.validate().is_ok());
}
#[test]
fn test_generate_random_triangulation_basic() {
let triangulation_2d = try_generate_random_triangulation::<(), (), 2>(
nonzero(10),
(-5.0, 5.0),
None,
Some(42),
)
.unwrap();
assert!(
triangulation_2d.number_of_vertices() >= 3,
"Expected at least 3 vertices in 2D triangulation, got {}",
triangulation_2d.number_of_vertices()
);
assert_eq!(triangulation_2d.dim(), 2);
triangulation_2d.is_valid_delaunay().unwrap();
let triangulation_3d = try_generate_random_triangulation::<i32, (), 3>(
nonzero(8),
(0.0, 1.0),
Some(123),
Some(456),
)
.unwrap();
assert!(
triangulation_3d.number_of_vertices() >= 4,
"Expected at least 4 vertices in 3D triangulation, got {}",
triangulation_3d.number_of_vertices()
);
assert_eq!(triangulation_3d.dim(), 3);
triangulation_3d.is_valid_delaunay().unwrap();
let triangulation_seeded = try_generate_random_triangulation::<(), (), 2>(
nonzero(5),
(-1.0, 1.0),
None,
Some(789),
)
.unwrap();
let triangulation_different_seed = try_generate_random_triangulation::<(), (), 2>(
nonzero(5),
(-1.0, 1.0),
None,
Some(790),
)
.unwrap();
triangulation_seeded.is_valid_delaunay().unwrap();
triangulation_different_seed.is_valid_delaunay().unwrap();
assert!(
triangulation_seeded.number_of_vertices() >= 3,
"Expected at least 3 vertices in seeded 2D triangulation, got {}",
triangulation_seeded.number_of_vertices()
);
assert!(
triangulation_different_seed.number_of_vertices() >= 3,
"Expected at least 3 vertices in second seeded 2D triangulation, got {}",
triangulation_different_seed.number_of_vertices()
);
}
#[test]
fn test_generate_random_triangulation_error_cases() {
let result = try_generate_random_triangulation::<(), (), 2>(
nonzero(10),
(5.0, 1.0), None,
Some(42),
);
let Err(DelaunayTriangulationConstructionError::Triangulation(
DelaunayConstructionFailure::RandomPointGeneration { source },
)) = result
else {
panic!("expected RandomPointGeneration error");
};
assert_eq!(
source,
RandomPointGenerationError::InvalidCoordinateRange {
source: CoordinateRangeError::NonIncreasing {
ordering: CoordinateRangeOrdering::Decreasing,
min: 5.0,
max: 1.0,
},
}
);
assert_eq!(NonZeroUsize::new(0), None);
}
#[test]
fn test_generate_random_triangulation_rejects_nonfinite_bounds_as_generation_error() {
let nan_bounds = try_generate_random_triangulation::<(), (), 2>(
nonzero(10),
(f64::NAN, 1.0),
None,
Some(42),
);
assert_matches!(
nan_bounds,
Err(DelaunayTriangulationConstructionError::Triangulation(
DelaunayConstructionFailure::RandomPointGeneration {
source: RandomPointGenerationError::InvalidCoordinateRange {
source: CoordinateRangeError::NonFiniteBound { bound, value }
}
}
)) if bound == CoordinateRangeBound::Minimum && value == InvalidCoordinateValue::Nan
);
let Err(RandomTriangulationBuilderError::CoordinateRange {
source: CoordinateRangeError::NonFiniteBound { bound, value },
}) = RandomTriangulationBuilder::<2>::try_new(nonzero(10), (0.0, f64::INFINITY))
else {
panic!("expected invalid infinite bounds to fail");
};
assert_eq!(bound, CoordinateRangeBound::Maximum);
assert_eq!(value, InvalidCoordinateValue::PositiveInfinity);
}
#[test]
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::<(), (), 2>(nonzero(10), range, None, Some(42))
.unwrap();
assert_eq!(triangulation.dim(), 2);
triangulation.is_valid_delaunay().unwrap();
let count = RandomPointCount::<2>::try_new(nonzero(10)).unwrap();
let builder_triangulation: DelaunayTriangulation<_, (), (), 2> =
RandomTriangulationBuilder::new_in_range(count, range)
.seed(43)
.build()
.unwrap();
assert_eq!(builder_triangulation.dim(), 2);
builder_triangulation.is_valid_delaunay().unwrap();
let guaranteed_triangulation =
generate_random_triangulation_in_range_with_topology_guarantee::<(), (), 2>(
nonzero(10),
range,
None,
Some(44),
TopologyGuarantee::Pseudomanifold,
)
.unwrap();
assert_eq!(
guaranteed_triangulation.topology_guarantee(),
TopologyGuarantee::Pseudomanifold
);
guaranteed_triangulation.is_valid_delaunay().unwrap();
}
#[test]
fn test_random_triangulation_builder_success_and_error_paths() {
let triangulation: DelaunayTriangulation<_, (), (), 2> =
RandomTriangulationBuilder::try_new(nonzero(10), (-5.0, 5.0))
.unwrap()
.seed(42)
.build()
.unwrap();
assert_eq!(triangulation.dim(), 2);
assert!(triangulation.number_of_vertices() >= 3);
triangulation.is_valid_delaunay().unwrap();
let triangulation_with_data: DelaunayTriangulation<_, u32, (), 2> =
RandomTriangulationBuilder::try_new(nonzero(10), (-5.0, 5.0))
.unwrap()
.seed(43)
.vertex_data(7_u32)
.build()
.unwrap();
let vertex_data: Vec<_> = triangulation_with_data
.tds()
.vertices()
.filter_map(|(_, vertex)| vertex.data().copied())
.collect();
assert_eq!(
vertex_data.len(),
triangulation_with_data.number_of_vertices()
);
assert!(vertex_data.iter().all(|&data| data == 7));
triangulation_with_data.is_valid_delaunay().unwrap();
let too_few_vertices = RandomTriangulationBuilder::<2>::try_new(nonzero(2), (-1.0, 1.0));
let Err(RandomTriangulationBuilderError::PointCount { source }) = too_few_vertices else {
panic!("expected builder point-count error");
};
assert_eq!(
source,
RandomPointCountError::InsufficientPoints {
actual: 2,
expected: 3,
dimension: 2,
}
);
let invalid_bounds = RandomTriangulationBuilder::<2>::try_new(nonzero(10), (5.0, 1.0));
let Err(RandomTriangulationBuilderError::CoordinateRange {
source: CoordinateRangeError::NonIncreasing { ordering, min, max },
}) = invalid_bounds
else {
panic!("expected invalid bounds to fail");
};
assert_eq!(ordering, CoordinateRangeOrdering::Decreasing);
assert_relative_eq!(min, 5.0, epsilon = f64::EPSILON);
assert_relative_eq!(max, 1.0, epsilon = f64::EPSILON);
let equal_bounds = RandomTriangulationBuilder::<2>::try_new(nonzero(10), (2.0, 2.0));
let Err(RandomTriangulationBuilderError::CoordinateRange {
source: CoordinateRangeError::NonIncreasing { ordering, min, max },
}) = equal_bounds
else {
panic!("expected equal bounds to fail");
};
assert_eq!(ordering, CoordinateRangeOrdering::Equal);
assert_relative_eq!(min, 2.0, epsilon = f64::EPSILON);
assert_relative_eq!(max, 2.0, epsilon = f64::EPSILON);
}
#[test]
fn test_generate_random_triangulation_reproducibility() {
let triangulation1 = try_generate_random_triangulation::<(), (), 3>(
nonzero(6),
(-2.0, 2.0),
None,
Some(12345),
)
.unwrap();
let triangulation2 = try_generate_random_triangulation::<(), (), 3>(
nonzero(6),
(-2.0, 2.0),
None,
Some(12345),
)
.unwrap();
assert_eq!(
triangulation1.number_of_vertices(),
triangulation2.number_of_vertices()
);
assert_eq!(
triangulation1.number_of_simplices(),
triangulation2.number_of_simplices()
);
assert_eq!(triangulation1.dim(), triangulation2.dim());
}
#[test]
fn test_random_triangulation_try_with_vertices_exercises_fallbacks() {
let vertices: Vec<Vertex<(), 2>> = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let result = random_triangulation_try_with_vertices::<(), (), 2>(
&vertices,
vertices.len() + 1,
Some(7),
TopologyGuarantee::PLManifold,
);
assert!(result.unwrap().is_none());
}
#[test]
fn test_generate_random_triangulation_dimensions() {
let tri_2d = try_generate_random_triangulation::<(), (), 2>(
nonzero(15),
(0.0, 10.0),
None,
Some(555),
)
.unwrap();
assert_eq!(tri_2d.dim(), 2);
assert!(tri_2d.number_of_simplices() > 0);
let tri_3d = try_generate_random_triangulation::<(), (), 3>(
nonzero(20),
(-3.0, 3.0),
None,
Some(666),
)
.unwrap();
assert_eq!(tri_3d.dim(), 3);
assert!(tri_3d.number_of_simplices() > 0);
let tri_4d = try_generate_random_triangulation::<(), (), 4>(
nonzero(12),
(-1.0, 1.0),
None,
Some(777),
)
.unwrap();
assert_eq!(tri_4d.dim(), 4);
assert!(tri_4d.number_of_simplices() > 0);
let tri_5d = try_generate_random_triangulation::<(), (), 5>(
nonzero(10),
(0.0, 5.0),
None,
Some(888),
)
.unwrap();
assert_eq!(tri_5d.dim(), 5);
assert!(tri_5d.number_of_simplices() > 0);
}
#[test]
fn test_generate_random_triangulation_with_data() {
let tri_with_char_array = try_generate_random_triangulation::<[char; 8], (), 2>(
nonzero(6),
(-2.0, 2.0),
Some(['v', 'e', 'r', 't', 'e', 'x', '_', 'd']),
Some(888),
)
.unwrap();
assert!(
tri_with_char_array.number_of_vertices() >= 3,
"Expected at least 3 vertices in 2D triangulation with data, got {}",
tri_with_char_array.number_of_vertices()
);
tri_with_char_array.tds().is_valid().unwrap();
let char_array_data = ['v', 'e', 'r', 't', 'e', 'x', '_', 'd'];
let string_representation: String = char_array_data.iter().collect();
assert_eq!(string_representation, "vertex_d");
let seeds = [999_u64, 123, 456, 789, 2024];
let mut tri_with_int_data: Option<DelaunayTriangulation<AdaptiveKernel<f64>, u32, (), 3>> =
None;
let mut last_err: Option<String> = None;
for seed in seeds {
match try_generate_random_triangulation::<u32, (), 3>(
nonzero(8),
(0.0, 5.0),
Some(42u32),
Some(seed),
) {
Ok(tri) => {
tri_with_int_data = Some(tri);
break;
}
Err(e) => {
last_err = Some(format!("{e}"));
}
}
}
let tri_with_int_data = tri_with_int_data.unwrap_or_else(|| {
panic!("All seeds failed to generate 3D triangulation with int data: {last_err:?}")
});
assert!(
tri_with_int_data.number_of_vertices() >= 4,
"Expected at least 4 vertices in 3D triangulation with data, got {}",
tri_with_int_data.number_of_vertices()
);
tri_with_int_data.tds().is_valid().unwrap();
let tri_no_data = try_generate_random_triangulation::<(), (), 2>(
nonzero(5),
(-1.0, 1.0),
None,
Some(111),
)
.unwrap();
assert!(
tri_no_data.number_of_vertices() >= 3,
"Expected at least 3 vertices in 2D triangulation without data, got {}",
tri_no_data.number_of_vertices()
);
tri_no_data.tds().is_valid().unwrap();
}
}