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 undx<T>(
parents: &[&RangeChromosome<T>],
_num_parents: usize,
sigma_xi_override: Option<f64>,
sigma_eta_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 = "undx"; "Starting UNDX crossover with {} parents", parents.len());
if parents.len() < 3 {
return Err(GaError::CrossoverError(
"UNDX 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 = "undx"; "UNDX crossover finished");
return Ok(vec![child]);
}
let n_par = parents.len() as f64;
let mut centroid = vec![0.0_f64; expected];
for p in parents.iter() {
for (i, gene) in p.dna().iter().enumerate() {
centroid[i] += T::to_f64(gene.value);
}
}
for c in centroid.iter_mut() {
*c /= n_par;
}
let sigma_xi = sigma_xi_override.unwrap_or_else(|| 0.35 / (n_par - 1.0).max(1.0).sqrt());
let sigma_eta = sigma_eta_override.unwrap_or_else(|| 0.35 / n_par.sqrt());
let mut dir: Vec<f64> = parents[0]
.dna()
.iter()
.enumerate()
.map(|(i, gene)| T::to_f64(gene.value) - centroid[i])
.collect();
let dir_norm = dir.iter().map(|x| x * x).sum::<f64>().sqrt().max(1e-14);
for d in dir.iter_mut() {
*d /= dir_norm;
}
let mut rng = crate::rng::make_rng();
let u1_eta: f64 = rng.random_range(f64::EPSILON..1.0);
let u2_eta: f64 = rng.random_range(0.0..std::f64::consts::TAU);
let eta_noise: f64 = (-2.0 * u1_eta.ln()).sqrt() * u2_eta.cos() * sigma_eta;
let raw_xi: Vec<f64> = (0..expected)
.map(|_| {
let u1: f64 = rng.random_range(f64::EPSILON..1.0);
let u2: f64 = rng.random_range(0.0..std::f64::consts::TAU);
(-2.0 * u1.ln()).sqrt() * u2.cos()
})
.collect();
let dot: f64 = raw_xi
.iter()
.zip(dir.iter())
.map(|(n, d)| n * d)
.sum::<f64>();
let xi_perp: Vec<f64> = raw_xi
.iter()
.zip(dir.iter())
.map(|(n, d)| (n - dot * d) * sigma_xi)
.collect();
let mut child_dna = Vec::with_capacity(expected);
let dna0 = parents[0].dna();
for i in 0..expected {
let raw = centroid[i] + eta_noise * dir[i] + xi_perp[i];
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 = "undx"; "UNDX crossover finished");
Ok(vec![child])
}