1#![forbid(unsafe_code)]
2
3use crate::cdt::ergodic_moves::MoveType;
6use crate::cdt::foliation::FoliationError;
7use crate::config::CdtTopology;
8use markov_chain_monte_carlo::{McmcError, StepOutcome};
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[non_exhaustive]
14pub enum DelaunayValidationLevel {
15 One,
17 Two,
19 Three,
21 Four,
23}
24
25impl fmt::Display for DelaunayValidationLevel {
26 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 Self::One => formatter.write_str("Level 1"),
29 Self::Two => formatter.write_str("Level 1-2"),
30 Self::Three => formatter.write_str("Level 1-3"),
31 Self::Four => formatter.write_str("Level 1-4"),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62#[non_exhaustive]
63pub enum ConfigurationSetting {
64 Dimension,
66 Vertices,
68 Timeslices,
70 Temperature,
72 Steps,
74 ThermalizationSteps,
76 MeasurementFrequency,
78 MeasurementSchedule,
80 Coupling0,
82 Coupling2,
84 CosmologicalConstant,
86 VolumeProfile,
88}
89
90impl fmt::Display for ConfigurationSetting {
91 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::Dimension => formatter.write_str("dimension"),
94 Self::Vertices => formatter.write_str("vertices"),
95 Self::Timeslices => formatter.write_str("timeslices"),
96 Self::Temperature => formatter.write_str("temperature"),
97 Self::Steps => formatter.write_str("steps"),
98 Self::ThermalizationSteps => formatter.write_str("thermalization_steps"),
99 Self::MeasurementFrequency => formatter.write_str("measurement_frequency"),
100 Self::MeasurementSchedule => formatter.write_str("measurement schedule"),
101 Self::Coupling0 => formatter.write_str("coupling_0"),
102 Self::Coupling2 => formatter.write_str("coupling_2"),
103 Self::CosmologicalConstant => formatter.write_str("cosmological_constant"),
104 Self::VolumeProfile => formatter.write_str("volume_profile"),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
133#[non_exhaustive]
134pub enum GenerationParameterIssue {
135 InvalidCoordinateRange,
137 InvalidToroidalDomain,
139 NonFiniteVertexCoordinate,
141 InsufficientVertexCount,
143 InsufficientVerticesPerSlice,
145 InsufficientNumberOfTimeSlices,
147 NonPositiveSliceCount,
149 EmptyVolumeProfile,
151 VolumeProfileLengthOverflow,
153 InsufficientVerticesInVolumeProfileSlice,
155 VertexCountOverflow,
157 SimplexCountOverflow,
159}
160
161impl fmt::Display for GenerationParameterIssue {
162 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 Self::InvalidCoordinateRange => formatter.write_str("Invalid coordinate range"),
165 Self::InvalidToroidalDomain => formatter.write_str("Invalid toroidal domain"),
166 Self::NonFiniteVertexCoordinate => formatter.write_str("Non-finite vertex coordinate"),
167 Self::InsufficientVertexCount => formatter.write_str("Insufficient vertex count"),
168 Self::InsufficientVerticesPerSlice => {
169 formatter.write_str("Insufficient vertices per slice")
170 }
171 Self::InsufficientNumberOfTimeSlices => {
172 formatter.write_str("Insufficient number of time slices")
173 }
174 Self::NonPositiveSliceCount => formatter.write_str("Number of slices must be positive"),
175 Self::EmptyVolumeProfile => formatter.write_str("Empty volume profile"),
176 Self::VolumeProfileLengthOverflow => {
177 formatter.write_str("Volume profile length overflow")
178 }
179 Self::InsufficientVerticesInVolumeProfileSlice => {
180 formatter.write_str("Insufficient vertices in volume-profile slice")
181 }
182 Self::VertexCountOverflow => formatter.write_str("Vertex count overflow"),
183 Self::SimplexCountOverflow => formatter.write_str("Simplex count overflow"),
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
216#[non_exhaustive]
217pub enum TriangulationMetadataField {
218 Timeslices,
220 Dimension,
222}
223
224impl fmt::Display for TriangulationMetadataField {
225 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match self {
227 Self::Timeslices => formatter.write_str("timeslices"),
228 Self::Dimension => formatter.write_str("dimension"),
229 }
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
235#[non_exhaustive]
236pub enum OutputFormat {
237 Csv,
239 Json,
241}
242
243impl fmt::Display for OutputFormat {
244 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
245 match self {
246 Self::Csv => formatter.write_str("CSV"),
247 Self::Json => formatter.write_str("JSON"),
248 }
249 }
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
254#[non_exhaustive]
255pub enum CheckpointOperation {
256 Serialize,
258 Deserialize,
260}
261
262impl fmt::Display for CheckpointOperation {
263 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
264 match self {
265 Self::Serialize => formatter.write_str("serialize"),
266 Self::Deserialize => formatter.write_str("deserialize"),
267 }
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
273#[non_exhaustive]
274pub enum BackendMutationOperation {
275 SetSimplexDataByKey,
277 SetVertexDataByKey,
279 SetVertexData,
281 SubdivideFace,
283 RemoveVertex,
285 FlipEdge,
287}
288
289impl fmt::Display for BackendMutationOperation {
290 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
291 match self {
292 Self::SetSimplexDataByKey => formatter.write_str("set_simplex_data_by_key"),
293 Self::SetVertexDataByKey => formatter.write_str("set_vertex_data_by_key"),
294 Self::SetVertexData => formatter.write_str("set_vertex_data"),
295 Self::SubdivideFace => formatter.write_str("subdivide_face"),
296 Self::RemoveVertex => formatter.write_str("remove_vertex"),
297 Self::FlipEdge => formatter.write_str("flip_edge"),
298 }
299 }
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
304#[non_exhaustive]
305pub enum CdtValidationCheck {
306 Geometry,
308 FoliationAssignment,
310 Causality,
312 SimplexClassification,
314 ErgodicMoveCandidateGeometry,
316}
317
318impl fmt::Display for CdtValidationCheck {
319 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
320 match self {
321 Self::Geometry => formatter.write_str("geometry"),
322 Self::FoliationAssignment => formatter.write_str("foliation_assignment"),
323 Self::Causality => formatter.write_str("causality"),
324 Self::SimplexClassification => formatter.write_str("simplex_classification"),
325 Self::ErgodicMoveCandidateGeometry => {
326 formatter.write_str("ergodic_move_candidate_geometry")
327 }
328 }
329 }
330}
331
332#[derive(Debug, Clone, PartialEq, Eq, Hash)]
353#[non_exhaustive]
354pub enum CdtValidationFailure {
355 BackendGeometry {
357 detail: String,
359 },
360 FaceVerticesUnavailable {
362 face: String,
364 detail: String,
366 },
367 FaceVertexCount {
369 face: String,
371 actual: usize,
373 expected: usize,
375 },
376 MissingVertexTimeLabel {
378 vertex: String,
380 },
381 InvalidCdtTriangle {
383 face: String,
385 spacelike_edges: u8,
387 timelike_edges: u8,
389 },
390 VertexCoordinateReadFailed {
392 vertex: String,
394 detail: String,
396 },
397 VertexCoordinateDimension {
399 vertex: String,
401 actual: usize,
403 expected_minimum: usize,
405 },
406 NonStrictSimplex {
408 face: String,
410 },
411 ErgodicMoveCandidateGeometry {
413 detail: String,
415 },
416}
417
418impl fmt::Display for CdtValidationFailure {
419 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
420 match self {
421 Self::BackendGeometry { detail } | Self::ErgodicMoveCandidateGeometry { detail } => {
422 formatter.write_str(detail)
423 }
424 Self::FaceVerticesUnavailable { face, detail } => {
425 write!(
426 formatter,
427 "failed to resolve vertices for face {face}: {detail}"
428 )
429 }
430 Self::FaceVertexCount {
431 face,
432 actual,
433 expected,
434 } => write!(
435 formatter,
436 "face {face} has {actual} vertices, expected {expected}"
437 ),
438 Self::MissingVertexTimeLabel { vertex } => write!(
439 formatter,
440 "vertex {vertex} has no time label in a foliated triangulation"
441 ),
442 Self::InvalidCdtTriangle {
443 face,
444 spacelike_edges,
445 timelike_edges,
446 } => write!(
447 formatter,
448 "invalid CDT triangle at face {face}: spacelike={spacelike_edges}, timelike={timelike_edges}"
449 ),
450 Self::VertexCoordinateReadFailed { vertex, detail } => {
451 write!(
452 formatter,
453 "failed to read coordinates for vertex {vertex}: {detail}"
454 )
455 }
456 Self::VertexCoordinateDimension {
457 vertex,
458 actual,
459 expected_minimum,
460 } => write!(
461 formatter,
462 "vertex {vertex} has {actual} coordinates, expected ≥ {expected_minimum}"
463 ),
464 Self::NonStrictSimplex { face } => write!(
465 formatter,
466 "face {face} is not a strict CDT simplex (expected Up or Down)"
467 ),
468 }
469 }
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
486#[non_exhaustive]
487pub enum CheckpointMoveCounter {
488 Attempted,
490 Accepted,
492 Rejected,
494}
495
496impl fmt::Display for CheckpointMoveCounter {
497 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
498 match self {
499 Self::Attempted => formatter.write_str("attempted"),
500 Self::Accepted => formatter.write_str("accepted"),
501 Self::Rejected => formatter.write_str("rejected"),
502 }
503 }
504}
505
506#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
512#[non_exhaustive]
513pub enum ProposalTelemetryCounter {
514 MoveFamilyProposals,
516 AcceptedTransitions,
518 RejectedTransitions,
520}
521
522impl fmt::Display for ProposalTelemetryCounter {
523 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
524 match self {
525 Self::MoveFamilyProposals => formatter.write_str("move-family proposals"),
526 Self::AcceptedTransitions => formatter.write_str("accepted transitions"),
527 Self::RejectedTransitions => formatter.write_str("rejected transitions"),
528 }
529 }
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
542#[non_exhaustive]
543pub enum ScalarTraceField {
544 LogProb,
546 Action,
548 DeltaAction,
550 ActionBefore,
552 ActionAfter,
554}
555
556impl fmt::Display for ScalarTraceField {
557 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
558 match self {
559 Self::LogProb => formatter.write_str("log_prob"),
560 Self::Action => formatter.write_str("action"),
561 Self::DeltaAction => formatter.write_str("delta_action"),
562 Self::ActionBefore => formatter.write_str("action_before"),
563 Self::ActionAfter => formatter.write_str("action_after"),
564 }
565 }
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
578#[non_exhaustive]
579pub enum MeasurementCountField {
580 Vertices,
582 Edges,
584 Triangles,
586}
587
588impl fmt::Display for MeasurementCountField {
589 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
590 match self {
591 Self::Vertices => formatter.write_str("vertices"),
592 Self::Edges => formatter.write_str("edges"),
593 Self::Triangles => formatter.write_str("triangles"),
594 }
595 }
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
611#[non_exhaustive]
612pub enum SimplexCountField {
613 Vertices,
615 Edges,
617 Triangles,
619}
620
621impl fmt::Display for SimplexCountField {
622 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
623 match self {
624 Self::Vertices => formatter.write_str("vertices"),
625 Self::Edges => formatter.write_str("edges"),
626 Self::Triangles => formatter.write_str("triangles"),
627 }
628 }
629}
630
631#[derive(Debug, Clone, PartialEq, thiserror::Error)]
659#[non_exhaustive]
660pub enum CheckpointResumeFailure {
661 #[error("resumed step count exceeds u32::MAX")]
663 StepCountOverflow,
664 #[error("checkpoint action mismatch: stored {stored}, recomputed {recomputed}")]
666 ActionMismatch {
667 stored: f64,
669 recomputed: f64,
671 },
672 #[error("checkpoint action is non-finite: stored {stored}")]
674 NonFiniteCheckpointAction {
675 stored: f64,
677 },
678 #[error("action configuration differs from checkpoint")]
680 IncompatibleActionConfiguration,
681 #[error("temperature differs from checkpoint")]
683 IncompatibleTemperature,
684 #[error("thermalization schedule differs from checkpoint")]
686 IncompatibleThermalizationSchedule,
687 #[error("measurement frequency differs from checkpoint")]
689 IncompatibleMeasurementFrequency,
690 #[error(
692 "chain counters do not match move statistics: chain accepted={chain_accepted}, rejected={chain_rejected}; move accepted={move_accepted}, rejected={move_rejected}"
693 )]
694 ChainCounterMismatch {
695 chain_accepted: usize,
697 chain_rejected: usize,
699 move_accepted: usize,
701 move_rejected: usize,
703 },
704 #[error(
706 "chain step count does not match checkpoint step: chain steps={chain_steps}, checkpoint step={checkpoint_step}"
707 )]
708 ChainStepMismatch {
709 chain_steps: usize,
711 checkpoint_step: u32,
713 },
714 #[error("step telemetry length mismatch: got {actual}, expected {expected}")]
716 StepTelemetryLengthMismatch {
717 actual: usize,
719 expected: usize,
721 },
722 #[error("accepted step count mismatch: got {actual}, expected {expected}")]
724 StepTelemetryAcceptedCountMismatch {
725 actual: usize,
727 expected: usize,
729 },
730 #[error("step telemetry index exceeds u32::MAX")]
732 StepTelemetryIndexOverflow,
733 #[error("step telemetry must be sequential: got step {actual}, expected {expected}")]
735 StepTelemetrySequenceMismatch {
736 actual: u32,
738 expected: u32,
740 },
741 #[error("step {step} has non-finite action_before")]
743 NonFiniteStepActionBefore {
744 step: u32,
746 },
747 #[error("step {step} has non-finite delta_action")]
749 NonFiniteStepDeltaAction {
750 step: u32,
752 },
753 #[error("step {step} action_after does not match delta_action")]
755 StepActionAfterDeltaMismatch {
756 step: u32,
758 },
759 #[error("step {step} has non-finite action_after")]
761 NonFiniteStepActionAfter {
762 step: u32,
764 },
765 #[error("scalar trace row count mismatch: got {actual}, expected {expected}")]
767 ScalarTraceLengthMismatch {
768 actual: usize,
770 expected: usize,
772 },
773 #[error("scalar trace step mismatch: got {actual}, expected {expected}")]
775 ScalarTraceStepMismatch {
776 actual: u32,
778 expected: u32,
780 },
781 #[error("scalar trace step must be nonzero: got {actual}")]
783 ScalarTraceStepZero {
784 actual: u32,
786 },
787 #[error("step {step} scalar trace move type mismatch: got {actual:?}, expected {expected:?}")]
789 ScalarTraceMoveTypeMismatch {
790 step: u32,
792 actual: MoveType,
794 expected: MoveType,
796 },
797 #[error("step {step} scalar trace accepted mismatch: got {actual}, expected {expected}")]
799 ScalarTraceAcceptedMismatch {
800 step: u32,
802 actual: bool,
804 expected: bool,
806 },
807 #[error("step {step} scalar trace delta_action does not match step telemetry")]
809 ScalarTraceDeltaActionMismatch {
810 step: u32,
812 },
813 #[error("step {step} scalar trace action_before does not match step telemetry")]
815 ScalarTraceActionBeforeMismatch {
816 step: u32,
818 },
819 #[error("step {step} scalar trace action does not match step telemetry")]
821 ScalarTraceActionMismatch {
822 step: u32,
824 },
825 #[error("step {step} scalar trace action_after does not match step telemetry")]
827 ScalarTraceActionAfterMismatch {
828 step: u32,
830 },
831 #[error("step {step} scalar trace log_prob does not match action and temperature")]
833 ScalarTraceLogProbMismatch {
834 step: u32,
836 },
837 #[error("step {step} scalar trace seed mismatch: got {actual:?}, expected {expected:?}")]
839 ScalarTraceSeedMismatch {
840 step: u32,
842 actual: Option<u64>,
844 expected: Option<u64>,
846 },
847 #[error(
849 "step {step} scalar trace volume profile total {profile_total} exceeds triangle count {triangles}"
850 )]
851 ScalarTraceVolumeProfileExceedsTriangles {
852 step: u32,
854 profile_total: u64,
856 triangles: u32,
858 },
859 #[error("step {step} scalar trace field {field} is non-finite")]
861 NonFiniteScalarTraceValue {
862 step: u32,
864 field: ScalarTraceField,
866 },
867 #[error("scalar trace accepted count mismatch: got {actual}, expected {expected}")]
869 ScalarTraceAcceptedCountMismatch {
870 actual: u64,
872 expected: u64,
874 },
875 #[error("scalar trace rejected-proposal count mismatch: got {actual}, expected {expected}")]
877 ScalarTraceRejectedProposalCountMismatch {
878 actual: u64,
880 expected: u64,
882 },
883 #[error("scalar trace no-proposal count mismatch: got {actual}, expected {expected}")]
885 ScalarTraceNoProposalCountMismatch {
886 actual: u64,
888 expected: u64,
890 },
891 #[error("scheduled measurement count exceeds usize::MAX")]
893 MeasurementCountOverflow,
894 #[error("scheduled measurement count mismatch: got {actual}, expected {expected}")]
896 MeasurementCountMismatch {
897 actual: usize,
899 expected: usize,
901 },
902 #[error("scheduled measurement step exceeds u32::MAX")]
904 MeasurementStepOverflow,
905 #[error("measurement telemetry step mismatch: got {actual}, expected {expected}")]
907 MeasurementStepMismatch {
908 actual: u32,
910 expected: u32,
912 },
913 #[error("{counter} move count exceeds u64::MAX")]
915 MoveCounterOverflow {
916 counter: CheckpointMoveCounter,
918 },
919 #[error("{move_type:?} hard-failure move count must be zero in resumable checkpoints")]
921 MoveHardFailures {
922 move_type: MoveType,
924 },
925 #[error("{move_type:?} accepted move count exceeds attempted move count")]
927 MoveAcceptedExceedsAttempted {
928 move_type: MoveType,
930 },
931 #[error("accepted move count exceeds attempted move count")]
933 TotalAcceptedExceedsAttempted,
934 #[error("{counter} move count exceeds usize::MAX")]
936 CounterConversionOverflow {
937 counter: CheckpointMoveCounter,
939 },
940 #[error("{counter} proposal telemetry count exceeds u64::MAX")]
942 ProposalCounterOverflow {
943 counter: ProposalTelemetryCounter,
945 },
946 #[error("proposal telemetry has {actual} hard failures; expected 0")]
948 ProposalHardFailures {
949 actual: u64,
951 },
952 #[error("proposal move-family count mismatch: got {actual}, expected {expected}")]
954 ProposalMoveFamilyCountMismatch {
955 actual: u64,
957 expected: u64,
959 },
960 #[error("proposal accepted-transition count mismatch: got {actual}, expected {expected}")]
962 ProposalAcceptedCountMismatch {
963 actual: u64,
965 expected: u64,
967 },
968 #[error("proposal rejected-transition count mismatch: got {actual}, expected {expected}")]
970 ProposalRejectedCountMismatch {
971 actual: u64,
973 expected: u64,
975 },
976}
977
978#[derive(Debug, Clone, PartialEq, thiserror::Error)]
1002#[non_exhaustive]
1003pub enum MetropolisMoveApplicationFailure {
1004 #[error("backend mutation failed [{operation}] on {target}: {detail}")]
1006 BackendMutation {
1007 operation: BackendMutationOperation,
1009 target: String,
1011 detail: String,
1013 },
1014 #[error(
1016 "backend mutation failed [{operation}] on {target}: {detail}; rollback failed: {rollback_errors}"
1017 )]
1018 BackendRollback {
1019 operation: BackendMutationOperation,
1021 target: String,
1023 detail: String,
1025 rollback_errors: String,
1027 },
1028 #[error("Delaunay validation failed [{level}]: {detail}")]
1030 DelaunayValidation {
1031 level: DelaunayValidationLevel,
1033 detail: String,
1035 },
1036 #[error("validation failed [{check}]: {failure}")]
1038 Validation {
1039 check: CdtValidationCheck,
1041 failure: CdtValidationFailure,
1043 },
1044 #[error(
1046 "topology mismatch for {topology}: Euler characteristic χ={euler_characteristic}, expected one of {expected_euler_characteristics:?} (V={vertices}, E={edges}, F={faces})"
1047 )]
1048 TopologyMismatch {
1049 topology: CdtTopology,
1051 euler_characteristic: i128,
1053 expected_euler_characteristics: Vec<i128>,
1055 vertices: usize,
1057 edges: usize,
1059 faces: usize,
1061 },
1062 #[error("foliation validation failed: {0}")]
1064 Foliation(FoliationError),
1065 #[error("{}", format_causality_violation(*time_0, *time_1, *step_distance))]
1067 CausalityViolation {
1068 time_0: u32,
1070 time_1: u32,
1072 step_distance: u32,
1074 },
1075 #[error("unexpected accepted-move failure: {detail}")]
1077 Unexpected {
1078 detail: String,
1080 },
1081}
1082
1083impl From<CdtError> for MetropolisMoveApplicationFailure {
1084 fn from(error: CdtError) -> Self {
1085 match error {
1086 CdtError::BackendMutationFailed {
1087 operation,
1088 target,
1089 detail,
1090 } => Self::BackendMutation {
1091 operation,
1092 target,
1093 detail,
1094 },
1095 CdtError::BackendRollbackFailed {
1096 operation,
1097 target,
1098 detail,
1099 rollback_errors,
1100 } => Self::BackendRollback {
1101 operation,
1102 target,
1103 detail,
1104 rollback_errors,
1105 },
1106 CdtError::DelaunayValidationFailed { level, detail } => {
1107 Self::DelaunayValidation { level, detail }
1108 }
1109 CdtError::ValidationFailed { check, failure } => Self::Validation { check, failure },
1110 CdtError::TopologyMismatch {
1111 topology,
1112 euler_characteristic,
1113 expected_euler_characteristics,
1114 vertices,
1115 edges,
1116 faces,
1117 } => Self::TopologyMismatch {
1118 topology,
1119 euler_characteristic,
1120 expected_euler_characteristics,
1121 vertices,
1122 edges,
1123 faces,
1124 },
1125 CdtError::Foliation(error) => Self::Foliation(error),
1126 CdtError::CausalityViolation {
1127 time_0,
1128 time_1,
1129 step_distance,
1130 } => Self::CausalityViolation {
1131 time_0,
1132 time_1,
1133 step_distance,
1134 },
1135 CdtError::MetropolisMoveApplicationFailed { source, .. }
1136 | CdtError::ProposalApplicationFailed { source, .. } => source,
1137 unexpected @ (CdtError::UnsupportedDimension(_)
1138 | CdtError::DelaunayGenerationFailed { .. }
1139 | CdtError::InvalidGenerationParameters { .. }
1140 | CdtError::InvalidConfiguration { .. }
1141 | CdtError::InvalidSimplexCount { .. }
1142 | CdtError::InvalidMeasurementAction { .. }
1143 | CdtError::InvalidMeasurementCount { .. }
1144 | CdtError::InvalidMeasurementVolumeProfile { .. }
1145 | CdtError::InvalidScalarTraceCount { .. }
1146 | CdtError::MeasurementCountOverflow { .. }
1147 | CdtError::InvalidSimulationConfiguration { .. }
1148 | CdtError::PlannedProposalStepFailed { .. }
1149 | CdtError::UnexpectedPlannedStepOutcome { .. }
1150 | CdtError::PlannedProposalTelemetryMissing { .. }
1151 | CdtError::InvalidTriangulationMetadata { .. }
1152 | CdtError::VertexBuildFailed { .. }
1153 | CdtError::Mcmc(_)
1154 | CdtError::OutputWriteFailed { .. }
1155 | CdtError::OutputPathResolutionFailed { .. }
1156 | CdtError::OutputPathConflict { .. }
1157 | CdtError::OutputReadFailed { .. }
1158 | CdtError::CheckpointSerializationFailed { .. }
1159 | CdtError::CheckpointResumeFailed { .. }) => Self::Unexpected {
1160 detail: unexpected.to_string(),
1161 },
1162 }
1163 }
1164}
1165
1166#[derive(Debug, Clone, PartialEq, thiserror::Error)]
1168#[non_exhaustive]
1169pub enum CdtError {
1170 #[error("Unsupported dimension: {0}. Only 2D is currently supported")]
1172 UnsupportedDimension(u32),
1173 #[error(
1175 "Delaunay triangulation generation failed: {vertex_count} vertices, range [{}, {}], attempt {attempt}: {underlying_error}",
1176 coordinate_range.0,
1177 coordinate_range.1
1178 )]
1179 DelaunayGenerationFailed {
1180 vertex_count: u32,
1182 coordinate_range: (f64, f64),
1184 attempt: u32,
1186 underlying_error: String,
1188 },
1189 #[error("Delaunay validation failed [{level}]: {detail}")]
1191 DelaunayValidationFailed {
1192 level: DelaunayValidationLevel,
1194 detail: String,
1196 },
1197 #[error(
1199 "Invalid triangulation parameters: {issue} (got: {provided_value}, expected: {expected_range})"
1200 )]
1201 InvalidGenerationParameters {
1202 issue: GenerationParameterIssue,
1204 provided_value: String,
1206 expected_range: String,
1208 },
1209 #[error("Invalid configuration: {setting} (got: {provided_value}, expected: {expected})")]
1211 InvalidConfiguration {
1212 setting: ConfigurationSetting,
1214 provided_value: String,
1216 expected: String,
1218 },
1219 #[error(
1221 "Invalid simulation configuration: {setting} (got: {provided_value}, expected: {expected})"
1222 )]
1223 InvalidSimulationConfiguration {
1224 setting: ConfigurationSetting,
1226 provided_value: String,
1228 expected: String,
1230 },
1231 #[error(
1233 "Invalid CDT simplex count: {field} (got: {provided_value}, expected: strictly positive)"
1234 )]
1235 InvalidSimplexCount {
1236 field: SimplexCountField,
1238 provided_value: usize,
1240 },
1241 #[error(
1244 "Invalid measurement count: {field} (got: {provided_value}, expected: strictly positive)"
1245 )]
1246 InvalidMeasurementCount {
1247 field: MeasurementCountField,
1249 provided_value: u32,
1251 },
1252 #[error(
1255 "Invalid measurement volume profile at step {step}: total {profile_total} exceeds triangle count {triangles}"
1256 )]
1257 InvalidMeasurementVolumeProfile {
1258 step: u32,
1260 profile_total: u64,
1262 triangles: u32,
1264 },
1265 #[error(
1267 "Invalid scalar trace count: {field} (got: {provided_value}, expected: strictly positive)"
1268 )]
1269 InvalidScalarTraceCount {
1270 field: MeasurementCountField,
1272 provided_value: u32,
1274 },
1275 #[error("Measurement count overflow: {field} (got: {provided_value}, max: {max})")]
1278 MeasurementCountOverflow {
1279 field: MeasurementCountField,
1281 provided_value: usize,
1283 max: u32,
1285 },
1286 #[error("Invalid measurement action at step {step}: got {provided_value}, expected finite")]
1289 InvalidMeasurementAction {
1290 step: u32,
1292 provided_value: f64,
1294 },
1295 #[error(
1302 "Metropolis accepted {move_type:?} at step {step}, but applying it failed after {attempts} attempts; source: {source}"
1303 )]
1304 MetropolisMoveApplicationFailed {
1305 step: u32,
1307 move_type: MoveType,
1309 attempts: usize,
1311 source: MetropolisMoveApplicationFailure,
1313 },
1314 #[error("CDT proposal failed while applying {move_type:?} on attempt {attempt}: {source}")]
1316 ProposalApplicationFailed {
1317 move_type: MoveType,
1319 attempt: usize,
1321 source: MetropolisMoveApplicationFailure,
1323 },
1324 #[error("planned CDT proposal step {step} completed without required proposal telemetry")]
1326 PlannedProposalTelemetryMissing {
1327 step: u32,
1329 },
1330 #[error("planned CDT proposal step {step} failed: {detail}")]
1332 PlannedProposalStepFailed {
1333 step: u32,
1335 detail: String,
1337 },
1338 #[error("planned CDT proposal step {step} returned unsupported upstream outcome {outcome:?}")]
1340 UnexpectedPlannedStepOutcome {
1341 step: u32,
1343 outcome: StepOutcome,
1345 },
1346 #[error(
1348 "Invalid triangulation metadata: {field} for {topology} (got: {provided_value}, expected: {expected})"
1349 )]
1350 InvalidTriangulationMetadata {
1351 field: TriangulationMetadataField,
1353 topology: CdtTopology,
1355 provided_value: String,
1357 expected: String,
1359 },
1360 #[error("Validation failed [{check}]: {failure}")]
1366 ValidationFailed {
1367 check: CdtValidationCheck,
1369 failure: CdtValidationFailure,
1371 },
1372 #[error(
1374 "Topology mismatch for {topology}: Euler characteristic χ={euler_characteristic}, expected one of {expected_euler_characteristics:?} (V={vertices}, E={edges}, F={faces})"
1375 )]
1376 TopologyMismatch {
1377 topology: CdtTopology,
1379 euler_characteristic: i128,
1381 expected_euler_characteristics: Vec<i128>,
1383 vertices: usize,
1385 edges: usize,
1387 faces: usize,
1389 },
1390 #[error("Foliation validation failed: {0}")]
1392 Foliation(#[from] FoliationError),
1393 #[error("Vertex construction failed [{context}]: {underlying_error}")]
1395 VertexBuildFailed {
1396 context: String,
1398 underlying_error: String,
1400 },
1401 #[error("Backend mutation failed [{operation}] on {target}: {detail}")]
1403 BackendMutationFailed {
1404 operation: BackendMutationOperation,
1406 target: String,
1408 detail: String,
1410 },
1411 #[error(
1413 "Backend mutation failed [{operation}] on {target}: {detail}; rollback failed: {rollback_errors}"
1414 )]
1415 BackendRollbackFailed {
1416 operation: BackendMutationOperation,
1418 target: String,
1420 detail: String,
1422 rollback_errors: String,
1424 },
1425 #[error("{}", format_causality_violation(*time_0, *time_1, *step_distance))]
1428 CausalityViolation {
1429 time_0: u32,
1431 time_1: u32,
1433 step_distance: u32,
1441 },
1442 #[error("MCMC error: {0}")]
1444 Mcmc(#[from] McmcError),
1445 #[error("Failed to write {format} output to {path}: {detail}")]
1447 OutputWriteFailed {
1448 path: String,
1450 format: OutputFormat,
1452 detail: String,
1454 },
1455 #[error("Failed to resolve output path from base {base_path}: {detail}")]
1457 OutputPathResolutionFailed {
1458 base_path: String,
1460 detail: String,
1462 },
1463 #[error("CSV output path {csv_path} and JSON output path {json_path} resolve to the same file")]
1465 OutputPathConflict {
1466 csv_path: String,
1468 json_path: String,
1470 },
1471 #[error("Failed to read {format} output from {path}: {detail}")]
1473 OutputReadFailed {
1474 path: String,
1476 format: OutputFormat,
1478 detail: String,
1480 },
1481 #[error("Failed to {operation} {target} checkpoint: {detail}")]
1483 CheckpointSerializationFailed {
1484 operation: CheckpointOperation,
1486 target: String,
1488 detail: String,
1490 },
1491 #[error("Failed to resume MCMC checkpoint: {failure}")]
1497 CheckpointResumeFailed {
1498 #[source]
1500 failure: CheckpointResumeFailure,
1501 },
1502}
1503
1504fn format_causality_violation(time_0: u32, time_1: u32, step_distance: u32) -> String {
1506 let raw = time_0.abs_diff(time_1);
1507 if raw == step_distance {
1508 format!(
1509 "Causality violation: edge spans {step_distance} time-slice steps \
1510 (t={time_0} to t={time_1}), maximum allowed is 1"
1511 )
1512 } else {
1513 format!(
1516 "Causality violation: edge spans {step_distance} time-slice steps \
1517 (t={time_0} to t={time_1}, |Δt|={raw} on the time circle), \
1518 maximum allowed is 1"
1519 )
1520 }
1521}
1522
1523pub type CdtResult<T> = Result<T, CdtError>;
1525
1526#[cfg(test)]
1527mod tests {
1528 use super::*;
1529 use std::assert_matches;
1530 use std::error::Error;
1531
1532 #[test]
1533 fn test_invalid_configuration_error() {
1534 let error = CdtError::InvalidConfiguration {
1535 setting: ConfigurationSetting::Vertices,
1536 provided_value: "2".to_string(),
1537 expected: "≥ 3".to_string(),
1538 };
1539 let display = format!("{error}");
1540 assert_eq!(
1541 display,
1542 "Invalid configuration: vertices (got: 2, expected: ≥ 3)"
1543 );
1544 }
1545
1546 #[test]
1547 fn test_invalid_simulation_configuration_error() {
1548 let error = CdtError::InvalidSimulationConfiguration {
1549 setting: ConfigurationSetting::Temperature,
1550 provided_value: "NaN".to_string(),
1551 expected: "finite and positive".to_string(),
1552 };
1553 let display = format!("{error}");
1554 assert_eq!(
1555 display,
1556 "Invalid simulation configuration: temperature (got: NaN, expected: finite and positive)"
1557 );
1558 }
1559
1560 #[test]
1561 fn test_invalid_triangulation_metadata_error() {
1562 let error = CdtError::InvalidTriangulationMetadata {
1563 field: TriangulationMetadataField::Timeslices,
1564 topology: CdtTopology::Toroidal,
1565 provided_value: "2".to_string(),
1566 expected: "≥ 3".to_string(),
1567 };
1568 let display = format!("{error}");
1569 assert_eq!(
1570 display,
1571 "Invalid triangulation metadata: timeslices for toroidal (got: 2, expected: ≥ 3)"
1572 );
1573 }
1574
1575 #[test]
1576 fn test_delaunay_generation_failed_error() {
1577 let error = CdtError::DelaunayGenerationFailed {
1578 vertex_count: 10,
1579 coordinate_range: (-1.0, 1.0),
1580 attempt: 5,
1581 underlying_error: "Too many duplicate points".to_string(),
1582 };
1583 let display = format!("{error}");
1584 assert_eq!(
1585 display,
1586 "Delaunay triangulation generation failed: 10 vertices, range [-1, 1], attempt 5: Too many duplicate points"
1587 );
1588 }
1589
1590 #[test]
1591 fn test_delaunay_validation_failed_error() {
1592 let error = CdtError::DelaunayValidationFailed {
1593 level: DelaunayValidationLevel::Four,
1594 detail: "upstream validation failed".to_string(),
1595 };
1596 let display = format!("{error}");
1597 assert_eq!(
1598 display,
1599 "Delaunay validation failed [Level 1-4]: upstream validation failed"
1600 );
1601 }
1602
1603 #[test]
1604 fn validation_level_display_covers_all_levels() {
1605 assert_eq!(DelaunayValidationLevel::One.to_string(), "Level 1");
1606 assert_eq!(DelaunayValidationLevel::Two.to_string(), "Level 1-2");
1607 assert_eq!(DelaunayValidationLevel::Three.to_string(), "Level 1-3");
1608 assert_eq!(DelaunayValidationLevel::Four.to_string(), "Level 1-4");
1609 }
1610
1611 #[test]
1612 fn validation_check_display_covers_all_categories() {
1613 assert_eq!(CdtValidationCheck::Geometry.to_string(), "geometry");
1614 assert_eq!(
1615 CdtValidationCheck::FoliationAssignment.to_string(),
1616 "foliation_assignment"
1617 );
1618 assert_eq!(CdtValidationCheck::Causality.to_string(), "causality");
1619 assert_eq!(
1620 CdtValidationCheck::SimplexClassification.to_string(),
1621 "simplex_classification"
1622 );
1623 assert_eq!(
1624 CdtValidationCheck::ErgodicMoveCandidateGeometry.to_string(),
1625 "ergodic_move_candidate_geometry"
1626 );
1627 }
1628
1629 #[test]
1630 fn configuration_setting_display_covers_all_settings() {
1631 let cases = [
1632 (ConfigurationSetting::Dimension, "dimension"),
1633 (ConfigurationSetting::Vertices, "vertices"),
1634 (ConfigurationSetting::Timeslices, "timeslices"),
1635 (ConfigurationSetting::Temperature, "temperature"),
1636 (ConfigurationSetting::Steps, "steps"),
1637 (
1638 ConfigurationSetting::ThermalizationSteps,
1639 "thermalization_steps",
1640 ),
1641 (
1642 ConfigurationSetting::MeasurementFrequency,
1643 "measurement_frequency",
1644 ),
1645 (
1646 ConfigurationSetting::MeasurementSchedule,
1647 "measurement schedule",
1648 ),
1649 (ConfigurationSetting::Coupling0, "coupling_0"),
1650 (ConfigurationSetting::Coupling2, "coupling_2"),
1651 (
1652 ConfigurationSetting::CosmologicalConstant,
1653 "cosmological_constant",
1654 ),
1655 (ConfigurationSetting::VolumeProfile, "volume_profile"),
1656 ];
1657
1658 for (setting, expected) in cases {
1659 assert_eq!(setting.to_string(), expected);
1660 }
1661 }
1662
1663 #[test]
1664 fn generation_parameter_issue_display_covers_all_issues() {
1665 let cases = [
1666 (
1667 GenerationParameterIssue::InvalidCoordinateRange,
1668 "Invalid coordinate range",
1669 ),
1670 (
1671 GenerationParameterIssue::InvalidToroidalDomain,
1672 "Invalid toroidal domain",
1673 ),
1674 (
1675 GenerationParameterIssue::NonFiniteVertexCoordinate,
1676 "Non-finite vertex coordinate",
1677 ),
1678 (
1679 GenerationParameterIssue::InsufficientVertexCount,
1680 "Insufficient vertex count",
1681 ),
1682 (
1683 GenerationParameterIssue::InsufficientVerticesPerSlice,
1684 "Insufficient vertices per slice",
1685 ),
1686 (
1687 GenerationParameterIssue::InsufficientNumberOfTimeSlices,
1688 "Insufficient number of time slices",
1689 ),
1690 (
1691 GenerationParameterIssue::NonPositiveSliceCount,
1692 "Number of slices must be positive",
1693 ),
1694 (
1695 GenerationParameterIssue::EmptyVolumeProfile,
1696 "Empty volume profile",
1697 ),
1698 (
1699 GenerationParameterIssue::VolumeProfileLengthOverflow,
1700 "Volume profile length overflow",
1701 ),
1702 (
1703 GenerationParameterIssue::InsufficientVerticesInVolumeProfileSlice,
1704 "Insufficient vertices in volume-profile slice",
1705 ),
1706 (
1707 GenerationParameterIssue::VertexCountOverflow,
1708 "Vertex count overflow",
1709 ),
1710 (
1711 GenerationParameterIssue::SimplexCountOverflow,
1712 "Simplex count overflow",
1713 ),
1714 ];
1715
1716 for (issue, expected) in cases {
1717 assert_eq!(issue.to_string(), expected);
1718 }
1719 }
1720
1721 #[test]
1722 fn triangulation_metadata_field_display_covers_all_fields() {
1723 assert_eq!(
1724 TriangulationMetadataField::Timeslices.to_string(),
1725 "timeslices"
1726 );
1727 assert_eq!(
1728 TriangulationMetadataField::Dimension.to_string(),
1729 "dimension"
1730 );
1731 }
1732
1733 #[test]
1734 fn output_format_display_covers_all_formats() {
1735 assert_eq!(OutputFormat::Csv.to_string(), "CSV");
1736 assert_eq!(OutputFormat::Json.to_string(), "JSON");
1737 }
1738
1739 #[test]
1740 fn checkpoint_operation_display_covers_all_operations() {
1741 assert_eq!(CheckpointOperation::Serialize.to_string(), "serialize");
1742 assert_eq!(CheckpointOperation::Deserialize.to_string(), "deserialize");
1743 }
1744
1745 #[test]
1746 fn backend_mutation_operation_display_covers_all_operations() {
1747 let cases = [
1748 (
1749 BackendMutationOperation::SetSimplexDataByKey,
1750 "set_simplex_data_by_key",
1751 ),
1752 (
1753 BackendMutationOperation::SetVertexDataByKey,
1754 "set_vertex_data_by_key",
1755 ),
1756 (BackendMutationOperation::SetVertexData, "set_vertex_data"),
1757 (BackendMutationOperation::SubdivideFace, "subdivide_face"),
1758 (BackendMutationOperation::RemoveVertex, "remove_vertex"),
1759 (BackendMutationOperation::FlipEdge, "flip_edge"),
1760 ];
1761
1762 for (operation, expected) in cases {
1763 assert_eq!(operation.to_string(), expected);
1764 }
1765 }
1766
1767 #[test]
1768 fn checkpoint_move_counter_display_covers_all_categories() {
1769 let cases = [
1770 (CheckpointMoveCounter::Attempted, "attempted"),
1771 (CheckpointMoveCounter::Accepted, "accepted"),
1772 (CheckpointMoveCounter::Rejected, "rejected"),
1773 ];
1774
1775 for (counter, expected) in cases {
1776 assert_eq!(counter.to_string(), expected);
1777 }
1778 }
1779
1780 #[test]
1781 fn proposal_telemetry_counter_display_covers_all_categories() {
1782 let cases = [
1783 (
1784 ProposalTelemetryCounter::MoveFamilyProposals,
1785 "move-family proposals",
1786 ),
1787 (
1788 ProposalTelemetryCounter::AcceptedTransitions,
1789 "accepted transitions",
1790 ),
1791 (
1792 ProposalTelemetryCounter::RejectedTransitions,
1793 "rejected transitions",
1794 ),
1795 ];
1796
1797 for (counter, expected) in cases {
1798 assert_eq!(counter.to_string(), expected);
1799 }
1800 }
1801
1802 #[test]
1803 fn scalar_trace_field_display_covers_all_fields() {
1804 let cases = [
1805 (ScalarTraceField::LogProb, "log_prob"),
1806 (ScalarTraceField::Action, "action"),
1807 (ScalarTraceField::DeltaAction, "delta_action"),
1808 (ScalarTraceField::ActionBefore, "action_before"),
1809 (ScalarTraceField::ActionAfter, "action_after"),
1810 ];
1811
1812 for (field, expected) in cases {
1813 assert_eq!(field.to_string(), expected);
1814 }
1815 }
1816
1817 #[test]
1818 fn simplex_count_field_display_covers_all_fields() {
1819 let cases = [
1820 (SimplexCountField::Vertices, "vertices"),
1821 (SimplexCountField::Edges, "edges"),
1822 (SimplexCountField::Triangles, "triangles"),
1823 ];
1824
1825 for (field, expected) in cases {
1826 assert_eq!(field.to_string(), expected);
1827 }
1828 }
1829
1830 #[test]
1831 fn checkpoint_resume_failure_display_includes_structured_context() {
1832 let failure = CheckpointResumeFailure::ChainCounterMismatch {
1833 chain_accepted: 1,
1834 chain_rejected: 2,
1835 move_accepted: 3,
1836 move_rejected: 4,
1837 };
1838
1839 assert_eq!(
1840 failure.to_string(),
1841 "chain counters do not match move statistics: chain accepted=1, rejected=2; move accepted=3, rejected=4"
1842 );
1843
1844 let action_failure =
1845 CheckpointResumeFailure::NonFiniteCheckpointAction { stored: f64::NAN };
1846 assert_eq!(
1847 action_failure.to_string(),
1848 "checkpoint action is non-finite: stored NaN"
1849 );
1850 }
1851
1852 #[test]
1853 fn proposal_resume_failures_display_structured_context() {
1854 let overflow = CheckpointResumeFailure::ProposalCounterOverflow {
1855 counter: ProposalTelemetryCounter::AcceptedTransitions,
1856 };
1857 assert_eq!(
1858 overflow.to_string(),
1859 "accepted transitions proposal telemetry count exceeds u64::MAX"
1860 );
1861
1862 let hard_failures = CheckpointResumeFailure::ProposalHardFailures { actual: 3 };
1863 assert_eq!(
1864 hard_failures.to_string(),
1865 "proposal telemetry has 3 hard failures; expected 0"
1866 );
1867 }
1868
1869 #[test]
1870 fn test_unsupported_dimension_error() {
1871 let error = CdtError::UnsupportedDimension(3);
1872 let display = format!("{error}");
1873 assert_eq!(
1874 display,
1875 "Unsupported dimension: 3. Only 2D is currently supported"
1876 );
1877 }
1878
1879 #[test]
1880 fn test_invalid_generation_parameters_error() {
1881 let error = CdtError::InvalidGenerationParameters {
1882 issue: GenerationParameterIssue::InsufficientVertexCount,
1883 provided_value: "2".to_string(),
1884 expected_range: "at least 3".to_string(),
1885 };
1886 let display = format!("{error}");
1887 assert_eq!(
1888 display,
1889 "Invalid triangulation parameters: Insufficient vertex count (got: 2, expected: at least 3)"
1890 );
1891 }
1892
1893 #[test]
1894 fn test_validation_failed_error() {
1895 let error = CdtError::ValidationFailed {
1896 check: CdtValidationCheck::Geometry,
1897 failure: CdtValidationFailure::BackendGeometry {
1898 detail: "backend reported invalid triangulation structure".to_string(),
1899 },
1900 };
1901 let display = format!("{error}");
1902 assert_eq!(
1903 display,
1904 "Validation failed [geometry]: backend reported invalid triangulation structure"
1905 );
1906 }
1907
1908 #[test]
1909 fn cdt_validation_failure_display_covers_structured_variants() {
1910 let cases = [
1911 (
1912 CdtValidationFailure::BackendGeometry {
1913 detail: "backend rejected structure".to_string(),
1914 },
1915 "backend rejected structure",
1916 ),
1917 (
1918 CdtValidationFailure::FaceVerticesUnavailable {
1919 face: "FaceKey(3v1)".to_string(),
1920 detail: "backend reported invalid simplex key".to_string(),
1921 },
1922 "failed to resolve vertices for face FaceKey(3v1): backend reported invalid simplex key",
1923 ),
1924 (
1925 CdtValidationFailure::FaceVertexCount {
1926 face: "FaceKey(3v1)".to_string(),
1927 actual: 4,
1928 expected: 3,
1929 },
1930 "face FaceKey(3v1) has 4 vertices, expected 3",
1931 ),
1932 (
1933 CdtValidationFailure::MissingVertexTimeLabel {
1934 vertex: "VertexKey(7v1)".to_string(),
1935 },
1936 "vertex VertexKey(7v1) has no time label in a foliated triangulation",
1937 ),
1938 (
1939 CdtValidationFailure::InvalidCdtTriangle {
1940 face: "FaceKey(3v1)".to_string(),
1941 spacelike_edges: 3,
1942 timelike_edges: 0,
1943 },
1944 "invalid CDT triangle at face FaceKey(3v1): spacelike=3, timelike=0",
1945 ),
1946 (
1947 CdtValidationFailure::VertexCoordinateReadFailed {
1948 vertex: "VertexKey(7v1)".to_string(),
1949 detail: "missing vertex".to_string(),
1950 },
1951 "failed to read coordinates for vertex VertexKey(7v1): missing vertex",
1952 ),
1953 (
1954 CdtValidationFailure::VertexCoordinateDimension {
1955 vertex: "VertexKey(7v1)".to_string(),
1956 actual: 1,
1957 expected_minimum: 2,
1958 },
1959 "vertex VertexKey(7v1) has 1 coordinates, expected ≥ 2",
1960 ),
1961 (
1962 CdtValidationFailure::NonStrictSimplex {
1963 face: "FaceKey(3v1)".to_string(),
1964 },
1965 "face FaceKey(3v1) is not a strict CDT simplex (expected Up or Down)",
1966 ),
1967 (
1968 CdtValidationFailure::ErgodicMoveCandidateGeometry {
1969 detail: "candidate edge has no adjacent faces".to_string(),
1970 },
1971 "candidate edge has no adjacent faces",
1972 ),
1973 ];
1974
1975 for (failure, expected) in cases {
1976 assert_eq!(failure.to_string(), expected);
1977 }
1978 }
1979
1980 #[test]
1981 fn test_topology_mismatch_error() {
1982 let error = CdtError::TopologyMismatch {
1983 topology: CdtTopology::Toroidal,
1984 euler_characteristic: 1,
1985 expected_euler_characteristics: vec![0],
1986 vertices: 3,
1987 edges: 3,
1988 faces: 1,
1989 };
1990 let display = format!("{error}");
1991 assert_eq!(
1992 display,
1993 "Topology mismatch for toroidal: Euler characteristic χ=1, expected one of [0] (V=3, E=3, F=1)"
1994 );
1995 }
1996
1997 #[test]
1998 fn test_foliation_error_variant() {
1999 let error = CdtError::Foliation(FoliationError::EmptySlice { slice: 3 });
2000 let display = format!("{error}");
2001 assert_eq!(
2002 display,
2003 "Foliation validation failed: time slice 3 is empty"
2004 );
2005 }
2006
2007 #[test]
2008 fn test_vertex_build_failed_error() {
2009 let error = CdtError::VertexBuildFailed {
2010 context: "explicit CDT vertex 7".to_string(),
2011 underlying_error: "Missing required field: `point`".to_string(),
2012 };
2013 let display = format!("{error}");
2014 assert_eq!(
2015 display,
2016 "Vertex construction failed [explicit CDT vertex 7]: Missing required field: `point`"
2017 );
2018 }
2019
2020 #[test]
2021 fn test_backend_rollback_failed_error() {
2022 let error = CdtError::BackendRollbackFailed {
2023 operation: BackendMutationOperation::SetVertexDataByKey,
2024 target: "vertex VertexKey(123v1)".to_string(),
2025 detail: "backend reported invalid vertex key".to_string(),
2026 rollback_errors: "vertex VertexKey(7v1): backend reported invalid vertex key"
2027 .to_string(),
2028 };
2029 let display = format!("{error}");
2030 assert_eq!(
2031 display,
2032 "Backend mutation failed [set_vertex_data_by_key] on vertex VertexKey(123v1): backend reported invalid vertex key; rollback failed: vertex VertexKey(7v1): backend reported invalid vertex key"
2033 );
2034 }
2035
2036 #[test]
2037 fn test_backend_mutation_failed_error() {
2038 let error = CdtError::BackendMutationFailed {
2039 operation: BackendMutationOperation::SetVertexData,
2040 target: "vertex VertexKey(123v1)".to_string(),
2041 detail: "backend reported invalid vertex key".to_string(),
2042 };
2043 let display = format!("{error}");
2044 assert_eq!(
2045 display,
2046 "Backend mutation failed [set_vertex_data] on vertex VertexKey(123v1): backend reported invalid vertex key"
2047 );
2048 }
2049
2050 #[test]
2051 fn test_metropolis_move_application_failed_error() {
2052 let source = MetropolisMoveApplicationFailure::BackendMutation {
2053 operation: BackendMutationOperation::SetVertexData,
2054 target: "vertex VertexKey(123v1)".to_string(),
2055 detail: "backend reported invalid vertex key".to_string(),
2056 };
2057 let error = CdtError::MetropolisMoveApplicationFailed {
2058 step: 17,
2059 move_type: MoveType::Move31Remove,
2060 attempts: 8,
2061 source: source.clone(),
2062 };
2063 let display = format!("{error}");
2064 assert_eq!(
2065 display,
2066 "Metropolis accepted Move31Remove at step 17, but applying it failed after 8 attempts; source: backend mutation failed [set_vertex_data] on vertex VertexKey(123v1): backend reported invalid vertex key"
2067 );
2068 assert_eq!(
2069 Error::source(&error).map(ToString::to_string),
2070 Some(source.to_string())
2071 );
2072 }
2073
2074 #[test]
2075 fn test_proposal_application_failed_error() {
2076 let source = MetropolisMoveApplicationFailure::BackendMutation {
2077 operation: BackendMutationOperation::SetVertexDataByKey,
2078 target: "vertex VertexKey(123v1)".to_string(),
2079 detail: "backend reported invalid vertex key".to_string(),
2080 };
2081 let error = CdtError::ProposalApplicationFailed {
2082 move_type: MoveType::Move13Add,
2083 attempt: 2,
2084 source: source.clone(),
2085 };
2086 let display = format!("{error}");
2087 assert_eq!(
2088 display,
2089 "CDT proposal failed while applying Move13Add on attempt 2: backend mutation failed [set_vertex_data_by_key] on vertex VertexKey(123v1): backend reported invalid vertex key"
2090 );
2091 assert_eq!(
2092 Error::source(&error).map(ToString::to_string),
2093 Some(source.to_string())
2094 );
2095 }
2096
2097 #[test]
2098 fn planned_proposal_telemetry_missing_reports_step_without_fake_move_type() {
2099 let error = CdtError::PlannedProposalTelemetryMissing { step: 23 };
2100
2101 assert_eq!(
2102 format!("{error}"),
2103 "planned CDT proposal step 23 completed without required proposal telemetry"
2104 );
2105 assert!(Error::source(&error).is_none());
2106 }
2107
2108 #[test]
2109 fn planned_proposal_step_failed_preserves_upstream_detail() {
2110 let error = CdtError::PlannedProposalStepFailed {
2111 step: 23,
2112 detail: "future upstream sampler failure".to_string(),
2113 };
2114
2115 assert_eq!(
2116 format!("{error}"),
2117 "planned CDT proposal step 23 failed: future upstream sampler failure"
2118 );
2119 assert!(Error::source(&error).is_none());
2120 }
2121
2122 #[test]
2123 fn metropolis_move_application_failure_preserves_backend_mutation_fields() {
2124 let failure = MetropolisMoveApplicationFailure::from(CdtError::BackendMutationFailed {
2125 operation: BackendMutationOperation::RemoveVertex,
2126 target: "vertex VertexKey(7v1)".to_string(),
2127 detail: "backend reported invalid vertex key".to_string(),
2128 });
2129
2130 let MetropolisMoveApplicationFailure::BackendMutation {
2131 operation,
2132 target,
2133 detail,
2134 } = failure
2135 else {
2136 panic!("expected backend mutation failure source");
2137 };
2138
2139 assert_eq!(operation, BackendMutationOperation::RemoveVertex);
2140 assert_eq!(target, "vertex VertexKey(7v1)");
2141 assert_eq!(detail, "backend reported invalid vertex key");
2142 }
2143
2144 #[test]
2145 fn metropolis_move_application_failure_preserves_validation_fields() {
2146 let validation_failure = CdtValidationFailure::InvalidCdtTriangle {
2147 face: "FaceKey(3v1)".to_string(),
2148 spacelike_edges: 3,
2149 timelike_edges: 0,
2150 };
2151 let failure = MetropolisMoveApplicationFailure::from(CdtError::ValidationFailed {
2152 check: CdtValidationCheck::Causality,
2153 failure: validation_failure.clone(),
2154 });
2155
2156 let MetropolisMoveApplicationFailure::Validation { check, failure } = failure else {
2157 panic!("expected validation failure source");
2158 };
2159
2160 assert_eq!(check, CdtValidationCheck::Causality);
2161 assert_eq!(failure, validation_failure);
2162 }
2163
2164 #[test]
2165 fn metropolis_move_application_failure_preserves_structured_sources() {
2166 let cases = [
2167 (
2168 CdtError::BackendRollbackFailed {
2169 operation: BackendMutationOperation::FlipEdge,
2170 target: "edge EdgeKey(5v1)".to_string(),
2171 detail: "flip failed".to_string(),
2172 rollback_errors: "rollback failed".to_string(),
2173 },
2174 MetropolisMoveApplicationFailure::BackendRollback {
2175 operation: BackendMutationOperation::FlipEdge,
2176 target: "edge EdgeKey(5v1)".to_string(),
2177 detail: "flip failed".to_string(),
2178 rollback_errors: "rollback failed".to_string(),
2179 },
2180 ),
2181 (
2182 CdtError::DelaunayValidationFailed {
2183 level: DelaunayValidationLevel::Three,
2184 detail: "invalid triangulation".to_string(),
2185 },
2186 MetropolisMoveApplicationFailure::DelaunayValidation {
2187 level: DelaunayValidationLevel::Three,
2188 detail: "invalid triangulation".to_string(),
2189 },
2190 ),
2191 (
2192 CdtError::TopologyMismatch {
2193 topology: CdtTopology::Toroidal,
2194 euler_characteristic: 1,
2195 expected_euler_characteristics: vec![0],
2196 vertices: 3,
2197 edges: 3,
2198 faces: 1,
2199 },
2200 MetropolisMoveApplicationFailure::TopologyMismatch {
2201 topology: CdtTopology::Toroidal,
2202 euler_characteristic: 1,
2203 expected_euler_characteristics: vec![0],
2204 vertices: 3,
2205 edges: 3,
2206 faces: 1,
2207 },
2208 ),
2209 (
2210 CdtError::Foliation(FoliationError::EmptySlice { slice: 3 }),
2211 MetropolisMoveApplicationFailure::Foliation(FoliationError::EmptySlice {
2212 slice: 3,
2213 }),
2214 ),
2215 (
2216 CdtError::CausalityViolation {
2217 time_0: 0,
2218 time_1: 2,
2219 step_distance: 2,
2220 },
2221 MetropolisMoveApplicationFailure::CausalityViolation {
2222 time_0: 0,
2223 time_1: 2,
2224 step_distance: 2,
2225 },
2226 ),
2227 ];
2228
2229 for (error, expected) in cases {
2230 assert_eq!(MetropolisMoveApplicationFailure::from(error), expected);
2231 }
2232 }
2233
2234 #[test]
2235 fn metropolis_move_application_failure_from_wrapper_preserves_source() {
2236 let source = MetropolisMoveApplicationFailure::BackendMutation {
2237 operation: BackendMutationOperation::RemoveVertex,
2238 target: "vertex VertexKey(7v1)".to_string(),
2239 detail: "backend reported invalid vertex key".to_string(),
2240 };
2241 let failure =
2242 MetropolisMoveApplicationFailure::from(CdtError::MetropolisMoveApplicationFailed {
2243 step: 17,
2244 move_type: MoveType::Move31Remove,
2245 attempts: 8,
2246 source: source.clone(),
2247 });
2248
2249 assert_eq!(failure, source);
2250 }
2251
2252 #[test]
2253 fn metropolis_move_application_failure_unexpected_retains_diagnostic() {
2254 let failure = MetropolisMoveApplicationFailure::from(CdtError::UnsupportedDimension(3));
2255
2256 let MetropolisMoveApplicationFailure::Unexpected { detail } = failure else {
2257 panic!("expected unexpected failure source");
2258 };
2259
2260 assert_eq!(
2261 detail,
2262 "Unsupported dimension: 3. Only 2D is currently supported"
2263 );
2264 }
2265
2266 #[test]
2267 fn test_causality_violation_open_boundary_error() {
2268 let error = CdtError::CausalityViolation {
2270 time_0: 0,
2271 time_1: 3,
2272 step_distance: 3,
2273 };
2274 let display = format!("{error}");
2275 assert_eq!(
2276 display,
2277 "Causality violation: edge spans 3 time-slice steps (t=0 to t=3), maximum allowed is 1"
2278 );
2279 }
2280
2281 #[test]
2282 fn test_causality_violation_toroidal_error_reports_circular_distance() {
2283 let error = CdtError::CausalityViolation {
2285 time_0: 0,
2286 time_1: 8,
2287 step_distance: 2,
2288 };
2289 let display = format!("{error}");
2290 assert_eq!(
2291 display,
2292 "Causality violation: edge spans 2 time-slice steps \
2293 (t=0 to t=8, |Δt|=8 on the time circle), maximum allowed is 1"
2294 );
2295 }
2296
2297 #[test]
2298 fn test_mcmc_error() {
2299 let error = CdtError::Mcmc(McmcError::NanProposedLogProb);
2300 let display = format!("{error}");
2301 assert_eq!(
2302 display,
2303 "MCMC error: target returned NaN log-probability for a proposed state"
2304 );
2305 }
2306
2307 #[test]
2308 fn test_mcmc_error_from_conversion() {
2309 let mcmc_err = McmcError::NanProposedLogProb;
2310 let cdt_err: CdtError = mcmc_err.into();
2311 assert_matches!(cdt_err, CdtError::Mcmc(McmcError::NanProposedLogProb));
2312 let display = format!("{cdt_err}");
2313 assert!(
2314 display.contains("MCMC error"),
2315 "Should contain MCMC error prefix: {display}"
2316 );
2317 assert!(
2318 display.contains("NaN"),
2319 "Should contain NaN context: {display}"
2320 );
2321 }
2322
2323 #[test]
2324 fn test_output_write_failed_error() {
2325 let error = CdtError::OutputWriteFailed {
2326 path: "trace.csv".to_string(),
2327 format: OutputFormat::Csv,
2328 detail: "permission denied".to_string(),
2329 };
2330 let CdtError::OutputWriteFailed {
2331 path,
2332 format,
2333 detail,
2334 } = &error
2335 else {
2336 panic!("expected OutputWriteFailed variant");
2337 };
2338 assert_eq!(path, "trace.csv");
2339 assert_eq!(*format, OutputFormat::Csv);
2340 assert_eq!(detail, "permission denied");
2341 let display = format!("{error}");
2342 assert_eq!(
2343 display,
2344 "Failed to write CSV output to trace.csv: permission denied"
2345 );
2346 }
2347
2348 #[test]
2349 fn test_output_path_resolution_failed_error() {
2350 let error = CdtError::OutputPathResolutionFailed {
2351 base_path: ".".to_string(),
2352 detail: "No such file or directory".to_string(),
2353 };
2354 let CdtError::OutputPathResolutionFailed { base_path, detail } = &error else {
2355 panic!("expected OutputPathResolutionFailed variant");
2356 };
2357 assert_eq!(base_path, ".");
2358 assert_eq!(detail, "No such file or directory");
2359 let display = format!("{error}");
2360 assert_eq!(
2361 display,
2362 "Failed to resolve output path from base .: No such file or directory"
2363 );
2364 }
2365
2366 #[test]
2367 fn test_output_path_conflict_error() {
2368 let error = CdtError::OutputPathConflict {
2369 csv_path: "output/results".to_string(),
2370 json_path: "output/results".to_string(),
2371 };
2372 let CdtError::OutputPathConflict {
2373 csv_path,
2374 json_path,
2375 } = &error
2376 else {
2377 panic!("expected OutputPathConflict variant");
2378 };
2379 assert_eq!(csv_path, "output/results");
2380 assert_eq!(json_path, "output/results");
2381 assert_eq!(
2382 format!("{error}"),
2383 "CSV output path output/results and JSON output path output/results resolve to the same file"
2384 );
2385 }
2386
2387 #[test]
2388 fn test_output_read_failed_error() {
2389 let error = CdtError::OutputReadFailed {
2390 path: "summary.json".to_string(),
2391 format: OutputFormat::Json,
2392 detail: "expected value at line 1 column 1".to_string(),
2393 };
2394 let CdtError::OutputReadFailed {
2395 path,
2396 format,
2397 detail,
2398 } = &error
2399 else {
2400 panic!("expected OutputReadFailed variant");
2401 };
2402 assert_eq!(path, "summary.json");
2403 assert_eq!(*format, OutputFormat::Json);
2404 assert_eq!(detail, "expected value at line 1 column 1");
2405 let display = format!("{error}");
2406 assert_eq!(
2407 display,
2408 "Failed to read JSON output from summary.json: expected value at line 1 column 1"
2409 );
2410 }
2411
2412 #[test]
2413 fn test_checkpoint_serialization_failed_error() {
2414 let error = CdtError::CheckpointSerializationFailed {
2415 operation: CheckpointOperation::Deserialize,
2416 target: "final triangulation".to_string(),
2417 detail: "missing field `geometry`".to_string(),
2418 };
2419 let CdtError::CheckpointSerializationFailed {
2420 operation,
2421 target,
2422 detail,
2423 } = &error
2424 else {
2425 panic!("expected CheckpointSerializationFailed variant");
2426 };
2427 assert_eq!(*operation, CheckpointOperation::Deserialize);
2428 assert_eq!(target, "final triangulation");
2429 assert_eq!(detail, "missing field `geometry`");
2430 let display = format!("{error}");
2431 assert_eq!(
2432 display,
2433 "Failed to deserialize final triangulation checkpoint: missing field `geometry`"
2434 );
2435 }
2436
2437 #[test]
2438 fn test_checkpoint_resume_failed_error() {
2439 let error = CdtError::CheckpointResumeFailed {
2440 failure: CheckpointResumeFailure::IncompatibleTemperature,
2441 };
2442 let CdtError::CheckpointResumeFailed { failure } = &error else {
2443 panic!("expected CheckpointResumeFailed variant");
2444 };
2445 assert_eq!(*failure, CheckpointResumeFailure::IncompatibleTemperature);
2446 assert_eq!(
2447 format!("{error}"),
2448 "Failed to resume MCMC checkpoint: temperature differs from checkpoint"
2449 );
2450 assert_eq!(
2451 Error::source(&error).map(ToString::to_string),
2452 Some("temperature differs from checkpoint".to_string())
2453 );
2454 }
2455
2456 #[test]
2457 fn test_error_equality() {
2458 let error1 = CdtError::InvalidConfiguration {
2459 setting: ConfigurationSetting::Steps,
2460 provided_value: "0".to_string(),
2461 expected: "≥ 1".to_string(),
2462 };
2463 let error2 = CdtError::InvalidConfiguration {
2464 setting: ConfigurationSetting::Steps,
2465 provided_value: "0".to_string(),
2466 expected: "≥ 1".to_string(),
2467 };
2468 let error3 = CdtError::InvalidConfiguration {
2469 setting: ConfigurationSetting::Steps,
2470 provided_value: "10".to_string(),
2471 expected: "≥ 1".to_string(),
2472 };
2473
2474 assert_eq!(error1, error2);
2475 assert_ne!(error1, error3);
2476 }
2477
2478 #[test]
2479 fn test_error_clone() {
2480 let error = CdtError::UnsupportedDimension(4);
2481 let cloned = error.clone();
2482 assert_eq!(error, cloned);
2483 }
2484
2485 #[test]
2486 fn test_error_debug() {
2487 let error = CdtError::InvalidConfiguration {
2488 setting: ConfigurationSetting::Vertices,
2489 provided_value: "2".to_string(),
2490 expected: "≥ 3".to_string(),
2491 };
2492 let debug_str = format!("{error:?}");
2493 assert!(debug_str.contains("InvalidConfiguration"));
2494 assert!(debug_str.contains("Vertices"));
2495 }
2496
2497 #[test]
2498 fn test_cdt_result_type() {
2499 let success: CdtResult<i32> = Ok(42);
2500 let failure: CdtResult<i32> = Err(CdtError::InvalidConfiguration {
2501 setting: ConfigurationSetting::Steps,
2502 provided_value: "0".to_string(),
2503 expected: "≥ 1".to_string(),
2504 });
2505
2506 assert!(success.is_ok());
2507 assert!(failure.is_err());
2508 assert_eq!(success, Ok(42));
2509 }
2510
2511 #[test]
2512 fn test_error_is_send_sync() {
2513 fn assert_send_sync<T: Send + Sync>() {}
2514 assert_send_sync::<CdtError>();
2515 }
2516
2517 #[test]
2518 fn test_std_error_trait() {
2519 let error = CdtError::InvalidConfiguration {
2520 setting: ConfigurationSetting::Temperature,
2521 provided_value: "NaN".to_string(),
2522 expected: "finite and positive".to_string(),
2523 };
2524 let _: &dyn Error = &error;
2525 }
2527}