causal-triangulations 0.1.0

Causal Dynamical Triangulations in d-dimensions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
#![forbid(unsafe_code)]

//! Delaunay triangulation generators.
//!
//! This module constructs 2D Delaunay triangulations via the `delaunay` crate.
//! Together with `src/geometry/backends/delaunay.rs` it forms the only boundary
//! that directly imports from the `delaunay` crate (see
//! `docs/dev/rust.md ยง Geometry Backend Isolation`).

use crate::errors::{CdtError, CdtResult, GenerationParameterIssue};
pub use delaunay::TopologyGuarantee;
use delaunay::geometry::kernel::AdaptiveKernel;
use delaunay::geometry::point::Point;
use delaunay::geometry::traits::coordinate::Coordinate;
use delaunay::geometry::util::{generate_random_points, generate_random_points_seeded};
use delaunay::prelude::VertexBuilder;
pub use delaunay::topology::traits::{GlobalTopology, ToroidalConstructionMode};
use delaunay::{DelaunayTriangulation, DelaunayTriangulationBuilder};

/// Type alias for the 2D Delaunay triangulation returned by this crate's generators.
///
/// Uses [`AdaptiveKernel`] (the default for [`DelaunayTriangulationBuilder::build`]) and
/// `u32` vertex data storing the per-vertex time-slice label (foliation).
pub type DelaunayTriangulation2D = DelaunayTriangulation<AdaptiveKernel<f64>, u32, i32, 2>;

/// Keeps `generate_delaunay2` vertex-build failures tied to the public constructor name.
fn generate_delaunay2_vertex_build_error(
    number_of_vertices: u32,
    underlying_error: String,
) -> CdtError {
    CdtError::VertexBuildFailed {
        context: format!("generate_delaunay2({number_of_vertices} vertices)"),
        underlying_error,
    }
}

/// Builds a consistent typed validation error for generator argument checks.
fn invalid_generation_parameters(
    issue: GenerationParameterIssue,
    provided_value: String,
    expected_range: &str,
) -> CdtError {
    CdtError::InvalidGenerationParameters {
        issue,
        provided_value,
        expected_range: expected_range.to_string(),
    }
}

/// Rejects coordinate ranges before they reach random point generation.
fn validate_coordinate_range(coordinate_range: (f64, f64)) -> CdtResult<()> {
    let (min, max) = coordinate_range;
    if min.is_finite() && max.is_finite() && min < max {
        Ok(())
    } else {
        Err(invalid_generation_parameters(
            GenerationParameterIssue::InvalidCoordinateRange,
            format!("[{min}, {max}]"),
            "finite min < max",
        ))
    }
}

/// Rejects explicit vertex coordinates that geometric predicates cannot order.
fn validate_explicit_coordinates(coords_with_data: &[([f64; 2], u32)]) -> CdtResult<()> {
    for (vertex_index, (coord, _)) in coords_with_data.iter().enumerate() {
        for (axis, value) in coord.iter().copied().enumerate() {
            if !value.is_finite() {
                return Err(invalid_generation_parameters(
                    GenerationParameterIssue::NonFiniteVertexCoordinate,
                    format!("vertex {vertex_index} axis {axis} = {value}"),
                    "finite coordinate values",
                ));
            }
        }
    }
    Ok(())
}

/// Rejects toroidal periods that cannot define a finite positive quotient domain.
fn validate_toroidal_domain(domain: [f64; 2]) -> CdtResult<()> {
    for (axis, period) in domain.into_iter().enumerate() {
        if !period.is_finite() || period <= 0.0 {
            return Err(invalid_generation_parameters(
                GenerationParameterIssue::InvalidToroidalDomain,
                format!("axis {axis} period {period}"),
                "finite and positive periods",
            ));
        }
    }
    Ok(())
}

/// Generates a Delaunay triangulation with optional seed for deterministic testing.
///
/// Uses [`DelaunayTriangulationBuilder`] (introduced in delaunay v0.7.2) for
/// construction, which provides deterministic tie-breaking and coherent orientation
/// as first-class invariants.
///
/// # Errors
///
/// Returns [`crate::CdtError::InvalidGenerationParameters`] if
/// `number_of_vertices < 3` or `coordinate_range` is not finite with `min < max`.
/// Returns [`crate::CdtError::DelaunayGenerationFailed`] if random point
/// generation or Delaunay construction fails, and
/// [`crate::CdtError::VertexBuildFailed`] if an upstream vertex cannot be built.
///
/// # Examples
///
/// ```
/// use causal_triangulations::CdtResult;
/// use causal_triangulations::prelude::geometry::*;
///
/// fn main() -> CdtResult<()> {
///     let dt = generate_delaunay2(5, (0.0, 1.0), Some(7))?;
///     assert_eq!(dt.number_of_vertices(), 5);
///     Ok(())
/// }
/// ```
pub fn generate_delaunay2(
    number_of_vertices: u32,
    coordinate_range: (f64, f64),
    seed: Option<u64>,
) -> CdtResult<DelaunayTriangulation2D> {
    // Validate parameters before attempting generation
    if number_of_vertices < 3 {
        return Err(invalid_generation_parameters(
            GenerationParameterIssue::InsufficientVertexCount,
            number_of_vertices.to_string(),
            "โ‰ฅ 3",
        ));
    }

    validate_coordinate_range(coordinate_range)?;

    // Generate random points, then build triangulation via the builder API
    let n = number_of_vertices as usize;
    let points = seed
        .map_or_else(
            || generate_random_points::<f64, 2>(n, coordinate_range),
            |s| generate_random_points_seeded::<f64, 2>(n, coordinate_range, s),
        )
        .map_err(|e| CdtError::DelaunayGenerationFailed {
            vertex_count: number_of_vertices,
            coordinate_range,
            attempt: 1,
            underlying_error: format!("Point generation failed: {e}"),
        })?;

    // Explicitly type the builder as Vertex<f64, u32, 2> so the triangulation
    // has u32 vertex data available for time-slice labels (even if unset here).
    let vertices: Vec<_> = points
        .into_iter()
        .map(|point| VertexBuilder::<f64, u32, 2>::default().point(point).build())
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| generate_delaunay2_vertex_build_error(number_of_vertices, e.to_string()))?;

    let dt = DelaunayTriangulationBuilder::from_vertices(&vertices)
        .build::<i32>()
        .map_err(|e| CdtError::DelaunayGenerationFailed {
            vertex_count: number_of_vertices,
            coordinate_range,
            attempt: 1,
            underlying_error: e.to_string(),
        })?;

    Ok(dt)
}

