#[cfg(feature = "ppl")]
use fugue::{addr, ChoiceValue, Trace};
use rand::Rng;
use serde::{Deserialize, Serialize};
use crate::error::GenomeError;
use crate::genome::bounds::MultiBounds;
use crate::genome::traits::{EvolutionaryGenome, RealValuedGenome};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DynamicRealVector {
genes: Vec<f64>,
min_length: usize,
max_length: usize,
}
impl DynamicRealVector {
pub fn new(genes: Vec<f64>, min_length: usize, max_length: usize) -> Result<Self, GenomeError> {
if genes.len() < min_length || genes.len() > max_length {
return Err(GenomeError::InvalidStructure(format!(
"Gene length {} outside bounds [{}, {}]",
genes.len(),
min_length,
max_length
)));
}
if min_length > max_length {
return Err(GenomeError::InvalidStructure(format!(
"min_length ({}) > max_length ({})",
min_length, max_length
)));
}
Ok(Self {
genes,
min_length,
max_length,
})
}
pub fn with_defaults(genes: Vec<f64>) -> Self {
Self {
genes,
min_length: 1,
max_length: usize::MAX,
}
}
pub fn zeros(
dimension: usize,
min_length: usize,
max_length: usize,
) -> Result<Self, GenomeError> {
Self::new(vec![0.0; dimension], min_length, max_length)
}
pub fn min_length(&self) -> usize {
self.min_length
}
pub fn max_length(&self) -> usize {
self.max_length
}
pub fn into_inner(self) -> Vec<f64> {
self.genes
}
pub fn as_vec(&self) -> &Vec<f64> {
&self.genes
}
pub fn norm(&self) -> f64 {
self.genes.iter().map(|x| x * x).sum::<f64>().sqrt()
}
pub fn norm_squared(&self) -> f64 {
self.genes.iter().map(|x| x * x).sum::<f64>()
}
pub fn push(&mut self, gene: f64) -> Result<(), GenomeError> {
if self.genes.len() >= self.max_length {
return Err(GenomeError::ConstraintViolation(format!(
"Cannot add gene: would exceed max_length {}",
self.max_length
)));
}
self.genes.push(gene);
Ok(())
}
pub fn pop(&mut self) -> Result<f64, GenomeError> {
if self.genes.len() <= self.min_length {
return Err(GenomeError::ConstraintViolation(format!(
"Cannot remove gene: would go below min_length {}",
self.min_length
)));
}
Ok(self.genes.pop().unwrap())
}
pub fn insert(&mut self, index: usize, gene: f64) -> Result<(), GenomeError> {
if self.genes.len() >= self.max_length {
return Err(GenomeError::ConstraintViolation(format!(
"Cannot insert gene: would exceed max_length {}",
self.max_length
)));
}
if index > self.genes.len() {
return Err(GenomeError::InvalidStructure(format!(
"Insert index {} out of bounds for length {}",
index,
self.genes.len()
)));
}
self.genes.insert(index, gene);
Ok(())
}
pub fn remove(&mut self, index: usize) -> Result<f64, GenomeError> {
if self.genes.len() <= self.min_length {
return Err(GenomeError::ConstraintViolation(format!(
"Cannot remove gene: would go below min_length {}",
self.min_length
)));
}
if index >= self.genes.len() {
return Err(GenomeError::InvalidStructure(format!(
"Remove index {} out of bounds for length {}",
index,
self.genes.len()
)));
}
Ok(self.genes.remove(index))
}
pub fn can_grow(&self) -> bool {
self.genes.len() < self.max_length
}
pub fn can_shrink(&self) -> bool {
self.genes.len() > self.min_length
}
pub fn add(&self, other: &Self) -> Result<Self, GenomeError> {
if self.genes.len() != other.genes.len() {
return Err(GenomeError::DimensionMismatch {
expected: self.genes.len(),
actual: other.genes.len(),
});
}
Self::new(
self.genes
.iter()
.zip(other.genes.iter())
.map(|(a, b)| a + b)
.collect(),
self.min_length,
self.max_length,
)
}
pub fn sub(&self, other: &Self) -> Result<Self, GenomeError> {
if self.genes.len() != other.genes.len() {
return Err(GenomeError::DimensionMismatch {
expected: self.genes.len(),
actual: other.genes.len(),
});
}
Self::new(
self.genes
.iter()
.zip(other.genes.iter())
.map(|(a, b)| a - b)
.collect(),
self.min_length,
self.max_length,
)
}
pub fn scale(&self, scalar: f64) -> Self {
Self {
genes: self.genes.iter().map(|x| x * scalar).collect(),
min_length: self.min_length,
max_length: self.max_length,
}
}
pub fn try_generate<R: Rng>(rng: &mut R, bounds: &MultiBounds) -> Result<Self, GenomeError> {
let max_len = bounds.dimension();
if max_len == 0 {
return Err(GenomeError::InvalidStructure(
"Cannot generate a DynamicRealVector from empty (0-dimension) bounds".to_string(),
));
}
let min_len = 1;
let length = if min_len == max_len {
min_len
} else {
rng.gen_range(min_len..=max_len)
};
let genes: Vec<f64> = (0..length)
.map(|i| {
let b = bounds
.get(i)
.or_else(|| bounds.get(0))
.expect("bounds is non-empty");
rng.gen_range(b.min..=b.max)
})
.collect();
Ok(Self {
genes,
min_length: min_len,
max_length: max_len,
})
}
pub fn generate_with_len<R: Rng>(
rng: &mut R,
min_length: usize,
max_length: usize,
value_bounds: &MultiBounds,
) -> Result<Self, GenomeError> {
if min_length > max_length {
return Err(GenomeError::InvalidStructure(format!(
"min_length ({min_length}) > max_length ({max_length})"
)));
}
if value_bounds.dimension() == 0 {
return Err(GenomeError::InvalidStructure(
"value_bounds must have at least one dimension".to_string(),
));
}
let length = if min_length == max_length {
min_length
} else {
rng.gen_range(min_length..=max_length)
};
let genes: Vec<f64> = (0..length)
.map(|i| {
let b = value_bounds
.get(i)
.or_else(|| value_bounds.get(0))
.expect("value_bounds is non-empty");
rng.gen_range(b.min..=b.max)
})
.collect();
Self::new(genes, min_length, max_length)
}
}
impl EvolutionaryGenome for DynamicRealVector {
type Allele = f64;
type Phenotype = Vec<f64>;
fn decode(&self) -> Self::Phenotype {
self.genes.clone()
}
fn dimension(&self) -> usize {
self.genes.len()
}
fn generate<R: Rng>(rng: &mut R, bounds: &MultiBounds) -> Self {
Self::try_generate(rng, bounds).unwrap_or_else(|_| Self {
genes: Vec::new(),
min_length: 0,
max_length: 0,
})
}
fn distance(&self, other: &Self) -> f64 {
let common_len = self.genes.len().min(other.genes.len());
let mut dist_sq = 0.0;
for i in 0..common_len {
let diff = self.genes[i] - other.genes[i];
dist_sq += diff * diff;
}
let length_penalty = (self.genes.len() as f64 - other.genes.len() as f64).abs();
dist_sq.sqrt() + length_penalty
}
fn try_distance(&self, other: &Self) -> Result<f64, GenomeError> {
Ok(self.distance(other))
}
}
#[cfg(feature = "ppl")]
impl crate::genome::trace_genome::TraceGenome for DynamicRealVector {
fn to_trace(&self) -> Trace {
let mut trace = Trace::default();
for (i, &gene) in self.genes.iter().enumerate() {
trace.insert_choice(addr!(Self::trace_prefix(), i), ChoiceValue::F64(gene), 0.0);
}
trace.insert_choice(
addr!("meta", "min_length"),
ChoiceValue::I64(self.min_length as i64),
0.0,
);
trace.insert_choice(
addr!("meta", "max_length"),
ChoiceValue::I64(self.max_length as i64),
0.0,
);
trace.insert_choice(
addr!("meta", "length"),
ChoiceValue::I64(self.genes.len() as i64),
0.0,
);
trace
}
fn from_trace(trace: &Trace) -> Result<Self, GenomeError> {
let min_length = trace
.get_i64(&addr!("meta", "min_length"))
.map(|v| v as usize)
.unwrap_or(1);
let max_length = trace
.get_i64(&addr!("meta", "max_length"))
.map(|v| v as usize)
.unwrap_or(usize::MAX);
let expected_length = trace.get_i64(&addr!("meta", "length")).map(|v| v as usize);
let mut genes = Vec::new();
let mut i = 0;
while let Some(val) = trace.get_f64(&addr!(Self::trace_prefix(), i)) {
genes.push(val);
i += 1;
if let Some(len) = expected_length {
if i >= len {
break;
}
}
}
if genes.is_empty() {
return Err(GenomeError::InvalidStructure(
"No genes found in trace".to_string(),
));
}
Self::new(genes, min_length, max_length)
}
fn trace_prefix() -> &'static str {
"dyn_gene"
}
}
impl RealValuedGenome for DynamicRealVector {
fn genes(&self) -> &[f64] {
&self.genes
}
fn genes_mut(&mut self) -> &mut [f64] {
&mut self.genes
}
fn from_genes(genes: Vec<f64>) -> Result<Self, GenomeError> {
if genes.is_empty() {
return Err(GenomeError::InvalidStructure(
"Cannot create DynamicRealVector with empty genes".to_string(),
));
}
Ok(Self::with_defaults(genes))
}
fn apply_bounds(&mut self, bounds: &MultiBounds) {
for (i, gene) in self.genes.iter_mut().enumerate() {
if let Some(b) = bounds.get(i) {
*gene = gene.clamp(b.min, b.max);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dynamic_real_vector_creation() {
let genes = vec![1.0, 2.0, 3.0];
let genome = DynamicRealVector::new(genes.clone(), 1, 10).unwrap();
assert_eq!(genome.genes(), &genes[..]);
assert_eq!(genome.dimension(), 3);
}
#[test]
fn test_dynamic_real_vector_length_constraints() {
let result = DynamicRealVector::new(vec![1.0], 2, 10);
assert!(result.is_err());
let result = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 2);
assert!(result.is_err());
let result = DynamicRealVector::new(vec![1.0, 2.0], 5, 3);
assert!(result.is_err());
}
#[test]
fn test_push_pop() {
let mut genome = DynamicRealVector::new(vec![1.0, 2.0], 1, 5).unwrap();
genome.push(3.0).unwrap();
assert_eq!(genome.dimension(), 3);
assert_eq!(genome.genes()[2], 3.0);
let val = genome.pop().unwrap();
assert_eq!(val, 3.0);
assert_eq!(genome.dimension(), 2);
}
#[test]
fn test_push_pop_bounds() {
let mut genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 3, 3).unwrap();
assert!(genome.push(4.0).is_err());
assert!(genome.pop().is_err());
}
#[test]
fn test_insert_remove() {
let mut genome = DynamicRealVector::new(vec![1.0, 3.0], 1, 5).unwrap();
genome.insert(1, 2.0).unwrap();
assert_eq!(genome.genes(), &[1.0, 2.0, 3.0]);
let val = genome.remove(1).unwrap();
assert_eq!(val, 2.0);
assert_eq!(genome.genes(), &[1.0, 3.0]);
}
#[test]
fn test_can_grow_shrink() {
let genome = DynamicRealVector::new(vec![1.0, 2.0], 1, 3).unwrap();
assert!(genome.can_grow());
assert!(genome.can_shrink());
let genome_at_max = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 3).unwrap();
assert!(!genome_at_max.can_grow());
assert!(genome_at_max.can_shrink());
let genome_at_min = DynamicRealVector::new(vec![1.0], 1, 3).unwrap();
assert!(genome_at_min.can_grow());
assert!(!genome_at_min.can_shrink());
}
#[test]
#[cfg(feature = "ppl")]
fn test_trace_roundtrip() {
use crate::genome::trace_genome::TraceGenome;
let genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 2, 5).unwrap();
let trace = genome.to_trace();
let restored = DynamicRealVector::from_trace(&trace).unwrap();
assert_eq!(genome.genes(), restored.genes());
assert_eq!(genome.min_length(), restored.min_length());
assert_eq!(genome.max_length(), restored.max_length());
}
#[test]
fn test_distance() {
let g1 = DynamicRealVector::new(vec![0.0, 0.0], 1, 10).unwrap();
let g2 = DynamicRealVector::new(vec![3.0, 4.0], 1, 10).unwrap();
let dist = g1.distance(&g2);
assert!((dist - 5.0).abs() < 0.001);
let g3 = DynamicRealVector::new(vec![0.0, 0.0, 0.0], 1, 10).unwrap();
let dist_with_penalty = g1.distance(&g3);
assert!(dist_with_penalty > 0.0);
}
#[test]
fn test_generate_random() {
let bounds = MultiBounds::symmetric(5.0, 5);
let mut rng = rand::thread_rng();
let genome = DynamicRealVector::generate(&mut rng, &bounds);
assert!(genome.dimension() >= 1);
assert!(genome.dimension() <= 5);
for gene in genome.genes() {
assert!(*gene >= -5.0 && *gene <= 5.0);
}
}
#[test]
fn test_arithmetic_operations() {
let g1 = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 10).unwrap();
let g2 = DynamicRealVector::new(vec![4.0, 5.0, 6.0], 1, 10).unwrap();
let sum = g1.add(&g2).unwrap();
assert_eq!(sum.genes(), &[5.0, 7.0, 9.0]);
let diff = g2.sub(&g1).unwrap();
assert_eq!(diff.genes(), &[3.0, 3.0, 3.0]);
let scaled = g1.scale(2.0);
assert_eq!(scaled.genes(), &[2.0, 4.0, 6.0]);
}
#[test]
fn test_norm() {
let genome = DynamicRealVector::new(vec![3.0, 4.0], 1, 10).unwrap();
assert!((genome.norm() - 5.0).abs() < 0.001);
assert!((genome.norm_squared() - 25.0).abs() < 0.001);
}
#[test]
fn test_generate_empty_bounds_does_not_panic() {
let bounds = MultiBounds::new(vec![]);
let mut rng = rand::thread_rng();
let genome = DynamicRealVector::generate(&mut rng, &bounds);
assert_eq!(genome.dimension(), 0);
}
#[test]
fn test_try_generate_empty_bounds_errors() {
let bounds = MultiBounds::new(vec![]);
let mut rng = rand::thread_rng();
assert!(DynamicRealVector::try_generate(&mut rng, &bounds).is_err());
let ok_bounds = MultiBounds::symmetric(5.0, 4);
let g = DynamicRealVector::try_generate(&mut rng, &ok_bounds).unwrap();
assert!(g.dimension() >= 1 && g.dimension() <= 4);
}
#[test]
#[cfg(feature = "ppl")]
fn test_trace_prefix_matches_addresses() {
use crate::genome::trace_genome::TraceGenome;
use fugue::addr;
let genome = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 1, 5).unwrap();
let trace = genome.to_trace();
let prefix = DynamicRealVector::trace_prefix();
assert!(trace.get_f64(&addr!(prefix, 0)).is_some());
assert_eq!(trace.get_f64(&addr!(prefix, 0)), Some(1.0));
let restored = DynamicRealVector::from_trace(&trace).unwrap();
assert_eq!(restored.genes(), genome.genes());
}
#[test]
fn test_generate_with_len_explicit() {
let mut rng = rand::thread_rng();
let value_bounds = MultiBounds::symmetric(2.0, 6);
let g = DynamicRealVector::generate_with_len(&mut rng, 3, 5, &value_bounds).unwrap();
assert!(g.dimension() >= 3 && g.dimension() <= 5);
for gene in g.genes() {
assert!(*gene >= -2.0 && *gene <= 2.0);
}
assert!(DynamicRealVector::generate_with_len(&mut rng, 5, 3, &value_bounds).is_err());
assert!(
DynamicRealVector::generate_with_len(&mut rng, 1, 3, &MultiBounds::new(vec![]))
.is_err()
);
}
}