1use fugue::{addr, ChoiceValue, Trace};
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9
10use crate::error::GenomeError;
11use crate::genome::bounds::MultiBounds;
12use crate::genome::traits::EvolutionaryGenome;
13
14#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26#[serde(
27 bound = "A: Serialize + for<'de2> Deserialize<'de2>, B: Serialize + for<'de2> Deserialize<'de2>"
28)]
29pub struct CompositeGenome<A, B>
30where
31 A: EvolutionaryGenome,
32 B: EvolutionaryGenome,
33{
34 pub first: A,
36 pub second: B,
38}
39
40impl<A, B> CompositeGenome<A, B>
41where
42 A: EvolutionaryGenome,
43 B: EvolutionaryGenome,
44{
45 pub fn new(first: A, second: B) -> Self {
47 Self { first, second }
48 }
49
50 pub fn first(&self) -> &A {
52 &self.first
53 }
54
55 pub fn first_mut(&mut self) -> &mut A {
57 &mut self.first
58 }
59
60 pub fn second(&self) -> &B {
62 &self.second
63 }
64
65 pub fn second_mut(&mut self) -> &mut B {
67 &mut self.second
68 }
69
70 pub fn into_parts(self) -> (A, B) {
72 (self.first, self.second)
73 }
74
75 pub fn map_first<F, C>(self, f: F) -> CompositeGenome<C, B>
77 where
78 F: FnOnce(A) -> C,
79 C: EvolutionaryGenome,
80 {
81 CompositeGenome {
82 first: f(self.first),
83 second: self.second,
84 }
85 }
86
87 pub fn map_second<F, C>(self, f: F) -> CompositeGenome<A, C>
89 where
90 F: FnOnce(B) -> C,
91 C: EvolutionaryGenome,
92 {
93 CompositeGenome {
94 first: self.first,
95 second: f(self.second),
96 }
97 }
98}
99
100impl<A, B> EvolutionaryGenome for CompositeGenome<A, B>
101where
102 A: EvolutionaryGenome + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>,
103 B: EvolutionaryGenome + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>,
104{
105 type Allele = (A::Allele, B::Allele);
106 type Phenotype = (A::Phenotype, B::Phenotype);
107
108 fn to_trace(&self) -> Trace {
112 let mut trace = Trace::default();
113
114 trace.insert_choice(
116 addr!("composite", "first_dim"),
117 ChoiceValue::Usize(self.first.dimension()),
118 0.0,
119 );
120 trace.insert_choice(
121 addr!("composite", "second_dim"),
122 ChoiceValue::Usize(self.second.dimension()),
123 0.0,
124 );
125
126 let first_trace = self.first.to_trace();
128 for i in 0..self.first.dimension() {
129 if let Some(val) = first_trace.get_f64(&addr!("gene", i)) {
130 trace.insert_choice(addr!("first_gene", i), ChoiceValue::F64(val), 0.0);
131 } else if let Some(val) = first_trace.get_bool(&addr!("bit", i)) {
132 trace.insert_choice(addr!("first_bit", i), ChoiceValue::Bool(val), 0.0);
133 } else if let Some(val) = first_trace.get_usize(&addr!("element", i)) {
134 trace.insert_choice(addr!("first_element", i), ChoiceValue::Usize(val), 0.0);
135 }
136 }
137
138 let second_trace = self.second.to_trace();
140 for i in 0..self.second.dimension() {
141 if let Some(val) = second_trace.get_f64(&addr!("gene", i)) {
142 trace.insert_choice(addr!("second_gene", i), ChoiceValue::F64(val), 0.0);
143 } else if let Some(val) = second_trace.get_bool(&addr!("bit", i)) {
144 trace.insert_choice(addr!("second_bit", i), ChoiceValue::Bool(val), 0.0);
145 } else if let Some(val) = second_trace.get_usize(&addr!("element", i)) {
146 trace.insert_choice(addr!("second_element", i), ChoiceValue::Usize(val), 0.0);
147 }
148 }
149
150 trace
151 }
152
153 fn from_trace(trace: &Trace) -> Result<Self, GenomeError> {
158 let first_dim = trace
160 .get_usize(&addr!("composite", "first_dim"))
161 .ok_or_else(|| GenomeError::MissingAddress("composite#first_dim".to_string()))?;
162 let second_dim = trace
163 .get_usize(&addr!("composite", "second_dim"))
164 .ok_or_else(|| GenomeError::MissingAddress("composite#second_dim".to_string()))?;
165
166 let mut first_trace = Trace::default();
168 for i in 0..first_dim {
169 if let Some(val) = trace.get_f64(&addr!("first_gene", i)) {
170 first_trace.insert_choice(addr!("gene", i), ChoiceValue::F64(val), 0.0);
171 } else if let Some(val) = trace.get_bool(&addr!("first_bit", i)) {
172 first_trace.insert_choice(addr!("bit", i), ChoiceValue::Bool(val), 0.0);
173 } else if let Some(val) = trace.get_usize(&addr!("first_element", i)) {
174 first_trace.insert_choice(addr!("element", i), ChoiceValue::Usize(val), 0.0);
175 }
176 }
177
178 let mut second_trace = Trace::default();
180 for i in 0..second_dim {
181 if let Some(val) = trace.get_f64(&addr!("second_gene", i)) {
182 second_trace.insert_choice(addr!("gene", i), ChoiceValue::F64(val), 0.0);
183 } else if let Some(val) = trace.get_bool(&addr!("second_bit", i)) {
184 second_trace.insert_choice(addr!("bit", i), ChoiceValue::Bool(val), 0.0);
185 } else if let Some(val) = trace.get_usize(&addr!("second_element", i)) {
186 second_trace.insert_choice(addr!("element", i), ChoiceValue::Usize(val), 0.0);
187 }
188 }
189
190 let first = A::from_trace(&first_trace)?;
191 let second = B::from_trace(&second_trace)?;
192
193 Ok(Self { first, second })
194 }
195
196 fn decode(&self) -> Self::Phenotype {
197 (self.first.decode(), self.second.decode())
198 }
199
200 fn dimension(&self) -> usize {
201 self.first.dimension() + self.second.dimension()
202 }
203
204 fn generate<R: Rng>(rng: &mut R, bounds: &MultiBounds) -> Self {
205 let first_dim = bounds.dimension() / 2;
208 let second_dim = bounds.dimension() - first_dim;
209
210 let first_bounds =
211 MultiBounds::new(bounds.bounds.iter().take(first_dim).cloned().collect());
212 let second_bounds = MultiBounds::new(
213 bounds
214 .bounds
215 .iter()
216 .skip(first_dim)
217 .take(second_dim)
218 .cloned()
219 .collect(),
220 );
221
222 Self {
223 first: A::generate(rng, &first_bounds),
224 second: B::generate(rng, &second_bounds),
225 }
226 }
227
228 fn distance(&self, other: &Self) -> f64 {
229 self.first.distance(&other.first) + self.second.distance(&other.second)
231 }
232
233 fn trace_prefix() -> &'static str {
234 "composite"
235 }
236}
237
238#[derive(Clone, Debug)]
240pub struct CompositeBounds {
241 pub first_bounds: MultiBounds,
243 pub second_bounds: MultiBounds,
245}
246
247impl CompositeBounds {
248 pub fn new(first_bounds: MultiBounds, second_bounds: MultiBounds) -> Self {
250 Self {
251 first_bounds,
252 second_bounds,
253 }
254 }
255
256 pub fn combined(&self) -> MultiBounds {
258 let mut all_bounds: Vec<_> = self.first_bounds.bounds.to_vec();
259 all_bounds.extend(self.second_bounds.bounds.iter().cloned());
260 MultiBounds::new(all_bounds)
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267 use crate::genome::bit_string::BitString;
268 use crate::genome::real_vector::RealVector;
269 use crate::genome::traits::{BinaryGenome, RealValuedGenome};
270
271 #[test]
272 fn test_composite_creation() {
273 let real = RealVector::new(vec![1.0, 2.0, 3.0]);
274 let binary = BitString::new(vec![true, false, true, false]);
275
276 let composite = CompositeGenome::new(real.clone(), binary.clone());
277
278 assert_eq!(composite.first().genes(), real.genes());
279 assert_eq!(composite.second().bits(), binary.bits());
280 }
281
282 #[test]
283 fn test_composite_dimension() {
284 let real = RealVector::new(vec![1.0, 2.0, 3.0]);
285 let binary = BitString::new(vec![true, false, true, false]);
286
287 let composite = CompositeGenome::new(real, binary);
288
289 assert_eq!(composite.dimension(), 7);
291 }
292
293 #[test]
294 fn test_composite_decode() {
295 let real = RealVector::new(vec![1.0, 2.0, 3.0]);
296 let binary = BitString::new(vec![true, false, true, false]);
297
298 let composite = CompositeGenome::new(real, binary);
299 let (decoded_real, decoded_binary) = composite.decode();
300
301 assert_eq!(decoded_real, vec![1.0, 2.0, 3.0]);
302 assert_eq!(decoded_binary, vec![true, false, true, false]);
303 }
304
305 #[test]
306 fn test_composite_into_parts() {
307 let real = RealVector::new(vec![1.0, 2.0]);
308 let binary = BitString::new(vec![true, true, false]);
309
310 let composite = CompositeGenome::new(real.clone(), binary.clone());
311 let (r, b) = composite.into_parts();
312
313 assert_eq!(r.genes(), real.genes());
314 assert_eq!(b.bits(), binary.bits());
315 }
316
317 #[test]
318 fn test_composite_map() {
319 let real = RealVector::new(vec![1.0, 2.0]);
320 let binary = BitString::new(vec![true, false]);
321
322 let composite = CompositeGenome::new(real, binary);
323
324 let mapped = composite.map_first(|r| r.scale(2.0));
326 assert_eq!(mapped.first().genes(), &[2.0, 4.0]);
327 }
328
329 #[test]
330 fn test_composite_distance() {
331 let c1 = CompositeGenome::new(
332 RealVector::new(vec![0.0, 0.0]),
333 BitString::new(vec![true, false]),
334 );
335
336 let c2 = CompositeGenome::new(
337 RealVector::new(vec![3.0, 4.0]),
338 BitString::new(vec![false, true]),
339 );
340
341 let dist = c1.distance(&c2);
342
343 assert!(dist > 0.0);
345 }
346
347 #[test]
348 fn test_composite_generate() {
349 let bounds = MultiBounds::symmetric(5.0, 6); let mut rng = rand::thread_rng();
351
352 let composite: CompositeGenome<RealVector, RealVector> =
355 CompositeGenome::generate(&mut rng, &bounds);
356
357 assert_eq!(composite.dimension(), 6);
358 }
359
360 #[test]
361 fn test_composite_bounds() {
362 use crate::genome::bounds::Bounds;
363
364 let first_bounds = MultiBounds::symmetric(5.0, 3);
365 let second_bounds = MultiBounds::uniform(Bounds::unit(), 4);
366
367 let composite_bounds = CompositeBounds::new(first_bounds, second_bounds);
368 let combined = composite_bounds.combined();
369
370 assert_eq!(combined.dimension(), 7);
371 }
372
373 #[test]
374 fn test_composite_first_mut() {
375 let real = RealVector::new(vec![1.0, 2.0, 3.0]);
376 let binary = BitString::new(vec![true, false]);
377
378 let mut composite = CompositeGenome::new(real, binary);
379
380 composite.first_mut().genes_mut()[0] = 10.0;
382
383 assert_eq!(composite.first().genes()[0], 10.0);
384 }
385
386 #[test]
387 fn test_composite_second_mut() {
388 let real = RealVector::new(vec![1.0, 2.0]);
389 let binary = BitString::new(vec![true, false, true]);
390
391 let mut composite = CompositeGenome::new(real, binary);
392
393 composite.second_mut().bits_mut()[0] = false;
395
396 assert!(!composite.second().bits()[0]);
397 }
398
399 #[test]
400 fn test_composite_map_second() {
401 let real = RealVector::new(vec![1.0, 2.0]);
402 let second_real = RealVector::new(vec![3.0, 4.0]);
403
404 let composite = CompositeGenome::new(real, second_real);
405
406 let mapped = composite.map_second(|r| r.scale(2.0));
408 assert_eq!(mapped.second().genes(), &[6.0, 8.0]);
409 }
410
411 #[test]
412 fn test_composite_trace_roundtrip_real_vectors() {
413 let first = RealVector::new(vec![1.5, 2.5, 3.5]);
414 let second = RealVector::new(vec![4.5, 5.5]);
415
416 let composite = CompositeGenome::new(first.clone(), second.clone());
417 let trace = composite.to_trace();
418 let recovered: CompositeGenome<RealVector, RealVector> =
419 CompositeGenome::from_trace(&trace).expect("Should deserialize");
420
421 assert_eq!(recovered.first().genes(), first.genes());
422 assert_eq!(recovered.second().genes(), second.genes());
423 }
424
425 #[test]
426 fn test_composite_trace_roundtrip_mixed() {
427 let real = RealVector::new(vec![1.0, 2.0]);
428 let binary = BitString::new(vec![true, false, true]);
429
430 let composite = CompositeGenome::new(real.clone(), binary.clone());
431 let trace = composite.to_trace();
432
433 assert!(trace.get_usize(&addr!("composite", "first_dim")).is_some());
436 assert!(trace.get_usize(&addr!("composite", "second_dim")).is_some());
437 }
438
439 #[test]
440 fn test_composite_trace_prefix() {
441 assert_eq!(
442 <CompositeGenome<RealVector, BitString>>::trace_prefix(),
443 "composite"
444 );
445 }
446
447 #[test]
448 fn test_composite_from_trace_missing_dim_error() {
449 use fugue::Trace;
450 let empty_trace = Trace::default();
451
452 let result: Result<CompositeGenome<RealVector, RealVector>, _> =
453 CompositeGenome::from_trace(&empty_trace);
454
455 assert!(result.is_err());
456 }
457}