/// Builds a 2D Delaunay triangulation from coordinate-data pairs.
///
/// Each element provides `[x, y]` coordinates and `u32` vertex data
/// (e.g., a time-slice label).  The vertex data is embedded directly on
/// each vertex of the underlying Delaunay triangulation.
///
/// # Errors
///
/// Returns [`crate::CdtError::InvalidGenerationParameters`] if any coordinate is
/// NaN or infinite. Returns [`crate::CdtError::VertexBuildFailed`] if a vertex
/// cannot be constructed, or [`crate::CdtError::DelaunayGenerationFailed`] if
/// the Delaunay builder rejects the finite vertex set.
///
/// # Examples
///
/// ```
/// use causal_triangulations::CdtResult;
/// use causal_triangulations::prelude::geometry::*;
///
/// fn main() -> CdtResult<()> {
///     let dt = build_delaunay2_with_data(&[
///         ([0.0, 0.0], 0),
///         ([1.0, 0.0], 0),
///         ([0.5, 1.0], 1),
///     ])?;
///     assert_eq!(dt.number_of_vertices(), 3);
///     Ok(())
/// }
/// ```
pub fn build_delaunay2_with_data(
    coords_with_data: &[([f64; 2], u32)],
) -> CdtResult<DelaunayTriangulation2D> {
    validate_explicit_coordinates(coords_with_data)?;

    let vertices: Vec<_> = coords_with_data
        .iter()
        .enumerate()
        .map(|(i, (coord, data))| {
            let point = Point::<f64, 2>::new(*coord);
            VertexBuilder::<f64, u32, 2>::default()
                .point(point)
                .data(*data)
                .build()
                .map_err(|e| CdtError::VertexBuildFailed {
                    context: format!("vertex {i}"),
                    underlying_error: e.to_string(),
                })
        })
        .collect::<CdtResult<Vec<_>>>()?;

    let vertex_count = u32::try_from(vertices.len()).unwrap_or(u32::MAX);
    let coordinate_range = coords_with_data
        .iter()
        .flat_map(|(c, _)| c.iter().copied())
        .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
            (lo.min(v), hi.max(v))
        });
    DelaunayTriangulationBuilder::from_vertices(&vertices)
        .build::<i32>()
        .map_err(|e| CdtError::DelaunayGenerationFailed {
            vertex_count,
            coordinate_range,
            attempt: 1,
            underlying_error: e.to_string(),
        })
}

/// Builds a 2D triangulation from explicit vertex coordinates, data, and simplex connectivity.
///
/// Each vertex is specified as `([x, y], data)`. Each simplex is a `Vec<usize>` of
/// vertex indices (must contain exactly 3 indices for 2D).  The triangulation is
/// assembled combinatorially โ€” **no Delaunay point-insertion** is performed.
///
/// Topology defaults to [`TopologyGuarantee::DEFAULT`] (PL-manifold) and
/// [`GlobalTopology::Euclidean`].  For explicit meshes that need non-default
/// topology metadata, use [`build_delaunay2_with_topology`].  For toroidal CDT
/// meshes, prefer [`build_periodic_toroidal_delaunay2`] or
/// [`CdtTriangulation::from_toroidal_cdt`](crate::CdtTriangulation::from_toroidal_cdt):
/// `delaunay` v0.7.8 rejects explicit non-Euclidean connectivity for toroidal
/// construction.
///
/// This is one of the only call sites for
/// [`DelaunayTriangulationBuilder::from_vertices_and_simplices`], maintaining
/// geometry backend isolation.
///
/// # Errors
///
/// Returns [`crate::CdtError::InvalidGenerationParameters`] if any coordinate is
/// NaN or infinite. Returns [`crate::CdtError::VertexBuildFailed`] if a vertex
/// cannot be constructed, or [`crate::CdtError::DelaunayGenerationFailed`] if
/// the explicit simplex builder rejects the input (for example invalid simplex arity,
/// out-of-bounds indices, or topological validation failure).
///
/// # Examples
///
/// ```
/// use causal_triangulations::CdtResult;
/// use causal_triangulations::prelude::geometry::*;
///
/// fn main() -> CdtResult<()> {
///     // Single labeled triangle (PL-manifold-with-boundary, Euclidean):
///     let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
///     let simplices = vec![vec![0, 1, 2]];
///
///     let dt = build_delaunay2_from_simplices(&vertices, &simplices)?;
///     assert_eq!(dt.number_of_vertices(), 3);
///     assert_eq!(dt.number_of_simplices(), 1);
///     Ok(())
/// }
/// ```
pub fn build_delaunay2_from_simplices(
    coords_with_data: &[([f64; 2], u32)],
    simplices: &[Vec<usize>],
) -> CdtResult<DelaunayTriangulation2D> {
    build_delaunay2_with_topology(
        coords_with_data,
        simplices,
        TopologyGuarantee::DEFAULT,
        GlobalTopology::Euclidean,
    )
}

