#![cfg_attr(not(feature = "slow-tests"), allow(dead_code))]
#![forbid(unsafe_code)]
use delaunay::geometry::kernel::{ExactPredicates, Kernel, RobustKernel};
use delaunay::geometry::util::{
generate_random_points_in_ball_seeded, generate_random_points_seeded, safe_usize_to_scalar,
};
use delaunay::prelude::construction::{
ConstructionOptions, ConstructionStatistics, DelaunayRepairPolicy, DelaunayTriangulation,
DelaunayTriangulationConstructionErrorWithStatistics, InitialSimplexStrategy,
TopologyGuarantee, Vertex, vertex,
};
use delaunay::prelude::diagnostics::ConstructionTelemetry;
#[cfg(feature = "diagnostics")]
use delaunay::prelude::insertion::InsertionResult;
use delaunay::prelude::insertion::{InsertionOutcome, InsertionStatistics};
use delaunay::prelude::repair::{DelaunayCheckPolicy, DelaunayRepairHeuristicConfig};
use delaunay::prelude::tds::{InvariantKind, TriangulationValidationReport};
use delaunay::prelude::validation::ValidationCadence;
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
use std::env;
use std::fmt;
use std::io::{self, Write};
use std::num::NonZeroUsize;
use std::process;
use std::sync::{
Once,
mpsc::{self, RecvTimeoutError, SyncSender},
};
use std::thread;
use std::time::{Duration, Instant};
fn write_timeout_abort_message<W: Write>(mut writer: W, max_secs: u64) -> io::Result<()> {
writeln!(
writer,
"=== TIMEOUT: wall time exceeded {max_secs} seconds — aborting ==="
)?;
writer.flush()
}
fn install_runtime_cap(max_secs: u64) -> SyncSender<()> {
let (tx, rx) = mpsc::sync_channel::<()>(0);
thread::spawn(move || {
match rx.recv_timeout(Duration::from_secs(max_secs)) {
Ok(()) | Err(RecvTimeoutError::Disconnected) => {}
Err(RecvTimeoutError::Timeout) => {
if let Err(err) = write_timeout_abort_message(io::stderr().lock(), max_secs) {
tracing::warn!(?err, "failed to flush timeout message before abort");
}
process::abort();
}
}
});
tx
}
#[derive(Debug, Clone)]
struct SkipSample<const D: usize> {
index: usize,
uuid: uuid::Uuid,
coords: Option<[f64; D]>,
attempts: usize,
error: String,
}
#[derive(Debug, Clone)]
#[cfg(feature = "diagnostics")]
struct SlowInsertionSample {
index: usize,
uuid: uuid::Uuid,
attempts: usize,
result: InsertionResult,
elapsed_nanos: u64,
simplices_after: usize,
locate_calls: usize,
locate_walk_steps_total: usize,
conflict_region_calls: usize,
conflict_region_simplices_total: usize,
cavity_insertion_calls: usize,
global_conflict_scans: usize,
hull_extension_calls: usize,
topology_validation_calls: usize,
}
#[derive(Debug, Default, Clone)]
struct InsertionSummary<const D: usize> {
inserted: usize,
skipped_duplicate: usize,
skipped_degeneracy: usize,
total_attempts: usize,
max_attempts: usize,
attempts_histogram: Vec<usize>,
used_perturbation: usize,
simplices_removed_total: usize,
simplices_removed_max: usize,
telemetry: ConstructionTelemetry,
#[cfg(feature = "diagnostics")]
slow_insertions: Vec<SlowInsertionSample>,
skip_samples: Vec<SkipSample<D>>,
}
impl<const D: usize> InsertionSummary<D> {
fn record(&mut self, stats: InsertionStatistics) {
self.total_attempts = self.total_attempts.saturating_add(stats.attempts);
self.max_attempts = self.max_attempts.max(stats.attempts);
if self.attempts_histogram.len() <= stats.attempts {
self.attempts_histogram.resize(stats.attempts + 1, 0);
}
self.attempts_histogram[stats.attempts] =
self.attempts_histogram[stats.attempts].saturating_add(1);
if stats.used_perturbation() {
self.used_perturbation = self.used_perturbation.saturating_add(1);
}
self.simplices_removed_total = self
.simplices_removed_total
.saturating_add(stats.simplices_removed_during_repair);
self.simplices_removed_max = self
.simplices_removed_max
.max(stats.simplices_removed_during_repair);
}
fn record_inserted(&mut self, stats: InsertionStatistics) {
self.inserted = self.inserted.saturating_add(1);
self.record(stats);
}
fn record_skipped(&mut self, sample: SkipSample<D>, stats: InsertionStatistics) {
if stats.skipped_duplicate() {
self.skipped_duplicate = self.skipped_duplicate.saturating_add(1);
} else {
self.skipped_degeneracy = self.skipped_degeneracy.saturating_add(1);
}
if self.skip_samples.len() < 8 {
self.skip_samples.push(sample);
}
self.record(stats);
}
const fn total_skipped(&self) -> usize {
self.skipped_duplicate + self.skipped_degeneracy
}
}
impl<const D: usize> From<ConstructionStatistics> for InsertionSummary<D> {
fn from(stats: ConstructionStatistics) -> Self {
#[cfg(feature = "diagnostics")]
let slow_insertions = stats
.slow_insertions
.into_iter()
.map(|sample| SlowInsertionSample {
index: sample.index,
uuid: sample.uuid,
attempts: sample.attempts,
result: sample.result,
elapsed_nanos: sample.elapsed_nanos,
simplices_after: sample.simplices_after,
locate_calls: sample.locate_calls,
locate_walk_steps_total: sample.locate_walk_steps_total,
conflict_region_calls: sample.conflict_region_calls,
conflict_region_simplices_total: sample.conflict_region_simplices_total,
cavity_insertion_calls: sample.cavity_insertion_calls,
global_conflict_scans: sample.global_conflict_scans,
hull_extension_calls: sample.hull_extension_calls,
topology_validation_calls: sample.topology_validation_calls,
})
.collect();
let skip_samples: Vec<SkipSample<D>> = stats
.skip_samples
.into_iter()
.map(|s| {
let coords = if s.coords_available {
s.coords.as_slice().try_into().map_or_else(
|_| {
tracing::warn!(
index = s.index,
uuid = %s.uuid,
coords_len = s.coords.len(),
expected_dim = D,
"preserving skip sample without coordinates due to coordinate dimension mismatch"
);
None
},
Some,
)
} else {
None
};
SkipSample {
index: s.index,
uuid: s.uuid,
coords,
attempts: s.attempts,
error: s.error,
}
})
.collect();
Self {
inserted: stats.inserted,
skipped_duplicate: stats.skipped_duplicate,
skipped_degeneracy: stats.skipped_degeneracy,
total_attempts: stats.total_attempts,
max_attempts: stats.max_attempts,
attempts_histogram: stats.attempts_histogram,
used_perturbation: stats.used_perturbation,
simplices_removed_total: stats.simplices_removed_total,
simplices_removed_max: stats.simplices_removed_max,
telemetry: stats.telemetry,
#[cfg(feature = "diagnostics")]
slow_insertions,
skip_samples,
}
}
}
fn parse_u64(s: &str) -> Option<u64> {
let s = s.trim();
s.strip_prefix("0x")
.or_else(|| s.strip_prefix("0X"))
.map_or_else(|| s.parse().ok(), |hex| u64::from_str_radix(hex, 16).ok())
}
fn env_u64(name: &str) -> Option<u64> {
env::var(name).ok().and_then(|v| parse_u64(&v))
}
fn env_usize(name: &str) -> Option<usize> {
env::var(name).ok().and_then(|v| {
let trimmed = v.trim();
trimmed.parse().ok().or_else(|| {
trimmed
.split_once('=')
.and_then(|(_, rhs)| rhs.trim().parse().ok())
})
})
}
fn bulk_progress_every_from_env() -> Option<usize> {
env_usize("DELAUNAY_BULK_PROGRESS_EVERY")
.or_else(|| env_usize("DELAUNAY_LARGE_DEBUG_PROGRESS_EVERY"))
.filter(|every| *every > 0)
}
fn env_flag(name: &str) -> bool {
env::var(name).ok().is_some_and(|v| {
let v = v.trim();
!v.is_empty() && v != "0" && v != "false"
})
}
fn init_tracing() {
static INIT: Once = Once::new();
INIT.call_once(|| {
let debug_env_vars = [
"DELAUNAY_INSERT_TRACE",
"DELAUNAY_DEBUG_RETRYABLE_SKIP",
"DELAUNAY_DEBUG_CAVITY_REDUCTION_ONCE",
"DELAUNAY_DEBUG_RIDGE_FAN_ONCE",
"DELAUNAY_REPAIR_DEBUG_POSTCONDITION_FACET",
"DELAUNAY_REPAIR_DEBUG_RIDGE_MIN_MULTIPLICITY",
"DELAUNAY_BULK_PROGRESS_EVERY",
"DELAUNAY_LARGE_DEBUG_PROGRESS_EVERY",
"DELAUNAY_BATCH_REPAIR_TRACE",
"DELAUNAY_DEBUG_SHUFFLE",
];
let default_filter = if debug_env_vars
.iter()
.any(|name| env::var_os(name).is_some())
{
"debug"
} else {
"warn"
};
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(default_filter));
let _ = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_test_writer()
.try_init();
});
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PointDistribution {
Ball,
Box,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ConstructionMode {
New,
Incremental,
}
impl ConstructionMode {
const fn name(self) -> &'static str {
match self {
Self::New => "new",
Self::Incremental => "incremental",
}
}
}
fn initial_simplex_strategy_name(strategy: InitialSimplexStrategy) -> &'static str {
match strategy {
InitialSimplexStrategy::First => "first",
InitialSimplexStrategy::Balanced => "balanced",
InitialSimplexStrategy::MaxVolume => "max-volume",
_ => {
tracing::debug!(?strategy, "unknown initial simplex strategy");
"unknown"
}
}
}
fn initial_simplex_strategy_from_name(raw: &str) -> Option<InitialSimplexStrategy> {
let raw = raw.trim();
if raw.is_empty() {
return Some(InitialSimplexStrategy::MaxVolume);
}
if raw.eq_ignore_ascii_case("first") {
return Some(InitialSimplexStrategy::First);
}
if raw.eq_ignore_ascii_case("balanced") {
return Some(InitialSimplexStrategy::Balanced);
}
if raw.eq_ignore_ascii_case("max-volume")
|| raw.eq_ignore_ascii_case("max_volume")
|| raw.eq_ignore_ascii_case("maxvolume")
{
return Some(InitialSimplexStrategy::MaxVolume);
}
None
}
fn initial_simplex_strategy_from_env() -> InitialSimplexStrategy {
let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_INITIAL_SIMPLEX") else {
return InitialSimplexStrategy::MaxVolume;
};
initial_simplex_strategy_from_name(&raw).unwrap_or_else(|| {
panic!(
"invalid DELAUNAY_LARGE_DEBUG_INITIAL_SIMPLEX={raw:?} (expected 'max-volume', 'balanced', or 'first')"
)
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DebugMode {
Cadenced,
Strict,
}
impl DebugMode {
const fn name(self) -> &'static str {
match self {
Self::Cadenced => "cadenced",
Self::Strict => "strict",
}
}
}
const fn topology_for_debug_mode(debug_mode: DebugMode) -> TopologyGuarantee {
match debug_mode {
DebugMode::Cadenced => TopologyGuarantee::PLManifold,
DebugMode::Strict => TopologyGuarantee::PLManifoldStrict,
}
}
const fn repair_policy_from_repair_every(repair_every: usize) -> DelaunayRepairPolicy {
match NonZeroUsize::new(repair_every) {
None => DelaunayRepairPolicy::Never,
Some(n) if n.get() == 1 => DelaunayRepairPolicy::EveryInsertion,
Some(n) => DelaunayRepairPolicy::EveryN(n),
}
}
impl PointDistribution {
const fn name(self) -> &'static str {
match self {
Self::Ball => "ball",
Self::Box => "box",
}
}
}
fn max_skip_pct_from_env() -> f64 {
let max_skip_pct = env_f64("DELAUNAY_LARGE_DEBUG_MAX_SKIP_PCT").unwrap_or(5.0);
assert!(
max_skip_pct.is_finite() && max_skip_pct >= 0.0,
"invalid DELAUNAY_LARGE_DEBUG_MAX_SKIP_PCT={max_skip_pct:?} (expected finite non-negative percentage)"
);
max_skip_pct
}
fn skip_percentage(skipped: usize, total: usize) -> f64 {
if total == 0 {
return 0.0;
}
let skipped = safe_usize_to_scalar::<f64>(skipped)
.expect("skipped-vertex count should fit in f64 for debug reporting");
let total = safe_usize_to_scalar::<f64>(total)
.expect("point count should fit in f64 for debug reporting");
(skipped / total) * 100.0
}
fn env_f64(name: &str) -> Option<f64> {
let Ok(raw) = env::var(name) else {
return None;
};
let raw = raw.trim();
if raw.is_empty() {
return None;
}
Some(
raw.parse()
.unwrap_or_else(|e| panic!("invalid {name}={raw:?}: {e}")),
)
}
fn point_distribution_from_env() -> PointDistribution {
let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_DISTRIBUTION") else {
return PointDistribution::Ball;
};
let raw = raw.trim();
if raw.is_empty() || raw.eq_ignore_ascii_case("ball") {
return PointDistribution::Ball;
}
if raw.eq_ignore_ascii_case("box") {
return PointDistribution::Box;
}
panic!("invalid DELAUNAY_LARGE_DEBUG_DISTRIBUTION={raw:?} (expected 'ball' or 'box')");
}
fn construction_mode_from_env() -> ConstructionMode {
let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_CONSTRUCTION_MODE") else {
return ConstructionMode::New;
};
let raw = raw.trim();
if raw.is_empty() || raw.eq_ignore_ascii_case("new") {
return ConstructionMode::New;
}
if raw.eq_ignore_ascii_case("incremental") {
return ConstructionMode::Incremental;
}
panic!(
"invalid DELAUNAY_LARGE_DEBUG_CONSTRUCTION_MODE={raw:?} (expected 'new' or 'incremental')"
);
}
fn debug_mode_from_env() -> DebugMode {
let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_DEBUG_MODE") else {
return DebugMode::Cadenced;
};
let raw = raw.trim();
if raw.is_empty() || raw.eq_ignore_ascii_case("cadenced") {
return DebugMode::Cadenced;
}
if raw.eq_ignore_ascii_case("strict") {
return DebugMode::Strict;
}
panic!("invalid DELAUNAY_LARGE_DEBUG_DEBUG_MODE={raw:?} (expected 'cadenced' or 'strict')");
}
fn seed_for_case<const D: usize>(base_seed: u64, n_points: usize) -> u64 {
const SEED_SALT: u64 = 0x9E37_79B9_7F4A_7C15;
base_seed
.wrapping_add((n_points as u64).wrapping_mul(SEED_SALT))
.wrapping_add((D as u64).wrapping_mul(SEED_SALT.rotate_left(17)))
}
#[derive(Debug, Clone)]
enum DebugOutcome {
Success,
ConstructionFailure {
error: String,
},
SkippedVertices {
skipped: usize,
total: usize,
skip_pct: f64,
max_skip_pct: f64,
},
RepairNonConvergence {
error: String,
},
ValidationFailure {
kind: InvariantKind,
details: String,
},
}
impl fmt::Display for DebugOutcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Success => write!(f, "Success"),
Self::ConstructionFailure { error } => {
write!(f, "ConstructionFailure | {error}")
}
Self::SkippedVertices {
skipped,
total,
skip_pct,
max_skip_pct,
} => {
write!(
f,
"SkippedVertices | {skipped}/{total} skipped ({skip_pct:.2}%, max {max_skip_pct:.2}%)"
)
}
Self::RepairNonConvergence { error } => {
write!(f, "RepairNonConvergence | {error}")
}
Self::ValidationFailure { kind, details } => {
write!(f, "ValidationFailure({kind:?}) | {details}")
}
}
}
}
fn print_abort_summary<const D: usize>(
outcome: &DebugOutcome,
seed: u64,
n_points: usize,
phase: &str,
) {
println!();
println!("OUTCOME: {outcome}");
println!("Phase: {phase}");
println!();
println!("Replay command:");
println!(
" DELAUNAY_LARGE_DEBUG_N_{D}D={n_points} DELAUNAY_LARGE_DEBUG_CASE_SEED_{D}D=0x{seed:X} DELAUNAY_LARGE_DEBUG_ALLOW_SKIPS=1 cargo test --release --features slow-tests --test large_scale_debug debug_large_scale_{D}d -- --nocapture"
);
}
fn classify_validation_report(report: &TriangulationValidationReport) -> DebugOutcome {
let first = report.violations.first();
let (kind, details) = first.map_or_else(
|| {
(
InvariantKind::Topology,
"no violations captured".to_string(),
)
},
|violation| (violation.kind, format!("{}", violation.error)),
);
DebugOutcome::ValidationFailure { kind, details }
}
fn print_validation_report(report: &TriangulationValidationReport) {
println!(
"Validation report: {} violation(s)",
report.violations.len()
);
for (idx, violation) in report.violations.iter().enumerate() {
println!(" {}. {:?}: {}", idx + 1, violation.kind, violation.error);
}
}
#[cfg(feature = "diagnostics")]
fn usize_to_u128(value: usize) -> u128 {
u128::try_from(value).expect("usize should always fit in u128")
}
#[cfg(feature = "diagnostics")]
fn format_ratio_2(numerator: usize, denominator: usize) -> String {
format_ratio_2_u128(usize_to_u128(numerator), usize_to_u128(denominator))
}
#[cfg(feature = "diagnostics")]
fn format_ratio_2_u128(numerator: u128, denominator: u128) -> String {
if denominator == 0 {
return "0.00".to_string();
}
let scaled = numerator.saturating_mul(100) / denominator;
format!("{}.{:02}", scaled / 100, scaled % 100)
}
#[cfg(feature = "diagnostics")]
fn format_nanos_as_ms(nanos: u64) -> String {
let micros = u128::from(nanos) / 1_000;
format!("{}.{:03}", micros / 1_000, micros % 1_000)
}
#[cfg(feature = "diagnostics")]
fn format_avg_nanos_as_ms(total_nanos: u64, count: usize) -> String {
if count == 0 {
return "0.000".to_string();
}
let count = u64::try_from(count).expect("sample count should fit in u64 for debug reporting");
format_nanos_as_ms(total_nanos / count)
}
#[cfg(feature = "diagnostics")]
fn print_timing_summary(label: &str, calls: usize, total_nanos: u64, max_nanos: u64) {
if calls == 0 {
return;
}
println!(
" {label}: calls={calls} total_ms={} avg_ms={} max_ms={}",
format_nanos_as_ms(total_nanos),
format_avg_nanos_as_ms(total_nanos, calls),
format_nanos_as_ms(max_nanos)
);
}
#[cfg(feature = "diagnostics")]
fn print_repair_seed_accumulation_telemetry(telemetry: &ConstructionTelemetry) {
if telemetry.repair_seed_accumulation_calls == 0 {
return;
}
println!(
" repair_seed_accumulation: calls={} simplices_added_total={} avg_simplices_added={} max_simplices_added={} total_ms={} avg_ms={} max_ms={}",
telemetry.repair_seed_accumulation_calls,
telemetry.repair_seed_simplices_added_total,
format_ratio_2(
telemetry.repair_seed_simplices_added_total,
telemetry.repair_seed_accumulation_calls,
),
telemetry.repair_seed_simplices_added_max,
format_nanos_as_ms(telemetry.repair_seed_accumulation_nanos),
format_avg_nanos_as_ms(
telemetry.repair_seed_accumulation_nanos,
telemetry.repair_seed_accumulation_calls,
),
format_nanos_as_ms(telemetry.repair_seed_accumulation_nanos_max)
);
}
#[cfg(feature = "diagnostics")]
fn print_local_repair_frontier_telemetry(telemetry: &ConstructionTelemetry) {
if telemetry.local_repair_calls == 0 {
return;
}
println!(
" local_repair_frontiers: seed_simplices_total={} avg_seed_simplices={} max_seed_simplices={} cadence_triggers={} backlog_triggers={}",
telemetry.local_repair_seed_simplices_total,
format_ratio_2(
telemetry.local_repair_seed_simplices_total,
telemetry.local_repair_calls,
),
telemetry.local_repair_seed_simplices_max,
telemetry.local_repair_cadence_triggers,
telemetry.local_repair_backlog_triggers
);
}
#[cfg(feature = "diagnostics")]
fn print_local_repair_work_telemetry(telemetry: &ConstructionTelemetry) {
if telemetry.local_repair_calls == 0 {
return;
}
println!(
" local_repair_work: checked_total={} avg_checked={} flips_total={} avg_flips={} max_flips={} max_queue={} no_flip_calls={}",
telemetry.local_repair_items_checked_total,
format_ratio_2(
telemetry.local_repair_items_checked_total,
telemetry.local_repair_calls,
),
telemetry.local_repair_flips_total,
format_ratio_2(
telemetry.local_repair_flips_total,
telemetry.local_repair_calls
),
telemetry.local_repair_flips_max,
telemetry.local_repair_queue_len_max,
telemetry.local_repair_no_flip_calls
);
}
#[cfg(feature = "diagnostics")]
fn print_local_repair_slow_samples(telemetry: &ConstructionTelemetry) {
if telemetry.local_repair_slow_samples.is_empty() {
return;
}
println!(
" local_repair_slow_samples (top {}):",
telemetry.local_repair_slow_samples.len()
);
for sample in &telemetry.local_repair_slow_samples {
println!(
" idx={} trigger={:?} seed_simplices={} elapsed_ms={} checked={} flips={} max_queue={} facet_ms={} ridge_ms={} postcondition_ms={}",
sample.index,
sample.trigger,
sample.seed_simplices,
format_nanos_as_ms(sample.elapsed_nanos),
sample.items_checked,
sample.flips_performed,
sample.max_queue_len,
format_nanos_as_ms(sample.facet_nanos),
format_nanos_as_ms(sample.ridge_nanos),
format_nanos_as_ms(sample.postcondition_nanos)
);
}
}
#[cfg(feature = "diagnostics")]
fn print_local_repair_phase_telemetry(telemetry: &ConstructionTelemetry) {
let phase_total = telemetry
.local_repair_snapshot_nanos
.saturating_add(telemetry.local_repair_attempt_nanos)
.saturating_add(telemetry.local_repair_postcondition_nanos)
.saturating_add(telemetry.local_repair_restore_nanos);
if phase_total == 0 {
return;
}
print_timing_summary(
"local_repair_snapshot",
telemetry.local_repair_calls,
telemetry.local_repair_snapshot_nanos,
telemetry.local_repair_snapshot_nanos_max,
);
print_timing_summary(
"local_repair_attempts",
telemetry.local_repair_calls,
telemetry.local_repair_attempt_nanos,
telemetry.local_repair_attempt_nanos_max,
);
print_timing_summary(
"local_repair_attempt_seed",
telemetry.local_repair_calls,
telemetry.local_repair_attempt_seed_nanos,
telemetry.local_repair_attempt_seed_nanos_max,
);
print_timing_summary(
"local_repair_attempt_facets",
telemetry.local_repair_calls,
telemetry.local_repair_attempt_facet_nanos,
telemetry.local_repair_attempt_facet_nanos_max,
);
print_timing_summary(
"local_repair_attempt_ridges",
telemetry.local_repair_calls,
telemetry.local_repair_attempt_ridge_nanos,
telemetry.local_repair_attempt_ridge_nanos_max,
);
print_timing_summary(
"local_repair_attempt_edges",
telemetry.local_repair_calls,
telemetry.local_repair_attempt_edge_nanos,
telemetry.local_repair_attempt_edge_nanos_max,
);
print_timing_summary(
"local_repair_attempt_triangles",
telemetry.local_repair_calls,
telemetry.local_repair_attempt_triangle_nanos,
telemetry.local_repair_attempt_triangle_nanos_max,
);
print_timing_summary(
"local_repair_postconditions",
telemetry.local_repair_calls,
telemetry.local_repair_postcondition_nanos,
telemetry.local_repair_postcondition_nanos_max,
);
print_timing_summary(
"local_repair_restores",
telemetry.local_repair_calls,
telemetry.local_repair_restore_nanos,
telemetry.local_repair_restore_nanos_max,
);
}
#[cfg(feature = "diagnostics")]
fn print_construction_phase_telemetry(telemetry: &ConstructionTelemetry) {
let outer_total_nanos = telemetry
.construction_preprocessing_nanos
.saturating_add(telemetry.construction_insert_loop_nanos)
.saturating_add(telemetry.construction_finalize_nanos)
.saturating_add(telemetry.construction_final_delaunay_validation_nanos);
if outer_total_nanos == 0 {
return;
}
println!(
" construction_phases: preprocessing_ms={} insert_loop_ms={} finalize_ms={} final_delaunay_validation_ms={} outer_total_ms={}",
format_nanos_as_ms(telemetry.construction_preprocessing_nanos),
format_nanos_as_ms(telemetry.construction_insert_loop_nanos),
format_nanos_as_ms(telemetry.construction_finalize_nanos),
format_nanos_as_ms(telemetry.construction_final_delaunay_validation_nanos),
format_nanos_as_ms(outer_total_nanos)
);
let finalize_breakdown_nanos = telemetry
.construction_completion_repair_nanos
.saturating_add(telemetry.construction_orientation_nanos)
.saturating_add(telemetry.construction_topology_validation_nanos);
if finalize_breakdown_nanos == 0 {
return;
}
println!(
" finalize_breakdown: completion_repair_ms={} orientation_ms={} topology_validation_ms={}",
format_nanos_as_ms(telemetry.construction_completion_repair_nanos),
format_nanos_as_ms(telemetry.construction_orientation_nanos),
format_nanos_as_ms(telemetry.construction_topology_validation_nanos)
);
}
#[cfg_attr(
not(feature = "diagnostics"),
expect(
clippy::missing_const_for_fn,
reason = "the diagnostics build of this helper emits detailed output"
)
)]
fn print_construction_telemetry(telemetry: &ConstructionTelemetry) {
#[cfg(not(feature = "diagnostics"))]
let _ = telemetry.has_data();
#[cfg(feature = "diagnostics")]
if telemetry.has_data() {
println!();
println!(" insertion telemetry:");
print_construction_phase_telemetry(telemetry);
print_timing_summary(
"insertion_wall",
telemetry.insertion_wall_time_calls,
telemetry.insertion_wall_time_nanos,
telemetry.insertion_wall_time_nanos_max,
);
println!(
" locate: calls={} walk_steps_total={} avg_walk={} max_walk={} hint_uses={} scan_fallbacks={}",
telemetry.locate_calls,
telemetry.locate_walk_steps_total,
format_ratio_2(telemetry.locate_walk_steps_total, telemetry.locate_calls),
telemetry.locate_walk_steps_max,
telemetry.locate_hint_uses,
telemetry.locate_scan_fallbacks
);
println!(
" locate_results: inside={} outside={} boundary={}",
telemetry.located_inside, telemetry.located_outside, telemetry.located_on_boundary
);
if telemetry.conflict_region_calls > 0 {
println!(
" conflict_regions: calls={} simplices_total={} avg_simplices={} max_simplices={} total_ms={} avg_ms={} max_ms={}",
telemetry.conflict_region_calls,
telemetry.conflict_region_simplices_total,
format_ratio_2(
telemetry.conflict_region_simplices_total,
telemetry.conflict_region_calls,
),
telemetry.conflict_region_simplices_max,
format_nanos_as_ms(telemetry.conflict_region_nanos),
format_avg_nanos_as_ms(
telemetry.conflict_region_nanos,
telemetry.conflict_region_calls,
),
format_nanos_as_ms(telemetry.conflict_region_nanos_max)
);
}
print_timing_summary(
"cavity_insertions",
telemetry.cavity_insertion_calls,
telemetry.cavity_insertion_nanos,
telemetry.cavity_insertion_nanos_max,
);
print_timing_summary(
"hull_extensions",
telemetry.hull_extension_calls,
telemetry.hull_extension_nanos,
telemetry.hull_extension_nanos_max,
);
print_timing_summary(
"topology_validations",
telemetry.topology_validation_calls,
telemetry.topology_validation_nanos,
telemetry.topology_validation_nanos_max,
);
print_timing_summary(
"local_repairs",
telemetry.local_repair_calls,
telemetry.local_repair_nanos,
telemetry.local_repair_nanos_max,
);
print_local_repair_phase_telemetry(telemetry);
print_local_repair_frontier_telemetry(telemetry);
print_local_repair_work_telemetry(telemetry);
print_local_repair_slow_samples(telemetry);
print_repair_seed_accumulation_telemetry(telemetry);
if telemetry.global_conflict_scans > 0 {
let scans = u64::try_from(telemetry.global_conflict_scans)
.expect("scan count should fit in u64 for debug reporting");
println!(
" global_conflict_scans: scans={} simplices_scanned_total={} avg_simplices_scanned={} simplices_found_total={} avg_simplices_found={} max_simplices_found={} total_ms={} avg_ms={}",
telemetry.global_conflict_scans,
telemetry.global_conflict_simplices_scanned,
format_ratio_2(
telemetry.global_conflict_simplices_scanned,
telemetry.global_conflict_scans,
),
telemetry.global_conflict_simplices_found_total,
format_ratio_2(
telemetry.global_conflict_simplices_found_total,
telemetry.global_conflict_scans,
),
telemetry.global_conflict_simplices_found_max,
format_nanos_as_ms(telemetry.global_conflict_scan_nanos),
format_nanos_as_ms(telemetry.global_conflict_scan_nanos / scans)
);
}
}
}
fn print_insertion_summary<const D: usize>(
summary: &InsertionSummary<D>,
elapsed: Duration,
include_batch_diagnostics: bool,
) {
println!("Insertion summary:");
println!(" inserted: {}", summary.inserted);
println!(" skipped_duplicate: {}", summary.skipped_duplicate);
println!(" skipped_degeneracy:{}", summary.skipped_degeneracy);
println!(" total_skipped: {}", summary.total_skipped());
println!();
println!(" total_attempts: {}", summary.total_attempts);
println!(" max_attempts: {}", summary.max_attempts);
println!(" used_perturbation: {}", summary.used_perturbation);
println!(
" simplices_removed_during_repair: total={}, max={} (insertion safety-net / repair bookkeeping)",
summary.simplices_removed_total, summary.simplices_removed_max
);
if !summary.attempts_histogram.is_empty() {
println!(" attempts_histogram (attempts -> count):");
for (attempts, count) in summary.attempts_histogram.iter().enumerate().skip(1) {
if *count > 0 {
println!(" {attempts} -> {count}");
}
}
}
if include_batch_diagnostics {
print_construction_telemetry(&summary.telemetry);
}
#[cfg(feature = "diagnostics")]
if include_batch_diagnostics && !summary.slow_insertions.is_empty() {
println!();
println!(
" slow_insertions (top {} by transactional insertion wall time):",
summary.slow_insertions.len()
);
for s in &summary.slow_insertions {
println!(
" idx={} uuid={} attempts={} result={:?} elapsed_ms={} simplices_after={} locate_calls={} walk_steps={} conflict_calls={} conflict_simplices={} cavity_calls={} global_scans={} hull_calls={} validation_calls={}",
s.index,
s.uuid,
s.attempts,
s.result,
format_nanos_as_ms(s.elapsed_nanos),
s.simplices_after,
s.locate_calls,
s.locate_walk_steps_total,
s.conflict_region_calls,
s.conflict_region_simplices_total,
s.cavity_insertion_calls,
s.global_conflict_scans,
s.hull_extension_calls,
s.topology_validation_calls
);
}
}
if !summary.skip_samples.is_empty() {
println!();
println!(" skip_samples (first {}):", summary.skip_samples.len());
for s in &summary.skip_samples {
let coords = s.coords.as_ref().map_or_else(
|| "<unavailable>".to_string(),
|coords| format!("{coords:?}"),
);
println!(
" idx={} uuid={} attempts={} coords={} error={}",
s.index, s.uuid, s.attempts, coords, s.error
);
}
}
println!();
println!("Insertion wall time: {elapsed:?}");
}
#[expect(
clippy::too_many_lines,
reason = "Intentional debug harness; kept as a single flow for manual inspection"
)]
fn debug_large_case<const D: usize>(dimension_name: &str, default_n_points: usize) -> DebugOutcome
where
RobustKernel<f64>: ExactPredicates<D> + Kernel<D, Scalar = f64>,
{
init_tracing();
let max_runtime_secs = env_usize("DELAUNAY_LARGE_DEBUG_MAX_RUNTIME_SECS").unwrap_or(600);
let _watchdog = (max_runtime_secs > 0).then(|| install_runtime_cap(max_runtime_secs as u64));
let base_seed = env_u64("DELAUNAY_LARGE_DEBUG_SEED").unwrap_or(42);
let n_points = env_usize(&format!("DELAUNAY_LARGE_DEBUG_N_{D}D"))
.or_else(|| env_usize("DELAUNAY_LARGE_DEBUG_N"))
.unwrap_or(default_n_points)
.max(D + 1);
let seed = env_u64(&format!("DELAUNAY_LARGE_DEBUG_CASE_SEED_{D}D"))
.or_else(|| env_u64("DELAUNAY_LARGE_DEBUG_CASE_SEED"))
.unwrap_or_else(|| seed_for_case::<D>(base_seed, n_points));
let distribution = point_distribution_from_env();
let ball_radius = env_f64("DELAUNAY_LARGE_DEBUG_BALL_RADIUS").unwrap_or(100.0);
let box_half_width = env_f64("DELAUNAY_LARGE_DEBUG_BOX_HALF_WIDTH").unwrap_or(100.0);
let mode = construction_mode_from_env();
let initial_simplex_strategy = initial_simplex_strategy_from_env();
let debug_mode = debug_mode_from_env();
let topology_guarantee = topology_for_debug_mode(debug_mode);
let shuffle_seed = env_u64("DELAUNAY_LARGE_DEBUG_SHUFFLE_SEED");
let progress_every = env_usize("DELAUNAY_LARGE_DEBUG_PROGRESS_EVERY")
.unwrap_or(1000)
.max(1);
let bulk_progress_every = bulk_progress_every_from_env();
let allow_skips = env_flag("DELAUNAY_LARGE_DEBUG_ALLOW_SKIPS");
let max_skip_pct = max_skip_pct_from_env();
let skip_final_repair = env_flag("DELAUNAY_LARGE_DEBUG_SKIP_FINAL_REPAIR");
let repair_every = env_usize("DELAUNAY_LARGE_DEBUG_REPAIR_EVERY").unwrap_or(1);
let repair_policy = repair_policy_from_repair_every(repair_every);
let repair_max_flips = env_usize("DELAUNAY_LARGE_DEBUG_REPAIR_MAX_FLIPS");
let validate_every = env_usize("DELAUNAY_LARGE_DEBUG_VALIDATE_EVERY").or_else(|| {
if matches!(debug_mode, DebugMode::Cadenced) {
(repair_every != 0).then_some(repair_every)
} else {
None
}
});
let validation_cadence = ValidationCadence::from_optional_every(validate_every);
println!("=============================================");
println!("Large-scale triangulation debug: {dimension_name}");
println!("=============================================");
println!("Config:");
println!(" D: {D}");
println!(" n_points: {n_points}");
println!(" base_seed: 0x{base_seed:X} ({base_seed})");
println!(" case_seed: 0x{seed:X} ({seed})");
println!(
" distribution: {distribution}",
distribution = distribution.name()
);
match distribution {
PointDistribution::Ball => println!(" ball_radius: {ball_radius}"),
PointDistribution::Box => println!(" box_half_width:{box_half_width}"),
}
println!(" construction_mode: {}", mode.name());
println!(
" initial_simplex: {}",
initial_simplex_strategy_name(initial_simplex_strategy)
);
println!(" debug_mode: {}", debug_mode.name());
println!(" topology_guarantee: {topology_guarantee:?}");
println!(" shuffle_seed: {shuffle_seed:?}");
match mode {
ConstructionMode::New => {
let bulk_progress = bulk_progress_every
.map_or_else(|| "disabled".to_owned(), |every| every.to_string());
println!(" bulk_progress_every:{bulk_progress}");
println!(" bulk_progress_scope: remaining vertices after initial simplex");
}
ConstructionMode::Incremental => println!(" progress_every:{progress_every}"),
}
println!(" validation_cadence: {validation_cadence:?}");
println!(" allow_skips: {allow_skips}");
println!(" max_skip_pct: {max_skip_pct}");
println!(" skip_final_repair: {skip_final_repair}");
println!(" repair_every: {repair_every}");
println!(" repair_policy: {repair_policy:?}");
println!(" repair_max_flips: {repair_max_flips:?}");
if max_runtime_secs > 0 {
println!(" max_runtime_secs: {max_runtime_secs}");
}
println!();
println!("Generating points...");
let t_gen = Instant::now();
let points = match distribution {
PointDistribution::Ball => {
generate_random_points_in_ball_seeded::<f64, D>(n_points, ball_radius, seed)
.unwrap_or_else(|e| {
panic!(
"failed to generate deterministic ball points (radius={ball_radius}): {e}"
)
})
}
PointDistribution::Box => {
let range = (-box_half_width, box_half_width);
generate_random_points_seeded::<f64, D>(n_points, range, seed).unwrap_or_else(|e| {
panic!("failed to generate deterministic box points (range={range:?}): {e}")
})
}
};
println!("Generated {} points in {:?}", points.len(), t_gen.elapsed());
println!("Building vertices...");
let t_vertices = Instant::now();
let mut vertices: Vec<Vertex<f64, (), D>> = points.into_iter().map(|p| vertex!(p)).collect();
println!(
"Built {} vertices in {:?}",
vertices.len(),
t_vertices.elapsed()
);
let t_insert = Instant::now();
let mut dt: DelaunayTriangulation<_, (), (), D> = match mode {
ConstructionMode::New => {
let kernel = RobustKernel::<f64>::new();
println!("Starting batch construction (new)...");
let t_batch = Instant::now();
let options = ConstructionOptions::default()
.with_initial_simplex_strategy(initial_simplex_strategy)
.with_batch_repair_policy(repair_policy);
match DelaunayTriangulation::with_options_and_statistics(
&kernel,
&vertices,
topology_guarantee,
options,
) {
Ok((dt, stats)) => {
let summary: InsertionSummary<D> = stats.into();
print_insertion_summary(&summary, t_insert.elapsed(), true);
println!("Batch construction completed in {:?}", t_batch.elapsed());
dt
}
Err(e) => {
let DelaunayTriangulationConstructionErrorWithStatistics {
error,
statistics,
..
} = e;
let summary: InsertionSummary<D> = statistics.into();
print_insertion_summary(&summary, t_insert.elapsed(), true);
println!("Batch construction failed after {:?}", t_batch.elapsed());
println!("construction failed: {error}");
let outcome = DebugOutcome::ConstructionFailure {
error: format!("{error}"),
};
print_abort_summary::<D>(&outcome, seed, n_points, "batch construction");
return outcome;
}
}
}
ConstructionMode::Incremental => {
if let Some(shuffle_seed) = shuffle_seed {
let mut rng = StdRng::seed_from_u64(shuffle_seed);
vertices.shuffle(&mut rng);
println!("Shuffled insertion order with seed {shuffle_seed}");
}
let validation_policy = topology_guarantee.default_validation_policy();
let mut dt: DelaunayTriangulation<_, (), (), D> =
DelaunayTriangulation::with_empty_kernel_and_topology_guarantee(
RobustKernel::<f64>::new(),
topology_guarantee,
);
dt.set_delaunay_repair_policy(repair_policy);
dt.set_delaunay_check_policy(DelaunayCheckPolicy::EndOnly);
dt.set_validation_policy(validation_policy);
println!("Policies:");
println!(" topology_guarantee: {:?}", dt.topology_guarantee());
println!(" validation_policy: {:?}", dt.validation_policy());
println!(" delaunay_repair_policy:{:?}", dt.delaunay_repair_policy());
println!(" delaunay_check_policy: {:?}", dt.delaunay_check_policy());
println!();
let mut summary: InsertionSummary<D> = InsertionSummary::default();
let mut had_simplices = false;
let mut t_last_progress = Instant::now();
for (idx, vertex) in vertices.iter().copied().enumerate() {
let coords = *vertex.point().coords();
let uuid = vertex.uuid();
let inserted_this_loop = match dt.insert_best_effort_with_statistics(vertex) {
Ok((InsertionOutcome::Inserted { .. }, stats)) => {
summary.record_inserted(stats);
true
}
Ok((InsertionOutcome::Skipped { error }, stats)) => {
let sample = SkipSample {
index: idx,
uuid,
coords: Some(coords),
attempts: stats.attempts,
error: error.to_string(),
};
summary.record_skipped(sample, stats);
false
}
Err(err) => {
println!(
"Non-retryable insertion error at idx={idx} uuid={uuid} coords={coords:?}"
);
println!(" error: {err}");
if let Err(report) = dt.validation_report() {
print_validation_report(&report);
} else {
println!("validation_report: OK (after rollback)");
}
let outcome = DebugOutcome::ConstructionFailure {
error: format!("{err}"),
};
print_abort_summary::<D>(&outcome, seed, n_points, "incremental insertion");
return outcome;
}
};
if !had_simplices && dt.number_of_simplices() > 0 {
had_simplices = true;
println!("Initial simplex created at insertion {}", idx + 1);
}
if inserted_this_loop
&& had_simplices
&& validation_cadence.should_validate(summary.inserted)
&& let Err(e) = dt.as_triangulation().is_valid()
{
println!("Topology validation failed at idx={idx}: {e}");
let outcome = if let Err(report) = dt.validation_report() {
print_validation_report(&report);
classify_validation_report(&report)
} else {
DebugOutcome::ValidationFailure {
kind: InvariantKind::Topology,
details: format!("{e}"),
}
};
print_abort_summary::<D>(&outcome, seed, n_points, "periodic validation");
return outcome;
}
if (idx + 1) % progress_every == 0 {
let chunk_elapsed = t_last_progress.elapsed();
let progress_f64: f64 =
safe_usize_to_scalar(progress_every).unwrap_or(f64::NAN);
let rate = progress_f64 / chunk_elapsed.as_secs_f64().max(1e-9);
println!(
"progress: {}/{} inserted={} skipped={} simplices={} elapsed={:?} ({:.1} pts/s last {})",
idx + 1,
n_points,
summary.inserted,
summary.total_skipped(),
dt.number_of_simplices(),
t_insert.elapsed(),
rate,
progress_every,
);
t_last_progress = Instant::now();
}
}
print_insertion_summary(&summary, t_insert.elapsed(), false);
dt
}
};
let skipped_total = n_points.saturating_sub(dt.number_of_vertices());
println!(
"Triangulation size: vertices={} (skipped={skipped_total}) simplices={} dim={}",
dt.number_of_vertices(),
dt.number_of_simplices(),
dt.dim()
);
let skipped_pct = skip_percentage(skipped_total, n_points);
if !allow_skips && skipped_pct > max_skip_pct {
let outcome = DebugOutcome::SkippedVertices {
skipped: skipped_total,
total: n_points,
skip_pct: skipped_pct,
max_skip_pct,
};
print_abort_summary::<D>(&outcome, seed, n_points, "skip check");
return outcome;
}
let mut repair_failure: Option<String> = None;
if !skip_final_repair && dt.number_of_simplices() > 0 {
println!();
println!("Running final flip-based repair (advanced)...");
let t_repair = Instant::now();
let mut repair_config = DelaunayRepairHeuristicConfig::default();
repair_config.max_flips = repair_max_flips;
match dt.repair_delaunay_with_flips_advanced(repair_config) {
Ok(outcome) => {
println!(
"repair: checked={} flips={} max_queue={} used_heuristic={}",
outcome.stats.facets_checked,
outcome.stats.flips_performed,
outcome.stats.max_queue_len,
outcome.used_heuristic()
);
if let Some(seeds) = outcome.heuristic {
println!(
"repair heuristic seeds: shuffle_seed={} perturbation_seed={}",
seeds.shuffle_seed, seeds.perturbation_seed
);
}
}
Err(e) => {
println!("repair failed: {e}");
repair_failure = Some(format!("{e}"));
}
}
println!("repair wall time: {:?}", t_repair.elapsed());
}
println!();
println!("Running validation_report (Levels 1–4)...");
let t_validate = Instant::now();
let validation_result = dt.validation_report();
println!("validation_report wall time: {:?}", t_validate.elapsed());
match validation_result {
Ok(()) => println!("validation_report: OK"),
Err(report) => {
print_validation_report(&report);
let outcome = classify_validation_report(&report);
print_abort_summary::<D>(&outcome, seed, n_points, "final validation");
return outcome;
}
}
if let Some(error) = repair_failure {
let outcome = DebugOutcome::RepairNonConvergence { error };
print_abort_summary::<D>(&outcome, seed, n_points, "final repair");
return outcome;
}
println!();
println!("Total wall time: {:?}", t_gen.elapsed());
DebugOutcome::Success
}
#[derive(Clone, Copy)]
enum FailingWriterMode {
Write,
Flush,
}
struct FailingWriter {
mode: FailingWriterMode,
kind: io::ErrorKind,
}
impl Write for FailingWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if matches!(self.mode, FailingWriterMode::Write) {
return Err(io::Error::new(self.kind, "synthetic write failure"));
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
if matches!(self.mode, FailingWriterMode::Flush) {
return Err(io::Error::new(self.kind, "synthetic flush failure"));
}
Ok(())
}
}
#[test]
fn test_write_timeout_abort_message_flushes_message() {
let mut output = Vec::new();
write_timeout_abort_message(&mut output, 17).expect("timeout diagnostic write should succeed");
let message = String::from_utf8(output).expect("timeout diagnostic should be UTF-8");
assert_eq!(
message,
"=== TIMEOUT: wall time exceeded 17 seconds — aborting ===\n"
);
}
#[test]
fn test_write_timeout_abort_message_propagates_error() {
let cases = [
(FailingWriterMode::Write, io::ErrorKind::BrokenPipe),
(FailingWriterMode::Flush, io::ErrorKind::WriteZero),
];
for (mode, kind) in cases {
let err = write_timeout_abort_message(FailingWriter { mode, kind }, 17)
.expect_err("timeout diagnostic should propagate writer failures");
assert_eq!(err.kind(), kind);
}
}
#[test]
fn test_topology_for_debug_mode_uses_ridge_links_by_default() {
assert_eq!(
topology_for_debug_mode(DebugMode::Cadenced),
TopologyGuarantee::PLManifold
);
assert_eq!(
topology_for_debug_mode(DebugMode::Strict),
TopologyGuarantee::PLManifoldStrict
);
}
#[test]
fn test_initial_simplex_strategy_from_name_maps_supported_values() {
assert_eq!(
initial_simplex_strategy_from_name(""),
Some(InitialSimplexStrategy::MaxVolume)
);
assert_eq!(
initial_simplex_strategy_from_name("first"),
Some(InitialSimplexStrategy::First)
);
assert_eq!(
initial_simplex_strategy_from_name("BALANCED"),
Some(InitialSimplexStrategy::Balanced)
);
assert_eq!(
initial_simplex_strategy_from_name("max-volume"),
Some(InitialSimplexStrategy::MaxVolume)
);
assert_eq!(
initial_simplex_strategy_from_name("MAX_VOLUME"),
Some(InitialSimplexStrategy::MaxVolume)
);
assert_eq!(initial_simplex_strategy_from_name("unknown"), None);
assert_eq!(
initial_simplex_strategy_name(InitialSimplexStrategy::Balanced),
"balanced"
);
assert_eq!(
initial_simplex_strategy_name(InitialSimplexStrategy::MaxVolume),
"max-volume"
);
}
#[test]
fn test_skip_percentage_reports_ratio() {
assert!((skip_percentage(0, 100) - 0.0).abs() < f64::EPSILON);
assert!((skip_percentage(0, 0) - 0.0).abs() < f64::EPSILON);
assert!((skip_percentage(4, 400) - 1.0).abs() < f64::EPSILON);
assert!((skip_percentage(12, 100) - 12.0).abs() < f64::EPSILON);
}
#[test]
fn test_repair_policy_from_repair_every_maps_cadence() {
assert_eq!(
repair_policy_from_repair_every(0),
DelaunayRepairPolicy::Never
);
assert_eq!(
repair_policy_from_repair_every(1),
DelaunayRepairPolicy::EveryInsertion
);
assert_eq!(
repair_policy_from_repair_every(2),
DelaunayRepairPolicy::EveryN(NonZeroUsize::new(2).unwrap())
);
assert_eq!(
repair_policy_from_repair_every(4),
DelaunayRepairPolicy::EveryN(NonZeroUsize::new(4).unwrap())
);
}
#[cfg(feature = "slow-tests")]
#[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::<f64, 3>(1000, 100.0, seed)
.expect("point generation should succeed");
let vertices: Vec<Vertex<f64, (), 3>> = points.into_iter().map(|p| vertex!(p)).collect();
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new_with_topology_guarantee(
&vertices,
TopologyGuarantee::PLManifold,
)
.expect("construction must not fail (#228 regression)");
assert!(
dt.as_triangulation().validate().is_ok(),
"Topology validation (L1-L3) must pass (#228 regression, seed=0x{seed:X})"
);
assert!(
dt.is_delaunay_via_flips().is_ok(),
"Delaunay property must hold (#228 regression, seed=0x{seed:X})"
);
}
#[cfg(feature = "slow-tests")]
#[test]
fn regression_issue_230_4d_100_orientation() {
let seed = seed_for_case::<4>(42, 100);
let points = generate_random_points_in_ball_seeded::<f64, 4>(100, 100.0, seed)
.expect("point generation should succeed");
let vertices: Vec<Vertex<f64, (), 4>> = points.into_iter().map(|p| vertex!(p)).collect();
let kernel = RobustKernel::<f64>::new();
let (dt, stats) =
DelaunayTriangulation::<RobustKernel<f64>, (), (), 4>::with_options_and_statistics(
&kernel,
&vertices,
TopologyGuarantee::PLManifoldStrict,
ConstructionOptions::default(),
)
.expect("construction must not fail (#230 regression)");
println!(
"regression_issue_230: inserted={} skipped={} (duplicate={} degeneracy={}) seed=0x{seed:X}",
stats.inserted,
stats.total_skipped(),
stats.skipped_duplicate,
stats.skipped_degeneracy
);
assert!(
dt.as_triangulation().validate().is_ok(),
"Topology validation (L1-L3) must pass (#230 regression, seed=0x{seed:X})"
);
}
#[test]
#[cfg(feature = "slow-tests")]
fn debug_large_scale_2d() {
let outcome = debug_large_case::<2>("2D", 40_000);
assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}
#[test]
#[cfg(feature = "slow-tests")]
fn debug_large_scale_3d() {
let outcome = debug_large_case::<3>("3D", 7_500);
assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}
#[test]
#[cfg(feature = "slow-tests")]
fn debug_large_scale_4d() {
let outcome = debug_large_case::<4>("4D", 900);
assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}
#[test]
#[cfg(feature = "slow-tests")]
fn debug_large_scale_5d() {
let outcome = debug_large_case::<5>("5D", 150);
assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}