1use thiserror::Error;
7
8pub type SimResult<T> = Result<T, SimError>;
10
11#[derive(Debug, Error)]
20pub enum SimError {
21 #[error("Jidoka: non-finite value detected at {location}")]
24 NonFiniteValue {
25 location: String,
27 },
28
29 #[error("Jidoka: energy drift {drift:.6e} exceeds tolerance {tolerance:.6e}")]
31 EnergyDrift {
32 drift: f64,
34 tolerance: f64,
36 },
37
38 #[error(
40 "Jidoka: constraint '{name}' violated by {violation:.6e} (tolerance: {tolerance:.6e})"
41 )]
42 ConstraintViolation {
43 name: String,
45 violation: f64,
47 tolerance: f64,
49 },
50
51 #[error("Configuration error: {message}")]
54 Config {
55 message: String,
57 },
58
59 #[error("YAML parsing error: {0}")]
61 YamlParse(#[from] serde_yaml::Error),
62
63 #[error("Validation error: {0}")]
65 Validation(#[from] validator::ValidationErrors),
66
67 #[error("Checkpoint integrity violation: hash mismatch")]
70 CheckpointIntegrity,
71
72 #[error("Checkpoint not found for time {0:?}")]
74 CheckpointNotFound(crate::engine::SimTime),
75
76 #[error("Journal error: {0}")]
78 Journal(String),
79
80 #[error("I/O error: {0}")]
83 Io(#[from] std::io::Error),
84
85 #[error("Serialization error: {0}")]
87 Serialization(String),
88
89 #[error("Physics error: {0}")]
92 Physics(String),
93
94 #[error("Monte Carlo error: {0}")]
96 MonteCarlo(String),
97
98 #[error("Optimization error: {0}")]
100 Optimization(String),
101
102 #[error("Hypothesis falsified: {reason} (p-value: {p_value:.6})")]
105 HypothesisFalsified {
106 reason: String,
108 p_value: f64,
110 },
111}
112
113impl SimError {
114 #[must_use]
116 pub fn config(message: impl Into<String>) -> Self {
117 Self::Config {
118 message: message.into(),
119 }
120 }
121
122 #[must_use]
124 pub fn serialization(message: impl Into<String>) -> Self {
125 Self::Serialization(message.into())
126 }
127
128 #[must_use]
130 pub fn journal(message: impl Into<String>) -> Self {
131 Self::Journal(message.into())
132 }
133
134 #[must_use]
136 pub fn optimization(message: impl Into<String>) -> Self {
137 Self::Optimization(message.into())
138 }
139
140 #[must_use]
142 pub fn jidoka(message: impl Into<String>) -> Self {
143 Self::Config {
144 message: format!("Jidoka violation: {}", message.into()),
145 }
146 }
147
148 #[must_use]
150 pub fn io(message: impl Into<String>) -> Self {
151 Self::Io(std::io::Error::other(message.into()))
152 }
153
154 #[must_use]
156 pub const fn is_jidoka_violation(&self) -> bool {
157 matches!(
158 self,
159 Self::NonFiniteValue { .. }
160 | Self::EnergyDrift { .. }
161 | Self::ConstraintViolation { .. }
162 )
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_jidoka_violation_detection() {
172 let non_finite = SimError::NonFiniteValue {
173 location: "position.x".to_string(),
174 };
175 assert!(non_finite.is_jidoka_violation());
176
177 let energy = SimError::EnergyDrift {
178 drift: 0.001,
179 tolerance: 0.0001,
180 };
181 assert!(energy.is_jidoka_violation());
182
183 let constraint = SimError::ConstraintViolation {
184 name: "mass_positive".to_string(),
185 violation: -1.0,
186 tolerance: 0.0,
187 };
188 assert!(constraint.is_jidoka_violation());
189
190 let config = SimError::config("invalid");
191 assert!(!config.is_jidoka_violation());
192 }
193
194 #[test]
195 fn test_error_display() {
196 let err = SimError::EnergyDrift {
197 drift: 0.001_234_567,
198 tolerance: 0.000_001,
199 };
200 let msg = err.to_string();
201 assert!(msg.contains("energy drift"));
202 assert!(msg.contains("1.234567e-3"));
203 }
204
205 #[test]
206 fn test_error_config() {
207 let err = SimError::config("invalid parameter");
208 assert!(!err.is_jidoka_violation());
209 let msg = err.to_string();
210 assert!(msg.contains("Configuration error"));
211 assert!(msg.contains("invalid parameter"));
212 }
213
214 #[test]
215 fn test_error_serialization() {
216 let err = SimError::serialization("failed to serialize");
217 assert!(!err.is_jidoka_violation());
218 let msg = err.to_string();
219 assert!(msg.contains("Serialization error"));
220 assert!(msg.contains("failed to serialize"));
221 }
222
223 #[test]
224 fn test_error_journal() {
225 let err = SimError::journal("corrupted journal");
226 assert!(!err.is_jidoka_violation());
227 let msg = err.to_string();
228 assert!(msg.contains("Journal error"));
229 assert!(msg.contains("corrupted journal"));
230 }
231
232 #[test]
233 fn test_error_optimization() {
234 let err = SimError::optimization("convergence failed");
235 assert!(!err.is_jidoka_violation());
236 let msg = err.to_string();
237 assert!(msg.contains("Optimization error"));
238 assert!(msg.contains("convergence failed"));
239 }
240
241 #[test]
242 fn test_error_jidoka() {
243 let err = SimError::jidoka("critical failure");
244 assert!(!err.is_jidoka_violation());
246 let msg = err.to_string();
247 assert!(msg.contains("Jidoka violation"));
248 assert!(msg.contains("critical failure"));
249 }
250
251 #[test]
252 fn test_error_io() {
253 let err = SimError::io("file not found");
254 assert!(!err.is_jidoka_violation());
255 let msg = err.to_string();
256 assert!(msg.contains("I/O error"));
257 assert!(msg.contains("file not found"));
258 }
259
260 #[test]
261 fn test_error_non_finite_display() {
262 let err = SimError::NonFiniteValue {
263 location: "velocity.y".to_string(),
264 };
265 let msg = err.to_string();
266 assert!(msg.contains("non-finite value"));
267 assert!(msg.contains("velocity.y"));
268 }
269
270 #[test]
271 fn test_error_constraint_violation_display() {
272 let err = SimError::ConstraintViolation {
273 name: "mass_positive".to_string(),
274 violation: -5.0,
275 tolerance: 0.001,
276 };
277 let msg = err.to_string();
278 assert!(msg.contains("constraint"));
279 assert!(msg.contains("mass_positive"));
280 assert!(msg.contains("violated"));
281 }
282
283 #[test]
284 fn test_error_checkpoint_integrity() {
285 let err = SimError::CheckpointIntegrity;
286 assert!(!err.is_jidoka_violation());
287 let msg = err.to_string();
288 assert!(msg.contains("Checkpoint integrity"));
289 }
290
291 #[test]
292 fn test_error_checkpoint_not_found() {
293 let err = SimError::CheckpointNotFound(crate::engine::SimTime::from_secs(10.0));
294 assert!(!err.is_jidoka_violation());
295 let msg = err.to_string();
296 assert!(msg.contains("Checkpoint not found"));
297 }
298
299 #[test]
300 fn test_error_physics() {
301 let err = SimError::Physics("invalid force".to_string());
302 assert!(!err.is_jidoka_violation());
303 let msg = err.to_string();
304 assert!(msg.contains("Physics error"));
305 }
306
307 #[test]
308 fn test_error_monte_carlo() {
309 let err = SimError::MonteCarlo("invalid sample".to_string());
310 assert!(!err.is_jidoka_violation());
311 let msg = err.to_string();
312 assert!(msg.contains("Monte Carlo error"));
313 }
314
315 #[test]
316 fn test_error_hypothesis_falsified() {
317 let err = SimError::HypothesisFalsified {
318 reason: "energy not conserved".to_string(),
319 p_value: 0.001,
320 };
321 assert!(!err.is_jidoka_violation());
322 let msg = err.to_string();
323 assert!(msg.contains("Hypothesis falsified"));
324 assert!(msg.contains("energy not conserved"));
325 assert!(msg.contains("0.001"));
326 }
327
328 #[test]
329 fn test_error_debug() {
330 let err = SimError::config("test");
331 let debug = format!("{:?}", err);
332 assert!(debug.contains("Config"));
333 }
334}