/// Like [`build_delaunay2_from_simplices`] but with explicit [`TopologyGuarantee`] and
/// [`GlobalTopology`] metadata.
///
/// Use [`TopologyGuarantee::Pseudomanifold`] for supported explicit meshes whose
/// Euler characteristic differs from the default closed-sphere expectation, and
/// pair it with the matching [`GlobalTopology`] so the builder validates against
/// the correct expected ฯ‡.  For toroidal CDT meshes, use
/// [`build_periodic_toroidal_delaunay2`]; `delaunay` v0.7.8 rejects
/// [`GlobalTopology::Toroidal`] explicit simplex connectivity pending upstream
/// quotient-validation support.
///
/// # Errors
///
/// Same as [`build_delaunay2_from_simplices`]: coordinates must be finite, vertices
/// must build successfully, and the explicit simplices must satisfy the selected
/// topology guarantee and global topology.
///
/// # Examples
///
/// Import topology metadata from this crate's geometry prelude:
///
/// ```
/// use causal_triangulations::CdtResult;
/// use causal_triangulations::prelude::geometry::*;
///
/// fn main() -> CdtResult<()> {
///     // Single labeled triangle, default PL-manifold guarantee, Euclidean global topology.
///     let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
///     let simplices = vec![vec![0, 1, 2]];
///
///     let dt = build_delaunay2_with_topology(
///         &vertices,
///         &simplices,
///         TopologyGuarantee::DEFAULT,
///         GlobalTopology::Euclidean,
///     )?;
///     assert_eq!(dt.number_of_vertices(), 3);
///     assert_eq!(dt.number_of_simplices(), 1);
///     Ok(())
/// }
/// ```
pub fn build_delaunay2_with_topology(
    coords_with_data: &[([f64; 2], u32)],
    simplices: &[Vec<usize>],
    topology_guarantee: TopologyGuarantee,
    global_topology: GlobalTopology<2>,
) -> CdtResult<DelaunayTriangulation2D> {
    validate_explicit_coordinates(coords_with_data)?;

    let vertices: Vec<_> = coords_with_data
        .iter()
        .enumerate()
        .map(|(i, (coord, data))| {
            let point = Point::<f64, 2>::new(*coord);
            VertexBuilder::<f64, u32, 2>::default()
                .point(point)
                .data(*data)
                .build()
                .map_err(|e| CdtError::VertexBuildFailed {
                    context: format!("vertex {i}"),
                    underlying_error: e.to_string(),
                })
        })
        .collect::<CdtResult<Vec<_>>>()?;

    let vertex_count = u32::try_from(vertices.len()).unwrap_or(u32::MAX);
    let coordinate_range = coords_with_data
        .iter()
        .flat_map(|(c, _)| c.iter().copied())
        .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
            (lo.min(v), hi.max(v))
        });
    DelaunayTriangulationBuilder::from_vertices_and_simplices(&vertices, simplices)
        .topology_guarantee(topology_guarantee)
        .global_topology(global_topology)
        .build::<i32>()
        .map_err(|e| CdtError::DelaunayGenerationFailed {
            vertex_count,
            coordinate_range,
            attempt: 1,
            underlying_error: e.to_string(),
        })
}

/// Attempts to build a 2D toroidal explicit triangulation.
///
/// Sets [`TopologyGuarantee::Pseudomanifold`] and
/// [`GlobalTopology::Toroidal`] with [`ToroidalConstructionMode::Explicit`]
/// so the builder validates the mesh against ฯ‡ = 0 instead of the default
/// closed-sphere expectation.
///
/// `delaunay` v0.7.8 rejects explicit non-Euclidean connectivity for toroidal
/// topology before quotient validation can run.  This helper remains as the
/// stable explicit-topology entry point, but callers that need an actual
/// toroidal CDT mesh should use [`build_periodic_toroidal_delaunay2`] or the
/// higher-level
/// [`CdtTriangulation::from_toroidal_cdt`](crate::CdtTriangulation::from_toroidal_cdt)
/// constructor.
///
/// # Errors
///
/// Returns [`crate::CdtError::InvalidGenerationParameters`] if either toroidal
/// period in `domain` is NaN, infinite, or non-positive. Otherwise the error
/// behavior is the same as [`build_delaunay2_with_topology`], including the
/// upstream explicit-toroidal rejection described above.
///
/// # Examples
///
/// The helper validates the toroidal domain before forwarding explicit simplex
/// connectivity to `delaunay`:
///
/// ```
/// use causal_triangulations::{CdtError, CdtResult};
/// use causal_triangulations::prelude::geometry::*;
/// use std::assert_matches;
///
/// fn main() -> CdtResult<()> {
///     const N: usize = 3;
///     const LABELS: [u32; 3] = [0, 1, 2];
///     const T: usize = LABELS.len();
///
///     // Vertex (i, t) lives at index i + t*N, with x = i/N, y = t/T, label = t.
///     let mut vertices: Vec<([f64; 2], u32)> = Vec::with_capacity(N * T);
///     for (t, label) in LABELS.into_iter().enumerate() {
///         for i in 0..N {
///             #[allow(clippy::cast_precision_loss)]
///             let coord = [i as f64 / N as f64, t as f64 / T as f64];
///             vertices.push((coord, label));
///         }
///     }
///
///     // Each (i, t) quad contributes one Up and one Down triangle.
///     let mut simplices: Vec<Vec<usize>> = Vec::with_capacity(2 * N * T);
///     for t in 0..T {
///         let t_next = (t + 1) % T;
///         for i in 0..N {
///             let i_next = (i + 1) % N;
///             simplices.push(vec![i + t * N, i_next + t * N, i + t_next * N]);
///             simplices.push(vec![i_next + t * N, i_next + t_next * N, i + t_next * N]);
///         }
///     }
///
///     let result = build_toroidal_delaunay2(&vertices, &simplices, [1.0, 1.0]);
///     assert_matches!(
///         result,
///         Err(CdtError::DelaunayGenerationFailed { ref underlying_error, .. })
///             if underlying_error.contains("Explicit non-Euclidean connectivity")
///     );
///     Ok(())
/// }
/// ```
pub fn build_toroidal_delaunay2(
    coords_with_data: &[([f64; 2], u32)],
    simplices: &[Vec<usize>],
    domain: [f64; 2],
) -> CdtResult<DelaunayTriangulation2D> {
    validate_toroidal_domain(domain)?;

    build_delaunay2_with_topology(
        coords_with_data,
        simplices,
        TopologyGuarantee::Pseudomanifold,
        GlobalTopology::Toroidal {
            domain,
            mode: ToroidalConstructionMode::Explicit,
        },
    )
}

