1#[cfg(feature = "ppl")]
7use fugue::{addr, ChoiceValue, Trace};
8use rand::Rng;
9use serde::{Deserialize, Serialize};
10
11use crate::error::GenomeError;
12use crate::genome::bounds::MultiBounds;
13use crate::genome::traits::{EvolutionaryGenome, RealValuedGenome};
14
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
23pub struct DynamicRealVector {
24 genes: Vec<f64>,
26 min_length: usize,
28 max_length: usize,
30}
31
32impl DynamicRealVector {
33 pub fn new(genes: Vec<f64>, min_length: usize, max_length: usize) -> Result<Self, GenomeError> {
35 if genes.len() < min_length || genes.len() > max_length {
36 return Err(GenomeError::InvalidStructure(format!(
37 "Gene length {} outside bounds [{}, {}]",
38 genes.len(),
39 min_length,
40 max_length
41 )));
42 }
43 if min_length > max_length {
44 return Err(GenomeError::InvalidStructure(format!(
45 "min_length ({}) > max_length ({})",
46 min_length, max_length
47 )));
48 }
49 Ok(Self {
50 genes,
51 min_length,
52 max_length,
53 })
54 }
55
56 pub fn with_defaults(genes: Vec<f64>) -> Self {
58 Self {
59 genes,
60 min_length: 1,
61 max_length: usize::MAX,
62 }
63 }
64
65 pub fn zeros(
67 dimension: usize,
68 min_length: usize,
69 max_length: usize,
70 ) -> Result<Self, GenomeError> {
71 Self::new(vec![0.0; dimension], min_length, max_length)
72 }
73
74 pub fn min_length(&self) -> usize {
76 self.min_length
77 }
78
79 pub fn max_length(&self) -> usize {
81 self.max_length
82 }
83
84 pub fn into_inner(self) -> Vec<f64> {
86 self.genes
87 }
88
89 pub fn as_vec(&self) -> &Vec<f64> {
91 &self.genes
92 }
93
94 pub fn norm(&self) -> f64 {
96 self.genes.iter().map(|x| x * x).sum::<f64>().sqrt()
97 }
98
99 pub fn norm_squared(&self) -> f64 {
101 self.genes.iter().map(|x| x * x).sum::<f64>()
102 }
103
104 pub fn push(&mut self, gene: f64) -> Result<(), GenomeError> {
106 if self.genes.len() >= self.max_length {
107 return Err(GenomeError::ConstraintViolation(format!(
108 "Cannot add gene: would exceed max_length {}",
109 self.max_length
110 )));
111 }
112 self.genes.push(gene);
113 Ok(())
114 }
115
116 pub fn pop(&mut self) -> Result<f64, GenomeError> {
118 if self.genes.len() <= self.min_length {
119 return Err(GenomeError::ConstraintViolation(format!(
120 "Cannot remove gene: would go below min_length {}",
121 self.min_length
122 )));
123 }
124 Ok(self.genes.pop().unwrap())
125 }
126
127 pub fn insert(&mut self, index: usize, gene: f64) -> Result<(), GenomeError> {
129 if self.genes.len() >= self.max_length {
130 return Err(GenomeError::ConstraintViolation(format!(
131 "Cannot insert gene: would exceed max_length {}",
132 self.max_length
133 )));
134 }
135 if index > self.genes.len() {
136 return Err(GenomeError::InvalidStructure(format!(
137 "Insert index {} out of bounds for length {}",
138 index,
139 self.genes.len()
140 )));
141 }
142 self.genes.insert(index, gene);
143 Ok(())
144 }
145
146 pub fn remove(&mut self, index: usize) -> Result<f64, GenomeError> {
148 if self.genes.len() <= self.min_length {
149 return Err(GenomeError::ConstraintViolation(format!(
150 "Cannot remove gene: would go below min_length {}",
151 self.min_length
152 )));
153 }
154 if index >= self.genes.len() {
155 return Err(GenomeError::InvalidStructure(format!(
156 "Remove index {} out of bounds for length {}",
157 index,
158 self.genes.len()
159 )));
160 }
161 Ok(self.genes.remove(index))
162 }
163
164 pub fn can_grow(&self) -> bool {
166 self.genes.len() < self.max_length
167 }
168
169 pub fn can_shrink(&self) -> bool {
171 self.genes.len() > self.min_length
172 }
173
174 pub fn add(&self, other: &Self) -> Result<Self, GenomeError> {
176 if self.genes.len() != other.genes.len() {
177 return Err(GenomeError::DimensionMismatch {
178 expected: self.genes.len(),
179 actual: other.genes.len(),
180 });
181 }
182 Self::new(
183 self.genes
184 .iter()
185 .zip(other.genes.iter())
186 .map(|(a, b)| a + b)
187 .collect(),
188 self.min_length,
189 self.max_length,
190 )
191 }
192
193 pub fn sub(&self, other: &Self) -> Result<Self, GenomeError> {
195 if self.genes.len() != other.genes.len() {
196 return Err(GenomeError::DimensionMismatch {
197 expected: self.genes.len(),
198 actual: other.genes.len(),
199 });
200 }
201 Self::new(
202 self.genes
203 .iter()
204 .zip(other.genes.iter())
205 .map(|(a, b)| a - b)
206 .collect(),
207 self.min_length,
208 self.max_length,
209 )
210 }
211
212 pub fn scale(&self, scalar: f64) -> Self {
214 Self {
215 genes: self.genes.iter().map(|x| x * scalar).collect(),
216 min_length: self.min_length,
217 max_length: self.max_length,
218 }
219 }
220
221 pub fn try_generate<R: Rng>(rng: &mut R, bounds: &MultiBounds) -> Result<Self, GenomeError> {
229 let max_len = bounds.dimension();
230 if max_len == 0 {
231 return Err(GenomeError::InvalidStructure(
232 "Cannot generate a DynamicRealVector from empty (0-dimension) bounds".to_string(),
233 ));
234 }
235 let min_len = 1;
236 let length = if min_len == max_len {
237 min_len
238 } else {
239 rng.gen_range(min_len..=max_len)
240 };
241
242 let genes: Vec<f64> = (0..length)
243 .map(|i| {
244 let b = bounds
247 .get(i)
248 .or_else(|| bounds.get(0))
249 .expect("bounds is non-empty");
250 rng.gen_range(b.min..=b.max)
251 })
252 .collect();
253
254 Ok(Self {
255 genes,
256 min_length: min_len,
257 max_length: max_len,
258 })
259 }
260
261 pub fn generate_with_len<R: Rng>(
271 rng: &mut R,
272 min_length: usize,
273 max_length: usize,
274 value_bounds: &MultiBounds,
275 ) -> Result<Self, GenomeError> {
276 if min_length > max_length {
277 return Err(GenomeError::InvalidStructure(format!(
278 "min_length ({min_length}) > max_length ({max_length})"
279 )));
280 }
281 if value_bounds.dimension() == 0 {
282 return Err(GenomeError::InvalidStructure(
283 "value_bounds must have at least one dimension".to_string(),
284 ));
285 }
286 let length = if min_length == max_length {
287 min_length
288 } else {
289 rng.gen_range(min_length..=max_length)
290 };
291 let genes: Vec<f64> = (0..length)
292 .map(|i| {
293 let b = value_bounds
294 .get(i)
295 .or_else(|| value_bounds.get(0))
296 .expect("value_bounds is non-empty");
297 rng.gen_range(b.min..=b.max)
298 })
299 .collect();
300 Self::new(genes, min_length, max_length)
301 }
302}
303
304impl EvolutionaryGenome for DynamicRealVector {
305 type Allele = f64;
306 type Phenotype = Vec<f64>;
307
308 fn decode(&self) -> Self::Phenotype {
309 self.genes.clone()
310 }
311
312 fn dimension(&self) -> usize {
313 self.genes.len()
314 }
315
316 fn generate<R: Rng>(rng: &mut R, bounds: &MultiBounds) -> Self {
326 Self::try_generate(rng, bounds).unwrap_or_else(|_| Self {
327 genes: Vec::new(),
328 min_length: 0,
329 max_length: 0,
330 })
331 }
332
333 fn distance(&self, other: &Self) -> f64 {
334 let common_len = self.genes.len().min(other.genes.len());
338 let mut dist_sq = 0.0;
339
340 for i in 0..common_len {
341 let diff = self.genes[i] - other.genes[i];
342 dist_sq += diff * diff;
343 }
344
345 let length_penalty = (self.genes.len() as f64 - other.genes.len() as f64).abs();
347 dist_sq.sqrt() + length_penalty
348 }
349
350 fn try_distance(&self, other: &Self) -> Result<f64, GenomeError> {
351 Ok(self.distance(other))
354 }
355}
356
357#[cfg(feature = "ppl")]
358impl crate::genome::trace_genome::TraceGenome for DynamicRealVector {
359 fn to_trace(&self) -> Trace {
366 let mut trace = Trace::default();
367 for (i, &gene) in self.genes.iter().enumerate() {
368 trace.insert_choice(addr!(Self::trace_prefix(), i), ChoiceValue::F64(gene), 0.0);
369 }
370 trace.insert_choice(
372 addr!("meta", "min_length"),
373 ChoiceValue::I64(self.min_length as i64),
374 0.0,
375 );
376 trace.insert_choice(
377 addr!("meta", "max_length"),
378 ChoiceValue::I64(self.max_length as i64),
379 0.0,
380 );
381 trace.insert_choice(
382 addr!("meta", "length"),
383 ChoiceValue::I64(self.genes.len() as i64),
384 0.0,
385 );
386 trace
387 }
388
389 fn from_trace(trace: &Trace) -> Result<Self, GenomeError> {
391 let min_length = trace
392 .get_i64(&addr!("meta", "min_length"))
393 .map(|v| v as usize)
394 .unwrap_or(1);
395 let max_length = trace
396 .get_i64(&addr!("meta", "max_length"))
397 .map(|v| v as usize)
398 .unwrap_or(usize::MAX);
399 let expected_length = trace.get_i64(&addr!("meta", "length")).map(|v| v as usize);
400
401 let mut genes = Vec::new();
402 let mut i = 0;
403 while let Some(val) = trace.get_f64(&addr!(Self::trace_prefix(), i)) {
404 genes.push(val);
405 i += 1;
406 if let Some(len) = expected_length {
408 if i >= len {
409 break;
410 }
411 }
412 }
413
414 if genes.is_empty() {
415 return Err(GenomeError::InvalidStructure(
416 "No genes found in trace".to_string(),
417 ));
418 }
419
420 Self::new(genes, min_length, max_length)
421 }
422
423 fn trace_prefix() -> &'static str {
424 "dyn_gene"
425 }
426}
427
428impl RealValuedGenome for DynamicRealVector {
429 fn genes(&self) -> &[f64] {
430 &self.genes
431 }
432
433 fn genes_mut(&mut self) -> &mut [f64] {
434 &mut self.genes
435 }
436
437 fn from_genes(genes: Vec<f64>) -> Result<Self, GenomeError> {
438 if genes.is_empty() {
439 return Err(GenomeError::InvalidStructure(
440 "Cannot create DynamicRealVector with empty genes".to_string(),
441 ));
442 }
443 Ok(Self::with_defaults(genes))
444 }
445
446 fn apply_bounds(&mut self, bounds: &MultiBounds) {
447 for (i, gene) in self.genes.iter_mut().enumerate() {
448 if let Some(b) = bounds.get(i) {
449 *gene = gene.clamp(b.min, b.max);
450 }
451 }
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn test_dynamic_real_vector_creation() {
461 let genes = vec![1.0, 2.0, 3.0];
462 let genome = DynamicRealVector::new(genes.clone(), 1, 10).unwrap();
463 assert_eq!(genome.genes(), &genes[..]);
464 assert_eq!(genome.dimension(), 3);
465 }
466
467 #[test]
468 fn test_dynamic_real_vector_length_constraints() {
469 let result = DynamicRealVector::new(vec![1.0], 2, 10);
471 assert!(result.is_err());
472
473 let result = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 2);
475 assert!(result.is_err());
476
477 let result = DynamicRealVector::new(vec![1.0, 2.0], 5, 3);
479 assert!(result.is_err());
480 }
481
482 #[test]
483 fn test_push_pop() {
484 let mut genome = DynamicRealVector::new(vec![1.0, 2.0], 1, 5).unwrap();
485
486 genome.push(3.0).unwrap();
488 assert_eq!(genome.dimension(), 3);
489 assert_eq!(genome.genes()[2], 3.0);
490
491 let val = genome.pop().unwrap();
493 assert_eq!(val, 3.0);
494 assert_eq!(genome.dimension(), 2);
495 }
496
497 #[test]
498 fn test_push_pop_bounds() {
499 let mut genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 3, 3).unwrap();
500
501 assert!(genome.push(4.0).is_err());
503
504 assert!(genome.pop().is_err());
506 }
507
508 #[test]
509 fn test_insert_remove() {
510 let mut genome = DynamicRealVector::new(vec![1.0, 3.0], 1, 5).unwrap();
511
512 genome.insert(1, 2.0).unwrap();
514 assert_eq!(genome.genes(), &[1.0, 2.0, 3.0]);
515
516 let val = genome.remove(1).unwrap();
518 assert_eq!(val, 2.0);
519 assert_eq!(genome.genes(), &[1.0, 3.0]);
520 }
521
522 #[test]
523 fn test_can_grow_shrink() {
524 let genome = DynamicRealVector::new(vec![1.0, 2.0], 1, 3).unwrap();
525 assert!(genome.can_grow());
526 assert!(genome.can_shrink());
527
528 let genome_at_max = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 3).unwrap();
529 assert!(!genome_at_max.can_grow());
530 assert!(genome_at_max.can_shrink());
531
532 let genome_at_min = DynamicRealVector::new(vec![1.0], 1, 3).unwrap();
533 assert!(genome_at_min.can_grow());
534 assert!(!genome_at_min.can_shrink());
535 }
536
537 #[test]
538 #[cfg(feature = "ppl")]
539 fn test_trace_roundtrip() {
540 use crate::genome::trace_genome::TraceGenome;
541 let genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 2, 5).unwrap();
542 let trace = genome.to_trace();
543 let restored = DynamicRealVector::from_trace(&trace).unwrap();
544
545 assert_eq!(genome.genes(), restored.genes());
546 assert_eq!(genome.min_length(), restored.min_length());
547 assert_eq!(genome.max_length(), restored.max_length());
548 }
549
550 #[test]
551 fn test_distance() {
552 let g1 = DynamicRealVector::new(vec![0.0, 0.0], 1, 10).unwrap();
553 let g2 = DynamicRealVector::new(vec![3.0, 4.0], 1, 10).unwrap();
554
555 let dist = g1.distance(&g2);
557 assert!((dist - 5.0).abs() < 0.001);
558
559 let g3 = DynamicRealVector::new(vec![0.0, 0.0, 0.0], 1, 10).unwrap();
561 let dist_with_penalty = g1.distance(&g3);
562 assert!(dist_with_penalty > 0.0);
563 }
564
565 #[test]
566 fn test_generate_random() {
567 let bounds = MultiBounds::symmetric(5.0, 5);
568 let mut rng = rand::thread_rng();
569
570 let genome = DynamicRealVector::generate(&mut rng, &bounds);
571
572 assert!(genome.dimension() >= 1);
573 assert!(genome.dimension() <= 5);
574 for gene in genome.genes() {
575 assert!(*gene >= -5.0 && *gene <= 5.0);
576 }
577 }
578
579 #[test]
580 fn test_arithmetic_operations() {
581 let g1 = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 10).unwrap();
582 let g2 = DynamicRealVector::new(vec![4.0, 5.0, 6.0], 1, 10).unwrap();
583
584 let sum = g1.add(&g2).unwrap();
585 assert_eq!(sum.genes(), &[5.0, 7.0, 9.0]);
586
587 let diff = g2.sub(&g1).unwrap();
588 assert_eq!(diff.genes(), &[3.0, 3.0, 3.0]);
589
590 let scaled = g1.scale(2.0);
591 assert_eq!(scaled.genes(), &[2.0, 4.0, 6.0]);
592 }
593
594 #[test]
595 fn test_norm() {
596 let genome = DynamicRealVector::new(vec![3.0, 4.0], 1, 10).unwrap();
597 assert!((genome.norm() - 5.0).abs() < 0.001);
598 assert!((genome.norm_squared() - 25.0).abs() < 0.001);
599 }
600
601 #[test]
602 fn test_generate_empty_bounds_does_not_panic() {
603 let bounds = MultiBounds::new(vec![]);
606 let mut rng = rand::thread_rng();
607 let genome = DynamicRealVector::generate(&mut rng, &bounds);
608 assert_eq!(genome.dimension(), 0);
609 }
610
611 #[test]
612 fn test_try_generate_empty_bounds_errors() {
613 let bounds = MultiBounds::new(vec![]);
615 let mut rng = rand::thread_rng();
616 assert!(DynamicRealVector::try_generate(&mut rng, &bounds).is_err());
617
618 let ok_bounds = MultiBounds::symmetric(5.0, 4);
620 let g = DynamicRealVector::try_generate(&mut rng, &ok_bounds).unwrap();
621 assert!(g.dimension() >= 1 && g.dimension() <= 4);
622 }
623
624 #[test]
625 #[cfg(feature = "ppl")]
626 fn test_trace_prefix_matches_addresses() {
627 use crate::genome::trace_genome::TraceGenome;
630 use fugue::addr;
631 let genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 5).unwrap();
632 let trace = genome.to_trace();
633 let prefix = DynamicRealVector::trace_prefix();
634
635 assert!(trace.get_f64(&addr!(prefix, 0)).is_some());
637 assert_eq!(trace.get_f64(&addr!(prefix, 0)), Some(1.0));
638 let restored = DynamicRealVector::from_trace(&trace).unwrap();
640 assert_eq!(restored.genes(), genome.genes());
641 }
642
643 #[test]
644 fn test_generate_with_len_explicit() {
645 let mut rng = rand::thread_rng();
647 let value_bounds = MultiBounds::symmetric(2.0, 6);
648 let g = DynamicRealVector::generate_with_len(&mut rng, 3, 5, &value_bounds).unwrap();
649 assert!(g.dimension() >= 3 && g.dimension() <= 5);
650 for gene in g.genes() {
651 assert!(*gene >= -2.0 && *gene <= 2.0);
652 }
653 assert!(DynamicRealVector::generate_with_len(&mut rng, 5, 3, &value_bounds).is_err());
655 assert!(
656 DynamicRealVector::generate_with_len(&mut rng, 1, 3, &MultiBounds::new(vec![]))
657 .is_err()
658 );
659 }
660}