use crate::chromosomes::Range as RangeChromosome;
use crate::error::GaError;
use crate::operations::crossover::sbx::SbxConvertible;
use crate::traits::LinearChromosome;
use rand::Rng;
use std::borrow::Cow;
use std::fmt::Debug;
pub fn pcx<T>(
parents: &[&RangeChromosome<T>],
_num_parents: usize,
sigma_eta_override: Option<f64>,
sigma_zeta_override: Option<f64>,
) -> Result<Vec<RangeChromosome<T>>, GaError>
where
T: Sync + Send + Clone + Default + Debug + PartialOrd + Copy + 'static + SbxConvertible,
{
crate::log_debug!(target: "crossover_events", method = "pcx"; "Starting PCX crossover with {} parents", parents.len());
if parents.len() < 3 {
return Err(GaError::CrossoverError(
"PCX requires at least 3 parents".to_string(),
));
}
let expected = parents[0].dna().len();
for (idx, p) in parents.iter().enumerate().skip(1) {
let actual = p.dna().len();
if actual != expected {
return Err(GaError::CrossoverError(format!(
"All parents must have the same DNA length. Expected {}, got {} (parent {})",
expected, actual, idx
)));
}
}
if expected == 0 {
let child = RangeChromosome::<T>::new();
crate::log_debug!(target: "crossover_events", method = "pcx"; "PCX crossover finished");
return Ok(vec![child]);
}
let sigma_eta = sigma_eta_override.unwrap_or(0.1_f64);
let sigma_zeta = sigma_zeta_override.unwrap_or(0.1_f64);
let spread: Vec<f64> = (0..expected)
.map(|i| {
let max_val = parents
.iter()
.map(|p| T::to_f64(p.dna()[i].value))
.fold(f64::NEG_INFINITY, f64::max);
let min_val = parents
.iter()
.map(|p| T::to_f64(p.dna()[i].value))
.fold(f64::INFINITY, f64::min);
max_val - min_val
})
.collect();
let mut rng = crate::rng::make_rng();
let dna0 = parents[0].dna();
let p0_vals: Vec<f64> = (0..expected).map(|i| T::to_f64(dna0[i].value)).collect();
let mut directional = vec![0.0_f64; expected];
for p in parents.iter().skip(1) {
let u1: f64 = rng.random_range(f64::EPSILON..1.0);
let u2: f64 = rng.random_range(0.0..std::f64::consts::TAU);
let eta_j: f64 = (-2.0 * u1.ln()).sqrt() * u2.cos() * sigma_eta;
for i in 0..expected {
directional[i] += eta_j * (T::to_f64(p.dna()[i].value) - p0_vals[i]);
}
}
let mut child_dna = Vec::with_capacity(expected);
for i in 0..expected {
let u1_z: f64 = rng.random_range(f64::EPSILON..1.0);
let u2_z: f64 = rng.random_range(0.0..std::f64::consts::TAU);
let zeta: f64 = (-2.0 * u1_z.ln()).sqrt() * u2_z.cos() * sigma_zeta * spread[i];
let raw = p0_vals[i] + directional[i] + zeta;
let clamped = if !dna0[i].ranges.is_empty() {
let lo: f64 = T::to_f64(dna0[i].ranges[0].0);
let hi: f64 = T::to_f64(dna0[i].ranges[0].1);
raw.clamp(lo, hi)
} else {
raw
};
let mut gene = dna0[i].clone();
gene.value = T::from_f64(clamped);
child_dna.push(gene);
}
let mut child = RangeChromosome::<T>::new();
child.set_dna(Cow::Owned(child_dna));
crate::log_debug!(target: "crossover_events", method = "pcx"; "PCX crossover finished");
Ok(vec![child])
}