/// Builds a periodic 2D toroidal Delaunay triangulation from coordinate-data pairs.
///
/// This uses the upstream periodic image-point constructor rather than explicit
/// simplex assembly. The builder requests [`TopologyGuarantee::PLManifold`], so
/// the resulting toroidal mesh is suitable for the full Delaunay Level 1-4
/// validation path exposed by
/// [`DelaunayBackend::validate_delaunay`](crate::geometry::backends::delaunay::DelaunayBackend::validate_delaunay).
///
/// # Errors
///
/// Returns [`crate::CdtError::InvalidGenerationParameters`] if any coordinate is
/// non-finite or either toroidal period is non-finite/non-positive. Returns
/// [`crate::CdtError::VertexBuildFailed`] if a vertex cannot be constructed, or
/// [`crate::CdtError::DelaunayGenerationFailed`] if upstream periodic Delaunay
/// construction rejects the point set.
///
/// # Examples
///
/// Build the minimal 3 ร— 3 periodic toroidal lattice used by
/// [`CdtTriangulation::from_toroidal_cdt`](crate::CdtTriangulation::from_toroidal_cdt)
/// and validate it with the upstream Level 1-4 checks:
///
/// ```
/// use causal_triangulations::{CdtError, CdtResult, DelaunayValidationLevel};
/// use causal_triangulations::prelude::geometry::*;
///
/// fn main() -> CdtResult<()> {
///     const N: usize = 3;
///     const LABELS: [u32; 3] = [0, 1, 2];
///     const T: usize = LABELS.len();
///     let mut vertices: Vec<([f64; 2], u32)> = Vec::with_capacity(N * T);
///
///     for (t, label) in LABELS.into_iter().enumerate() {
///         for i in 0..N {
///             #[allow(clippy::cast_precision_loss)]
///             let coord = [i as f64, t as f64];
///             vertices.push((coord, label));
///         }
///     }
///
///     let dt = build_periodic_toroidal_delaunay2(&vertices, [3.0, 3.0])?;
///     assert_eq!(dt.number_of_vertices(), N * T);
///     assert_eq!(dt.number_of_simplices(), 2 * N * T);
///
///     let backend = DelaunayBackend2D::from_triangulation(dt).map_err(|err| {
///         CdtError::DelaunayValidationFailed {
///             level: DelaunayValidationLevel::Four,
///             detail: err.to_string(),
///         }
///     })?;
///     backend.validate_delaunay().map_err(|err| CdtError::DelaunayValidationFailed {
///         level: DelaunayValidationLevel::Four,
///         detail: err.to_string(),
///     })?;
///     Ok(())
/// }
/// ```
pub fn build_periodic_toroidal_delaunay2(
    coords_with_data: &[([f64; 2], u32)],
    domain: [f64; 2],
) -> CdtResult<DelaunayTriangulation2D> {
    validate_toroidal_domain(domain)?;
    validate_explicit_coordinates(coords_with_data)?;

    let vertices: Vec<_> = coords_with_data
        .iter()
        .enumerate()
        .map(|(i, (coord, data))| {
            let point = Point::<f64, 2>::new(*coord);
            VertexBuilder::<f64, u32, 2>::default()
                .point(point)
                .data(*data)
                .build()
                .map_err(|e| CdtError::VertexBuildFailed {
                    context: format!("periodic toroidal vertex {i}"),
                    underlying_error: e.to_string(),
                })
        })
        .collect::<CdtResult<Vec<_>>>()?;

    let vertex_count = u32::try_from(vertices.len()).unwrap_or(u32::MAX);
    let coordinate_range = coords_with_data
        .iter()
        .flat_map(|(c, _)| c.iter().copied())
        .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), v| {
            (lo.min(v), hi.max(v))
        });

    DelaunayTriangulationBuilder::from_vertices(&vertices)
        .toroidal_periodic(domain)
        .topology_guarantee(TopologyGuarantee::PLManifold)
        .build::<i32>()
        .map_err(|e| CdtError::DelaunayGenerationFailed {
            vertex_count,
            coordinate_range,
            attempt: 1,
            underlying_error: e.to_string(),
        })
}

// =========================================================================
// Test helpers (panicking convenience wrappers, compiled only during tests)
// =========================================================================

/// Generates a random Delaunay triangulation. Panics on failure.
#[cfg(test)]
#[must_use]
pub(crate) fn random_delaunay2(
    number_of_vertices: u32,
    coordinate_range: (f64, f64),
) -> DelaunayTriangulation2D {
    generate_delaunay2(number_of_vertices, coordinate_range, None).unwrap_or_else(|_| {
        panic!(
            "Failed to generate random Delaunay triangulation with {number_of_vertices} vertices"
        )
    })
}

