#![forbid(unsafe_code)]
use crate::cdt::ergodic_moves::MoveType;
use crate::cdt::foliation::FoliationError;
use crate::config::CdtTopology;
use markov_chain_monte_carlo::{McmcError, StepOutcome};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum DelaunayValidationLevel {
One,
Two,
Three,
Four,
}
impl fmt::Display for DelaunayValidationLevel {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::One => formatter.write_str("Level 1"),
Self::Two => formatter.write_str("Level 1-2"),
Self::Three => formatter.write_str("Level 1-3"),
Self::Four => formatter.write_str("Level 1-4"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ConfigurationSetting {
Dimension,
Vertices,
Timeslices,
Temperature,
Steps,
ThermalizationSteps,
MeasurementFrequency,
MeasurementSchedule,
Coupling0,
Coupling2,
CosmologicalConstant,
VolumeProfile,
}
impl fmt::Display for ConfigurationSetting {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Dimension => formatter.write_str("dimension"),
Self::Vertices => formatter.write_str("vertices"),
Self::Timeslices => formatter.write_str("timeslices"),
Self::Temperature => formatter.write_str("temperature"),
Self::Steps => formatter.write_str("steps"),
Self::ThermalizationSteps => formatter.write_str("thermalization_steps"),
Self::MeasurementFrequency => formatter.write_str("measurement_frequency"),
Self::MeasurementSchedule => formatter.write_str("measurement schedule"),
Self::Coupling0 => formatter.write_str("coupling_0"),
Self::Coupling2 => formatter.write_str("coupling_2"),
Self::CosmologicalConstant => formatter.write_str("cosmological_constant"),
Self::VolumeProfile => formatter.write_str("volume_profile"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum GenerationParameterIssue {
InvalidCoordinateRange,
InvalidToroidalDomain,
NonFiniteVertexCoordinate,
InsufficientVertexCount,
InsufficientVerticesPerSlice,
InsufficientNumberOfTimeSlices,
NonPositiveSliceCount,
EmptyVolumeProfile,
VolumeProfileLengthOverflow,
InsufficientVerticesInVolumeProfileSlice,
VertexCountOverflow,
SimplexCountOverflow,
}
impl fmt::Display for GenerationParameterIssue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidCoordinateRange => formatter.write_str("Invalid coordinate range"),
Self::InvalidToroidalDomain => formatter.write_str("Invalid toroidal domain"),
Self::NonFiniteVertexCoordinate => formatter.write_str("Non-finite vertex coordinate"),
Self::InsufficientVertexCount => formatter.write_str("Insufficient vertex count"),
Self::InsufficientVerticesPerSlice => {
formatter.write_str("Insufficient vertices per slice")
}
Self::InsufficientNumberOfTimeSlices => {
formatter.write_str("Insufficient number of time slices")
}
Self::NonPositiveSliceCount => formatter.write_str("Number of slices must be positive"),
Self::EmptyVolumeProfile => formatter.write_str("Empty volume profile"),
Self::VolumeProfileLengthOverflow => {
formatter.write_str("Volume profile length overflow")
}
Self::InsufficientVerticesInVolumeProfileSlice => {
formatter.write_str("Insufficient vertices in volume-profile slice")
}
Self::VertexCountOverflow => formatter.write_str("Vertex count overflow"),
Self::SimplexCountOverflow => formatter.write_str("Simplex count overflow"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TriangulationMetadataField {
Timeslices,
Dimension,
}
impl fmt::Display for TriangulationMetadataField {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Timeslices => formatter.write_str("timeslices"),
Self::Dimension => formatter.write_str("dimension"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OutputFormat {
Csv,
Json,
}
impl fmt::Display for OutputFormat {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Csv => formatter.write_str("CSV"),
Self::Json => formatter.write_str("JSON"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CheckpointOperation {
Serialize,
Deserialize,
}
impl fmt::Display for CheckpointOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Serialize => formatter.write_str("serialize"),
Self::Deserialize => formatter.write_str("deserialize"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BackendMutationOperation {
SetSimplexDataByKey,
SetVertexDataByKey,
SetVertexData,
SubdivideFace,
RemoveVertex,
FlipEdge,
}
impl fmt::Display for BackendMutationOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SetSimplexDataByKey => formatter.write_str("set_simplex_data_by_key"),
Self::SetVertexDataByKey => formatter.write_str("set_vertex_data_by_key"),
Self::SetVertexData => formatter.write_str("set_vertex_data"),
Self::SubdivideFace => formatter.write_str("subdivide_face"),
Self::RemoveVertex => formatter.write_str("remove_vertex"),
Self::FlipEdge => formatter.write_str("flip_edge"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CdtValidationCheck {
Geometry,
FoliationAssignment,
Causality,
SimplexClassification,
ErgodicMoveCandidateGeometry,
}
impl fmt::Display for CdtValidationCheck {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Geometry => formatter.write_str("geometry"),
Self::FoliationAssignment => formatter.write_str("foliation_assignment"),
Self::Causality => formatter.write_str("causality"),
Self::SimplexClassification => formatter.write_str("simplex_classification"),
Self::ErgodicMoveCandidateGeometry => {
formatter.write_str("ergodic_move_candidate_geometry")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CdtValidationFailure {
BackendGeometry {
detail: String,
},
FaceVerticesUnavailable {
face: String,
detail: String,
},
FaceVertexCount {
face: String,
actual: usize,
expected: usize,
},
MissingVertexTimeLabel {
vertex: String,
},
InvalidCdtTriangle {
face: String,
spacelike_edges: u8,
timelike_edges: u8,
},
VertexCoordinateReadFailed {
vertex: String,
detail: String,
},
VertexCoordinateDimension {
vertex: String,
actual: usize,
expected_minimum: usize,
},
NonStrictSimplex {
face: String,
},
ErgodicMoveCandidateGeometry {
detail: String,
},
}
impl fmt::Display for CdtValidationFailure {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BackendGeometry { detail } | Self::ErgodicMoveCandidateGeometry { detail } => {
formatter.write_str(detail)
}
Self::FaceVerticesUnavailable { face, detail } => {
write!(
formatter,
"failed to resolve vertices for face {face}: {detail}"
)
}
Self::FaceVertexCount {
face,
actual,
expected,
} => write!(
formatter,
"face {face} has {actual} vertices, expected {expected}"
),
Self::MissingVertexTimeLabel { vertex } => write!(
formatter,
"vertex {vertex} has no time label in a foliated triangulation"
),
Self::InvalidCdtTriangle {
face,
spacelike_edges,
timelike_edges,
} => write!(
formatter,
"invalid CDT triangle at face {face}: spacelike={spacelike_edges}, timelike={timelike_edges}"
),
Self::VertexCoordinateReadFailed { vertex, detail } => {
write!(
formatter,
"failed to read coordinates for vertex {vertex}: {detail}"
)
}
Self::VertexCoordinateDimension {
vertex,
actual,
expected_minimum,
} => write!(
formatter,
"vertex {vertex} has {actual} coordinates, expected ≥ {expected_minimum}"
),
Self::NonStrictSimplex { face } => write!(
formatter,
"face {face} is not a strict CDT simplex (expected Up or Down)"
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CheckpointMoveCounter {
Attempted,
Accepted,
Rejected,
}
impl fmt::Display for CheckpointMoveCounter {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Attempted => formatter.write_str("attempted"),
Self::Accepted => formatter.write_str("accepted"),
Self::Rejected => formatter.write_str("rejected"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ProposalTelemetryCounter {
MoveFamilyProposals,
AcceptedTransitions,
RejectedTransitions,
}
impl fmt::Display for ProposalTelemetryCounter {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MoveFamilyProposals => formatter.write_str("move-family proposals"),
Self::AcceptedTransitions => formatter.write_str("accepted transitions"),
Self::RejectedTransitions => formatter.write_str("rejected transitions"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ScalarTraceField {
LogProb,
Action,
DeltaAction,
ActionBefore,
ActionAfter,
}
impl fmt::Display for ScalarTraceField {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LogProb => formatter.write_str("log_prob"),
Self::Action => formatter.write_str("action"),
Self::DeltaAction => formatter.write_str("delta_action"),
Self::ActionBefore => formatter.write_str("action_before"),
Self::ActionAfter => formatter.write_str("action_after"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MeasurementCountField {
Vertices,
Edges,
Triangles,
}
impl fmt::Display for MeasurementCountField {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Vertices => formatter.write_str("vertices"),
Self::Edges => formatter.write_str("edges"),
Self::Triangles => formatter.write_str("triangles"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SimplexCountField {
Vertices,
Edges,
Triangles,
}
impl fmt::Display for SimplexCountField {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Vertices => formatter.write_str("vertices"),
Self::Edges => formatter.write_str("edges"),
Self::Triangles => formatter.write_str("triangles"),
}
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum CheckpointResumeFailure {
#[error("resumed step count exceeds u32::MAX")]
StepCountOverflow,
#[error("checkpoint action mismatch: stored {stored}, recomputed {recomputed}")]
ActionMismatch {
stored: f64,
recomputed: f64,
},
#[error("checkpoint action is non-finite: stored {stored}")]
NonFiniteCheckpointAction {
stored: f64,
},
#[error("action configuration differs from checkpoint")]
IncompatibleActionConfiguration,
#[error("temperature differs from checkpoint")]
IncompatibleTemperature,
#[error("thermalization schedule differs from checkpoint")]
IncompatibleThermalizationSchedule,
#[error("measurement frequency differs from checkpoint")]
IncompatibleMeasurementFrequency,
#[error(
"chain counters do not match move statistics: chain accepted={chain_accepted}, rejected={chain_rejected}; move accepted={move_accepted}, rejected={move_rejected}"
)]
ChainCounterMismatch {
chain_accepted: usize,
chain_rejected: usize,
move_accepted: usize,
move_rejected: usize,
},
#[error(
"chain step count does not match checkpoint step: chain steps={chain_steps}, checkpoint step={checkpoint_step}"
)]
ChainStepMismatch {
chain_steps: usize,
checkpoint_step: u32,
},
#[error("step telemetry length mismatch: got {actual}, expected {expected}")]
StepTelemetryLengthMismatch {
actual: usize,
expected: usize,
},
#[error("accepted step count mismatch: got {actual}, expected {expected}")]
StepTelemetryAcceptedCountMismatch {
actual: usize,
expected: usize,
},
#[error("step telemetry index exceeds u32::MAX")]
StepTelemetryIndexOverflow,
#[error("step telemetry must be sequential: got step {actual}, expected {expected}")]
StepTelemetrySequenceMismatch {
actual: u32,
expected: u32,
},
#[error("step {step} has non-finite action_before")]
NonFiniteStepActionBefore {
step: u32,
},
#[error("step {step} has non-finite delta_action")]
NonFiniteStepDeltaAction {
step: u32,
},
#[error("step {step} action_after does not match delta_action")]
StepActionAfterDeltaMismatch {
step: u32,
},
#[error("step {step} has non-finite action_after")]
NonFiniteStepActionAfter {
step: u32,
},
#[error("scalar trace row count mismatch: got {actual}, expected {expected}")]
ScalarTraceLengthMismatch {
actual: usize,
expected: usize,
},
#[error("scalar trace step mismatch: got {actual}, expected {expected}")]
ScalarTraceStepMismatch {
actual: u32,
expected: u32,
},
#[error("scalar trace step must be nonzero: got {actual}")]
ScalarTraceStepZero {
actual: u32,
},
#[error("step {step} scalar trace move type mismatch: got {actual:?}, expected {expected:?}")]
ScalarTraceMoveTypeMismatch {
step: u32,
actual: MoveType,
expected: MoveType,
},
#[error("step {step} scalar trace accepted mismatch: got {actual}, expected {expected}")]
ScalarTraceAcceptedMismatch {
step: u32,
actual: bool,
expected: bool,
},
#[error("step {step} scalar trace delta_action does not match step telemetry")]
ScalarTraceDeltaActionMismatch {
step: u32,
},
#[error("step {step} scalar trace action_before does not match step telemetry")]
ScalarTraceActionBeforeMismatch {
step: u32,
},
#[error("step {step} scalar trace action does not match step telemetry")]
ScalarTraceActionMismatch {
step: u32,
},
#[error("step {step} scalar trace action_after does not match step telemetry")]
ScalarTraceActionAfterMismatch {
step: u32,
},
#[error("step {step} scalar trace log_prob does not match action and temperature")]
ScalarTraceLogProbMismatch {
step: u32,
},
#[error("step {step} scalar trace seed mismatch: got {actual:?}, expected {expected:?}")]
ScalarTraceSeedMismatch {
step: u32,
actual: Option<u64>,
expected: Option<u64>,
},
#[error(
"step {step} scalar trace volume profile total {profile_total} exceeds triangle count {triangles}"
)]
ScalarTraceVolumeProfileExceedsTriangles {
step: u32,
profile_total: u64,
triangles: u32,
},
#[error("step {step} scalar trace field {field} is non-finite")]
NonFiniteScalarTraceValue {
step: u32,
field: ScalarTraceField,
},
#[error("scalar trace accepted count mismatch: got {actual}, expected {expected}")]
ScalarTraceAcceptedCountMismatch {
actual: u64,
expected: u64,
},
#[error("scalar trace rejected-proposal count mismatch: got {actual}, expected {expected}")]
ScalarTraceRejectedProposalCountMismatch {
actual: u64,
expected: u64,
},
#[error("scalar trace no-proposal count mismatch: got {actual}, expected {expected}")]
ScalarTraceNoProposalCountMismatch {
actual: u64,
expected: u64,
},
#[error("scheduled measurement count exceeds usize::MAX")]
MeasurementCountOverflow,
#[error("scheduled measurement count mismatch: got {actual}, expected {expected}")]
MeasurementCountMismatch {
actual: usize,
expected: usize,
},
#[error("scheduled measurement step exceeds u32::MAX")]
MeasurementStepOverflow,
#[error("measurement telemetry step mismatch: got {actual}, expected {expected}")]
MeasurementStepMismatch {
actual: u32,
expected: u32,
},
#[error("{counter} move count exceeds u64::MAX")]
MoveCounterOverflow {
counter: CheckpointMoveCounter,
},
#[error("{move_type:?} hard-failure move count must be zero in resumable checkpoints")]
MoveHardFailures {
move_type: MoveType,
},
#[error("{move_type:?} accepted move count exceeds attempted move count")]
MoveAcceptedExceedsAttempted {
move_type: MoveType,
},
#[error("accepted move count exceeds attempted move count")]
TotalAcceptedExceedsAttempted,
#[error("{counter} move count exceeds usize::MAX")]
CounterConversionOverflow {
counter: CheckpointMoveCounter,
},
#[error("{counter} proposal telemetry count exceeds u64::MAX")]
ProposalCounterOverflow {
counter: ProposalTelemetryCounter,
},
#[error("proposal telemetry has {actual} hard failures; expected 0")]
ProposalHardFailures {
actual: u64,
},
#[error("proposal move-family count mismatch: got {actual}, expected {expected}")]
ProposalMoveFamilyCountMismatch {
actual: u64,
expected: u64,
},
#[error("proposal accepted-transition count mismatch: got {actual}, expected {expected}")]
ProposalAcceptedCountMismatch {
actual: u64,
expected: u64,
},
#[error("proposal rejected-transition count mismatch: got {actual}, expected {expected}")]
ProposalRejectedCountMismatch {
actual: u64,
expected: u64,
},
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum MetropolisMoveApplicationFailure {
#[error("backend mutation failed [{operation}] on {target}: {detail}")]
BackendMutation {
operation: BackendMutationOperation,
target: String,
detail: String,
},
#[error(
"backend mutation failed [{operation}] on {target}: {detail}; rollback failed: {rollback_errors}"
)]
BackendRollback {
operation: BackendMutationOperation,
target: String,
detail: String,
rollback_errors: String,
},
#[error("Delaunay validation failed [{level}]: {detail}")]
DelaunayValidation {
level: DelaunayValidationLevel,
detail: String,
},
#[error("validation failed [{check}]: {failure}")]
Validation {
check: CdtValidationCheck,
failure: CdtValidationFailure,
},
#[error(
"topology mismatch for {topology}: Euler characteristic χ={euler_characteristic}, expected one of {expected_euler_characteristics:?} (V={vertices}, E={edges}, F={faces})"
)]
TopologyMismatch {
topology: CdtTopology,
euler_characteristic: i128,
expected_euler_characteristics: Vec<i128>,
vertices: usize,
edges: usize,
faces: usize,
},
#[error("foliation validation failed: {0}")]
Foliation(FoliationError),
#[error("{}", format_causality_violation(*time_0, *time_1, *step_distance))]
CausalityViolation {
time_0: u32,
time_1: u32,
step_distance: u32,
},
#[error("unexpected accepted-move failure: {detail}")]
Unexpected {
detail: String,
},
}
impl From<CdtError> for MetropolisMoveApplicationFailure {
fn from(error: CdtError) -> Self {
match error {
CdtError::BackendMutationFailed {
operation,
target,
detail,
} => Self::BackendMutation {
operation,
target,
detail,
},
CdtError::BackendRollbackFailed {
operation,
target,
detail,
rollback_errors,
} => Self::BackendRollback {
operation,
target,
detail,
rollback_errors,
},
CdtError::DelaunayValidationFailed { level, detail } => {
Self::DelaunayValidation { level, detail }
}
CdtError::ValidationFailed { check, failure } => Self::Validation { check, failure },
CdtError::TopologyMismatch {
topology,
euler_characteristic,
expected_euler_characteristics,
vertices,
edges,
faces,
} => Self::TopologyMismatch {
topology,
euler_characteristic,
expected_euler_characteristics,
vertices,
edges,
faces,
},
CdtError::Foliation(error) => Self::Foliation(error),
CdtError::CausalityViolation {
time_0,
time_1,
step_distance,
} => Self::CausalityViolation {
time_0,
time_1,
step_distance,
},
CdtError::MetropolisMoveApplicationFailed { source, .. }
| CdtError::ProposalApplicationFailed { source, .. } => source,
unexpected @ (CdtError::UnsupportedDimension(_)
| CdtError::DelaunayGenerationFailed { .. }
| CdtError::InvalidGenerationParameters { .. }
| CdtError::InvalidConfiguration { .. }
| CdtError::InvalidSimplexCount { .. }
| CdtError::InvalidMeasurementAction { .. }
| CdtError::InvalidMeasurementCount { .. }
| CdtError::InvalidMeasurementVolumeProfile { .. }
| CdtError::InvalidScalarTraceCount { .. }
| CdtError::MeasurementCountOverflow { .. }
| CdtError::InvalidSimulationConfiguration { .. }
| CdtError::PlannedProposalStepFailed { .. }
| CdtError::UnexpectedPlannedStepOutcome { .. }
| CdtError::PlannedProposalTelemetryMissing { .. }
| CdtError::InvalidTriangulationMetadata { .. }
| CdtError::VertexBuildFailed { .. }
| CdtError::Mcmc(_)
| CdtError::OutputWriteFailed { .. }
| CdtError::OutputPathResolutionFailed { .. }
| CdtError::OutputPathConflict { .. }
| CdtError::OutputReadFailed { .. }
| CdtError::CheckpointSerializationFailed { .. }
| CdtError::CheckpointResumeFailed { .. }) => Self::Unexpected {
detail: unexpected.to_string(),
},
}
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum CdtError {
#[error("Unsupported dimension: {0}. Only 2D is currently supported")]
UnsupportedDimension(u32),
#[error(
"Delaunay triangulation generation failed: {vertex_count} vertices, range [{}, {}], attempt {attempt}: {underlying_error}",
coordinate_range.0,
coordinate_range.1
)]
DelaunayGenerationFailed {
vertex_count: u32,
coordinate_range: (f64, f64),
attempt: u32,
underlying_error: String,
},
#[error("Delaunay validation failed [{level}]: {detail}")]
DelaunayValidationFailed {
level: DelaunayValidationLevel,
detail: String,
},
#[error(
"Invalid triangulation parameters: {issue} (got: {provided_value}, expected: {expected_range})"
)]
InvalidGenerationParameters {
issue: GenerationParameterIssue,
provided_value: String,
expected_range: String,
},
#[error("Invalid configuration: {setting} (got: {provided_value}, expected: {expected})")]
InvalidConfiguration {
setting: ConfigurationSetting,
provided_value: String,
expected: String,
},
#[error(
"Invalid simulation configuration: {setting} (got: {provided_value}, expected: {expected})"
)]
InvalidSimulationConfiguration {
setting: ConfigurationSetting,
provided_value: String,
expected: String,
},
#[error(
"Invalid CDT simplex count: {field} (got: {provided_value}, expected: strictly positive)"
)]
InvalidSimplexCount {
field: SimplexCountField,
provided_value: usize,
},
#[error(
"Invalid measurement count: {field} (got: {provided_value}, expected: strictly positive)"
)]
InvalidMeasurementCount {
field: MeasurementCountField,
provided_value: u32,
},
#[error(
"Invalid measurement volume profile at step {step}: total {profile_total} exceeds triangle count {triangles}"
)]
InvalidMeasurementVolumeProfile {
step: u32,
profile_total: u64,
triangles: u32,
},
#[error(
"Invalid scalar trace count: {field} (got: {provided_value}, expected: strictly positive)"
)]
InvalidScalarTraceCount {
field: MeasurementCountField,
provided_value: u32,
},
#[error("Measurement count overflow: {field} (got: {provided_value}, max: {max})")]
MeasurementCountOverflow {
field: MeasurementCountField,
provided_value: usize,
max: u32,
},
#[error("Invalid measurement action at step {step}: got {provided_value}, expected finite")]
InvalidMeasurementAction {
step: u32,
provided_value: f64,
},
#[error(
"Metropolis accepted {move_type:?} at step {step}, but applying it failed after {attempts} attempts; source: {source}"
)]
MetropolisMoveApplicationFailed {
step: u32,
move_type: MoveType,
attempts: usize,
source: MetropolisMoveApplicationFailure,
},
#[error("CDT proposal failed while applying {move_type:?} on attempt {attempt}: {source}")]
ProposalApplicationFailed {
move_type: MoveType,
attempt: usize,
source: MetropolisMoveApplicationFailure,
},
#[error("planned CDT proposal step {step} completed without required proposal telemetry")]
PlannedProposalTelemetryMissing {
step: u32,
},
#[error("planned CDT proposal step {step} failed: {detail}")]
PlannedProposalStepFailed {
step: u32,
detail: String,
},
#[error("planned CDT proposal step {step} returned unsupported upstream outcome {outcome:?}")]
UnexpectedPlannedStepOutcome {
step: u32,
outcome: StepOutcome,
},
#[error(
"Invalid triangulation metadata: {field} for {topology} (got: {provided_value}, expected: {expected})"
)]
InvalidTriangulationMetadata {
field: TriangulationMetadataField,
topology: CdtTopology,
provided_value: String,
expected: String,
},
#[error("Validation failed [{check}]: {failure}")]
ValidationFailed {
check: CdtValidationCheck,
failure: CdtValidationFailure,
},
#[error(
"Topology mismatch for {topology}: Euler characteristic χ={euler_characteristic}, expected one of {expected_euler_characteristics:?} (V={vertices}, E={edges}, F={faces})"
)]
TopologyMismatch {
topology: CdtTopology,
euler_characteristic: i128,
expected_euler_characteristics: Vec<i128>,
vertices: usize,
edges: usize,
faces: usize,
},
#[error("Foliation validation failed: {0}")]
Foliation(#[from] FoliationError),
#[error("Vertex construction failed [{context}]: {underlying_error}")]
VertexBuildFailed {
context: String,
underlying_error: String,
},
#[error("Backend mutation failed [{operation}] on {target}: {detail}")]
BackendMutationFailed {
operation: BackendMutationOperation,
target: String,
detail: String,
},
#[error(
"Backend mutation failed [{operation}] on {target}: {detail}; rollback failed: {rollback_errors}"
)]
BackendRollbackFailed {
operation: BackendMutationOperation,
target: String,
detail: String,
rollback_errors: String,
},
#[error("{}", format_causality_violation(*time_0, *time_1, *step_distance))]
CausalityViolation {
time_0: u32,
time_1: u32,
step_distance: u32,
},
#[error("MCMC error: {0}")]
Mcmc(#[from] McmcError),
#[error("Failed to write {format} output to {path}: {detail}")]
OutputWriteFailed {
path: String,
format: OutputFormat,
detail: String,
},
#[error("Failed to resolve output path from base {base_path}: {detail}")]
OutputPathResolutionFailed {
base_path: String,
detail: String,
},
#[error("CSV output path {csv_path} and JSON output path {json_path} resolve to the same file")]
OutputPathConflict {
csv_path: String,
json_path: String,
},
#[error("Failed to read {format} output from {path}: {detail}")]
OutputReadFailed {
path: String,
format: OutputFormat,
detail: String,
},
#[error("Failed to {operation} {target} checkpoint: {detail}")]
CheckpointSerializationFailed {
operation: CheckpointOperation,
target: String,
detail: String,
},
#[error("Failed to resume MCMC checkpoint: {failure}")]
CheckpointResumeFailed {
#[source]
failure: CheckpointResumeFailure,
},
}
fn format_causality_violation(time_0: u32, time_1: u32, step_distance: u32) -> String {
let raw = time_0.abs_diff(time_1);
if raw == step_distance {
format!(
"Causality violation: edge spans {step_distance} time-slice steps \
(t={time_0} to t={time_1}), maximum allowed is 1"
)
} else {
format!(
"Causality violation: edge spans {step_distance} time-slice steps \
(t={time_0} to t={time_1}, |Δt|={raw} on the time circle), \
maximum allowed is 1"
)
}
}
pub type CdtResult<T> = Result<T, CdtError>;
#[cfg(test)]
mod tests {
use super::*;
use std::assert_matches;
use std::error::Error;
#[test]
fn test_invalid_configuration_error() {
let error = CdtError::InvalidConfiguration {
setting: ConfigurationSetting::Vertices,
provided_value: "2".to_string(),
expected: "≥ 3".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Invalid configuration: vertices (got: 2, expected: ≥ 3)"
);
}
#[test]
fn test_invalid_simulation_configuration_error() {
let error = CdtError::InvalidSimulationConfiguration {
setting: ConfigurationSetting::Temperature,
provided_value: "NaN".to_string(),
expected: "finite and positive".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Invalid simulation configuration: temperature (got: NaN, expected: finite and positive)"
);
}
#[test]
fn test_invalid_triangulation_metadata_error() {
let error = CdtError::InvalidTriangulationMetadata {
field: TriangulationMetadataField::Timeslices,
topology: CdtTopology::Toroidal,
provided_value: "2".to_string(),
expected: "≥ 3".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Invalid triangulation metadata: timeslices for toroidal (got: 2, expected: ≥ 3)"
);
}
#[test]
fn test_delaunay_generation_failed_error() {
let error = CdtError::DelaunayGenerationFailed {
vertex_count: 10,
coordinate_range: (-1.0, 1.0),
attempt: 5,
underlying_error: "Too many duplicate points".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Delaunay triangulation generation failed: 10 vertices, range [-1, 1], attempt 5: Too many duplicate points"
);
}
#[test]
fn test_delaunay_validation_failed_error() {
let error = CdtError::DelaunayValidationFailed {
level: DelaunayValidationLevel::Four,
detail: "upstream validation failed".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Delaunay validation failed [Level 1-4]: upstream validation failed"
);
}
#[test]
fn validation_level_display_covers_all_levels() {
assert_eq!(DelaunayValidationLevel::One.to_string(), "Level 1");
assert_eq!(DelaunayValidationLevel::Two.to_string(), "Level 1-2");
assert_eq!(DelaunayValidationLevel::Three.to_string(), "Level 1-3");
assert_eq!(DelaunayValidationLevel::Four.to_string(), "Level 1-4");
}
#[test]
fn validation_check_display_covers_all_categories() {
assert_eq!(CdtValidationCheck::Geometry.to_string(), "geometry");
assert_eq!(
CdtValidationCheck::FoliationAssignment.to_string(),
"foliation_assignment"
);
assert_eq!(CdtValidationCheck::Causality.to_string(), "causality");
assert_eq!(
CdtValidationCheck::SimplexClassification.to_string(),
"simplex_classification"
);
assert_eq!(
CdtValidationCheck::ErgodicMoveCandidateGeometry.to_string(),
"ergodic_move_candidate_geometry"
);
}
#[test]
fn configuration_setting_display_covers_all_settings() {
let cases = [
(ConfigurationSetting::Dimension, "dimension"),
(ConfigurationSetting::Vertices, "vertices"),
(ConfigurationSetting::Timeslices, "timeslices"),
(ConfigurationSetting::Temperature, "temperature"),
(ConfigurationSetting::Steps, "steps"),
(
ConfigurationSetting::ThermalizationSteps,
"thermalization_steps",
),
(
ConfigurationSetting::MeasurementFrequency,
"measurement_frequency",
),
(
ConfigurationSetting::MeasurementSchedule,
"measurement schedule",
),
(ConfigurationSetting::Coupling0, "coupling_0"),
(ConfigurationSetting::Coupling2, "coupling_2"),
(
ConfigurationSetting::CosmologicalConstant,
"cosmological_constant",
),
(ConfigurationSetting::VolumeProfile, "volume_profile"),
];
for (setting, expected) in cases {
assert_eq!(setting.to_string(), expected);
}
}
#[test]
fn generation_parameter_issue_display_covers_all_issues() {
let cases = [
(
GenerationParameterIssue::InvalidCoordinateRange,
"Invalid coordinate range",
),
(
GenerationParameterIssue::InvalidToroidalDomain,
"Invalid toroidal domain",
),
(
GenerationParameterIssue::NonFiniteVertexCoordinate,
"Non-finite vertex coordinate",
),
(
GenerationParameterIssue::InsufficientVertexCount,
"Insufficient vertex count",
),
(
GenerationParameterIssue::InsufficientVerticesPerSlice,
"Insufficient vertices per slice",
),
(
GenerationParameterIssue::InsufficientNumberOfTimeSlices,
"Insufficient number of time slices",
),
(
GenerationParameterIssue::NonPositiveSliceCount,
"Number of slices must be positive",
),
(
GenerationParameterIssue::EmptyVolumeProfile,
"Empty volume profile",
),
(
GenerationParameterIssue::VolumeProfileLengthOverflow,
"Volume profile length overflow",
),
(
GenerationParameterIssue::InsufficientVerticesInVolumeProfileSlice,
"Insufficient vertices in volume-profile slice",
),
(
GenerationParameterIssue::VertexCountOverflow,
"Vertex count overflow",
),
(
GenerationParameterIssue::SimplexCountOverflow,
"Simplex count overflow",
),
];
for (issue, expected) in cases {
assert_eq!(issue.to_string(), expected);
}
}
#[test]
fn triangulation_metadata_field_display_covers_all_fields() {
assert_eq!(
TriangulationMetadataField::Timeslices.to_string(),
"timeslices"
);
assert_eq!(
TriangulationMetadataField::Dimension.to_string(),
"dimension"
);
}
#[test]
fn output_format_display_covers_all_formats() {
assert_eq!(OutputFormat::Csv.to_string(), "CSV");
assert_eq!(OutputFormat::Json.to_string(), "JSON");
}
#[test]
fn checkpoint_operation_display_covers_all_operations() {
assert_eq!(CheckpointOperation::Serialize.to_string(), "serialize");
assert_eq!(CheckpointOperation::Deserialize.to_string(), "deserialize");
}
#[test]
fn backend_mutation_operation_display_covers_all_operations() {
let cases = [
(
BackendMutationOperation::SetSimplexDataByKey,
"set_simplex_data_by_key",
),
(
BackendMutationOperation::SetVertexDataByKey,
"set_vertex_data_by_key",
),
(BackendMutationOperation::SetVertexData, "set_vertex_data"),
(BackendMutationOperation::SubdivideFace, "subdivide_face"),
(BackendMutationOperation::RemoveVertex, "remove_vertex"),
(BackendMutationOperation::FlipEdge, "flip_edge"),
];
for (operation, expected) in cases {
assert_eq!(operation.to_string(), expected);
}
}
#[test]
fn checkpoint_move_counter_display_covers_all_categories() {
let cases = [
(CheckpointMoveCounter::Attempted, "attempted"),
(CheckpointMoveCounter::Accepted, "accepted"),
(CheckpointMoveCounter::Rejected, "rejected"),
];
for (counter, expected) in cases {
assert_eq!(counter.to_string(), expected);
}
}
#[test]
fn proposal_telemetry_counter_display_covers_all_categories() {
let cases = [
(
ProposalTelemetryCounter::MoveFamilyProposals,
"move-family proposals",
),
(
ProposalTelemetryCounter::AcceptedTransitions,
"accepted transitions",
),
(
ProposalTelemetryCounter::RejectedTransitions,
"rejected transitions",
),
];
for (counter, expected) in cases {
assert_eq!(counter.to_string(), expected);
}
}
#[test]
fn scalar_trace_field_display_covers_all_fields() {
let cases = [
(ScalarTraceField::LogProb, "log_prob"),
(ScalarTraceField::Action, "action"),
(ScalarTraceField::DeltaAction, "delta_action"),
(ScalarTraceField::ActionBefore, "action_before"),
(ScalarTraceField::ActionAfter, "action_after"),
];
for (field, expected) in cases {
assert_eq!(field.to_string(), expected);
}
}
#[test]
fn simplex_count_field_display_covers_all_fields() {
let cases = [
(SimplexCountField::Vertices, "vertices"),
(SimplexCountField::Edges, "edges"),
(SimplexCountField::Triangles, "triangles"),
];
for (field, expected) in cases {
assert_eq!(field.to_string(), expected);
}
}
#[test]
fn checkpoint_resume_failure_display_includes_structured_context() {
let failure = CheckpointResumeFailure::ChainCounterMismatch {
chain_accepted: 1,
chain_rejected: 2,
move_accepted: 3,
move_rejected: 4,
};
assert_eq!(
failure.to_string(),
"chain counters do not match move statistics: chain accepted=1, rejected=2; move accepted=3, rejected=4"
);
let action_failure =
CheckpointResumeFailure::NonFiniteCheckpointAction { stored: f64::NAN };
assert_eq!(
action_failure.to_string(),
"checkpoint action is non-finite: stored NaN"
);
}
#[test]
fn proposal_resume_failures_display_structured_context() {
let overflow = CheckpointResumeFailure::ProposalCounterOverflow {
counter: ProposalTelemetryCounter::AcceptedTransitions,
};
assert_eq!(
overflow.to_string(),
"accepted transitions proposal telemetry count exceeds u64::MAX"
);
let hard_failures = CheckpointResumeFailure::ProposalHardFailures { actual: 3 };
assert_eq!(
hard_failures.to_string(),
"proposal telemetry has 3 hard failures; expected 0"
);
}
#[test]
fn test_unsupported_dimension_error() {
let error = CdtError::UnsupportedDimension(3);
let display = format!("{error}");
assert_eq!(
display,
"Unsupported dimension: 3. Only 2D is currently supported"
);
}
#[test]
fn test_invalid_generation_parameters_error() {
let error = CdtError::InvalidGenerationParameters {
issue: GenerationParameterIssue::InsufficientVertexCount,
provided_value: "2".to_string(),
expected_range: "at least 3".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Invalid triangulation parameters: Insufficient vertex count (got: 2, expected: at least 3)"
);
}
#[test]
fn test_validation_failed_error() {
let error = CdtError::ValidationFailed {
check: CdtValidationCheck::Geometry,
failure: CdtValidationFailure::BackendGeometry {
detail: "backend reported invalid triangulation structure".to_string(),
},
};
let display = format!("{error}");
assert_eq!(
display,
"Validation failed [geometry]: backend reported invalid triangulation structure"
);
}
#[test]
fn cdt_validation_failure_display_covers_structured_variants() {
let cases = [
(
CdtValidationFailure::BackendGeometry {
detail: "backend rejected structure".to_string(),
},
"backend rejected structure",
),
(
CdtValidationFailure::FaceVerticesUnavailable {
face: "FaceKey(3v1)".to_string(),
detail: "backend reported invalid simplex key".to_string(),
},
"failed to resolve vertices for face FaceKey(3v1): backend reported invalid simplex key",
),
(
CdtValidationFailure::FaceVertexCount {
face: "FaceKey(3v1)".to_string(),
actual: 4,
expected: 3,
},
"face FaceKey(3v1) has 4 vertices, expected 3",
),
(
CdtValidationFailure::MissingVertexTimeLabel {
vertex: "VertexKey(7v1)".to_string(),
},
"vertex VertexKey(7v1) has no time label in a foliated triangulation",
),
(
CdtValidationFailure::InvalidCdtTriangle {
face: "FaceKey(3v1)".to_string(),
spacelike_edges: 3,
timelike_edges: 0,
},
"invalid CDT triangle at face FaceKey(3v1): spacelike=3, timelike=0",
),
(
CdtValidationFailure::VertexCoordinateReadFailed {
vertex: "VertexKey(7v1)".to_string(),
detail: "missing vertex".to_string(),
},
"failed to read coordinates for vertex VertexKey(7v1): missing vertex",
),
(
CdtValidationFailure::VertexCoordinateDimension {
vertex: "VertexKey(7v1)".to_string(),
actual: 1,
expected_minimum: 2,
},
"vertex VertexKey(7v1) has 1 coordinates, expected ≥ 2",
),
(
CdtValidationFailure::NonStrictSimplex {
face: "FaceKey(3v1)".to_string(),
},
"face FaceKey(3v1) is not a strict CDT simplex (expected Up or Down)",
),
(
CdtValidationFailure::ErgodicMoveCandidateGeometry {
detail: "candidate edge has no adjacent faces".to_string(),
},
"candidate edge has no adjacent faces",
),
];
for (failure, expected) in cases {
assert_eq!(failure.to_string(), expected);
}
}
#[test]
fn test_topology_mismatch_error() {
let error = CdtError::TopologyMismatch {
topology: CdtTopology::Toroidal,
euler_characteristic: 1,
expected_euler_characteristics: vec![0],
vertices: 3,
edges: 3,
faces: 1,
};
let display = format!("{error}");
assert_eq!(
display,
"Topology mismatch for toroidal: Euler characteristic χ=1, expected one of [0] (V=3, E=3, F=1)"
);
}
#[test]
fn test_foliation_error_variant() {
let error = CdtError::Foliation(FoliationError::EmptySlice { slice: 3 });
let display = format!("{error}");
assert_eq!(
display,
"Foliation validation failed: time slice 3 is empty"
);
}
#[test]
fn test_vertex_build_failed_error() {
let error = CdtError::VertexBuildFailed {
context: "explicit CDT vertex 7".to_string(),
underlying_error: "Missing required field: `point`".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Vertex construction failed [explicit CDT vertex 7]: Missing required field: `point`"
);
}
#[test]
fn test_backend_rollback_failed_error() {
let error = CdtError::BackendRollbackFailed {
operation: BackendMutationOperation::SetVertexDataByKey,
target: "vertex VertexKey(123v1)".to_string(),
detail: "backend reported invalid vertex key".to_string(),
rollback_errors: "vertex VertexKey(7v1): backend reported invalid vertex key"
.to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"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"
);
}
#[test]
fn test_backend_mutation_failed_error() {
let error = CdtError::BackendMutationFailed {
operation: BackendMutationOperation::SetVertexData,
target: "vertex VertexKey(123v1)".to_string(),
detail: "backend reported invalid vertex key".to_string(),
};
let display = format!("{error}");
assert_eq!(
display,
"Backend mutation failed [set_vertex_data] on vertex VertexKey(123v1): backend reported invalid vertex key"
);
}
#[test]
fn test_metropolis_move_application_failed_error() {
let source = MetropolisMoveApplicationFailure::BackendMutation {
operation: BackendMutationOperation::SetVertexData,
target: "vertex VertexKey(123v1)".to_string(),
detail: "backend reported invalid vertex key".to_string(),
};
let error = CdtError::MetropolisMoveApplicationFailed {
step: 17,
move_type: MoveType::Move31Remove,
attempts: 8,
source: source.clone(),
};
let display = format!("{error}");
assert_eq!(
display,
"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"
);
assert_eq!(
Error::source(&error).map(ToString::to_string),
Some(source.to_string())
);
}
#[test]
fn test_proposal_application_failed_error() {
let source = MetropolisMoveApplicationFailure::BackendMutation {
operation: BackendMutationOperation::SetVertexDataByKey,
target: "vertex VertexKey(123v1)".to_string(),
detail: "backend reported invalid vertex key".to_string(),
};
let error = CdtError::ProposalApplicationFailed {
move_type: MoveType::Move13Add,
attempt: 2,
source: source.clone(),
};
let display = format!("{error}");
assert_eq!(
display,
"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"
);
assert_eq!(
Error::source(&error).map(ToString::to_string),
Some(source.to_string())
);
}
#[test]
fn planned_proposal_telemetry_missing_reports_step_without_fake_move_type() {
let error = CdtError::PlannedProposalTelemetryMissing { step: 23 };
assert_eq!(
format!("{error}"),
"planned CDT proposal step 23 completed without required proposal telemetry"
);
assert!(Error::source(&error).is_none());
}
#[test]
fn planned_proposal_step_failed_preserves_upstream_detail() {
let error = CdtError::PlannedProposalStepFailed {
step: 23,
detail: "future upstream sampler failure".to_string(),
};
assert_eq!(
format!("{error}"),
"planned CDT proposal step 23 failed: future upstream sampler failure"
);
assert!(Error::source(&error).is_none());
}
#[test]
fn metropolis_move_application_failure_preserves_backend_mutation_fields() {
let failure = MetropolisMoveApplicationFailure::from(CdtError::BackendMutationFailed {
operation: BackendMutationOperation::RemoveVertex,
target: "vertex VertexKey(7v1)".to_string(),
detail: "backend reported invalid vertex key".to_string(),
});
let MetropolisMoveApplicationFailure::BackendMutation {
operation,
target,
detail,
} = failure
else {
panic!("expected backend mutation failure source");
};
assert_eq!(operation, BackendMutationOperation::RemoveVertex);
assert_eq!(target, "vertex VertexKey(7v1)");
assert_eq!(detail, "backend reported invalid vertex key");
}
#[test]
fn metropolis_move_application_failure_preserves_validation_fields() {
let validation_failure = CdtValidationFailure::InvalidCdtTriangle {
face: "FaceKey(3v1)".to_string(),
spacelike_edges: 3,
timelike_edges: 0,
};
let failure = MetropolisMoveApplicationFailure::from(CdtError::ValidationFailed {
check: CdtValidationCheck::Causality,
failure: validation_failure.clone(),
});
let MetropolisMoveApplicationFailure::Validation { check, failure } = failure else {
panic!("expected validation failure source");
};
assert_eq!(check, CdtValidationCheck::Causality);
assert_eq!(failure, validation_failure);
}
#[test]
fn metropolis_move_application_failure_preserves_structured_sources() {
let cases = [
(
CdtError::BackendRollbackFailed {
operation: BackendMutationOperation::FlipEdge,
target: "edge EdgeKey(5v1)".to_string(),
detail: "flip failed".to_string(),
rollback_errors: "rollback failed".to_string(),
},
MetropolisMoveApplicationFailure::BackendRollback {
operation: BackendMutationOperation::FlipEdge,
target: "edge EdgeKey(5v1)".to_string(),
detail: "flip failed".to_string(),
rollback_errors: "rollback failed".to_string(),
},
),
(
CdtError::DelaunayValidationFailed {
level: DelaunayValidationLevel::Three,
detail: "invalid triangulation".to_string(),
},
MetropolisMoveApplicationFailure::DelaunayValidation {
level: DelaunayValidationLevel::Three,
detail: "invalid triangulation".to_string(),
},
),
(
CdtError::TopologyMismatch {
topology: CdtTopology::Toroidal,
euler_characteristic: 1,
expected_euler_characteristics: vec![0],
vertices: 3,
edges: 3,
faces: 1,
},
MetropolisMoveApplicationFailure::TopologyMismatch {
topology: CdtTopology::Toroidal,
euler_characteristic: 1,
expected_euler_characteristics: vec![0],
vertices: 3,
edges: 3,
faces: 1,
},
),
(
CdtError::Foliation(FoliationError::EmptySlice { slice: 3 }),
MetropolisMoveApplicationFailure::Foliation(FoliationError::EmptySlice {
slice: 3,
}),
),
(
CdtError::CausalityViolation {
time_0: 0,
time_1: 2,
step_distance: 2,
},
MetropolisMoveApplicationFailure::CausalityViolation {
time_0: 0,
time_1: 2,
step_distance: 2,
},
),
];
for (error, expected) in cases {
assert_eq!(MetropolisMoveApplicationFailure::from(error), expected);
}
}
#[test]
fn metropolis_move_application_failure_from_wrapper_preserves_source() {
let source = MetropolisMoveApplicationFailure::BackendMutation {
operation: BackendMutationOperation::RemoveVertex,
target: "vertex VertexKey(7v1)".to_string(),
detail: "backend reported invalid vertex key".to_string(),
};
let failure =
MetropolisMoveApplicationFailure::from(CdtError::MetropolisMoveApplicationFailed {
step: 17,
move_type: MoveType::Move31Remove,
attempts: 8,
source: source.clone(),
});
assert_eq!(failure, source);
}
#[test]
fn metropolis_move_application_failure_unexpected_retains_diagnostic() {
let failure = MetropolisMoveApplicationFailure::from(CdtError::UnsupportedDimension(3));
let MetropolisMoveApplicationFailure::Unexpected { detail } = failure else {
panic!("expected unexpected failure source");
};
assert_eq!(
detail,
"Unsupported dimension: 3. Only 2D is currently supported"
);
}
#[test]
fn test_causality_violation_open_boundary_error() {
let error = CdtError::CausalityViolation {
time_0: 0,
time_1: 3,
step_distance: 3,
};
let display = format!("{error}");
assert_eq!(
display,
"Causality violation: edge spans 3 time-slice steps (t=0 to t=3), maximum allowed is 1"
);
}
#[test]
fn test_causality_violation_toroidal_error_reports_circular_distance() {
let error = CdtError::CausalityViolation {
time_0: 0,
time_1: 8,
step_distance: 2,
};
let display = format!("{error}");
assert_eq!(
display,
"Causality violation: edge spans 2 time-slice steps \
(t=0 to t=8, |Δt|=8 on the time circle), maximum allowed is 1"
);
}
#[test]
fn test_mcmc_error() {
let error = CdtError::Mcmc(McmcError::NanProposedLogProb);
let display = format!("{error}");
assert_eq!(
display,
"MCMC error: target returned NaN log-probability for a proposed state"
);
}
#[test]
fn test_mcmc_error_from_conversion() {
let mcmc_err = McmcError::NanProposedLogProb;
let cdt_err: CdtError = mcmc_err.into();
assert_matches!(cdt_err, CdtError::Mcmc(McmcError::NanProposedLogProb));
let display = format!("{cdt_err}");
assert!(
display.contains("MCMC error"),
"Should contain MCMC error prefix: {display}"
);
assert!(
display.contains("NaN"),
"Should contain NaN context: {display}"
);
}
#[test]
fn test_output_write_failed_error() {
let error = CdtError::OutputWriteFailed {
path: "trace.csv".to_string(),
format: OutputFormat::Csv,
detail: "permission denied".to_string(),
};
let CdtError::OutputWriteFailed {
path,
format,
detail,
} = &error
else {
panic!("expected OutputWriteFailed variant");
};
assert_eq!(path, "trace.csv");
assert_eq!(*format, OutputFormat::Csv);
assert_eq!(detail, "permission denied");
let display = format!("{error}");
assert_eq!(
display,
"Failed to write CSV output to trace.csv: permission denied"
);
}
#[test]
fn test_output_path_resolution_failed_error() {
let error = CdtError::OutputPathResolutionFailed {
base_path: ".".to_string(),
detail: "No such file or directory".to_string(),
};
let CdtError::OutputPathResolutionFailed { base_path, detail } = &error else {
panic!("expected OutputPathResolutionFailed variant");
};
assert_eq!(base_path, ".");
assert_eq!(detail, "No such file or directory");
let display = format!("{error}");
assert_eq!(
display,
"Failed to resolve output path from base .: No such file or directory"
);
}
#[test]
fn test_output_path_conflict_error() {
let error = CdtError::OutputPathConflict {
csv_path: "output/results".to_string(),
json_path: "output/results".to_string(),
};
let CdtError::OutputPathConflict {
csv_path,
json_path,
} = &error
else {
panic!("expected OutputPathConflict variant");
};
assert_eq!(csv_path, "output/results");
assert_eq!(json_path, "output/results");
assert_eq!(
format!("{error}"),
"CSV output path output/results and JSON output path output/results resolve to the same file"
);
}
#[test]
fn test_output_read_failed_error() {
let error = CdtError::OutputReadFailed {
path: "summary.json".to_string(),
format: OutputFormat::Json,
detail: "expected value at line 1 column 1".to_string(),
};
let CdtError::OutputReadFailed {
path,
format,
detail,
} = &error
else {
panic!("expected OutputReadFailed variant");
};
assert_eq!(path, "summary.json");
assert_eq!(*format, OutputFormat::Json);
assert_eq!(detail, "expected value at line 1 column 1");
let display = format!("{error}");
assert_eq!(
display,
"Failed to read JSON output from summary.json: expected value at line 1 column 1"
);
}
#[test]
fn test_checkpoint_serialization_failed_error() {
let error = CdtError::CheckpointSerializationFailed {
operation: CheckpointOperation::Deserialize,
target: "final triangulation".to_string(),
detail: "missing field `geometry`".to_string(),
};
let CdtError::CheckpointSerializationFailed {
operation,
target,
detail,
} = &error
else {
panic!("expected CheckpointSerializationFailed variant");
};
assert_eq!(*operation, CheckpointOperation::Deserialize);
assert_eq!(target, "final triangulation");
assert_eq!(detail, "missing field `geometry`");
let display = format!("{error}");
assert_eq!(
display,
"Failed to deserialize final triangulation checkpoint: missing field `geometry`"
);
}
#[test]
fn test_checkpoint_resume_failed_error() {
let error = CdtError::CheckpointResumeFailed {
failure: CheckpointResumeFailure::IncompatibleTemperature,
};
let CdtError::CheckpointResumeFailed { failure } = &error else {
panic!("expected CheckpointResumeFailed variant");
};
assert_eq!(*failure, CheckpointResumeFailure::IncompatibleTemperature);
assert_eq!(
format!("{error}"),
"Failed to resume MCMC checkpoint: temperature differs from checkpoint"
);
assert_eq!(
Error::source(&error).map(ToString::to_string),
Some("temperature differs from checkpoint".to_string())
);
}
#[test]
fn test_error_equality() {
let error1 = CdtError::InvalidConfiguration {
setting: ConfigurationSetting::Steps,
provided_value: "0".to_string(),
expected: "≥ 1".to_string(),
};
let error2 = CdtError::InvalidConfiguration {
setting: ConfigurationSetting::Steps,
provided_value: "0".to_string(),
expected: "≥ 1".to_string(),
};
let error3 = CdtError::InvalidConfiguration {
setting: ConfigurationSetting::Steps,
provided_value: "10".to_string(),
expected: "≥ 1".to_string(),
};
assert_eq!(error1, error2);
assert_ne!(error1, error3);
}
#[test]
fn test_error_clone() {
let error = CdtError::UnsupportedDimension(4);
let cloned = error.clone();
assert_eq!(error, cloned);
}
#[test]
fn test_error_debug() {
let error = CdtError::InvalidConfiguration {
setting: ConfigurationSetting::Vertices,
provided_value: "2".to_string(),
expected: "≥ 3".to_string(),
};
let debug_str = format!("{error:?}");
assert!(debug_str.contains("InvalidConfiguration"));
assert!(debug_str.contains("Vertices"));
}
#[test]
fn test_cdt_result_type() {
let success: CdtResult<i32> = Ok(42);
let failure: CdtResult<i32> = Err(CdtError::InvalidConfiguration {
setting: ConfigurationSetting::Steps,
provided_value: "0".to_string(),
expected: "≥ 1".to_string(),
});
assert!(success.is_ok());
assert!(failure.is_err());
assert_eq!(success, Ok(42));
}
#[test]
fn test_error_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<CdtError>();
}
#[test]
fn test_std_error_trait() {
let error = CdtError::InvalidConfiguration {
setting: ConfigurationSetting::Temperature,
provided_value: "NaN".to_string(),
expected: "finite and positive".to_string(),
};
let _: &dyn Error = &error;
}
}