use crate::core::{
context::Context, individual::Individual, offspring::Offspring, population::Population,
state::State,
};
use crate::fitness::Maximize;
use crate::operators::GeneticOperator;
use crate::operators::sequential::combinator::{Combine, Fill, Pipeline, Repeat, Weighted};
use crate::operators::sequential::crossover::SinglePoint;
use crate::operators::sequential::mutation::RandomReset;
use crate::operators::sequential::selection::Elitism;
use crate::operators::sequential::selection::TournamentSelection;
use std::num::NonZero;
fn id(g: &[i32; 4]) -> i32 {
g.iter().sum()
}
fn make_state(genomes: &[[i32; 4]]) -> State<[i32; 4], i32> {
let pop: Population<[i32; 4], i32> = genomes.iter().map(|g| Individual::new(*g)).collect();
State::new(pop, 0)
}
fn make_ctx(
rng: &mut impl rand::Rng,
) -> Context<
'_,
fn(&[i32; 4]) -> i32,
impl rand::Rng + '_,
impl crate::fitness::FitnessComparator<i32> + '_,
> {
#[cfg(feature = "parallel")]
{
use std::sync::LazyLock;
static RUNTIME: LazyLock<pooled::Runtime> = LazyLock::new(|| pooled::Runtime::new(1));
Context::new(&(id as fn(&[i32; 4]) -> i32), rng, &Maximize, &RUNTIME)
}
#[cfg(not(feature = "parallel"))]
Context::new(&(id as fn(&[i32; 4]) -> i32), rng, &Maximize)
}
#[test]
fn tournament_selection_returns_single() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let sel = TournamentSelection::new(NonZero::new(2).unwrap());
assert_eq!(sel.apply(&state, &mut ctx).num_offspring(), 1);
}
#[test]
fn random_reset_single_individual_returns_single() {
let state = make_state(&[[1, 2, 3, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = RandomReset::<i32>::new();
let offspring = op.apply(&state, &mut ctx);
assert_eq!(offspring.num_offspring(), 1);
assert!(matches!(offspring, Offspring::Single(_)));
}
#[test]
fn random_reset_multiple_individuals_returns_multiple() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = RandomReset::<i32>::new();
let offspring = op.apply(&state, &mut ctx);
assert_eq!(offspring.num_offspring(), 2);
assert!(matches!(offspring, Offspring::Multiple(_)));
}
#[test]
fn single_point_crossover_single_pair() {
let state = make_state(&[[1, 1, 1, 1], [2, 2, 2, 2]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = SinglePoint::<i32>::new();
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 2);
}
#[test]
fn single_point_crossover_even_population() {
let state = make_state(&[[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = SinglePoint::<i32>::new();
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 4);
}
#[test]
fn single_point_crossover_odd_drops_remainder() {
let state = make_state(&[[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = SinglePoint::<i32>::new();
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 2);
}
#[test]
fn single_point_crossover_children_contain_parent_genes() {
let p1 = [0i32, 0, 0, 0];
let p2 = [1i32, 1, 1, 1];
let state = make_state(&[p1, p2]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = SinglePoint::<i32>::new();
let pop = op.apply(&state, &mut ctx).into_population();
for ind in &pop {
for gene in ind.genome() {
assert!(*gene == 0 || *gene == 1);
}
}
}
#[test]
fn repeat_applies_n_times() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Repeat::new(RandomReset::<i32>::new(), 3);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 6);
}
#[test]
fn repeat_zero_produces_empty() {
let state = make_state(&[[1, 2, 3, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Repeat::new(RandomReset::<i32>::new(), 0);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 0);
}
#[test]
fn combine_merges_outputs() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Combine::new((RandomReset::<i32>::new(), RandomReset::<i32>::new()));
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 4);
}
#[test]
fn combine_single_element_tuple() {
let state = make_state(&[[1, 2, 3, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Combine::new((RandomReset::<i32>::new(),));
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 1);
}
#[test]
fn fill_from_population_size() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Fill::from_population_size(RandomReset::<i32>::new());
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 3);
}
#[test]
fn fill_from_fixed_size() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Fill::from_fixed_size(RandomReset::<i32>::new(), 7);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 7);
}
#[test]
fn fill_does_not_overshoot() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Fill::from_fixed_size(RandomReset::<i32>::new(), 5);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 5);
}
#[test]
fn weighted_picks_one_operator() {
let state = make_state(&[[1, 2, 3, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Weighted::new((
(RandomReset::<i32>::new(), NonZero::new(1u16).unwrap()),
(RandomReset::<i32>::new(), NonZero::new(1u16).unwrap()),
));
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 1);
}
#[test]
fn pipeline_single_operator() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Pipeline::new((RandomReset::<i32>::new(),));
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 2);
}
#[test]
fn pipeline_chains_operators() {
let state = make_state(&[[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Pipeline::new((SinglePoint::<i32>::new(), RandomReset::<i32>::new()));
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 4);
}
#[test]
fn elitism_default_returns_single_best() {
let state = make_state(&[[1, 0, 0, 0], [2, 0, 0, 0], [3, 0, 0, 0]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let elite = Elitism::default();
let offspring = elite.apply(&state, &mut ctx);
assert_eq!(offspring.num_offspring(), 1);
match offspring {
Offspring::Single(ind) => assert_eq!(*ind.fitness(&id), 3),
_ => panic!("expected Single"),
}
}
#[test]
fn elitism_returns_best_n() {
let state = make_state(&[
[1, 0, 0, 0],
[5, 0, 0, 0],
[3, 0, 0, 0],
[4, 0, 0, 0],
[2, 0, 0, 0],
]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let elite = Elitism::new(NonZero::new(3).unwrap());
let offspring = elite.apply(&state, &mut ctx);
assert_eq!(offspring.num_offspring(), 3);
let pop: Population<_, _> = offspring.into();
let mut fitnesses: Vec<i32> = pop.iter().map(|i| *i.fitness(&id)).collect();
fitnesses.sort();
assert_eq!(fitnesses, vec![3, 4, 5]);
}
#[test]
fn combine_vec_merges_outputs() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Combine::new(vec![
RandomReset::<i32>::new(),
RandomReset::<i32>::new(),
RandomReset::<i32>::new(),
]);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 6);
}
#[test]
fn pipeline_vec_chains_operators() {
let state = make_state(&[[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Pipeline::new(vec![RandomReset::<i32>::new(), RandomReset::<i32>::new()]);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 4);
}
#[test]
fn weighted_vec_picks_one_operator() {
let state = make_state(&[[1, 2, 3, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let op = Weighted::new(vec![
(RandomReset::<i32>::new(), NonZero::new(1u16).unwrap()),
(RandomReset::<i32>::new(), NonZero::new(1u16).unwrap()),
]);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 1);
}
#[test]
fn combine_boxed_slice_merges_outputs() {
let state = make_state(&[[1, 2, 3, 4], [5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let ops: Box<[RandomReset<i32>]> =
vec![RandomReset::new(), RandomReset::new()].into_boxed_slice();
let op = Combine::new(ops);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 4);
}
#[test]
fn pipeline_boxed_slice_chains_operators() {
let state = make_state(&[[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let ops: Box<[RandomReset<i32>]> =
vec![RandomReset::new(), RandomReset::new()].into_boxed_slice();
let op = Pipeline::new(ops);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 4);
}
#[test]
fn weighted_boxed_slice_picks_one_operator() {
let state = make_state(&[[1, 2, 3, 4]]);
let mut rng = rand::rng();
let mut ctx = make_ctx(&mut rng);
let ops: Box<[(RandomReset<i32>, NonZero<u16>)]> = vec![
(RandomReset::new(), NonZero::new(1u16).unwrap()),
(RandomReset::new(), NonZero::new(1u16).unwrap()),
]
.into_boxed_slice();
let op = Weighted::new(ops);
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 1);
}
fn id_vec(g: &Vec<i32>) -> i32 {
g.iter().sum()
}
fn make_state_vec(genomes: &[Vec<i32>]) -> State<Vec<i32>, i32> {
let pop: Population<Vec<i32>, i32> =
genomes.iter().map(|g| Individual::new(g.clone())).collect();
State::new(pop, 0)
}
fn make_ctx_vec(
rng: &mut impl rand::Rng,
) -> Context<
'_,
fn(&Vec<i32>) -> i32,
impl rand::Rng + '_,
impl crate::fitness::FitnessComparator<i32> + '_,
> {
#[cfg(feature = "parallel")]
{
use std::sync::LazyLock;
static RUNTIME: LazyLock<pooled::Runtime> = LazyLock::new(|| pooled::Runtime::new(1));
Context::new(&(id_vec as fn(&Vec<i32>) -> i32), rng, &Maximize, &RUNTIME)
}
#[cfg(not(feature = "parallel"))]
Context::new(&(id_vec as fn(&Vec<i32>) -> i32), rng, &Maximize)
}
#[test]
fn single_point_crossover_vec_single_pair() {
let state = make_state_vec(&[vec![1, 1, 1, 1], vec![2, 2, 2, 2]]);
let mut rng = rand::rng();
let mut ctx = make_ctx_vec(&mut rng);
let op = SinglePoint::<i32>::new();
assert_eq!(op.apply(&state, &mut ctx).num_offspring(), 2);
}
#[test]
fn single_point_crossover_vec_children_contain_parent_genes() {
let state = make_state_vec(&[vec![0, 0, 0, 0], vec![1, 1, 1, 1]]);
let mut rng = rand::rng();
let mut ctx = make_ctx_vec(&mut rng);
let op = SinglePoint::<i32>::new();
let pop = op.apply(&state, &mut ctx).into_population();
for ind in &pop {
for gene in ind.genome() {
assert!(*gene == 0 || *gene == 1);
}
}
}
#[test]
fn single_point_crossover_vec_variable_length_parents() {
let state = make_state_vec(&[vec![1, 2, 3], vec![4, 5, 6, 7, 8]]);
let mut rng = rand::rng();
let mut ctx = make_ctx_vec(&mut rng);
let op = SinglePoint::<i32>::new();
let pop = op.apply(&state, &mut ctx).into_population();
assert_eq!(pop.len(), 2);
let total: usize = pop.iter().map(|i| i.genome().len()).sum();
assert_eq!(total, 8); }
#[test]
fn segment_duplication_increases_length() {
use crate::operators::sequential::mutation::duplication::SegmentDuplication;
let genomes: Vec<Vec<i32>> = vec![vec![1, 2, 3, 4, 5]];
let pop: Population<Vec<i32>, i32> = genomes.into_iter().map(Individual::new).collect();
let state = State::new(pop, 0);
let mut rng = rand::rng();
fn eval(g: &Vec<i32>) -> i32 {
g.iter().sum()
}
#[cfg(feature = "parallel")]
let mut ctx = {
use std::sync::LazyLock;
static RT: LazyLock<pooled::Runtime> = LazyLock::new(|| pooled::Runtime::new(1));
Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize, &RT)
};
#[cfg(not(feature = "parallel"))]
let mut ctx = Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize);
let op = SegmentDuplication::<i32>::new(0.5, 10);
let offspring = op.apply(&state, &mut ctx);
let pop = offspring.into_population();
assert_eq!(pop.len(), 1);
assert!(pop.as_slice()[0].genome().len() > 5);
}
#[test]
fn segment_duplication_skips_when_exceeds_max() {
use crate::operators::sequential::mutation::duplication::SegmentDuplication;
let genomes: Vec<Vec<i32>> = vec![vec![1, 2, 3, 4, 5]];
let pop: Population<Vec<i32>, i32> = genomes.into_iter().map(Individual::new).collect();
let state = State::new(pop, 0);
let mut rng = rand::rng();
fn eval(g: &Vec<i32>) -> i32 {
g.iter().sum()
}
#[cfg(feature = "parallel")]
let mut ctx = {
use std::sync::LazyLock;
static RT: LazyLock<pooled::Runtime> = LazyLock::new(|| pooled::Runtime::new(1));
Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize, &RT)
};
#[cfg(not(feature = "parallel"))]
let mut ctx = Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize);
let op = SegmentDuplication::<i32>::new(0.5, 5);
let offspring = op.apply(&state, &mut ctx);
let pop = offspring.into_population();
assert_eq!(pop.as_slice()[0].genome().len(), 5);
}
#[test]
fn segment_deletion_decreases_length() {
use crate::operators::sequential::mutation::deletion::SegmentDeletion;
let genomes: Vec<Vec<i32>> = vec![vec![1, 2, 3, 4, 5, 6, 7, 8]];
let pop: Population<Vec<i32>, i32> = genomes.into_iter().map(Individual::new).collect();
let state = State::new(pop, 0);
let mut rng = rand::rng();
fn eval(g: &Vec<i32>) -> i32 {
g.iter().sum()
}
#[cfg(feature = "parallel")]
let mut ctx = {
use std::sync::LazyLock;
static RT: LazyLock<pooled::Runtime> = LazyLock::new(|| pooled::Runtime::new(1));
Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize, &RT)
};
#[cfg(not(feature = "parallel"))]
let mut ctx = Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize);
let op = SegmentDeletion::<i32>::new(0.5, 2);
let offspring = op.apply(&state, &mut ctx);
let pop = offspring.into_population();
assert_eq!(pop.len(), 1);
assert!(pop.as_slice()[0].genome().len() < 8);
}
#[test]
fn segment_deletion_skips_when_at_min_len() {
use crate::operators::sequential::mutation::deletion::SegmentDeletion;
let genomes: Vec<Vec<i32>> = vec![vec![1, 2, 3]];
let pop: Population<Vec<i32>, i32> = genomes.into_iter().map(Individual::new).collect();
let state = State::new(pop, 0);
let mut rng = rand::rng();
fn eval(g: &Vec<i32>) -> i32 {
g.iter().sum()
}
#[cfg(feature = "parallel")]
let mut ctx = {
use std::sync::LazyLock;
static RT: LazyLock<pooled::Runtime> = LazyLock::new(|| pooled::Runtime::new(1));
Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize, &RT)
};
#[cfg(not(feature = "parallel"))]
let mut ctx = Context::new(&(eval as fn(&Vec<i32>) -> i32), &mut rng, &Maximize);
let op = SegmentDeletion::<i32>::new(0.5, 3);
let offspring = op.apply(&state, &mut ctx);
let pop = offspring.into_population();
assert_eq!(pop.as_slice()[0].genome().len(), 3);
}