use burn::tensor::{Int, Tensor, TensorData, backend::Backend};
use rand::{Rng, RngExt};
use rlevo_core::probability::Probability;
use rlevo_core::rate::NonNegativeRate;
fn unit_uniform_rows(n: usize, d: usize, rng: &mut dyn Rng) -> Vec<f32> {
let mut rows = Vec::with_capacity(n * d);
for _ in 0..n * d {
rows.push(rng.random::<f32>());
}
rows
}
#[must_use]
pub fn blx_alpha<B: Backend>(
parent_a: Tensor<B, 2>,
parent_b: Tensor<B, 2>,
alpha: NonNegativeRate,
rng: &mut dyn Rng,
device: &<B as burn::tensor::backend::BackendTypes>::Device,
) -> Tensor<B, 2> {
assert_eq!(
parent_a.dims(),
parent_b.dims(),
"BLX-α: parents must have identical shapes"
);
let [n, d] = parent_a.dims();
let alpha = alpha.get();
let min = parent_a.clone().min_pair(parent_b.clone());
let max = parent_a.max_pair(parent_b);
let diff = max.clone() - min.clone();
let lo = min - diff.clone().mul_scalar(alpha);
let hi = max + diff.mul_scalar(alpha);
let u = Tensor::<B, 2>::from_data(
TensorData::new(unit_uniform_rows(n, d, rng), [n, d]),
device,
);
lo.clone() + u * (hi - lo)
}
#[must_use]
pub fn uniform_crossover<B: Backend>(
parent_a: Tensor<B, 2>,
parent_b: Tensor<B, 2>,
p: Probability,
rng: &mut dyn Rng,
device: &<B as burn::tensor::backend::BackendTypes>::Device,
) -> Tensor<B, 2> {
assert_eq!(
parent_a.dims(),
parent_b.dims(),
"uniform crossover: parents must have identical shapes"
);
let [n, d] = parent_a.dims();
let u = Tensor::<B, 2>::from_data(
TensorData::new(unit_uniform_rows(n, d, rng), [n, d]),
device,
);
let keep_a = u.lower_elem(p.get());
parent_a.mask_where(keep_a.bool_not(), parent_b)
}
#[must_use]
pub fn binary_uniform_crossover<B: Backend>(
parent_a: Tensor<B, 2, Int>,
parent_b: Tensor<B, 2, Int>,
p: Probability,
rng: &mut dyn Rng,
device: &<B as burn::tensor::backend::BackendTypes>::Device,
) -> Tensor<B, 2, Int> {
assert_eq!(
parent_a.dims(),
parent_b.dims(),
"binary uniform crossover: parents must have identical shapes"
);
let [n, d] = parent_a.dims();
let u = Tensor::<B, 2>::from_data(
TensorData::new(unit_uniform_rows(n, d, rng), [n, d]),
device,
);
let keep_a = u.lower_elem(p.get());
parent_a.mask_where(keep_a.bool_not(), parent_b)
}
#[cfg(test)]
mod tests {
use super::*;
use burn::backend::{Flex, flex::FlexDevice};
#[allow(unused_imports)]
use burn::tensor::backend::Backend as _;
use rand::SeedableRng;
use rand::rngs::StdRng;
type TestBackend = Flex;
#[test]
fn blx_alpha_lies_between_bounds() {
let device: FlexDevice = Default::default();
let mut rng = StdRng::seed_from_u64(13);
let a = Tensor::<TestBackend, 2>::from_data(
TensorData::new(vec![0.0_f32, 0.0, 0.0, 0.0], [2, 2]),
&device,
);
let b = Tensor::<TestBackend, 2>::from_data(
TensorData::new(vec![1.0_f32, 1.0, 1.0, 1.0], [2, 2]),
&device,
);
let c = blx_alpha(a, b, NonNegativeRate::new(0.0), &mut rng, &device);
let values = c
.into_data()
.into_vec::<f32>()
.expect("genome host-read of a tensor this test just built");
for v in values {
assert!((0.0..=1.0).contains(&v), "value out of bounds: {v}");
}
}
#[test]
fn uniform_all_from_a_when_p_is_one() {
let device: FlexDevice = Default::default();
let mut rng = StdRng::seed_from_u64(5);
let a = Tensor::<TestBackend, 2>::from_data(
TensorData::new(vec![7.0_f32, 7.0, 7.0, 7.0], [2, 2]),
&device,
);
let b = Tensor::<TestBackend, 2>::from_data(
TensorData::new(vec![-7.0_f32, -7.0, -7.0, -7.0], [2, 2]),
&device,
);
let c = uniform_crossover(a, b, Probability::new(1.0), &mut rng, &device);
let values = c
.into_data()
.into_vec::<f32>()
.expect("genome host-read of a tensor this test just built");
for v in values {
approx::assert_relative_eq!(v, 7.0, epsilon = 1e-6);
}
}
#[test]
fn uniform_all_from_b_when_p_is_zero() {
let device: FlexDevice = Default::default();
let mut rng = StdRng::seed_from_u64(5);
let a = Tensor::<TestBackend, 2>::from_data(
TensorData::new(vec![7.0_f32, 7.0, 7.0, 7.0], [2, 2]),
&device,
);
let b = Tensor::<TestBackend, 2>::from_data(
TensorData::new(vec![-7.0_f32, -7.0, -7.0, -7.0], [2, 2]),
&device,
);
let c = uniform_crossover(a, b, Probability::new(0.0), &mut rng, &device);
let values = c
.into_data()
.into_vec::<f32>()
.expect("genome host-read of a tensor this test just built");
for v in values {
approx::assert_relative_eq!(v, -7.0, epsilon = 1e-6);
}
}
#[test]
fn nan_and_inf_rates_are_unconstructable() {
assert!(Probability::try_new(f32::NAN).is_err());
assert!(Probability::try_new(1.5).is_err());
assert!(NonNegativeRate::try_new(f32::NAN).is_err());
assert!(NonNegativeRate::try_new(f32::INFINITY).is_err());
assert!(NonNegativeRate::try_new(-1.0).is_err());
}
}