use burn::tensor::{Int, Tensor, backend::Backend};
use rlevo_core::config::{self, ConfigError};
use crate::genome::{Binary, Integer, Permutation, Real, TensorGenome};
#[derive(Debug, Clone)]
pub struct Population<B: Backend, K: TensorGenome> {
pop_size: usize,
genome_dim: usize,
tensor: K::Tensor<B>,
}
impl<B: Backend, K: TensorGenome> Population<B, K> {
#[must_use]
pub fn pop_size(&self) -> usize {
self.pop_size
}
#[must_use]
pub fn genome_dim(&self) -> usize {
self.genome_dim
}
#[must_use]
pub fn tensor(&self) -> &K::Tensor<B> {
&self.tensor
}
#[must_use]
pub fn into_tensor(self) -> K::Tensor<B> {
self.tensor
}
}
impl<B: Backend> Population<B, Real> {
pub fn new_real(tensor: Tensor<B, 2>) -> Result<Self, ConfigError> {
let dims = tensor.dims();
config::nonzero("Population", "pop_size", dims[0])?;
config::nonzero("Population", "genome_dim", dims[1])?;
Ok(Self {
pop_size: dims[0],
genome_dim: dims[1],
tensor,
})
}
}
impl<B: Backend> Population<B, Binary> {
pub fn new_binary(tensor: Tensor<B, 2, Int>) -> Result<Self, ConfigError> {
let dims = tensor.dims();
config::nonzero("Population", "pop_size", dims[0])?;
config::nonzero("Population", "genome_dim", dims[1])?;
Ok(Self {
pop_size: dims[0],
genome_dim: dims[1],
tensor,
})
}
}
impl<B: Backend> Population<B, Integer> {
pub fn new_integer(tensor: Tensor<B, 2, Int>) -> Result<Self, ConfigError> {
let dims = tensor.dims();
config::nonzero("Population", "pop_size", dims[0])?;
config::nonzero("Population", "genome_dim", dims[1])?;
Ok(Self {
pop_size: dims[0],
genome_dim: dims[1],
tensor,
})
}
}
impl<B: Backend> Population<B, Permutation> {
pub fn new_permutation(tensor: Tensor<B, 2, Int>) -> Result<Self, ConfigError> {
let dims = tensor.dims();
config::nonzero("Population", "pop_size", dims[0])?;
config::nonzero("Population", "genome_dim", dims[1])?;
Ok(Self {
pop_size: dims[0],
genome_dim: dims[1],
tensor,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use burn::backend::Flex;
use burn::tensor::TensorData;
type TestBackend = Flex;
#[test]
fn real_population_reports_shape() {
let device = Default::default();
let data = TensorData::new(vec![1.0f32, 2.0, 3.0, 4.0], [2, 2]);
let tensor = Tensor::<TestBackend, 2>::from_data(data, &device);
let pop = Population::<TestBackend, Real>::new_real(tensor).unwrap();
assert_eq!(pop.pop_size(), 2);
assert_eq!(pop.genome_dim(), 2);
assert_eq!(pop.tensor().dims(), [2, 2]);
}
#[test]
fn binary_population_uses_int_tensor() {
let device = Default::default();
let data = TensorData::new(vec![0i64, 1, 1, 0, 1, 0], [2, 3]);
let tensor = Tensor::<TestBackend, 2, Int>::from_data(data, &device);
let pop = Population::<TestBackend, Binary>::new_binary(tensor).unwrap();
assert_eq!(pop.pop_size(), 2);
assert_eq!(pop.genome_dim(), 3);
}
#[test]
fn permutation_population_reports_shape() {
let device = Default::default();
let data = TensorData::new(vec![0i64, 1, 2, 3, 2, 0, 3, 1], [2, 4]);
let tensor = Tensor::<TestBackend, 2, Int>::from_data(data, &device);
let pop = Population::<TestBackend, Permutation>::new_permutation(tensor).unwrap();
assert_eq!(pop.pop_size(), 2);
assert_eq!(pop.genome_dim(), 4);
}
#[test]
fn new_real_rejects_zero_rows() {
let device = Default::default();
let data = TensorData::new(Vec::<f32>::new(), [0, 3]);
let tensor = Tensor::<TestBackend, 2>::from_data(data, &device);
let err = Population::<TestBackend, Real>::new_real(tensor).unwrap_err();
assert_eq!(err.field, "pop_size");
}
#[test]
fn new_real_rejects_zero_width() {
let device = Default::default();
let data = TensorData::new(Vec::<f32>::new(), [3, 0]);
let tensor = Tensor::<TestBackend, 2>::from_data(data, &device);
let err = Population::<TestBackend, Real>::new_real(tensor).unwrap_err();
assert_eq!(err.field, "genome_dim");
}
#[test]
fn population_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Population<TestBackend, Real>>();
assert_send_sync::<Population<TestBackend, Permutation>>();
}
}