use crate::models::{Gene, Morphology};
use serde::{Deserialize, Serialize};
use tracing::instrument;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Distribution {
LatinHypercube {
population_size: u32,
},
Random {
population_size: u32,
},
}
impl Distribution {
pub fn latin_hypercube(population_size: u32) -> Self {
Distribution::LatinHypercube { population_size }
}
pub fn random(population_size: u32) -> Self {
Distribution::Random { population_size }
}
#[instrument(level = "debug", skip(self, morphology), fields(distribution = ?self, morphology_dimensions = morphology.gene_bounds.len()))]
pub(crate) fn distribute(&self, morphology: &Morphology) -> Vec<Vec<Gene>> {
match self {
Distribution::LatinHypercube { population_size } => {
latin_hypercube(*population_size as usize, morphology)
}
Distribution::Random { population_size } => {
random_distribution(*population_size as usize, morphology)
}
}
}
}
#[instrument(level = "debug", skip(morphology), fields(n_samples = n_samples, n_dimensions = morphology.gene_bounds.len()))]
fn random_distribution(n_samples: usize, morphology: &Morphology) -> Vec<Vec<Gene>> {
let mut genomes = Vec::with_capacity(n_samples);
for _ in 0..n_samples {
let genome = morphology.random();
genomes.push(genome);
}
genomes
}
#[instrument(level = "debug", skip(morphology), fields(n_samples = n_samples, n_dimensions = morphology.gene_bounds.len()))]
fn latin_hypercube(n_samples: usize, morphology: &Morphology) -> Vec<Vec<Gene>> {
use rand::seq::SliceRandom;
let n_dimensions = morphology.gene_bounds.len();
let mut rng = rand::rng();
let mut genomes = Vec::with_capacity(n_samples);
for dim_idx in 0..n_dimensions {
let gene_bound = &morphology.gene_bounds[dim_idx];
let mut intervals: Vec<f64> = (0..n_samples)
.map(|i| (i as f64 + 0.5) / n_samples as f64) .collect();
intervals.shuffle(&mut rng);
let gene_values: Vec<Gene> = intervals
.iter()
.map(|&sample| gene_bound.from_sample(sample))
.collect();
for (genome_idx, &gene_value) in gene_values.iter().enumerate() {
if dim_idx == 0 {
genomes.push(vec![gene_value]);
} else {
genomes[genome_idx].push(gene_value);
}
}
}
genomes
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::GeneBounds;
fn create_test_morphology(gene_bounds: Vec<GeneBounds>) -> Morphology {
Morphology::new("test", 1, gene_bounds)
}
#[test]
fn test_latin_hypercube_2d_simple() {
let morphology = create_test_morphology(vec![
GeneBounds::integer(0, 3, 4).unwrap(), GeneBounds::integer(0, 3, 4).unwrap(), ]);
let dist = Distribution::latin_hypercube(4);
let genomes = dist.distribute(&morphology);
assert_eq!(genomes.len(), 4);
assert!(genomes.iter().all(|genome| genome.len() == 2));
let dim1: Vec<i64> = genomes.iter().map(|g| g[0]).collect();
let dim2: Vec<i64> = genomes.iter().map(|g| g[1]).collect();
let mut dim1_sorted = dim1.clone();
dim1_sorted.sort();
assert_eq!(dim1_sorted, vec![0, 1, 2, 3]);
let mut dim2_sorted = dim2.clone();
dim2_sorted.sort();
assert_eq!(dim2_sorted, vec![0, 1, 2, 3]);
}
#[test]
fn test_latin_hypercube_3d_simple() {
let morphology = create_test_morphology(vec![
GeneBounds::integer(0, 2, 3).unwrap(), GeneBounds::integer(0, 2, 3).unwrap(), GeneBounds::integer(0, 2, 3).unwrap(), ]);
let dist = Distribution::latin_hypercube(3);
let genomes = dist.distribute(&morphology);
assert_eq!(genomes.len(), 3);
assert!(genomes.iter().all(|genome| genome.len() == 3));
for dim in 0..3 {
let mut values: Vec<i64> = genomes.iter().map(|g| g[dim]).collect();
values.sort();
assert_eq!(values, vec![0, 1, 2]);
}
}
#[test]
fn test_latin_hypercube_single_sample() {
let morphology = create_test_morphology(vec![
GeneBounds::integer(0, 9, 10).unwrap(),
GeneBounds::integer(0, 9, 10).unwrap(),
]);
let dist = Distribution::latin_hypercube(1);
let genomes = dist.distribute(&morphology);
assert_eq!(genomes.len(), 1);
assert_eq!(genomes[0].len(), 2);
assert!((0..=9).contains(&genomes[0][0]));
assert!((0..=9).contains(&genomes[0][1]));
}
#[test]
fn test_distribution_distribute_random() {
let morphology = create_test_morphology(vec![
GeneBounds::integer(0, 5000, 1000).unwrap(),
GeneBounds::integer(0, 3000, 1000).unwrap(),
]);
let dist = Distribution::random(5);
let genomes1 = dist.distribute(&morphology);
let genomes2 = dist.distribute(&morphology);
assert_eq!(genomes1.len(), 5);
assert!(genomes1.iter().all(|genome| genome.len() == 2));
assert_ne!(genomes1, genomes2);
}
}