1#[cfg(feature = "ppl")]
7use fugue::{Address, Trace};
8use rand::Rng;
9use serde::{Deserialize, Serialize};
10
11use crate::error::GenomeError;
12use crate::genome::bounds::MultiBounds;
13use crate::genome::traits::EvolutionaryGenome;
14
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27#[serde(
28 bound = "A: Serialize + for<'de2> Deserialize<'de2>, B: Serialize + for<'de2> Deserialize<'de2>"
29)]
30pub struct CompositeGenome<A, B>
31where
32 A: EvolutionaryGenome,
33 B: EvolutionaryGenome,
34{
35 pub first: A,
37 pub second: B,
39}
40
41impl<A, B> CompositeGenome<A, B>
42where
43 A: EvolutionaryGenome,
44 B: EvolutionaryGenome,
45{
46 pub fn new(first: A, second: B) -> Self {
48 Self { first, second }
49 }
50
51 pub fn first(&self) -> &A {
53 &self.first
54 }
55
56 pub fn first_mut(&mut self) -> &mut A {
58 &mut self.first
59 }
60
61 pub fn second(&self) -> &B {
63 &self.second
64 }
65
66 pub fn second_mut(&mut self) -> &mut B {
68 &mut self.second
69 }
70
71 pub fn into_parts(self) -> (A, B) {
73 (self.first, self.second)
74 }
75
76 pub fn map_first<F, C>(self, f: F) -> CompositeGenome<C, B>
78 where
79 F: FnOnce(A) -> C,
80 C: EvolutionaryGenome,
81 {
82 CompositeGenome {
83 first: f(self.first),
84 second: self.second,
85 }
86 }
87
88 pub fn map_second<F, C>(self, f: F) -> CompositeGenome<A, C>
90 where
91 F: FnOnce(B) -> C,
92 C: EvolutionaryGenome,
93 {
94 CompositeGenome {
95 first: self.first,
96 second: f(self.second),
97 }
98 }
99}
100
101impl<A, B> EvolutionaryGenome for CompositeGenome<A, B>
102where
103 A: EvolutionaryGenome + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>,
104 B: EvolutionaryGenome + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>,
105{
106 type Allele = (A::Allele, B::Allele);
107 type Phenotype = (A::Phenotype, B::Phenotype);
108
109 fn decode(&self) -> Self::Phenotype {
110 (self.first.decode(), self.second.decode())
111 }
112
113 fn dimension(&self) -> usize {
114 self.first.dimension() + self.second.dimension()
115 }
116
117 fn generate<R: Rng>(rng: &mut R, bounds: &MultiBounds) -> Self {
118 let first_dim = bounds.dimension() / 2;
121 let second_dim = bounds.dimension() - first_dim;
122
123 let first_bounds =
124 MultiBounds::new(bounds.bounds.iter().take(first_dim).cloned().collect());
125 let second_bounds = MultiBounds::new(
126 bounds
127 .bounds
128 .iter()
129 .skip(first_dim)
130 .take(second_dim)
131 .cloned()
132 .collect(),
133 );
134
135 Self {
136 first: A::generate(rng, &first_bounds),
137 second: B::generate(rng, &second_bounds),
138 }
139 }
140
141 fn distance(&self, other: &Self) -> f64 {
142 self.first.distance(&other.first) + self.second.distance(&other.second)
144 }
145
146 fn try_distance(&self, other: &Self) -> Result<f64, GenomeError> {
147 Ok(self.first.try_distance(&other.first)? + self.second.try_distance(&other.second)?)
148 }
149}
150
151#[cfg(feature = "ppl")]
152impl<A, B> crate::genome::trace_genome::TraceGenome for CompositeGenome<A, B>
153where
154 A: crate::genome::trace_genome::TraceGenome
155 + Clone
156 + Send
157 + Sync
158 + Serialize
159 + for<'de> Deserialize<'de>,
160 B: crate::genome::trace_genome::TraceGenome
161 + Clone
162 + Send
163 + Sync
164 + Serialize
165 + for<'de> Deserialize<'de>,
166{
167 fn to_trace(&self) -> Trace {
177 let mut trace = Trace::default();
178 namespace_into(&self.first.to_trace(), "first", &mut trace);
179 namespace_into(&self.second.to_trace(), "second", &mut trace);
180 trace
181 }
182
183 fn from_trace(trace: &Trace) -> Result<Self, GenomeError> {
189 let (first_trace, saw_first) = extract_namespace(trace, "first");
190 let (second_trace, saw_second) = extract_namespace(trace, "second");
191
192 if !saw_first {
193 return Err(GenomeError::MissingAddress("first/*".to_string()));
194 }
195 if !saw_second {
196 return Err(GenomeError::MissingAddress("second/*".to_string()));
197 }
198
199 let first = A::from_trace(&first_trace)?;
200 let second = B::from_trace(&second_trace)?;
201
202 Ok(Self { first, second })
203 }
204
205 fn trace_prefix() -> &'static str {
206 "composite"
207 }
208}
209
210#[cfg(feature = "ppl")]
214fn namespace_into(src: &Trace, namespace: &str, dst: &mut Trace) {
215 for (addr, choice) in &src.choices {
216 dst.insert_choice(
217 Address::new(format!("{}/{}", namespace, addr.as_str())),
218 choice.value.clone(),
219 choice.logp,
220 );
221 }
222}
223
224#[cfg(feature = "ppl")]
228fn extract_namespace(trace: &Trace, namespace: &str) -> (Trace, bool) {
229 let prefix = format!("{namespace}/");
230 let mut sub = Trace::default();
231 let mut found = false;
232 for (addr, choice) in &trace.choices {
233 if let Some(rest) = addr.as_str().strip_prefix(&prefix) {
234 sub.insert_choice(
235 Address::new(rest.to_string()),
236 choice.value.clone(),
237 choice.logp,
238 );
239 found = true;
240 }
241 }
242 (sub, found)
243}
244
245#[derive(Clone, Debug)]
247pub struct CompositeBounds {
248 pub first_bounds: MultiBounds,
250 pub second_bounds: MultiBounds,
252}
253
254impl CompositeBounds {
255 pub fn new(first_bounds: MultiBounds, second_bounds: MultiBounds) -> Self {
257 Self {
258 first_bounds,
259 second_bounds,
260 }
261 }
262
263 pub fn combined(&self) -> MultiBounds {
265 let mut all_bounds: Vec<_> = self.first_bounds.bounds.to_vec();
266 all_bounds.extend(self.second_bounds.bounds.iter().cloned());
267 MultiBounds::new(all_bounds)
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274 use crate::genome::bit_string::BitString;
275 #[cfg(feature = "ppl")]
276 use crate::genome::permutation::Permutation;
277 use crate::genome::real_vector::RealVector;
278 #[cfg(feature = "ppl")]
279 use crate::genome::traits::PermutationGenome;
280 use crate::genome::traits::{BinaryGenome, RealValuedGenome};
281
282 #[test]
283 fn test_composite_creation() {
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.clone(), binary.clone());
288
289 assert_eq!(composite.first().genes(), real.genes());
290 assert_eq!(composite.second().bits(), binary.bits());
291 }
292
293 #[test]
294 fn test_composite_dimension() {
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
300 assert_eq!(composite.dimension(), 7);
302 }
303
304 #[test]
305 fn test_composite_decode() {
306 let real = RealVector::new(vec![1.0, 2.0, 3.0]);
307 let binary = BitString::new(vec![true, false, true, false]);
308
309 let composite = CompositeGenome::new(real, binary);
310 let (decoded_real, decoded_binary) = composite.decode();
311
312 assert_eq!(decoded_real, vec![1.0, 2.0, 3.0]);
313 assert_eq!(decoded_binary, vec![true, false, true, false]);
314 }
315
316 #[test]
317 fn test_composite_into_parts() {
318 let real = RealVector::new(vec![1.0, 2.0]);
319 let binary = BitString::new(vec![true, true, false]);
320
321 let composite = CompositeGenome::new(real.clone(), binary.clone());
322 let (r, b) = composite.into_parts();
323
324 assert_eq!(r.genes(), real.genes());
325 assert_eq!(b.bits(), binary.bits());
326 }
327
328 #[test]
329 fn test_composite_map() {
330 let real = RealVector::new(vec![1.0, 2.0]);
331 let binary = BitString::new(vec![true, false]);
332
333 let composite = CompositeGenome::new(real, binary);
334
335 let mapped = composite.map_first(|r| r.scale(2.0));
337 assert_eq!(mapped.first().genes(), &[2.0, 4.0]);
338 }
339
340 #[test]
341 fn test_composite_distance() {
342 let c1 = CompositeGenome::new(
343 RealVector::new(vec![0.0, 0.0]),
344 BitString::new(vec![true, false]),
345 );
346
347 let c2 = CompositeGenome::new(
348 RealVector::new(vec![3.0, 4.0]),
349 BitString::new(vec![false, true]),
350 );
351
352 let dist = c1.distance(&c2);
353
354 assert!(dist > 0.0);
356 }
357
358 #[test]
359 fn test_composite_generate() {
360 let bounds = MultiBounds::symmetric(5.0, 6); let mut rng = rand::thread_rng();
362
363 let composite: CompositeGenome<RealVector, RealVector> =
366 CompositeGenome::generate(&mut rng, &bounds);
367
368 assert_eq!(composite.dimension(), 6);
369 }
370
371 #[test]
372 fn test_composite_bounds() {
373 use crate::genome::bounds::Bounds;
374
375 let first_bounds = MultiBounds::symmetric(5.0, 3);
376 let second_bounds = MultiBounds::uniform(Bounds::unit(), 4);
377
378 let composite_bounds = CompositeBounds::new(first_bounds, second_bounds);
379 let combined = composite_bounds.combined();
380
381 assert_eq!(combined.dimension(), 7);
382 }
383
384 #[test]
385 fn test_composite_first_mut() {
386 let real = RealVector::new(vec![1.0, 2.0, 3.0]);
387 let binary = BitString::new(vec![true, false]);
388
389 let mut composite = CompositeGenome::new(real, binary);
390
391 composite.first_mut().genes_mut()[0] = 10.0;
393
394 assert_eq!(composite.first().genes()[0], 10.0);
395 }
396
397 #[test]
398 fn test_composite_second_mut() {
399 let real = RealVector::new(vec![1.0, 2.0]);
400 let binary = BitString::new(vec![true, false, true]);
401
402 let mut composite = CompositeGenome::new(real, binary);
403
404 composite.second_mut().bits_mut()[0] = false;
406
407 assert!(!composite.second().bits()[0]);
408 }
409
410 #[test]
411 fn test_composite_map_second() {
412 let real = RealVector::new(vec![1.0, 2.0]);
413 let second_real = RealVector::new(vec![3.0, 4.0]);
414
415 let composite = CompositeGenome::new(real, second_real);
416
417 let mapped = composite.map_second(|r| r.scale(2.0));
419 assert_eq!(mapped.second().genes(), &[6.0, 8.0]);
420 }
421
422 #[test]
423 #[cfg(feature = "ppl")]
424 fn test_composite_trace_roundtrip_real_vectors() {
425 use crate::genome::trace_genome::TraceGenome;
426 let first = RealVector::new(vec![1.5, 2.5, 3.5]);
427 let second = RealVector::new(vec![4.5, 5.5]);
428
429 let composite = CompositeGenome::new(first.clone(), second.clone());
430 let trace = composite.to_trace();
431 let recovered: CompositeGenome<RealVector, RealVector> =
432 CompositeGenome::from_trace(&trace).expect("Should deserialize");
433
434 assert_eq!(recovered.first().genes(), first.genes());
435 assert_eq!(recovered.second().genes(), second.genes());
436 }
437
438 #[test]
439 #[cfg(feature = "ppl")]
440 fn test_composite_trace_roundtrip_mixed() {
441 use crate::genome::trace_genome::TraceGenome;
443 let real = RealVector::new(vec![1.0, 2.0]);
444 let binary = BitString::new(vec![true, false, true]);
445
446 let composite = CompositeGenome::new(real.clone(), binary.clone());
447 let trace = composite.to_trace();
448 let recovered: CompositeGenome<RealVector, BitString> =
449 CompositeGenome::from_trace(&trace).expect("mixed composite should round-trip");
450
451 assert_eq!(recovered.first().genes(), real.genes());
452 assert_eq!(recovered.second().bits(), binary.bits());
453 }
454
455 #[test]
456 #[cfg(feature = "ppl")]
457 fn test_composite_trace_roundtrip_permutation_realvector() {
458 use crate::genome::trace_genome::TraceGenome;
462 let perm = Permutation::new(vec![2, 0, 3, 1]);
463 let real = RealVector::new(vec![1.5, -2.5, 3.5]);
464
465 let composite = CompositeGenome::new(perm.clone(), real.clone());
466 let trace = composite.to_trace();
467 let recovered: CompositeGenome<Permutation, RealVector> =
468 CompositeGenome::from_trace(&trace)
469 .expect("permutation+real composite should round-trip");
470
471 assert_eq!(recovered.first().permutation(), perm.permutation());
472 assert_eq!(recovered.second().genes(), real.genes());
473 assert_eq!(recovered, composite);
474 }
475
476 #[test]
477 #[cfg(feature = "ppl")]
478 fn test_composite_trace_roundtrip_bitstring_permutation() {
479 use crate::genome::trace_genome::TraceGenome;
481 let bits = BitString::new(vec![true, false, true, true]);
482 let perm = Permutation::new(vec![1, 3, 0, 2]);
483
484 let composite = CompositeGenome::new(bits.clone(), perm.clone());
485 let trace = composite.to_trace();
486 let recovered: CompositeGenome<BitString, Permutation> =
487 CompositeGenome::from_trace(&trace)
488 .expect("bitstring+permutation composite should round-trip");
489
490 assert_eq!(recovered.first().bits(), bits.bits());
491 assert_eq!(recovered.second().permutation(), perm.permutation());
492 assert_eq!(recovered, composite);
493 }
494
495 #[test]
496 #[cfg(feature = "ppl")]
497 fn test_composite_trace_prefix() {
498 use crate::genome::trace_genome::TraceGenome;
499 assert_eq!(
500 <CompositeGenome<RealVector, BitString>>::trace_prefix(),
501 "composite"
502 );
503 }
504
505 #[test]
506 #[cfg(feature = "ppl")]
507 fn test_composite_from_trace_missing_dim_error() {
508 use crate::genome::trace_genome::TraceGenome;
509 use fugue::Trace;
510 let empty_trace = Trace::default();
511
512 let result: Result<CompositeGenome<RealVector, RealVector>, _> =
513 CompositeGenome::from_trace(&empty_trace);
514
515 assert!(result.is_err());
516 }
517}