/// Generates a seeded Delaunay triangulation. Panics on failure.
#[cfg(test)]
#[must_use]
pub(crate) fn seeded_delaunay2(
    number_of_vertices: u32,
    coordinate_range: (f64, f64),
    seed: u64,
) -> DelaunayTriangulation2D {
    generate_delaunay2(number_of_vertices, coordinate_range, Some(seed)).unwrap_or_else(
        |_| {
            panic!(
                "Failed to generate seeded Delaunay triangulation with {number_of_vertices} vertices and seed {seed}"
            )
        },
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::errors::CdtError;
    use crate::geometry::DelaunayBackend2D;
    use std::assert_matches;
    use std::collections::HashMap;

    /// Produces an order-independent snapshot of vertices and simplex connectivity for seeded tests.
    fn triangulation_signature(dt: &DelaunayTriangulation2D) -> (Vec<String>, Vec<Vec<String>>) {
        let mut vertex_coords: Vec<_> = dt
            .vertices()
            .map(|(_, vertex)| format!("{:?}", vertex.point().coords()))
            .collect();
        vertex_coords.sort();

        let coord_by_key: HashMap<_, _> = dt
            .vertices()
            .map(|(key, vertex)| (key, format!("{:?}", vertex.point().coords())))
            .collect();

        let mut simplices: Vec<_> = dt
            .simplices()
            .map(|(_, simplex)| {
                let mut vertices: Vec<_> = simplex
                    .vertices()
                    .iter()
                    .map(|key| {
                        coord_by_key
                            .get(key)
                            .cloned()
                            .expect("simplex vertices should refer to live vertices")
                    })
                    .collect();
                vertices.sort();
                vertices
            })
            .collect();
        simplices.sort();

        (vertex_coords, simplices)
    }

    #[test]
    fn test_generate_delaunay2_vertex_build_error_context() {
        let error = generate_delaunay2_vertex_build_error(5, "missing point".to_string());

        assert_matches!(
            error,
            CdtError::VertexBuildFailed {
                ref context,
                ref underlying_error,
            } if context == "generate_delaunay2(5 vertices)"
                && underlying_error == "missing point"
        );
    }

    #[test]
    fn test_build_delaunay2_from_simplices_single_triangle() {
        // Default topology (PL-manifold + Euclidean) should accept a single
        // triangle with the standard 0-1 strip labeling.
        let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
        let simplices = vec![vec![0, 1, 2]];

        let dt = build_delaunay2_from_simplices(&vertices, &simplices)
            .expect("single-triangle explicit mesh should build with defaults");
        assert_eq!(dt.number_of_vertices(), 3);
        assert_eq!(dt.number_of_simplices(), 1);
    }

    #[test]
    fn test_build_delaunay2_from_simplices_rejects_bad_index() {
        // Simplex references vertex 3 which doesn't exist (only indices 0..3 are valid).
        let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
        let simplices = vec![vec![0, 1, 3]];

        let result = build_delaunay2_from_simplices(&vertices, &simplices);
        assert_matches!(
            result,
            Err(CdtError::DelaunayGenerationFailed { .. }),
            "explicit builder must reject out-of-bounds vertex indices"
        );
    }

    #[test]
    fn test_build_delaunay2_with_topology_euclidean() {
        // Same single-triangle mesh, but with explicit topology metadata.
        let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
        let simplices = vec![vec![0, 1, 2]];

        let dt = build_delaunay2_with_topology(
            &vertices,
            &simplices,
            TopologyGuarantee::DEFAULT,
            GlobalTopology::Euclidean,
        )
        .expect("single-triangle explicit mesh with explicit topology should build");
        assert_eq!(dt.number_of_vertices(), 3);
        assert_eq!(dt.number_of_simplices(), 1);
    }

    #[test]
    fn test_explicit_toroidal_simplices_are_rejected() {
        // A real 3ร—3 toroidal mesh: V=9, F=18, E=27, ฯ‡=0.
        const N: usize = 3;
        const T: usize = 3;
        let mut vertices: Vec<([f64; 2], u32)> = Vec::with_capacity(N * T);
        for t in 0..T {
            for i in 0..N {
                #[expect(
                    clippy::cast_precision_loss,
                    reason = "small deterministic test indices are converted to normalized f64 coordinates"
                )]
                let coord = [i as f64 / N as f64, t as f64 / T as f64];
                let label = u32::try_from(t).expect("slice index fits in u32");
                vertices.push((coord, label));
            }
        }
        let mut simplices: Vec<Vec<usize>> = Vec::with_capacity(2 * N * T);
        for t in 0..T {
            let t_next = (t + 1) % T;
            for i in 0..N {
                let i_next = (i + 1) % N;
                simplices.push(vec![i + t * N, i_next + t * N, i + t_next * N]);
                simplices.push(vec![i_next + t * N, i_next + t_next * N, i + t_next * N]);
            }
        }

        let error = build_toroidal_delaunay2(&vertices, &simplices, [1.0, 1.0])
            .expect_err("explicit toroidal topology should report upstream limitation");
        assert_matches!(
            error,
            CdtError::DelaunayGenerationFailed {
                vertex_count: 9,
                ref underlying_error,
                ..
            } if underlying_error.contains(
                "Explicit non-Euclidean connectivity is not supported for Toroidal"
            ),
            "explicit toroidal mesh should fail with the upstream topology limitation"
        );
    }

    #[test]
    fn test_build_periodic_toroidal_delaunay2_3x3_validates_level_1_to_4() {
        const N: usize = 3;
        const T: usize = 3;
        const DOMAIN: [f64; 2] = [3.0, 3.0];
        let mut vertices: Vec<([f64; 2], u32)> = Vec::with_capacity(N * T);
        for t in 0..T {
            for i in 0..N {
                #[expect(
                    clippy::cast_precision_loss,
                    reason = "small deterministic test indices are converted to f64 lattice coordinates"
                )]
                let coord = [i as f64, t as f64];
                let label = u32::try_from(t).expect("slice index fits in u32");
                vertices.push((coord, label));
            }
        }

        let dt = build_periodic_toroidal_delaunay2(&vertices, DOMAIN)
            .expect("periodic 3ร—3 toroidal mesh should build");
        assert_eq!(dt.number_of_vertices(), N * T);
        assert_eq!(dt.number_of_simplices(), 2 * N * T);

        let backend = DelaunayBackend2D::from_triangulation(dt)
            .expect("periodic toroidal mesh should validate");
        backend
            .validate_delaunay()
            .expect("periodic toroidal mesh must pass upstream Level 1-4 validation");
    }

    #[test]
    fn test_build_periodic_toroidal_delaunay2_rejects_invalid_domain() {
        let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.0, 1.0], 1)];

        for (domain, expected_value) in [
            ([0.0, 3.0], "axis 0 period 0"),
            ([-1.0, 3.0], "axis 0 period -1"),
            ([3.0, f64::NAN], "axis 1 period NaN"),
            ([f64::INFINITY, 3.0], "axis 0 period inf"),
        ] {
            let result = build_periodic_toroidal_delaunay2(&vertices, domain);
            assert_matches!(
                result,
                Err(CdtError::InvalidGenerationParameters {
                    ref issue,
                    ref provided_value,
                    ref expected_range,
                }) if *issue == GenerationParameterIssue::InvalidToroidalDomain
                    && provided_value == expected_value
                    && expected_range == "finite and positive periods",
                "invalid periodic toroidal domain {domain:?} should be rejected"
            );
        }
    }

    #[test]
    fn test_build_periodic_toroidal_delaunay2_rejects_non_finite_coordinate() {
        let vertices = [
            ([0.0, 0.0], 0u32),
            ([1.0, f64::NEG_INFINITY], 0),
            ([0.0, 1.0], 1),
        ];

        let result = build_periodic_toroidal_delaunay2(&vertices, [3.0, 3.0]);
        assert_matches!(
            result,
            Err(CdtError::InvalidGenerationParameters {
                ref issue,
                ref provided_value,
                ref expected_range,
            }) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
                && provided_value == "vertex 1 axis 1 = -inf"
                && expected_range == "finite coordinate values",
            "periodic toroidal non-finite coordinate should be rejected"
        );
    }

    #[test]
    fn test_generate_delaunay2_valid_parameters() {
        let result = generate_delaunay2(4, (0.0, 10.0), None);
        assert!(
            result.is_ok(),
            "Should successfully generate triangulation with valid parameters"
        );

        let dt = result.unwrap();
        assert_eq!(dt.number_of_vertices(), 4, "Should have 4 vertices");
        assert!(
            dt.number_of_simplices() > 0,
            "Should have at least one simplex"
        );
    }

    #[test]
    fn test_generate_delaunay2_with_seed() {
        let seed = 12345;
        let result1 = generate_delaunay2(4, (0.0, 10.0), Some(seed));
        let result2 = generate_delaunay2(4, (0.0, 10.0), Some(seed));

        assert!(result1.is_ok(), "First generation should succeed");
        assert!(result2.is_ok(), "Second generation should succeed");

        let dt1 = result1.unwrap();
        let dt2 = result2.unwrap();

        // With the same seed, should produce identical triangulations
        assert_eq!(
            dt1.number_of_vertices(),
            dt2.number_of_vertices(),
            "Should have same vertex count"
        );
        assert_eq!(
            dt1.number_of_simplices(),
            dt2.number_of_simplices(),
            "Should have same simplex count"
        );
        assert_eq!(
            triangulation_signature(&dt1),
            triangulation_signature(&dt2),
            "Seeded generation should produce identical vertex coordinates and simplex connectivity"
        );
    }

    #[test]
    fn test_generate_delaunay2_insufficient_vertices() {
        let result = generate_delaunay2(2, (0.0, 10.0), None);
        assert!(result.is_err(), "Should fail with insufficient vertices");

        match result.unwrap_err() {
            CdtError::InvalidGenerationParameters {
                issue,
                provided_value,
                expected_range,
            } => {
                assert_eq!(issue, GenerationParameterIssue::InsufficientVertexCount);
                assert_eq!(provided_value, "2");
                assert_eq!(expected_range, "โ‰ฅ 3");
            }
            _ => panic!("Expected InvalidGenerationParameters error"),
        }
    }

    #[test]
    fn test_generate_delaunay2_invalid_range() {
        let result = generate_delaunay2(4, (10.0, 5.0), None);
        assert!(result.is_err(), "Should fail with invalid coordinate range");

        match result.unwrap_err() {
            CdtError::InvalidGenerationParameters {
                issue,
                provided_value,
                expected_range,
            } => {
                assert_eq!(issue, GenerationParameterIssue::InvalidCoordinateRange);
                assert_eq!(provided_value, "[10, 5]");
                assert_eq!(expected_range, "finite min < max");
            }
            _ => panic!("Expected InvalidGenerationParameters error"),
        }
    }

    #[test]
    fn test_generate_delaunay2_equal_range() {
        let result = generate_delaunay2(4, (5.0, 5.0), None);
        assert!(result.is_err(), "Should fail with equal coordinate range");

        match result.unwrap_err() {
            CdtError::InvalidGenerationParameters { issue, .. } => {
                assert_eq!(issue, GenerationParameterIssue::InvalidCoordinateRange);
            }
            _ => panic!("Expected InvalidGenerationParameters error"),
        }
    }

    #[test]
    fn test_generate_delaunay2_rejects_non_finite_range() {
        for range in [(f64::NAN, 1.0), (0.0, f64::INFINITY)] {
            let result = generate_delaunay2(4, range, None);
            assert_matches!(
                result,
                Err(CdtError::InvalidGenerationParameters {
                    ref issue,
                    ref expected_range,
                    ..
                }) if *issue == GenerationParameterIssue::InvalidCoordinateRange
                    && expected_range == "finite min < max",
                "non-finite range {range:?} should be rejected"
            );
        }
    }

    #[test]
    fn test_build_delaunay2_with_data_rejects_non_finite_coordinate() {
        let vertices = [([0.0, 0.0], 0u32), ([1.0, f64::NAN], 0), ([0.5, 1.0], 1)];

        let result = build_delaunay2_with_data(&vertices);
        assert_matches!(
            result,
            Err(CdtError::InvalidGenerationParameters {
                ref issue,
                ref provided_value,
                ref expected_range,
            }) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
                && provided_value == "vertex 1 axis 1 = NaN"
                && expected_range == "finite coordinate values",
            "explicit non-finite coordinate should be rejected"
        );
    }

    #[test]
    fn test_build_delaunay2_from_simplices_rejects_non_finite_coordinate() {
        let vertices = [
            ([0.0, 0.0], 0u32),
            ([1.0, 0.0], 0),
            ([0.5, f64::NEG_INFINITY], 1),
        ];
        let simplices = vec![vec![0, 1, 2]];

        let result = build_delaunay2_from_simplices(&vertices, &simplices);
        assert_matches!(
            result,
            Err(CdtError::InvalidGenerationParameters {
                ref issue,
                ref provided_value,
                ref expected_range,
            }) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
                && provided_value == "vertex 2 axis 1 = -inf"
                && expected_range == "finite coordinate values",
            "delegating explicit-simplex builder should reject non-finite coordinates"
        );
    }

    #[test]
    fn test_build_delaunay2_with_topology_rejects_non_finite_coordinate() {
        let vertices = [
            ([0.0, 0.0], 0u32),
            ([f64::INFINITY, 0.0], 0),
            ([0.5, 1.0], 1),
        ];
        let simplices = vec![vec![0, 1, 2]];

        let result = build_delaunay2_with_topology(
            &vertices,
            &simplices,
            TopologyGuarantee::DEFAULT,
            GlobalTopology::Euclidean,
        );
        assert_matches!(
            result,
            Err(CdtError::InvalidGenerationParameters {
                ref issue,
                ref provided_value,
                ref expected_range,
            }) if *issue == GenerationParameterIssue::NonFiniteVertexCoordinate
                && provided_value == "vertex 1 axis 0 = inf"
                && expected_range == "finite coordinate values",
            "explicit non-finite topology coordinate should be rejected"
        );
    }

    #[test]
    fn test_build_toroidal_delaunay2_rejects_invalid_domain() {
        let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
        let simplices = vec![vec![0, 1, 2]];

        for (domain, expected_value) in [
            ([0.0, 1.0], "axis 0 period 0"),
            ([-1.0, 1.0], "axis 0 period -1"),
            ([1.0, f64::NAN], "axis 1 period NaN"),
            ([f64::INFINITY, 1.0], "axis 0 period inf"),
        ] {
            let result = build_toroidal_delaunay2(&vertices, &simplices, domain);
            assert_matches!(
                result,
                Err(CdtError::InvalidGenerationParameters {
                    ref issue,
                    ref provided_value,
                    ref expected_range,
                }) if *issue == GenerationParameterIssue::InvalidToroidalDomain
                    && provided_value == expected_value
                    && expected_range == "finite and positive periods",
                "invalid domain {domain:?} should be rejected"
            );
        }
    }

    #[test]
    fn test_invalid_toroidal_domain_display_is_actionable() {
        let vertices = [([0.0, 0.0], 0u32), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
        let simplices = vec![vec![0, 1, 2]];

        let error = build_toroidal_delaunay2(&vertices, &simplices, [-1.0, 1.0])
            .expect_err("negative toroidal period should be rejected");
        assert_eq!(
            error.to_string(),
            "Invalid triangulation parameters: Invalid toroidal domain (got: axis 0 period -1, expected: finite and positive periods)"
        );
    }

    #[test]
    fn test_generate_delaunay2_various_sizes() {
        let test_cases = [(3, "minimal"), (5, "small"), (10, "medium"), (20, "large")];

        for (vertex_count, description) in test_cases {
            let result = generate_delaunay2(vertex_count, (0.0, 100.0), None);
            assert!(
                result.is_ok(),
                "Should generate {description} triangulation with {vertex_count} vertices"
            );

            let dt = result.unwrap();
            assert_eq!(
                dt.number_of_vertices(),
                vertex_count as usize,
                "Should have {vertex_count} vertices for {description} triangulation"
            );
            assert!(
                dt.number_of_simplices() > 0,
                "Should have at least one simplex for {description} triangulation"
            );
        }
    }

    #[test]
    fn test_generate_delaunay2_different_ranges() {
        let ranges = [(0.0, 1.0), (-10.0, 10.0), (100.0, 200.0), (-50.0, 0.0)];

        for range in ranges {
            let result = generate_delaunay2(4, range, None);
            assert!(
                result.is_ok(),
                "Should generate triangulation with range {range:?}"
            );

            let dt = result.unwrap();
            assert_eq!(dt.number_of_vertices(), 4, "Should have 4 vertices");
        }
    }

    #[test]
    fn test_random_delaunay2_success() {
        let dt = random_delaunay2(5, (0.0, 10.0));
        assert_eq!(dt.number_of_vertices(), 5, "Should have 5 vertices");
        assert!(
            dt.number_of_simplices() > 0,
            "Should have at least one simplex"
        );
    }

    #[test]
    fn test_random_delaunay2_various_sizes() {
        let sizes = [3, 4, 6, 8, 12];

        for size in sizes {
            let dt = random_delaunay2(size, (0.0, 50.0));
            assert_eq!(
                dt.number_of_vertices(),
                size as usize,
                "Should have {size} vertices"
            );
            assert!(
                dt.number_of_simplices() > 0,
                "Should have simplices for size {size}"
            );
        }
    }

    #[test]
    #[should_panic(expected = "Failed to generate random Delaunay triangulation with 2 vertices")]
    fn test_random_delaunay2_panic_insufficient_vertices() {
        let _ = random_delaunay2(2, (0.0, 10.0));
    }

    #[test]
    #[should_panic(expected = "Failed to generate random Delaunay triangulation with 4 vertices")]
    fn test_random_delaunay2_panic_invalid_range() {
        let _ = random_delaunay2(4, (10.0, 5.0));
    }

    #[test]
    fn test_seeded_delaunay2_deterministic() {
        let seed = 42;
        let dt1 = seeded_delaunay2(6, (0.0, 20.0), seed);
        let dt2 = seeded_delaunay2(6, (0.0, 20.0), seed);

        // Should produce identical results
        assert_eq!(
            dt1.number_of_vertices(),
            dt2.number_of_vertices(),
            "Should have same vertex count"
        );
        assert_eq!(
            dt1.number_of_simplices(),
            dt2.number_of_simplices(),
            "Should have same simplex count"
        );

        // Verify expected properties
        assert_eq!(dt1.number_of_vertices(), 6, "Should have 6 vertices");
        assert!(dt1.number_of_simplices() > 0, "Should have simplices");
    }

    #[test]
    fn test_seeded_delaunay2_different_seeds() {
        let dt1 = seeded_delaunay2(5, (0.0, 10.0), 123);
        let dt2 = seeded_delaunay2(5, (0.0, 10.0), 456);

        // Both should succeed and have same vertex count
        assert_eq!(dt1.number_of_vertices(), 5, "First should have 5 vertices");
        assert_eq!(dt2.number_of_vertices(), 5, "Second should have 5 vertices");

        // With different seeds, they should potentially have different structures
        // (though this is probabilistic and not guaranteed)
    }

    #[test]
    fn test_seeded_delaunay2_various_seeds() {
        let seeds = [1, 100, 1000, u64::MAX];

        for seed in seeds {
            let dt = seeded_delaunay2(4, (-5.0, 5.0), seed);
            assert_eq!(
                dt.number_of_vertices(),
                4,
                "Should have 4 vertices with seed {seed}"
            );
            assert!(
                dt.number_of_simplices() > 0,
                "Should have simplices with seed {seed}"
            );
        }
    }

    #[test]
    #[should_panic(
        expected = "Failed to generate seeded Delaunay triangulation with 1 vertices and seed 42"
    )]
    fn test_seeded_delaunay2_panic_insufficient_vertices() {
        let _ = seeded_delaunay2(1, (0.0, 10.0), 42);
    }

    #[test]
    #[should_panic(
        expected = "Failed to generate seeded Delaunay triangulation with 5 vertices and seed 123"
    )]
    fn test_seeded_delaunay2_panic_invalid_range() {
        let _ = seeded_delaunay2(5, (15.0, 10.0), 123);
    }

    #[test]
    fn test_euler_characteristic_properties() {
        // Test that generated triangulations satisfy basic topological properties
        let dt = random_delaunay2(8, (0.0, 10.0));

        #[expect(
            clippy::cast_possible_truncation,
            clippy::cast_possible_wrap,
            reason = "test triangulation sizes are tiny and fit in i32"
        )]
        let v = dt.number_of_vertices() as i32;
        #[expect(
            clippy::cast_possible_truncation,
            clippy::cast_possible_wrap,
            reason = "test triangulation sizes are tiny and fit in i32"
        )]
        let c = dt.number_of_simplices() as i32; // faces in 2D

        // Basic sanity checks
        assert!(v >= 3, "Should have at least 3 vertices");
        assert!(c >= 1, "Should have at least 1 simplex/face");

        // For a 2D triangulation, we can estimate edge count
        // In a typical triangulation: E โ‰ˆ 3V - 6 for planar graphs
        // But this is an approximation since the delaunay crate may handle boundaries differently
    }

    #[test]
    fn test_coordinate_range_bounds() {
        // Test representative finite coordinate ranges.  Astronomical f64
        // spans can violate robust predicate preconditions before they are
        // meaningful geometry fixtures.
        let ranges = [
            (-1.0e6, 1.0e6), // Broad symmetric range
            (-1000.0, 1000.0),
            (0.001, 0.002),
            (-0.5, 0.5),
        ];

        for range in ranges {
            let result = generate_delaunay2(4, range, Some(789));
            assert!(result.is_ok(), "Should handle coordinate range {range:?}");
        }
    }

    #[test]
    fn test_build_delaunay2_with_data_empty_input() {
        // Empty input should produce a valid (but empty) triangulation
        // or fail gracefully โ€” either way, no panic.
        let result = build_delaunay2_with_data(&[]);
        // The delaunay builder may accept or reject zero vertices;
        // we just verify it doesn't panic.
        match result {
            Ok(dt) => assert_eq!(dt.number_of_vertices(), 0),
            Err(e) => {
                // Should be a generation error, not a panic
                let msg = e.to_string();
                assert!(
                    msg.contains("generation")
                        || msg.contains("failed")
                        || msg.contains("Delaunay"),
                    "Error should be descriptive: {msg}"
                );
            }
        }
    }

    #[test]
    fn test_build_delaunay2_with_data_single_point() {
        // A single point is insufficient for a triangulation.
        let result = build_delaunay2_with_data(&[([0.0, 0.0], 0)]);
        // May succeed with degenerate DT or fail โ€” no panic.
        if let Ok(dt) = result {
            assert_eq!(dt.number_of_vertices(), 1);
        }
    }

    #[test]
    fn test_build_delaunay2_with_data_valid_triangle() {
        let coords = [([0.0, 0.0], 0), ([1.0, 0.0], 0), ([0.5, 1.0], 1)];
        let dt = build_delaunay2_with_data(&coords)
            .expect("Should build triangulation from 3 non-degenerate points");
        assert_eq!(dt.number_of_vertices(), 3);
        assert!(dt.number_of_simplices() >= 1);
    }

    #[test]
    fn test_seeded_reproducibility_multiple_calls() {
        // Test that multiple calls with the same seed produce identical results
        let seed = 999;
        let params = (7, (-10.0, 10.0));

        let results: Vec<_> = (0..3)
            .map(|_| seeded_delaunay2(params.0, params.1, seed))
            .collect();

        // All results should have the same structure
        for (i, dt) in results.iter().enumerate() {
            assert_eq!(
                dt.number_of_vertices(),
                7,
                "Result {i} should have 7 vertices"
            );
            assert!(
                dt.number_of_simplices() > 0,
                "Result {i} should have simplices"
            );
        }

        // All results should be identical in structure
        let first_vertex_count = results[0].number_of_vertices();
        let first_simplex_count = results[0].number_of_simplices();

        for (i, dt) in results.iter().enumerate().skip(1) {
            assert_eq!(
                dt.number_of_vertices(),
                first_vertex_count,
                "Result {i} vertex count should match first result"
            );
            assert_eq!(
                dt.number_of_simplices(),
                first_simplex_count,
                "Result {i} simplex count should match first result"
            );
        }
    }
}