use crate::chromosomes::Range as RangeChromosome;
use crate::error::GaError;
use crate::traits::ChromosomeT;
use log::debug;
use rand::Rng;
use std::borrow::Cow;
use std::fmt::Debug;
pub fn polynomial_mutation<T>(
individual: &mut RangeChromosome<T>,
eta_m: f64,
) -> Result<(), GaError>
where
T: Sync + Send + Clone + Default + Debug + PartialOrd + Copy + 'static + PolynomialConvertible,
{
if eta_m < 0.0 {
return Err(GaError::MutationError(format!(
"Distribution index eta_m must be non-negative, got {}",
eta_m
)));
}
let len = individual.dna().len();
if len == 0 {
debug!(target="mutation_events", method="polynomial"; "Empty DNA, skipping polynomial mutation");
return Ok(());
}
let mut rng = crate::rng::make_rng();
let idx = rng.random_range(0..len);
let mut dna = individual.dna().to_vec();
let mut gene = dna[idx].clone();
if gene.ranges.is_empty() {
debug!(target="mutation_events", method="polynomial"; "Gene {} has no ranges, skipping", idx);
return Ok(());
}
let range_idx = rng.random_range(0..gene.ranges.len());
let (lo, hi) = gene.ranges[range_idx];
let current_f64 = T::to_f64(gene.value);
let lo_f64 = T::to_f64(lo);
let hi_f64 = T::to_f64(hi);
let range_span = hi_f64 - lo_f64;
if range_span <= 0.0 {
return Ok(());
}
let u: f64 = rng.random_range(f64::EPSILON..(1.0 - f64::EPSILON));
let delta = if u < 0.5 {
(2.0 * u).powf(1.0 / (eta_m + 1.0)) - 1.0
} else {
1.0 - (2.0 * (1.0 - u)).powf(1.0 / (eta_m + 1.0))
};
let new_val_f64 = (current_f64 + delta * range_span).clamp(lo_f64, hi_f64);
gene.value = T::from_f64(new_val_f64);
dna[idx] = gene;
individual.set_dna(Cow::Owned(dna));
debug!(target="mutation_events", method="polynomial"; "Polynomial mutation applied at gene {} with eta_m={}", idx, eta_m);
Ok(())
}
pub trait PolynomialConvertible {
fn from_f64(val: f64) -> Self;
fn to_f64(val: Self) -> f64;
}
impl PolynomialConvertible for f64 {
fn from_f64(val: f64) -> Self {
val
}
fn to_f64(val: Self) -> f64 {
val
}
}
impl PolynomialConvertible for f32 {
fn from_f64(val: f64) -> Self {
val as f32
}
fn to_f64(val: Self) -> f64 {
val as f64
}
}
impl PolynomialConvertible for i32 {
fn from_f64(val: f64) -> Self {
val.round() as i32
}
fn to_f64(val: Self) -> f64 {
val as f64
}
}
impl PolynomialConvertible for i64 {
fn from_f64(val: f64) -> Self {
val.round() as i64
}
fn to_f64(val: Self) -> f64 {
val as f64
}
}