use crate::error::GaError;
use crate::traits::{GeneT, LinearChromosome};
use rand::Rng;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
pub fn erx<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(
"EdgeRecombination crossover requires DNA of length >= 2".to_string(),
));
}
let ids_p1: HashSet<i32> = parent_1.dna().iter().map(|g| g.id()).collect();
if ids_p1.len() != len {
return Err(GaError::CrossoverError(
"EdgeRecombination crossover requires unique gene IDs in parent_1 (permutation chromosomes only)".to_string(),
));
}
let ids_p2: HashSet<i32> = parent_2.dna().iter().map(|g| g.id()).collect();
if ids_p2.len() != len {
return Err(GaError::CrossoverError(
"EdgeRecombination crossover requires unique gene IDs in parent_2 (permutation chromosomes only)".to_string(),
));
}
if ids_p1 != ids_p2 {
return Err(GaError::CrossoverError(
"EdgeRecombination crossover requires both parents to be permutations of the same gene set".to_string(),
));
}
crate::log_debug!(target="crossover_events", method="edge_recombination"; "Starting ERX crossover");
let mut rng = crate::rng::make_rng();
let mut adj: HashMap<i32, HashSet<i32>> = HashMap::with_capacity(len);
for g in parent_1.dna() {
adj.entry(g.id()).or_default();
}
for parent_dna in [parent_1.dna(), parent_2.dna()] {
for i in 0..len {
let curr = parent_dna[i].id();
let left = parent_dna[(i + len - 1) % len].id();
let right = parent_dna[(i + 1) % len].id();
adj.entry(curr).or_default().insert(left);
adj.entry(curr).or_default().insert(right);
}
}
let gene_by_id: HashMap<i32, U::Gene> =
parent_1.dna().iter().map(|g| (g.id(), g.clone())).collect();
let all_ids: Vec<i32> = parent_1.dna().iter().map(|g| g.id()).collect();
let start_1 = parent_1.dna()[0].id();
let start_2 = parent_2.dna()[0].id();
let child_ids_1 = erx_build_child(start_1, &mut adj.clone(), &all_ids, &mut rng);
let child_ids_2 = erx_build_child(start_2, &mut adj, &all_ids, &mut rng);
let dna_1: Vec<U::Gene> = child_ids_1
.iter()
.map(|id| {
gene_by_id.get(id).cloned().ok_or_else(|| {
GaError::CrossoverError(format!("ERX: gene id {} not found in parent_1", id))
})
})
.collect::<Result<_, _>>()?;
let dna_2: Vec<U::Gene> = child_ids_2
.iter()
.map(|id| {
gene_by_id.get(id).cloned().ok_or_else(|| {
GaError::CrossoverError(format!("ERX: gene id {} not found in parent_1", id))
})
})
.collect::<Result<_, _>>()?;
let mut child_1 = U::new();
let mut child_2 = U::new();
child_1.set_dna(Cow::Owned(dna_1));
child_2.set_dna(Cow::Owned(dna_2));
crate::log_debug!(target="crossover_events", method="edge_recombination"; "ERX crossover finished");
Ok(vec![child_1, child_2])
}
fn erx_build_child(
start: i32,
adj: &mut HashMap<i32, HashSet<i32>>,
all_ids: &[i32],
rng: &mut impl Rng,
) -> Vec<i32> {
let n = all_ids.len();
let mut child = Vec::with_capacity(n);
let mut visited: HashSet<i32> = HashSet::with_capacity(n);
let mut current = start;
for _ in 0..n {
child.push(current);
visited.insert(current);
let neighbors_of_current = adj.remove(¤t).unwrap_or_default();
for nbr_id in &neighbors_of_current {
if let Some(set) = adj.get_mut(nbr_id) {
set.remove(¤t);
}
}
if visited.len() == n {
break;
}
let unvisited_neighbors: Vec<i32> = neighbors_of_current
.into_iter()
.filter(|id| !visited.contains(id))
.collect();
current = if unvisited_neighbors.is_empty() {
let remaining: Vec<i32> = all_ids
.iter()
.copied()
.filter(|id| !visited.contains(id))
.collect();
if remaining.is_empty() {
break;
}
remaining[rng.random_range(0..remaining.len())]
} else {
*unvisited_neighbors
.iter()
.min_by_key(|id| {
adj.get(id)
.map(|s| s.iter().filter(|n| !visited.contains(n)).count())
.unwrap_or(0)
})
.unwrap()
};
}
child
}