1use thiserror::Error;
6
7pub type Address = String;
9
10#[derive(Debug, Error, Clone, PartialEq)]
12pub enum GenomeError {
13 #[error("Missing address in trace: {0}")]
15 MissingAddress(Address),
16
17 #[error("Type mismatch at address {address}: expected {expected}, got {actual}")]
19 TypeMismatch {
20 address: Address,
21 expected: String,
22 actual: String,
23 },
24
25 #[error("Invalid genome structure: {0}")]
27 InvalidStructure(String),
28
29 #[error("Constraint violation: {0}")]
31 ConstraintViolation(String),
32
33 #[error("Dimension mismatch: expected {expected}, got {actual}")]
35 DimensionMismatch { expected: usize, actual: usize },
36}
37
38#[derive(Debug, Error, Clone, PartialEq)]
40pub enum OperatorError {
41 #[error("Crossover failed: {0}")]
43 CrossoverFailed(String),
44
45 #[error("Mutation failed: {0}")]
47 MutationFailed(String),
48
49 #[error("Selection failed: {0}")]
51 SelectionFailed(String),
52
53 #[error("Invalid operator configuration: {0}")]
55 InvalidConfiguration(String),
56}
57
58#[derive(Debug, Error)]
60pub enum CheckpointError {
61 #[cfg(not(target_arch = "wasm32"))]
63 #[error("IO error: {0}")]
64 Io(#[from] std::io::Error),
65
66 #[cfg(target_arch = "wasm32")]
68 #[error("Storage error: {0}")]
69 Storage(String),
70
71 #[error("Serialization error: {0}")]
74 Serialization(String),
75
76 #[error("Deserialization error: {0}")]
79 Deserialization(String),
80
81 #[error("Failed to serialize checkpoint")]
87 SerializeError(#[source] Box<dyn std::error::Error + Send + Sync>),
88
89 #[error("Failed to deserialize checkpoint")]
95 DeserializeError(#[source] Box<dyn std::error::Error + Send + Sync>),
96
97 #[error("Checkpoint too large: {size} bytes exceeds limit of {limit} bytes")]
102 TooLarge {
103 size: u64,
105 limit: u64,
107 },
108
109 #[error("Version mismatch: expected {expected}, found {found}")]
111 VersionMismatch { expected: u32, found: u32 },
112
113 #[error("Checkpoint version {0} is newer than supported")]
115 VersionTooNew(u32),
116
117 #[error("Checkpoint version {0} is too old to load")]
119 VersionTooOld(u32),
120
121 #[error("Checkpoint not found: {0}")]
123 NotFound(String),
124
125 #[error("Corrupted checkpoint: {0}")]
127 Corrupted(String),
128}
129
130#[derive(Debug, Error)]
132pub enum EvolutionError {
133 #[error("Genome error: {0}")]
135 Genome(#[from] GenomeError),
136
137 #[error("Operator error: {0}")]
139 Operator(#[from] OperatorError),
140
141 #[error("Fitness evaluation failed: {0}")]
143 FitnessEvaluation(String),
144
145 #[error("Invalid configuration: {0}")]
147 Configuration(String),
148
149 #[error("Checkpoint error: {0}")]
151 Checkpoint(#[from] CheckpointError),
152
153 #[error("Numerical instability: {0}")]
155 Numerical(String),
156
157 #[error("Empty population")]
159 EmptyPopulation,
160
161 #[error("Interactive evaluation error: {0}")]
163 InteractiveEvaluation(String),
164
165 #[error("Insufficient coverage: {coverage:.1}% (need {required:.1}%)")]
167 InsufficientCoverage {
168 coverage: f64,
170 required: f64,
172 },
173}
174
175pub type EvoResult<T> = Result<T, EvolutionError>;
177
178#[derive(Debug, Clone)]
180pub struct RepairInfo {
181 pub constraint_violations: Vec<String>,
183 pub repair_method: &'static str,
185}
186
187#[derive(Debug, Clone)]
189pub enum OperatorResult<G> {
190 Success(G),
192 Repaired(G, RepairInfo),
194 Failed(OperatorError),
196}
197
198impl<G> OperatorResult<G> {
199 pub fn genome(self) -> Option<G> {
201 match self {
202 Self::Success(g) | Self::Repaired(g, _) => Some(g),
203 Self::Failed(_) => None,
204 }
205 }
206
207 pub fn is_ok(&self) -> bool {
209 !matches!(self, Self::Failed(_))
210 }
211
212 pub fn was_repaired(&self) -> bool {
214 matches!(self, Self::Repaired(_, _))
215 }
216
217 pub fn map<U, F: FnOnce(G) -> U>(self, f: F) -> OperatorResult<U> {
219 match self {
220 Self::Success(g) => OperatorResult::Success(f(g)),
221 Self::Repaired(g, info) => OperatorResult::Repaired(f(g), info),
222 Self::Failed(e) => OperatorResult::Failed(e),
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_genome_error_display() {
233 let err = GenomeError::MissingAddress("gene_0".to_string());
234 assert_eq!(err.to_string(), "Missing address in trace: gene_0");
235
236 let err = GenomeError::TypeMismatch {
237 address: "gene_1".to_string(),
238 expected: "f64".to_string(),
239 actual: "bool".to_string(),
240 };
241 assert_eq!(
242 err.to_string(),
243 "Type mismatch at address gene_1: expected f64, got bool"
244 );
245
246 let err = GenomeError::DimensionMismatch {
247 expected: 10,
248 actual: 5,
249 };
250 assert_eq!(err.to_string(), "Dimension mismatch: expected 10, got 5");
251 }
252
253 #[test]
254 fn test_operator_error_display() {
255 let err = OperatorError::CrossoverFailed("incompatible parents".to_string());
256 assert_eq!(err.to_string(), "Crossover failed: incompatible parents");
257
258 let err = OperatorError::InvalidConfiguration("eta must be positive".to_string());
259 assert_eq!(
260 err.to_string(),
261 "Invalid operator configuration: eta must be positive"
262 );
263 }
264
265 #[test]
266 fn test_evolution_error_from_genome_error() {
267 let genome_err = GenomeError::InvalidStructure("bad shape".to_string());
268 let evo_err: EvolutionError = genome_err.into();
269 assert!(matches!(evo_err, EvolutionError::Genome(_)));
270 }
271
272 #[test]
273 fn test_operator_result_success() {
274 let result: OperatorResult<i32> = OperatorResult::Success(42);
275 assert!(result.is_ok());
276 assert!(!result.was_repaired());
277 assert_eq!(result.genome(), Some(42));
278 }
279
280 #[test]
281 fn test_operator_result_repaired() {
282 let repair_info = RepairInfo {
283 constraint_violations: vec!["out of bounds".to_string()],
284 repair_method: "clamp",
285 };
286 let result: OperatorResult<i32> = OperatorResult::Repaired(42, repair_info);
287 assert!(result.is_ok());
288 assert!(result.was_repaired());
289 assert_eq!(result.genome(), Some(42));
290 }
291
292 #[test]
293 fn test_operator_result_failed() {
294 let result: OperatorResult<i32> =
295 OperatorResult::Failed(OperatorError::MutationFailed("test".to_string()));
296 assert!(!result.is_ok());
297 assert!(!result.was_repaired());
298 assert_eq!(result.genome(), None);
299 }
300
301 #[test]
302 fn test_operator_result_map() {
303 let result: OperatorResult<i32> = OperatorResult::Success(42);
304 let mapped = result.map(|x| x * 2);
305 assert_eq!(mapped.genome(), Some(84));
306 }
307
308 #[test]
309 fn test_checkpoint_error_preserves_source_chain() {
310 use std::error::Error;
313
314 let json_err = serde_json::from_str::<i32>("not a number").unwrap_err();
315 let err = CheckpointError::DeserializeError(Box::new(json_err));
316
317 let source = err.source().expect("source chain must be preserved");
319 assert!(
320 source.downcast_ref::<serde_json::Error>().is_some(),
321 "source must downcast to the original serde_json::Error"
322 );
323 }
324}