#![forbid(unsafe_code)]
use delaunay::core::triangulation::TopologyGuarantee;
use delaunay::geometry::kernel::RobustKernel;
use delaunay::geometry::util::{
generate_random_points_in_ball_seeded, generate_random_points_seeded, safe_usize_to_scalar,
};
use delaunay::prelude::triangulation::*;
use delaunay::triangulation::delaunay::{
ConstructionOptions, ConstructionStatistics, DelaunayRepairHeuristicConfig,
DelaunayTriangulationConstructionErrorWithStatistics,
};
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, 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,
cells_removed_total: usize,
cells_removed_max: usize,
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.cells_removed_total = self
.cells_removed_total
.saturating_add(stats.cells_removed_during_repair);
self.cells_removed_max = self
.cells_removed_max
.max(stats.cells_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 {
let skip_samples: Vec<SkipSample<D>> = stats
.skip_samples
.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.clone(),
}
})
.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,
cells_removed_total: stats.cells_removed_total,
cells_removed_max: stats.cells_removed_max,
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 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_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",
}
}
}
#[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",
}
}
}
impl PointDistribution {
const fn name(self) -> &'static str {
match self {
Self::Ball => "ball",
Self::Box => "box",
}
}
}
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,
},
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 } => {
write!(f, "SkippedVertices | {skipped}/{total} skipped")
}
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 --test large_scale_debug debug_large_scale_{D}d -- --ignored --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);
}
}
fn print_insertion_summary<const D: usize>(summary: &InsertionSummary<D>, elapsed: Duration) {
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!(
" cells_removed_during_repair: total={}, max={} (insertion safety-net / repair bookkeeping)",
summary.cells_removed_total, summary.cells_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 !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 {
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 debug_mode = debug_mode_from_env();
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 allow_skips = env_flag("DELAUNAY_LARGE_DEBUG_ALLOW_SKIPS");
let skip_final_repair = env_flag("DELAUNAY_LARGE_DEBUG_SKIP_FINAL_REPAIR");
let repair_every = env_usize("DELAUNAY_LARGE_DEBUG_REPAIR_EVERY").unwrap_or(128);
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
}
});
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!(" debug_mode: {}", debug_mode.name());
println!(" shuffle_seed: {shuffle_seed:?}");
println!(" progress_every:{progress_every}");
println!(" validate_every:{validate_every:?}");
println!(" allow_skips: {allow_skips}");
println!(" skip_final_repair: {skip_final_repair}");
println!(" repair_every: {repair_every}");
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();
match DelaunayTriangulation::with_topology_guarantee_and_options_with_construction_statistics(
&kernel,
&vertices,
TopologyGuarantee::PLManifoldStrict,
ConstructionOptions::default(),
) {
Ok((dt, stats)) => {
let summary: InsertionSummary<D> = stats.into();
print_insertion_summary(&summary, t_insert.elapsed());
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());
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 (topology_guarantee, validation_policy) = match debug_mode {
DebugMode::Cadenced => {
(TopologyGuarantee::PLManifold, ValidationPolicy::OnSuspicion)
}
DebugMode::Strict => (
TopologyGuarantee::PLManifoldStrict,
ValidationPolicy::Always,
),
};
let mut dt: DelaunayTriangulation<_, (), (), D> =
DelaunayTriangulation::with_empty_kernel_and_topology_guarantee(
RobustKernel::<f64>::new(),
topology_guarantee,
);
let repair_policy = match NonZeroUsize::new(repair_every) {
None => DelaunayRepairPolicy::Never,
Some(n) if n.get() == 1 => DelaunayRepairPolicy::EveryInsertion,
Some(n) => DelaunayRepairPolicy::EveryN(n),
};
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_cells = 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();
match dt.insert_with_statistics(vertex) {
Ok((InsertionOutcome::Inserted { .. }, stats)) => {
summary.record_inserted(stats);
}
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);
}
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_cells && dt.number_of_cells() > 0 {
had_cells = true;
println!("Initial simplex created at insertion {}", idx + 1);
}
if let Some(every) = validate_every
&& every > 0
&& had_cells
&& (idx + 1) % every == 0
&& 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={} cells={} elapsed={:?} ({:.1} pts/s last {})",
idx + 1,
n_points,
summary.inserted,
summary.total_skipped(),
dt.number_of_cells(),
t_insert.elapsed(),
rate,
progress_every,
);
t_last_progress = Instant::now();
}
}
print_insertion_summary(&summary, t_insert.elapsed());
dt
}
};
let skipped_total = n_points.saturating_sub(dt.number_of_vertices());
println!(
"Triangulation size: vertices={} (skipped={skipped_total}) cells={} dim={}",
dt.number_of_vertices(),
dt.number_of_cells(),
dt.dim()
);
if !allow_skips && skipped_total > 0 {
let outcome = DebugOutcome::SkippedVertices {
skipped: skipped_total,
total: n_points,
};
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_cells() > 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);
}
}
#[cfg(feature = "slow-tests")]
#[test]
#[ignore = "1000-point 3D construction exceeds CI timeout (~30min debug)"]
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]
#[ignore = "4D 100-point construction can take minutes in debug mode"]
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_topology_guarantee_and_options_with_construction_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]
#[ignore = "large-scale debug harness (manual run)"]
fn debug_large_scale_3d() {
let outcome = debug_large_case::<3>("3D", 10_000);
assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}
#[test]
#[ignore = "large-scale debug harness (manual run)"]
fn debug_large_scale_4d() {
let outcome = debug_large_case::<4>("4D", 3_000);
assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}
#[test]
#[ignore = "large-scale debug harness (manual run)"]
fn debug_large_scale_5d() {
let outcome = debug_large_case::<5>("5D", 1_000);
assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}