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}")]
73 Serialization(String),
74
75 #[error("Deserialization error: {0}")]
77 Deserialization(String),
78
79 #[error("Version mismatch: expected {expected}, found {found}")]
81 VersionMismatch { expected: u32, found: u32 },
82
83 #[error("Checkpoint version {0} is newer than supported")]
85 VersionTooNew(u32),
86
87 #[error("Checkpoint version {0} is too old to load")]
89 VersionTooOld(u32),
90
91 #[error("Checkpoint not found: {0}")]
93 NotFound(String),
94
95 #[error("Corrupted checkpoint: {0}")]
97 Corrupted(String),
98}
99
100#[derive(Debug, Error)]
102pub enum EvolutionError {
103 #[error("Genome error: {0}")]
105 Genome(#[from] GenomeError),
106
107 #[error("Operator error: {0}")]
109 Operator(#[from] OperatorError),
110
111 #[error("Fitness evaluation failed: {0}")]
113 FitnessEvaluation(String),
114
115 #[error("Invalid configuration: {0}")]
117 Configuration(String),
118
119 #[error("Checkpoint error: {0}")]
121 Checkpoint(#[from] CheckpointError),
122
123 #[error("Numerical instability: {0}")]
125 Numerical(String),
126
127 #[error("Empty population")]
129 EmptyPopulation,
130
131 #[error("Interactive evaluation error: {0}")]
133 InteractiveEvaluation(String),
134
135 #[error("Insufficient coverage: {coverage:.1}% (need {required:.1}%)")]
137 InsufficientCoverage {
138 coverage: f64,
140 required: f64,
142 },
143}
144
145pub type EvoResult<T> = Result<T, EvolutionError>;
147
148#[derive(Debug, Clone)]
150pub struct RepairInfo {
151 pub constraint_violations: Vec<String>,
153 pub repair_method: &'static str,
155}
156
157#[derive(Debug, Clone)]
159pub enum OperatorResult<G> {
160 Success(G),
162 Repaired(G, RepairInfo),
164 Failed(OperatorError),
166}
167
168impl<G> OperatorResult<G> {
169 pub fn genome(self) -> Option<G> {
171 match self {
172 Self::Success(g) | Self::Repaired(g, _) => Some(g),
173 Self::Failed(_) => None,
174 }
175 }
176
177 pub fn is_ok(&self) -> bool {
179 !matches!(self, Self::Failed(_))
180 }
181
182 pub fn was_repaired(&self) -> bool {
184 matches!(self, Self::Repaired(_, _))
185 }
186
187 pub fn map<U, F: FnOnce(G) -> U>(self, f: F) -> OperatorResult<U> {
189 match self {
190 Self::Success(g) => OperatorResult::Success(f(g)),
191 Self::Repaired(g, info) => OperatorResult::Repaired(f(g), info),
192 Self::Failed(e) => OperatorResult::Failed(e),
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_genome_error_display() {
203 let err = GenomeError::MissingAddress("gene_0".to_string());
204 assert_eq!(err.to_string(), "Missing address in trace: gene_0");
205
206 let err = GenomeError::TypeMismatch {
207 address: "gene_1".to_string(),
208 expected: "f64".to_string(),
209 actual: "bool".to_string(),
210 };
211 assert_eq!(
212 err.to_string(),
213 "Type mismatch at address gene_1: expected f64, got bool"
214 );
215
216 let err = GenomeError::DimensionMismatch {
217 expected: 10,
218 actual: 5,
219 };
220 assert_eq!(err.to_string(), "Dimension mismatch: expected 10, got 5");
221 }
222
223 #[test]
224 fn test_operator_error_display() {
225 let err = OperatorError::CrossoverFailed("incompatible parents".to_string());
226 assert_eq!(err.to_string(), "Crossover failed: incompatible parents");
227
228 let err = OperatorError::InvalidConfiguration("eta must be positive".to_string());
229 assert_eq!(
230 err.to_string(),
231 "Invalid operator configuration: eta must be positive"
232 );
233 }
234
235 #[test]
236 fn test_evolution_error_from_genome_error() {
237 let genome_err = GenomeError::InvalidStructure("bad shape".to_string());
238 let evo_err: EvolutionError = genome_err.into();
239 assert!(matches!(evo_err, EvolutionError::Genome(_)));
240 }
241
242 #[test]
243 fn test_operator_result_success() {
244 let result: OperatorResult<i32> = OperatorResult::Success(42);
245 assert!(result.is_ok());
246 assert!(!result.was_repaired());
247 assert_eq!(result.genome(), Some(42));
248 }
249
250 #[test]
251 fn test_operator_result_repaired() {
252 let repair_info = RepairInfo {
253 constraint_violations: vec!["out of bounds".to_string()],
254 repair_method: "clamp",
255 };
256 let result: OperatorResult<i32> = OperatorResult::Repaired(42, repair_info);
257 assert!(result.is_ok());
258 assert!(result.was_repaired());
259 assert_eq!(result.genome(), Some(42));
260 }
261
262 #[test]
263 fn test_operator_result_failed() {
264 let result: OperatorResult<i32> =
265 OperatorResult::Failed(OperatorError::MutationFailed("test".to_string()));
266 assert!(!result.is_ok());
267 assert!(!result.was_repaired());
268 assert_eq!(result.genome(), None);
269 }
270
271 #[test]
272 fn test_operator_result_map() {
273 let result: OperatorResult<i32> = OperatorResult::Success(42);
274 let mapped = result.map(|x| x * 2);
275 assert_eq!(mapped.genome(), Some(84));
276 }
277}