use genetic_algorithms::chromosomes::MultiUniqueChromosome;
use genetic_algorithms::genotypes::UniqueGenotype;
use genetic_algorithms::initializers::unique_random_initialization;
use genetic_algorithms::operations::crossover::multi_group_pmx::multi_group_pmx;
use genetic_algorithms::traits::LinearChromosome;
use std::borrow::Cow;
use std::collections::HashSet;
fn make_parent_1() -> MultiUniqueChromosome<i32> {
let mut c =
MultiUniqueChromosome::<i32>::new(vec![vec![0, 1, 2], vec![10, 20, 30], vec![100, 200]]);
let genes = vec![
UniqueGenotype::new(0, 0),
UniqueGenotype::new(1, 1),
UniqueGenotype::new(2, 2),
UniqueGenotype::new(3, 10),
UniqueGenotype::new(4, 20),
UniqueGenotype::new(5, 30),
UniqueGenotype::new(6, 100),
UniqueGenotype::new(7, 200),
];
c.set_dna(Cow::Owned(genes));
c
}
fn make_parent_2() -> MultiUniqueChromosome<i32> {
let mut c =
MultiUniqueChromosome::<i32>::new(vec![vec![0, 1, 2], vec![10, 20, 30], vec![100, 200]]);
let genes = vec![
UniqueGenotype::new(2, 2),
UniqueGenotype::new(1, 1),
UniqueGenotype::new(0, 0),
UniqueGenotype::new(5, 30),
UniqueGenotype::new(4, 20),
UniqueGenotype::new(3, 10),
UniqueGenotype::new(7, 200),
UniqueGenotype::new(6, 100),
];
c.set_dna(Cow::Owned(genes));
c
}
fn gene_value_set(slice: &[UniqueGenotype<i32>]) -> HashSet<i32> {
slice.iter().map(|g| g.value).collect()
}
fn gene_id_set(slice: &[UniqueGenotype<i32>]) -> HashSet<i32> {
slice.iter().map(|g| g.id).collect()
}
#[test]
fn multi_group_pmx_produces_two_children_correct_length() {
let p1 = make_parent_1();
let p2 = make_parent_2();
let children = multi_group_pmx(&p1, &p2).expect("multi_group_pmx should succeed");
assert_eq!(children.len(), 2, "PMX should produce exactly 2 children");
assert_eq!(children[0].dna().len(), 8, "child 0 should have 8 genes");
assert_eq!(children[1].dna().len(), 8, "child 1 should have 8 genes");
}
#[test]
fn multi_group_pmx_group0_gene_multiset_preserved() {
let p1 = make_parent_1();
let p2 = make_parent_2();
let children = multi_group_pmx(&p1, &p2).expect("multi_group_pmx should succeed");
let expected_values: HashSet<i32> = [0, 1, 2].into_iter().collect();
for (i, child) in children.iter().enumerate() {
let child_group0_values = gene_value_set(&child.dna()[0..=2]);
assert_eq!(
child_group0_values, expected_values,
"child {} group-0 gene values should be {{0, 1, 2}}; got {:?}",
i, child_group0_values
);
}
}
#[test]
fn multi_group_pmx_group1_gene_multiset_preserved() {
let p1 = make_parent_1();
let p2 = make_parent_2();
let children = multi_group_pmx(&p1, &p2).expect("multi_group_pmx should succeed");
let expected_values: HashSet<i32> = [10, 20, 30].into_iter().collect();
for (i, child) in children.iter().enumerate() {
let child_group1_values = gene_value_set(&child.dna()[3..=5]);
assert_eq!(
child_group1_values, expected_values,
"child {} group-1 gene values should be {{10, 20, 30}}; got {:?}",
i, child_group1_values
);
}
}
#[test]
fn multi_group_pmx_group2_gene_multiset_preserved() {
let p1 = make_parent_1();
let p2 = make_parent_2();
let children = multi_group_pmx(&p1, &p2).expect("multi_group_pmx should succeed");
let expected_values: HashSet<i32> = [100, 200].into_iter().collect();
for (i, child) in children.iter().enumerate() {
let child_group2_values = gene_value_set(&child.dna()[6..=7]);
assert_eq!(
child_group2_values, expected_values,
"child {} group-2 gene values should be {{100, 200}}; got {:?}",
i, child_group2_values
);
}
}
#[test]
fn multi_group_pmx_no_gene_crosses_group_boundary() {
let p1 = make_parent_1();
let p2 = make_parent_2();
let children = multi_group_pmx(&p1, &p2).expect("multi_group_pmx should succeed");
let group0_values: HashSet<i32> = [0, 1, 2].into_iter().collect();
let group1_values: HashSet<i32> = [10, 20, 30].into_iter().collect();
let group2_values: HashSet<i32> = [100, 200].into_iter().collect();
for (i, child) in children.iter().enumerate() {
let dna = child.dna();
for g in &dna[0..=2] {
assert!(
group0_values.contains(&g.value),
"child {} group-0 gene value {} is out of group-0 alphabet",
i,
g.value
);
}
for g in &dna[3..=5] {
assert!(
group1_values.contains(&g.value),
"child {} group-1 gene value {} is out of group-1 alphabet",
i,
g.value
);
}
for g in &dna[6..=7] {
assert!(
group2_values.contains(&g.value),
"child {} group-2 gene value {} is out of group-2 alphabet",
i,
g.value
);
}
}
}
#[test]
fn multi_group_pmx_children_have_unique_gene_ids_per_group() {
let p1 = make_parent_1();
let p2 = make_parent_2();
let children = multi_group_pmx(&p1, &p2).expect("multi_group_pmx should succeed");
for (ci, child) in children.iter().enumerate() {
let dna = child.dna();
let ids_g0 = gene_id_set(&dna[0..=2]);
assert_eq!(
ids_g0.len(),
3,
"child {} group-0 should have 3 unique gene ids",
ci
);
let ids_g1 = gene_id_set(&dna[3..=5]);
assert_eq!(
ids_g1.len(),
3,
"child {} group-1 should have 3 unique gene ids",
ci
);
let ids_g2 = gene_id_set(&dna[6..=7]);
assert_eq!(
ids_g2.len(),
2,
"child {} group-2 should have 2 unique gene ids",
ci
);
}
}
#[test]
fn multi_group_pmx_empty_groups_returns_error() {
let p1 = MultiUniqueChromosome::<i32>::default(); let p2 = MultiUniqueChromosome::<i32>::default();
let result = multi_group_pmx(&p1, &p2);
assert!(
matches!(
result,
Err(genetic_algorithms::error::GaError::ConfigurationError(_))
),
"Expected ConfigurationError for empty-groups PMX; got {:?}",
result
);
}
#[test]
fn multi_group_pmx_mismatched_group_structures_returns_error() {
let mut p1 = MultiUniqueChromosome::<i32>::new(vec![vec![0, 1, 2], vec![10, 20]]);
p1.set_dna(Cow::Owned(vec![
UniqueGenotype::new(0, 0),
UniqueGenotype::new(1, 1),
UniqueGenotype::new(2, 2),
UniqueGenotype::new(3, 10),
UniqueGenotype::new(4, 20),
]));
let mut p2 = MultiUniqueChromosome::<i32>::new(vec![vec![0, 1], vec![10, 20], vec![100]]);
p2.set_dna(Cow::Owned(vec![
UniqueGenotype::new(0, 0),
UniqueGenotype::new(1, 1),
UniqueGenotype::new(2, 10),
UniqueGenotype::new(3, 20),
UniqueGenotype::new(4, 100),
]));
let result = multi_group_pmx(&p1, &p2);
assert!(
matches!(
result,
Err(genetic_algorithms::error::GaError::ConfigurationError(_))
),
"Expected ConfigurationError for mismatched group structures; got {:?}",
result
);
}
#[test]
fn multi_group_pmx_stress_100_runs_group_membership_preserved() {
let alphabet_g0: Vec<i32> = (0..5).collect();
let alphabet_g1: Vec<i32> = (100..105).collect();
let alphabet_g2: Vec<i32> = (200..202).collect();
for _ in 0..100 {
let mut p1 = MultiUniqueChromosome::<i32>::new(vec![
alphabet_g0.clone(),
alphabet_g1.clone(),
alphabet_g2.clone(),
]);
let mut p2 = MultiUniqueChromosome::<i32>::new(vec![
alphabet_g0.clone(),
alphabet_g1.clone(),
alphabet_g2.clone(),
]);
let mut all_dna_p1 = unique_random_initialization(&alphabet_g0);
all_dna_p1.extend(unique_random_initialization(&alphabet_g1));
all_dna_p1.extend(unique_random_initialization(&alphabet_g2));
let mut all_dna_p2 = unique_random_initialization(&alphabet_g0);
all_dna_p2.extend(unique_random_initialization(&alphabet_g1));
all_dna_p2.extend(unique_random_initialization(&alphabet_g2));
p1.set_dna(Cow::Owned(all_dna_p1));
p2.set_dna(Cow::Owned(all_dna_p2));
let children = multi_group_pmx(&p1, &p2).expect("multi_group_pmx should succeed");
let set_g0: HashSet<i32> = alphabet_g0.iter().cloned().collect();
let set_g1: HashSet<i32> = alphabet_g1.iter().cloned().collect();
let set_g2: HashSet<i32> = alphabet_g2.iter().cloned().collect();
for child in &children {
let dna = child.dna();
let vals_g0 = gene_value_set(&dna[0..5]);
assert_eq!(vals_g0, set_g0, "group-0 multiset mismatch in child");
let vals_g1 = gene_value_set(&dna[5..10]);
assert_eq!(vals_g1, set_g1, "group-1 multiset mismatch in child");
let vals_g2 = gene_value_set(&dna[10..12]);
assert_eq!(vals_g2, set_g2, "group-2 multiset mismatch in child");
}
}
}