use crate::errors::CdtResult;
use crate::geometry::backends::delaunay::DelaunayBackend;
use crate::geometry::operations::TriangulationOps;
use crate::geometry::traits::TriangulationMut;
use std::time::Instant;
#[derive(Debug)]
pub struct CdtTriangulation<B: TriangulationMut> {
geometry: B,
metadata: CdtMetadata,
cache: GeometryCache,
}
#[derive(Debug, Clone)]
pub struct CdtMetadata {
pub time_slices: u32,
pub dimension: u8,
pub creation_time: Instant,
pub last_modified: Instant,
pub modification_count: u64,
pub simulation_history: Vec<SimulationEvent>,
}
#[derive(Debug, Clone, Default)]
struct GeometryCache {
edge_count: Option<CachedValue<usize>>,
euler_char: Option<CachedValue<i32>>,
#[allow(dead_code)]
topology_hash: Option<CachedValue<u64>>,
}
#[derive(Debug, Clone)]
struct CachedValue<T> {
value: T,
#[allow(dead_code)]
computed_at: Instant,
modification_count: u64,
}
#[derive(Debug, Clone)]
pub enum SimulationEvent {
Created {
vertex_count: usize,
time_slices: u32,
},
MoveAttempted {
move_type: String,
step: u64,
},
MoveAccepted {
move_type: String,
step: u64,
action_change: f64,
},
MeasurementTaken {
step: u64,
action: f64,
},
}
impl<B: TriangulationMut> CdtTriangulation<B> {
pub fn new(geometry: B, time_slices: u32, dimension: u8) -> Self {
let vertex_count = geometry.vertex_count();
let creation_event = SimulationEvent::Created {
vertex_count,
time_slices,
};
Self {
geometry,
metadata: CdtMetadata {
time_slices,
dimension,
creation_time: Instant::now(),
last_modified: Instant::now(),
modification_count: 0,
simulation_history: vec![creation_event],
},
cache: GeometryCache::default(),
}
}
#[must_use]
pub const fn geometry(&self) -> &B {
&self.geometry
}
pub fn geometry_mut(&mut self) -> CdtGeometryMut<'_, B> {
self.invalidate_cache();
self.metadata.last_modified = Instant::now();
self.metadata.modification_count += 1;
CdtGeometryMut {
geometry: &mut self.geometry,
metadata: &mut self.metadata,
}
}
pub fn vertex_count(&self) -> usize {
self.geometry.vertex_count()
}
pub fn face_count(&self) -> usize {
self.geometry.face_count()
}
#[must_use]
pub const fn time_slices(&self) -> u32 {
self.metadata.time_slices
}
#[must_use]
pub const fn dimension(&self) -> u8 {
self.metadata.dimension
}
pub fn edge_count(&self) -> usize {
if let Some(cached) = &self.cache.edge_count
&& cached.modification_count == self.metadata.modification_count
{
return cached.value;
}
self.geometry.edge_count()
}
pub fn refresh_cache(&mut self) {
let now = Instant::now();
let mod_count = self.metadata.modification_count;
self.cache.edge_count = Some(CachedValue {
value: self.geometry.edge_count(),
computed_at: now,
modification_count: mod_count,
});
self.cache.euler_char = Some(CachedValue {
value: self.geometry.euler_characteristic(),
computed_at: now,
modification_count: mod_count,
});
}
pub fn validate(&self) -> CdtResult<()> {
if !self.geometry.is_valid() {
return Err(crate::errors::CdtError::ValidationFailed {
check: "geometry".to_string(),
detail: format!(
"triangulation is not valid (V={}, E={}, F={})",
self.geometry.vertex_count(),
self.geometry.edge_count(),
self.geometry.face_count(),
),
});
}
if !self.geometry.is_delaunay() {
return Err(crate::errors::CdtError::ValidationFailed {
check: "Delaunay".to_string(),
detail: format!(
"triangulation does not satisfy Delaunay property (V={}, E={}, F={})",
self.geometry.vertex_count(),
self.geometry.edge_count(),
self.geometry.face_count(),
),
});
}
self.validate_topology()?;
self.validate_causality()?;
self.validate_foliation()?;
Ok(())
}
fn validate_topology(&self) -> CdtResult<()> {
let euler_char = self.geometry.euler_characteristic();
if self.dimension() == 2 {
if euler_char != 1 && euler_char != 2 {
return Err(crate::errors::CdtError::ValidationFailed {
check: "topology".to_string(),
detail: format!(
"Euler characteristic χ={euler_char} unexpected for 2D triangulation \
(expected 1 for boundary or 2 for closed surface; \
V={}, E={}, F={})",
self.geometry.vertex_count(),
self.geometry.edge_count(),
self.geometry.face_count(),
),
});
}
}
Ok(())
}
#[allow(
clippy::missing_const_for_fn,
clippy::unnecessary_wraps,
clippy::unused_self
)]
fn validate_causality(&self) -> CdtResult<()> {
Ok(())
}
#[allow(
clippy::missing_const_for_fn,
clippy::unnecessary_wraps,
clippy::unused_self
)]
fn validate_foliation(&self) -> CdtResult<()> {
Ok(())
}
fn invalidate_cache(&mut self) {
self.cache = GeometryCache::default();
}
}
pub struct CdtGeometryMut<'a, B: TriangulationMut> {
geometry: &'a mut B,
metadata: &'a mut CdtMetadata,
}
impl<B: TriangulationMut> CdtGeometryMut<'_, B> {
pub fn record_event(&mut self, event: SimulationEvent) {
self.metadata.simulation_history.push(event);
}
pub const fn geometry_mut(&mut self) -> &mut B {
self.geometry
}
}
impl<B: TriangulationMut> std::ops::Deref for CdtGeometryMut<'_, B> {
type Target = B;
fn deref(&self) -> &Self::Target {
self.geometry
}
}
impl<B: TriangulationMut> std::ops::DerefMut for CdtGeometryMut<'_, B> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.geometry
}
}
impl CdtTriangulation<crate::geometry::backends::delaunay::DelaunayBackend2D> {
pub fn from_random_points(
vertices: u32,
time_slices: u32,
dimension: u8,
) -> crate::errors::CdtResult<Self> {
if dimension != 2 {
return Err(crate::errors::CdtError::UnsupportedDimension(
dimension.into(),
));
}
if vertices < 3 {
return Err(crate::errors::CdtError::InvalidGenerationParameters {
issue: "Insufficient vertex count".to_string(),
provided_value: vertices.to_string(),
expected_range: "≥ 3".to_string(),
});
}
let dt = crate::util::generate_delaunay2_with_context(vertices, (0.0, 10.0), None)?;
let backend = DelaunayBackend::from_triangulation(dt);
Ok(Self::new(backend, time_slices, dimension))
}
pub fn from_seeded_points(
vertices: u32,
time_slices: u32,
dimension: u8,
seed: u64,
) -> crate::errors::CdtResult<Self> {
if dimension != 2 {
return Err(crate::errors::CdtError::UnsupportedDimension(
dimension.into(),
));
}
if vertices < 3 {
return Err(crate::errors::CdtError::InvalidGenerationParameters {
issue: "Insufficient vertex count".to_string(),
provided_value: vertices.to_string(),
expected_range: "≥ 3".to_string(),
});
}
let dt = crate::util::generate_delaunay2_with_context(vertices, (0.0, 10.0), Some(seed))?;
let backend = DelaunayBackend::from_triangulation(dt);
Ok(Self::new(backend, time_slices, dimension))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::traits::TriangulationQuery;
use std::thread;
use std::time::Duration;
#[test]
fn test_from_random_points() {
let triangulation =
CdtTriangulation::from_random_points(10, 3, 2).expect("Failed to create triangulation");
assert_eq!(triangulation.dimension(), 2);
assert_eq!(triangulation.time_slices(), 3);
assert!(triangulation.vertex_count() > 0);
assert!(triangulation.edge_count() > 0);
assert!(triangulation.face_count() > 0);
}
#[test]
fn test_from_random_points_various_sizes() {
let test_cases = [
(3, 1, "minimal"),
(5, 2, "small"),
(10, 3, "medium"),
(20, 5, "large"),
];
for (vertices, time_slices, description) in test_cases {
let triangulation = CdtTriangulation::from_random_points(vertices, time_slices, 2)
.unwrap_or_else(|e| panic!("Failed to create {description} triangulation: {e}"));
assert_eq!(
triangulation.dimension(),
2,
"Dimension should be 2 for {description}"
);
assert_eq!(
triangulation.time_slices(),
time_slices,
"Time slices should match for {description}"
);
assert!(
triangulation.vertex_count() >= 3,
"Should have at least 3 vertices for {description}"
);
assert!(
triangulation.edge_count() > 0,
"Should have edges for {description}"
);
assert!(
triangulation.face_count() > 0,
"Should have faces for {description}"
);
}
}
#[test]
fn test_from_seeded_points() {
let seed = 42;
let triangulation = CdtTriangulation::from_seeded_points(8, 2, 2, seed)
.expect("Failed to create seeded triangulation");
assert_eq!(triangulation.dimension(), 2);
assert_eq!(triangulation.time_slices(), 2);
assert!(triangulation.vertex_count() > 0);
assert!(triangulation.edge_count() > 0);
assert!(triangulation.face_count() > 0);
}
#[test]
fn test_seeded_determinism() {
let seed = 123;
let params = (6, 3, 2);
let triangulation1 =
CdtTriangulation::from_seeded_points(params.0, params.1, params.2, seed)
.expect("Failed to create first triangulation");
let triangulation2 =
CdtTriangulation::from_seeded_points(params.0, params.1, params.2, seed)
.expect("Failed to create second triangulation");
assert_eq!(triangulation1.vertex_count(), triangulation2.vertex_count());
assert_eq!(triangulation1.edge_count(), triangulation2.edge_count());
assert_eq!(triangulation1.face_count(), triangulation2.face_count());
assert_eq!(triangulation1.dimension(), triangulation2.dimension());
assert_eq!(triangulation1.time_slices(), triangulation2.time_slices());
}
#[test]
fn test_seeded_different_seeds() {
let params = (7, 2, 2);
let tri1 = CdtTriangulation::from_seeded_points(params.0, params.1, params.2, 456)
.expect("Failed to create triangulation with seed 456");
let tri2 = CdtTriangulation::from_seeded_points(params.0, params.1, params.2, 789)
.expect("Failed to create triangulation with seed 789");
assert_eq!(tri1.dimension(), tri2.dimension());
assert_eq!(tri1.time_slices(), tri2.time_slices());
assert_eq!(tri1.vertex_count(), 7);
assert_eq!(tri2.vertex_count(), 7);
}
#[test]
fn test_invalid_dimension() {
let invalid_dimensions = [0, 1, 3, 4, 5];
for dim in invalid_dimensions {
let result = CdtTriangulation::from_random_points(10, 3, dim);
assert!(result.is_err(), "Should fail with dimension {dim}");
if let Err(crate::errors::CdtError::UnsupportedDimension(d)) = result {
assert_eq!(d, u32::from(dim), "Error should report correct dimension");
} else {
panic!("Expected UnsupportedDimension error for dimension {dim}");
}
}
}
#[test]
fn test_invalid_vertex_count() {
let invalid_counts = [0, 1, 2];
for count in invalid_counts {
let result = CdtTriangulation::from_random_points(count, 2, 2);
assert!(result.is_err(), "Should fail with {count} vertices");
match result {
Err(crate::errors::CdtError::InvalidGenerationParameters {
issue,
provided_value,
..
}) => {
assert_eq!(issue, "Insufficient vertex count");
assert_eq!(provided_value, count.to_string());
}
other => panic!(
"Expected InvalidGenerationParameters for {count} vertices, got {other:?}"
),
}
}
}
#[test]
fn test_invalid_vertex_count_seeded() {
let result = CdtTriangulation::from_seeded_points(2, 2, 2, 123);
assert!(result.is_err(), "Should fail with 2 vertices");
match result {
Err(crate::errors::CdtError::InvalidGenerationParameters {
issue,
provided_value,
..
}) => {
assert_eq!(issue, "Insufficient vertex count");
assert_eq!(provided_value, "2");
}
other => panic!("Expected InvalidGenerationParameters, got {other:?}"),
}
}
#[test]
fn test_geometry_access() {
let triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
let geometry = triangulation.geometry();
assert!(geometry.vertex_count() > 0);
assert!(geometry.is_valid());
assert_eq!(geometry.dimension(), 2);
}
#[test]
fn test_basic_properties() {
let triangulation =
CdtTriangulation::from_random_points(8, 4, 2).expect("Failed to create triangulation");
assert_eq!(triangulation.dimension(), 2);
assert_eq!(triangulation.time_slices(), 4);
assert_eq!(triangulation.vertex_count(), 8);
let edge_count = triangulation.edge_count();
let face_count = triangulation.face_count();
assert!(edge_count > 0, "Should have edges");
assert!(face_count > 0, "Should have faces");
assert!(
edge_count >= triangulation.vertex_count(),
"Usually E >= V for connected triangulation"
);
assert!(face_count >= 1, "Should have at least one face");
}
#[test]
fn test_metadata_initialization() {
let triangulation =
CdtTriangulation::from_random_points(6, 3, 2).expect("Failed to create triangulation");
assert_eq!(triangulation.dimension(), 2);
assert_eq!(triangulation.time_slices(), 3);
let debug_output = format!("{triangulation:?}");
assert!(debug_output.contains("CdtTriangulation"));
assert!(debug_output.contains("CdtMetadata"));
}
#[test]
fn test_creation_history() {
let triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
assert!(!triangulation.metadata.simulation_history.is_empty());
match &triangulation.metadata.simulation_history[0] {
SimulationEvent::Created {
vertex_count,
time_slices,
} => {
assert_eq!(*vertex_count, 5);
assert_eq!(*time_slices, 2);
}
_ => panic!("First event should be Creation"),
}
}
#[test]
fn test_geometry_mut_with_cache() {
let mut triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
let initial_edge_count = triangulation.edge_count();
assert!(initial_edge_count > 0);
let initial_mod_count = triangulation.metadata.modification_count;
{
let mut geometry_mut = triangulation.geometry_mut();
let _ = geometry_mut.geometry_mut();
}
assert_eq!(
triangulation.metadata.modification_count,
initial_mod_count + 1
);
let recalculated_edge_count = triangulation.edge_count();
assert_eq!(initial_edge_count, recalculated_edge_count);
}
#[test]
fn test_cache_refresh_functionality() {
let mut triangulation =
CdtTriangulation::from_random_points(6, 2, 2).expect("Failed to create triangulation");
let edge_count_1 = triangulation.edge_count();
triangulation.refresh_cache();
let edge_count_2 = triangulation.edge_count();
assert_eq!(
edge_count_1, edge_count_2,
"Cache should return consistent values"
);
let edge_count_3 = triangulation.edge_count();
assert_eq!(
edge_count_1, edge_count_3,
"Multiple cache hits should be consistent"
);
}
#[test]
fn test_cache_invalidation_on_mutation() {
let mut triangulation =
CdtTriangulation::from_random_points(6, 2, 2).expect("Failed to create triangulation");
triangulation.refresh_cache();
let cached_count = triangulation.edge_count();
{
let _geometry_mut = triangulation.geometry_mut();
}
let new_count = triangulation.edge_count();
assert_eq!(
cached_count, new_count,
"Edge count should remain consistent after cache invalidation"
);
}
#[test]
fn test_euler_characteristic() {
const TRIANGULATION_SEED: u64 = 53;
let triangulation = CdtTriangulation::from_seeded_points(5, 2, 2, TRIANGULATION_SEED)
.expect("Failed to create triangulation with fixed seed");
let result = triangulation.geometry().is_valid();
assert!(result, "Validation should succeed for closed triangulation");
}
#[test]
fn test_validation_success() {
const GOOD_SEED: u64 = 53;
let triangulation = CdtTriangulation::from_seeded_points(5, 2, 2, GOOD_SEED)
.expect("Failed to create triangulation");
let result = triangulation.validate();
assert!(
result.is_ok(),
"Validation should succeed for good triangulation: {result:?}"
);
}
#[test]
fn test_validate_topology() {
let seeds = [53, 87, 203];
for seed in seeds {
let triangulation = CdtTriangulation::from_seeded_points(5, 1, 2, seed)
.expect("Failed to create triangulation");
let result = triangulation.validate_topology();
assert!(
result.is_ok(),
"Topology validation should succeed for seed {seed}: {result:?}"
);
}
}
#[test]
fn test_validate_causality_placeholder() {
let triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
let result = triangulation.validate_causality();
assert!(
result.is_ok(),
"Causality validation should succeed (placeholder implementation)"
);
}
#[test]
fn test_validate_foliation_placeholder() {
let triangulation =
CdtTriangulation::from_random_points(5, 3, 2).expect("Failed to create triangulation");
let result = triangulation.validate_foliation();
assert!(
result.is_ok(),
"Foliation validation should succeed (placeholder implementation)"
);
}
#[test]
fn test_geometry_mut_wrapper() {
let mut triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
{
let mut wrapper = triangulation.geometry_mut();
assert!(wrapper.vertex_count() > 0);
assert!(wrapper.is_valid());
let geometry = wrapper.geometry_mut();
assert!(geometry.vertex_count() > 0);
}
}
#[test]
fn test_simulation_event_recording() {
let mut triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
let initial_history_len = triangulation.metadata.simulation_history.len();
{
let mut wrapper = triangulation.geometry_mut();
wrapper.record_event(SimulationEvent::MoveAttempted {
move_type: "test_move".to_string(),
step: 1,
});
wrapper.record_event(SimulationEvent::MoveAccepted {
move_type: "test_move".to_string(),
step: 1,
action_change: -0.5,
});
wrapper.record_event(SimulationEvent::MeasurementTaken {
step: 2,
action: 10.5,
});
}
assert_eq!(
triangulation.metadata.simulation_history.len(),
initial_history_len + 3
);
let history = &triangulation.metadata.simulation_history;
match &history[initial_history_len] {
SimulationEvent::MoveAttempted { move_type, step } => {
assert_eq!(move_type, "test_move");
assert_eq!(*step, 1);
}
_ => panic!("Expected MoveAttempted event"),
}
match &history[initial_history_len + 1] {
SimulationEvent::MoveAccepted {
move_type,
step,
action_change,
} => {
assert_eq!(move_type, "test_move");
assert_eq!(*step, 1);
approx::assert_relative_eq!(*action_change, -0.5);
}
_ => panic!("Expected MoveAccepted event"),
}
match &history[initial_history_len + 2] {
SimulationEvent::MeasurementTaken { step, action } => {
assert_eq!(*step, 2);
approx::assert_relative_eq!(*action, 10.5);
}
_ => panic!("Expected MeasurementTaken event"),
}
}
#[test]
fn test_metadata_timestamps() {
let start_time = std::time::Instant::now();
let mut triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
let creation_time = triangulation.metadata.creation_time;
let initial_last_modified = triangulation.metadata.last_modified;
assert!(creation_time >= start_time);
let time_diff = initial_last_modified.duration_since(creation_time);
assert!(time_diff < Duration::from_millis(10));
thread::sleep(Duration::from_millis(5));
{
let _wrapper = triangulation.geometry_mut();
}
let new_last_modified = triangulation.metadata.last_modified;
assert!(new_last_modified > initial_last_modified);
assert_eq!(triangulation.metadata.creation_time, creation_time);
}
#[test]
fn test_modification_count() {
let mut triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
assert_eq!(triangulation.metadata.modification_count, 0);
{
let _wrapper = triangulation.geometry_mut();
}
assert_eq!(triangulation.metadata.modification_count, 1);
{
let _wrapper = triangulation.geometry_mut();
}
assert_eq!(triangulation.metadata.modification_count, 2);
let _geometry = triangulation.geometry();
let _edge_count = triangulation.edge_count();
assert_eq!(triangulation.metadata.modification_count, 2);
}
#[test]
fn test_zero_time_slices() {
let result = CdtTriangulation::from_random_points(5, 0, 2);
assert!(result.is_ok(), "Should allow 0 time slices");
let triangulation = result.unwrap();
assert_eq!(triangulation.time_slices(), 0);
}
#[test]
fn test_large_time_slices() {
let result = CdtTriangulation::from_random_points(5, 100, 2);
assert!(result.is_ok(), "Should allow large time slice count");
let triangulation = result.unwrap();
assert_eq!(triangulation.time_slices(), 100);
}
#[test]
fn test_consistency_across_methods() {
let triangulation =
CdtTriangulation::from_random_points(8, 3, 2).expect("Failed to create triangulation");
let direct_vertex_count = triangulation.vertex_count();
let geometry_vertex_count = triangulation.geometry().vertex_count();
assert_eq!(
direct_vertex_count, geometry_vertex_count,
"Vertex count should be consistent"
);
let direct_face_count = triangulation.face_count();
let geometry_face_count = triangulation.geometry().face_count();
assert_eq!(
direct_face_count, geometry_face_count,
"Face count should be consistent"
);
let direct_edge_count = triangulation.edge_count();
let geometry_edge_count = triangulation.geometry().edge_count();
assert_eq!(
direct_edge_count, geometry_edge_count,
"Edge count should be consistent"
);
}
#[test]
fn test_debug_formatting() {
let triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
let debug_str = format!("{triangulation:?}");
assert!(debug_str.contains("CdtTriangulation"));
assert!(debug_str.contains("geometry"));
assert!(debug_str.contains("metadata"));
assert!(debug_str.contains("cache"));
}
#[test]
fn test_simulation_event_debug() {
let events = vec![
SimulationEvent::Created {
vertex_count: 5,
time_slices: 2,
},
SimulationEvent::MoveAttempted {
move_type: "flip".to_string(),
step: 1,
},
SimulationEvent::MoveAccepted {
move_type: "flip".to_string(),
step: 1,
action_change: 0.5,
},
SimulationEvent::MeasurementTaken {
step: 2,
action: 15.5,
},
];
for event in events {
let debug_str = format!("{event:?}");
assert!(!debug_str.is_empty());
}
}
#[test]
fn test_cdt_metadata_clone() {
let triangulation =
CdtTriangulation::from_random_points(5, 2, 2).expect("Failed to create triangulation");
let metadata1 = triangulation.metadata;
let metadata2 = metadata1.clone();
assert_eq!(metadata1.time_slices, metadata2.time_slices);
assert_eq!(metadata1.dimension, metadata2.dimension);
assert_eq!(metadata1.modification_count, metadata2.modification_count);
assert_eq!(
metadata1.simulation_history.len(),
metadata2.simulation_history.len()
);
}
#[test]
fn test_extreme_vertex_counts() {
let min_tri = CdtTriangulation::from_random_points(3, 1, 2)
.expect("Should create triangulation with 3 vertices");
assert_eq!(min_tri.vertex_count(), 3);
let large_tri = CdtTriangulation::from_random_points(50, 1, 2)
.expect("Should create triangulation with 50 vertices");
assert_eq!(large_tri.vertex_count(), 50);
assert!(
large_tri.edge_count() > 50,
"Large triangulation should have many edges"
);
assert!(
large_tri.face_count() > 10,
"Large triangulation should have many faces"
);
}
}
#[cfg(test)]
mod prop_tests {
use super::*;
use crate::geometry::traits::TriangulationQuery;
use proptest::prelude::*;
proptest! {
#[test]
fn triangulation_positive_simplex_counts(
vertices in 3u32..30,
timeslices in 1u32..5
) {
let triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
prop_assert!(triangulation.vertex_count() >= 3, "Must have at least 3 vertices");
prop_assert!(triangulation.edge_count() >= 3, "Must have at least 3 edges");
prop_assert!(triangulation.face_count() >= 1, "Must have at least 1 face");
}
#[test]
fn triangulation_validity_invariant(
vertices in 4u32..15, timeslices in 1u32..3 ) {
let triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
prop_assert!(
triangulation.geometry().is_valid(),
"Basic triangulation should be geometrically valid"
);
}
#[test]
fn cache_consistency(
vertices in 4u32..25,
timeslices in 1u32..4
) {
let mut triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
let count1 = triangulation.edge_count();
let count2 = triangulation.edge_count();
prop_assert_eq!(count1, count2, "Repeated edge counts should be identical");
triangulation.refresh_cache();
let count3 = triangulation.edge_count();
prop_assert_eq!(count1, count3, "Count should remain same after cache refresh");
}
#[test]
fn dimension_consistency(
vertices in 3u32..15
) {
let triangulation = CdtTriangulation::from_random_points(vertices, 2, 2)?;
prop_assert_eq!(triangulation.dimension(), 2, "Dimension should be 2 for 2D triangulation");
}
#[test]
fn vertex_count_scaling(
base_vertices in 5u32..15
) {
let small_tri = CdtTriangulation::from_random_points(base_vertices, 2, 2)?;
let large_tri = CdtTriangulation::from_random_points(base_vertices * 2, 2, 2)?;
let small_count = small_tri.vertex_count();
let large_count = large_tri.vertex_count();
let threshold = small_count.saturating_sub(small_count / 5); prop_assert!(
large_count >= small_count || large_count >= threshold,
"Larger input should produce comparable or more vertices: small={}, large={}, threshold={}",
small_count, large_count, threshold
);
}
#[test]
fn face_edge_relationship(
vertices in 4u32..12, timeslices in 1u32..3
) {
let triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
let v = i32::try_from(triangulation.vertex_count()).unwrap_or(i32::MAX);
let e = i32::try_from(triangulation.edge_count()).unwrap_or(i32::MAX);
let f = i32::try_from(triangulation.face_count()).unwrap_or(i32::MAX);
prop_assert!(v >= 3, "Must have at least 3 vertices");
prop_assert!(e >= 3, "Must have at least 3 edges");
prop_assert!(f >= 1, "Must have at least 1 face");
let euler = v - e + f;
prop_assert!(
(-10..=10).contains(&euler),
"Euler characteristic {} extremely out of range (V={}, E={}, F={})",
euler, v, e, f
);
}
#[test]
fn timeslice_parameter_consistency(
vertices in 4u32..20,
timeslices in 1u32..8
) {
let triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
prop_assert!(triangulation.vertex_count() > 0);
prop_assert!(triangulation.edge_count() > 0);
prop_assert!(triangulation.face_count() > 0);
}
#[test]
fn seeded_determinism_property(
vertices in 4u32..15,
timeslices in 1u32..4,
seed in 1u64..10000
) {
let tri1 = CdtTriangulation::from_seeded_points(vertices, timeslices, 2, seed)?;
let tri2 = CdtTriangulation::from_seeded_points(vertices, timeslices, 2, seed)?;
prop_assert_eq!(tri1.vertex_count(), tri2.vertex_count(), "Vertex counts should match");
prop_assert_eq!(tri1.edge_count(), tri2.edge_count(), "Edge counts should match");
prop_assert_eq!(tri1.face_count(), tri2.face_count(), "Face counts should match");
prop_assert_eq!(tri1.time_slices(), tri2.time_slices(), "Time slices should match");
prop_assert_eq!(tri1.dimension(), tri2.dimension(), "Dimensions should match");
}
#[test]
fn metadata_tracking_property(
vertices in 4u32..15,
timeslices in 1u32..5
) {
let mut triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
prop_assert_eq!(triangulation.time_slices(), timeslices, "Time slices should match input");
prop_assert_eq!(triangulation.dimension(), 2, "Dimension should be 2");
prop_assert_eq!(triangulation.metadata.modification_count, 0, "Initial modification count should be 0");
prop_assert!(!triangulation.metadata.simulation_history.is_empty(), "Should have creation event");
let initial_mod_count = triangulation.metadata.modification_count;
{
let _mut_wrapper = triangulation.geometry_mut();
}
prop_assert_eq!(triangulation.metadata.modification_count, initial_mod_count + 1,
"Modification count should increment after mutation");
}
#[test]
fn cache_invalidation_property(
vertices in 4u32..15,
timeslices in 1u32..4
) {
let mut triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
triangulation.refresh_cache();
let cached_count = triangulation.edge_count();
let cached_count_2 = triangulation.edge_count();
prop_assert_eq!(cached_count, cached_count_2, "Cache hits should be consistent");
{
let _mut_wrapper = triangulation.geometry_mut();
}
let recalculated_count = triangulation.edge_count();
prop_assert_eq!(cached_count, recalculated_count, "Count should remain same after cache invalidation");
}
#[test]
fn validation_success_property(
seed in 50u64..250, vertices in 4u32..8, timeslices in 1u32..3
) {
let triangulation = CdtTriangulation::from_seeded_points(vertices, timeslices, 2, seed)?;
prop_assert!(triangulation.geometry().is_valid(), "Geometry should be valid");
prop_assert!(triangulation.vertex_count() >= 3, "Should have >= 3 vertices");
prop_assert!(triangulation.edge_count() > 0, "Should have > 0 edges");
prop_assert!(triangulation.face_count() > 0, "Should have > 0 faces");
}
#[test]
fn simulation_event_recording_property(
vertices in 4u32..12,
timeslices in 1u32..4
) {
let mut triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
let initial_history_len = triangulation.metadata.simulation_history.len();
prop_assert!(initial_history_len >= 1, "Should have at least creation event");
{
let mut wrapper = triangulation.geometry_mut();
wrapper.record_event(SimulationEvent::MoveAttempted {
move_type: "test".to_string(),
step: 1,
});
wrapper.record_event(SimulationEvent::MeasurementTaken {
step: 2,
action: 5.0,
});
}
prop_assert_eq!(triangulation.metadata.simulation_history.len(), initial_history_len + 2,
"Should have 2 additional events after recording");
}
#[test]
fn geometric_invariants_property(
vertices in 4u32..15,
timeslices in 1u32..4,
seed in 1u64..10000
) {
let triangulation = CdtTriangulation::from_seeded_points(vertices, timeslices, 2, seed)?;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let v = triangulation.vertex_count() as i32;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let e = triangulation.edge_count() as i32;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let f = triangulation.face_count() as i32;
prop_assert!(v >= 3, "Must have at least 3 vertices");
prop_assert!(e >= 3, "Must have at least 3 edges");
prop_assert!(f >= 1, "Must have at least 1 face");
prop_assert!(e <= 4 * v, "Edge count should not be excessively large: E={}, 4V={}", e, 4 * v);
prop_assert!(f <= 2 * v, "Face count should not be excessively large: F={}, 2V={}", f, 2 * v);
prop_assert!(e >= (v - 1) / 2, "Should have reasonable edge count for degenerate triangulations: E={}, (V-1)/2={}", e, (v - 1) / 2);
}
#[test]
fn parameter_bounds_property(
vertices in 0u32..30, timeslices in 0u32..6
) {
let result = CdtTriangulation::from_random_points(vertices, timeslices, 2);
if vertices >= 3 {
prop_assert!(result.is_ok(), "Should succeed with valid vertex count: {}", vertices);
let triangulation = result.unwrap();
#[allow(clippy::cast_possible_truncation)]
let vertex_count_u32 = triangulation.vertex_count() as u32;
prop_assert_eq!(vertex_count_u32, vertices, "Vertex count should match input");
prop_assert_eq!(triangulation.time_slices(), timeslices, "Time slices should match input");
prop_assert_eq!(triangulation.dimension(), 2, "Dimension should be 2");
} else {
prop_assert!(result.is_err(), "Should fail with invalid vertex count: {}", vertices);
}
}
#[test]
fn access_method_consistency_property(
vertices in 4u32..15,
timeslices in 1u32..4
) {
let triangulation = CdtTriangulation::from_random_points(vertices, timeslices, 2)?;
let direct_vertex_count = triangulation.vertex_count();
let geometry_vertex_count = triangulation.geometry().vertex_count();
prop_assert_eq!(direct_vertex_count, geometry_vertex_count, "Vertex count access should be consistent");
let direct_face_count = triangulation.face_count();
let geometry_face_count = triangulation.geometry().face_count();
prop_assert_eq!(direct_face_count, geometry_face_count, "Face count access should be consistent");
let direct_edge_count = triangulation.edge_count();
let geometry_edge_count = triangulation.geometry().edge_count();
prop_assert_eq!(direct_edge_count, geometry_edge_count, "Edge count access should be consistent");
prop_assert_eq!(triangulation.dimension() as usize, triangulation.geometry().dimension(),
"Dimension should be consistent between wrapper and geometry");
}
}
}