use crate::error::GaError;
use crate::traits::{GeneT, LinearChromosome};
use rand::Rng;
use std::borrow::Cow;
use std::collections::HashMap;
pub fn pmx<U: LinearChromosome>(parent_1: &U, parent_2: &U) -> Result<Vec<U>, GaError> {
let len = parent_1.dna().len();
if len != parent_2.dna().len() {
return Err(GaError::CrossoverError(format!(
"Parents must have the same DNA length. Parent 1: {}, Parent 2: {}",
len,
parent_2.dna().len()
)));
}
if len < 2 {
return Err(GaError::CrossoverError(
"PMX crossover requires DNA of length >= 2".to_string(),
));
}
crate::log_debug!(target="crossover_events", method="pmx"; "Starting PMX crossover");
let mut rng = crate::rng::make_rng();
let mut start = rng.random_range(0..len);
let mut end = rng.random_range(0..len);
while start == end {
end = rng.random_range(0..len);
}
if start > end {
std::mem::swap(&mut start, &mut end);
}
let child_dna_1 = pmx_build_child(parent_1.dna(), parent_2.dna(), start, end);
let child_dna_2 = pmx_build_child(parent_2.dna(), parent_1.dna(), start, end);
let mut child_1 = U::new();
let mut child_2 = U::new();
child_1.set_dna(Cow::Owned(child_dna_1));
child_2.set_dna(Cow::Owned(child_dna_2));
crate::log_debug!(target="crossover_events", method="pmx"; "PMX crossover finished with points ({}, {})", start, end);
Ok(vec![child_1, child_2])
}
pub(crate) fn pmx_build_child<G: GeneT>(
donor: &[G],
other: &[G],
start: usize,
end: usize,
) -> Vec<G> {
let mut child = other.to_vec();
child[start..=end].clone_from_slice(&donor[start..=end]);
let segment_ids: HashMap<i32, usize> = (start..=end).map(|i| (donor[i].id(), i)).collect();
let pos_in_other: HashMap<i32, usize> =
other.iter().enumerate().map(|(i, g)| (g.id(), i)).collect();
for i in start..=end {
let gene = &other[i];
if segment_ids.contains_key(&gene.id()) {
continue;
}
let mut mapped_id = donor[i].id();
loop {
let pos = *pos_in_other
.get(&mapped_id)
.expect("PMX: gene ID not found in other parent — parents must be permutations");
if pos < start || pos > end {
child[pos] = gene.clone();
break;
}
mapped_id = donor[pos].id();
}
}
child
}