Skip to main content

fugue_evo/genome/
dynamic_real_vector.rs

1//! Dynamic (variable-length) real-valued vector genome
2//!
3//! This module provides a variable-length real-valued vector genome type
4//! for problems where the solution dimension is not fixed.
5
6use 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, RealValuedGenome};
13
14/// Variable-length real-valued vector genome
15///
16/// This genome type represents optimization problems where the solution
17/// dimension can vary. Useful for problems like:
18/// - Neural network architecture search (variable hidden layer sizes)
19/// - Variable-length feature selection
20/// - Adaptive-dimensional optimization
21#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
22pub struct DynamicRealVector {
23    /// The genes (values) of this genome
24    genes: Vec<f64>,
25    /// Minimum allowed length
26    min_length: usize,
27    /// Maximum allowed length
28    max_length: usize,
29}
30
31impl DynamicRealVector {
32    /// Create a new dynamic real vector with the given genes and length constraints
33    pub fn new(genes: Vec<f64>, min_length: usize, max_length: usize) -> Result<Self, GenomeError> {
34        if genes.len() < min_length || genes.len() > max_length {
35            return Err(GenomeError::InvalidStructure(format!(
36                "Gene length {} outside bounds [{}, {}]",
37                genes.len(),
38                min_length,
39                max_length
40            )));
41        }
42        if min_length > max_length {
43            return Err(GenomeError::InvalidStructure(format!(
44                "min_length ({}) > max_length ({})",
45                min_length, max_length
46            )));
47        }
48        Ok(Self {
49            genes,
50            min_length,
51            max_length,
52        })
53    }
54
55    /// Create with default length constraints (1 to usize::MAX)
56    pub fn with_defaults(genes: Vec<f64>) -> Self {
57        Self {
58            genes,
59            min_length: 1,
60            max_length: usize::MAX,
61        }
62    }
63
64    /// Create a zero-filled vector of the given dimension
65    pub fn zeros(
66        dimension: usize,
67        min_length: usize,
68        max_length: usize,
69    ) -> Result<Self, GenomeError> {
70        Self::new(vec![0.0; dimension], min_length, max_length)
71    }
72
73    /// Get the minimum allowed length
74    pub fn min_length(&self) -> usize {
75        self.min_length
76    }
77
78    /// Get the maximum allowed length
79    pub fn max_length(&self) -> usize {
80        self.max_length
81    }
82
83    /// Get the underlying vector
84    pub fn into_inner(self) -> Vec<f64> {
85        self.genes
86    }
87
88    /// Get a reference to the genes
89    pub fn as_vec(&self) -> &Vec<f64> {
90        &self.genes
91    }
92
93    /// Calculate Euclidean norm
94    pub fn norm(&self) -> f64 {
95        self.genes.iter().map(|x| x * x).sum::<f64>().sqrt()
96    }
97
98    /// Calculate squared Euclidean norm
99    pub fn norm_squared(&self) -> f64 {
100        self.genes.iter().map(|x| x * x).sum::<f64>()
101    }
102
103    /// Add a gene to the end (if within bounds)
104    pub fn push(&mut self, gene: f64) -> Result<(), GenomeError> {
105        if self.genes.len() >= self.max_length {
106            return Err(GenomeError::ConstraintViolation(format!(
107                "Cannot add gene: would exceed max_length {}",
108                self.max_length
109            )));
110        }
111        self.genes.push(gene);
112        Ok(())
113    }
114
115    /// Remove the last gene (if within bounds)
116    pub fn pop(&mut self) -> Result<f64, GenomeError> {
117        if self.genes.len() <= self.min_length {
118            return Err(GenomeError::ConstraintViolation(format!(
119                "Cannot remove gene: would go below min_length {}",
120                self.min_length
121            )));
122        }
123        Ok(self.genes.pop().unwrap())
124    }
125
126    /// Insert a gene at a specific position
127    pub fn insert(&mut self, index: usize, gene: f64) -> Result<(), GenomeError> {
128        if self.genes.len() >= self.max_length {
129            return Err(GenomeError::ConstraintViolation(format!(
130                "Cannot insert gene: would exceed max_length {}",
131                self.max_length
132            )));
133        }
134        if index > self.genes.len() {
135            return Err(GenomeError::InvalidStructure(format!(
136                "Insert index {} out of bounds for length {}",
137                index,
138                self.genes.len()
139            )));
140        }
141        self.genes.insert(index, gene);
142        Ok(())
143    }
144
145    /// Remove a gene at a specific position
146    pub fn remove(&mut self, index: usize) -> Result<f64, GenomeError> {
147        if self.genes.len() <= self.min_length {
148            return Err(GenomeError::ConstraintViolation(format!(
149                "Cannot remove gene: would go below min_length {}",
150                self.min_length
151            )));
152        }
153        if index >= self.genes.len() {
154            return Err(GenomeError::InvalidStructure(format!(
155                "Remove index {} out of bounds for length {}",
156                index,
157                self.genes.len()
158            )));
159        }
160        Ok(self.genes.remove(index))
161    }
162
163    /// Check if a gene can be added
164    pub fn can_grow(&self) -> bool {
165        self.genes.len() < self.max_length
166    }
167
168    /// Check if a gene can be removed
169    pub fn can_shrink(&self) -> bool {
170        self.genes.len() > self.min_length
171    }
172
173    /// Element-wise addition (requires same length)
174    pub fn add(&self, other: &Self) -> Result<Self, GenomeError> {
175        if self.genes.len() != other.genes.len() {
176            return Err(GenomeError::DimensionMismatch {
177                expected: self.genes.len(),
178                actual: other.genes.len(),
179            });
180        }
181        Self::new(
182            self.genes
183                .iter()
184                .zip(other.genes.iter())
185                .map(|(a, b)| a + b)
186                .collect(),
187            self.min_length,
188            self.max_length,
189        )
190    }
191
192    /// Element-wise subtraction (requires same length)
193    pub fn sub(&self, other: &Self) -> Result<Self, GenomeError> {
194        if self.genes.len() != other.genes.len() {
195            return Err(GenomeError::DimensionMismatch {
196                expected: self.genes.len(),
197                actual: other.genes.len(),
198            });
199        }
200        Self::new(
201            self.genes
202                .iter()
203                .zip(other.genes.iter())
204                .map(|(a, b)| a - b)
205                .collect(),
206            self.min_length,
207            self.max_length,
208        )
209    }
210
211    /// Scalar multiplication
212    pub fn scale(&self, scalar: f64) -> Self {
213        Self {
214            genes: self.genes.iter().map(|x| x * scalar).collect(),
215            min_length: self.min_length,
216            max_length: self.max_length,
217        }
218    }
219}
220
221impl EvolutionaryGenome for DynamicRealVector {
222    type Allele = f64;
223    type Phenotype = Vec<f64>;
224
225    /// Convert DynamicRealVector to Fugue trace.
226    ///
227    /// Stores genes at "gene#i" and metadata at "meta#min_length" and "meta#max_length".
228    fn to_trace(&self) -> Trace {
229        let mut trace = Trace::default();
230        for (i, &gene) in self.genes.iter().enumerate() {
231            trace.insert_choice(addr!("gene", i), ChoiceValue::F64(gene), 0.0);
232        }
233        // Store length constraints in trace
234        trace.insert_choice(
235            addr!("meta", "min_length"),
236            ChoiceValue::I64(self.min_length as i64),
237            0.0,
238        );
239        trace.insert_choice(
240            addr!("meta", "max_length"),
241            ChoiceValue::I64(self.max_length as i64),
242            0.0,
243        );
244        trace.insert_choice(
245            addr!("meta", "length"),
246            ChoiceValue::I64(self.genes.len() as i64),
247            0.0,
248        );
249        trace
250    }
251
252    /// Reconstruct DynamicRealVector from Fugue trace.
253    fn from_trace(trace: &Trace) -> Result<Self, GenomeError> {
254        let min_length = trace
255            .get_i64(&addr!("meta", "min_length"))
256            .map(|v| v as usize)
257            .unwrap_or(1);
258        let max_length = trace
259            .get_i64(&addr!("meta", "max_length"))
260            .map(|v| v as usize)
261            .unwrap_or(usize::MAX);
262        let expected_length = trace.get_i64(&addr!("meta", "length")).map(|v| v as usize);
263
264        let mut genes = Vec::new();
265        let mut i = 0;
266        while let Some(val) = trace.get_f64(&addr!("gene", i)) {
267            genes.push(val);
268            i += 1;
269            // Stop if we've reached expected length (handles sparse traces)
270            if let Some(len) = expected_length {
271                if i >= len {
272                    break;
273                }
274            }
275        }
276
277        if genes.is_empty() {
278            return Err(GenomeError::InvalidStructure(
279                "No genes found in trace".to_string(),
280            ));
281        }
282
283        Self::new(genes, min_length, max_length)
284    }
285
286    fn decode(&self) -> Self::Phenotype {
287        self.genes.clone()
288    }
289
290    fn dimension(&self) -> usize {
291        self.genes.len()
292    }
293
294    fn generate<R: Rng>(rng: &mut R, bounds: &MultiBounds) -> Self {
295        // Generate with random length within the bounds
296        let min_len = 1;
297        let max_len = bounds.dimension();
298        let length = if min_len == max_len {
299            min_len
300        } else {
301            rng.gen_range(min_len..=max_len)
302        };
303
304        let genes: Vec<f64> = (0..length)
305            .map(|i| {
306                let b = if i < bounds.dimension() {
307                    bounds.get(i).unwrap()
308                } else {
309                    // Use first bound if we exceed bounds dimension
310                    bounds.get(0).unwrap()
311                };
312                rng.gen_range(b.min..=b.max)
313            })
314            .collect();
315
316        Self {
317            genes,
318            min_length: min_len,
319            max_length: max_len,
320        }
321    }
322
323    fn distance(&self, other: &Self) -> f64 {
324        // Euclidean distance for common genes, plus penalty for different lengths
325        let common_len = self.genes.len().min(other.genes.len());
326        let mut dist_sq = 0.0;
327
328        for i in 0..common_len {
329            let diff = self.genes[i] - other.genes[i];
330            dist_sq += diff * diff;
331        }
332
333        // Add penalty for length difference
334        let length_penalty = (self.genes.len() as f64 - other.genes.len() as f64).abs();
335        dist_sq.sqrt() + length_penalty
336    }
337
338    fn trace_prefix() -> &'static str {
339        "dyn_gene"
340    }
341}
342
343impl RealValuedGenome for DynamicRealVector {
344    fn genes(&self) -> &[f64] {
345        &self.genes
346    }
347
348    fn genes_mut(&mut self) -> &mut [f64] {
349        &mut self.genes
350    }
351
352    fn from_genes(genes: Vec<f64>) -> Result<Self, GenomeError> {
353        if genes.is_empty() {
354            return Err(GenomeError::InvalidStructure(
355                "Cannot create DynamicRealVector with empty genes".to_string(),
356            ));
357        }
358        Ok(Self::with_defaults(genes))
359    }
360
361    fn apply_bounds(&mut self, bounds: &MultiBounds) {
362        for (i, gene) in self.genes.iter_mut().enumerate() {
363            if let Some(b) = bounds.get(i) {
364                *gene = gene.clamp(b.min, b.max);
365            }
366        }
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    #[test]
375    fn test_dynamic_real_vector_creation() {
376        let genes = vec![1.0, 2.0, 3.0];
377        let genome = DynamicRealVector::new(genes.clone(), 1, 10).unwrap();
378        assert_eq!(genome.genes(), &genes[..]);
379        assert_eq!(genome.dimension(), 3);
380    }
381
382    #[test]
383    fn test_dynamic_real_vector_length_constraints() {
384        // Too short
385        let result = DynamicRealVector::new(vec![1.0], 2, 10);
386        assert!(result.is_err());
387
388        // Too long
389        let result = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 2);
390        assert!(result.is_err());
391
392        // Invalid bounds
393        let result = DynamicRealVector::new(vec![1.0, 2.0], 5, 3);
394        assert!(result.is_err());
395    }
396
397    #[test]
398    fn test_push_pop() {
399        let mut genome = DynamicRealVector::new(vec![1.0, 2.0], 1, 5).unwrap();
400
401        // Push
402        genome.push(3.0).unwrap();
403        assert_eq!(genome.dimension(), 3);
404        assert_eq!(genome.genes()[2], 3.0);
405
406        // Pop
407        let val = genome.pop().unwrap();
408        assert_eq!(val, 3.0);
409        assert_eq!(genome.dimension(), 2);
410    }
411
412    #[test]
413    fn test_push_pop_bounds() {
414        let mut genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 3, 3).unwrap();
415
416        // Cannot push (at max)
417        assert!(genome.push(4.0).is_err());
418
419        // Cannot pop (at min)
420        assert!(genome.pop().is_err());
421    }
422
423    #[test]
424    fn test_insert_remove() {
425        let mut genome = DynamicRealVector::new(vec![1.0, 3.0], 1, 5).unwrap();
426
427        // Insert in middle
428        genome.insert(1, 2.0).unwrap();
429        assert_eq!(genome.genes(), &[1.0, 2.0, 3.0]);
430
431        // Remove from middle
432        let val = genome.remove(1).unwrap();
433        assert_eq!(val, 2.0);
434        assert_eq!(genome.genes(), &[1.0, 3.0]);
435    }
436
437    #[test]
438    fn test_can_grow_shrink() {
439        let genome = DynamicRealVector::new(vec![1.0, 2.0], 1, 3).unwrap();
440        assert!(genome.can_grow());
441        assert!(genome.can_shrink());
442
443        let genome_at_max = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 3).unwrap();
444        assert!(!genome_at_max.can_grow());
445        assert!(genome_at_max.can_shrink());
446
447        let genome_at_min = DynamicRealVector::new(vec![1.0], 1, 3).unwrap();
448        assert!(genome_at_min.can_grow());
449        assert!(!genome_at_min.can_shrink());
450    }
451
452    #[test]
453    fn test_trace_roundtrip() {
454        let genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 2, 5).unwrap();
455        let trace = genome.to_trace();
456        let restored = DynamicRealVector::from_trace(&trace).unwrap();
457
458        assert_eq!(genome.genes(), restored.genes());
459        assert_eq!(genome.min_length(), restored.min_length());
460        assert_eq!(genome.max_length(), restored.max_length());
461    }
462
463    #[test]
464    fn test_distance() {
465        let g1 = DynamicRealVector::new(vec![0.0, 0.0], 1, 10).unwrap();
466        let g2 = DynamicRealVector::new(vec![3.0, 4.0], 1, 10).unwrap();
467
468        // Euclidean distance should be 5
469        let dist = g1.distance(&g2);
470        assert!((dist - 5.0).abs() < 0.001);
471
472        // Different lengths add penalty
473        let g3 = DynamicRealVector::new(vec![0.0, 0.0, 0.0], 1, 10).unwrap();
474        let dist_with_penalty = g1.distance(&g3);
475        assert!(dist_with_penalty > 0.0);
476    }
477
478    #[test]
479    fn test_generate_random() {
480        let bounds = MultiBounds::symmetric(5.0, 5);
481        let mut rng = rand::thread_rng();
482
483        let genome = DynamicRealVector::generate(&mut rng, &bounds);
484
485        assert!(genome.dimension() >= 1);
486        assert!(genome.dimension() <= 5);
487        for gene in genome.genes() {
488            assert!(*gene >= -5.0 && *gene <= 5.0);
489        }
490    }
491
492    #[test]
493    fn test_arithmetic_operations() {
494        let g1 = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 10).unwrap();
495        let g2 = DynamicRealVector::new(vec![4.0, 5.0, 6.0], 1, 10).unwrap();
496
497        let sum = g1.add(&g2).unwrap();
498        assert_eq!(sum.genes(), &[5.0, 7.0, 9.0]);
499
500        let diff = g2.sub(&g1).unwrap();
501        assert_eq!(diff.genes(), &[3.0, 3.0, 3.0]);
502
503        let scaled = g1.scale(2.0);
504        assert_eq!(scaled.genes(), &[2.0, 4.0, 6.0]);
505    }
506
507    #[test]
508    fn test_norm() {
509        let genome = DynamicRealVector::new(vec![3.0, 4.0], 1, 10).unwrap();
510        assert!((genome.norm() - 5.0).abs() < 0.001);
511        assert!((genome.norm_squared() - 25.0).abs() < 0.001);
512    }
513}