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 non_uniform_mutation<T>(
individual: &mut RangeChromosome<T>,
generation: usize,
max_generations: usize,
b: f64,
) -> Result<(), GaError>
where
T: Sync + Send + Clone + Default + Debug + PartialOrd + Copy + 'static + NonUniformConvertible,
{
if max_generations == 0 {
return Err(GaError::MutationError(
"max_generations must be greater than 0".to_string(),
));
}
if b < 0.0 {
return Err(GaError::MutationError(format!(
"Decay exponent b must be non-negative, got {}",
b
)));
}
let len = individual.dna().len();
if len == 0 {
debug!(target="mutation_events", method="non_uniform"; "Empty DNA, skipping non-uniform 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="non_uniform"; "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 t_ratio = generation as f64 / max_generations as f64;
let tau = (1.0 - t_ratio.min(1.0)).powf(b);
let r: f64 = rng.random_range(0.0_f64..1.0_f64);
let coin: bool = rng.random_bool(0.5);
let new_val_f64 = if coin {
current_f64 + (hi_f64 - current_f64) * (1.0 - r.powf(tau))
} else {
current_f64 - (current_f64 - lo_f64) * (1.0 - r.powf(tau))
};
let clamped = new_val_f64.clamp(lo_f64, hi_f64);
gene.value = T::from_f64(clamped);
dna[idx] = gene;
individual.set_dna(Cow::Owned(dna));
debug!(
target="mutation_events", method="non_uniform";
"Non-uniform mutation applied at gene {} (gen={}/{}, b={}, tau={:.4})",
idx, generation, max_generations, b, tau
);
Ok(())
}
pub trait NonUniformConvertible {
fn from_f64(val: f64) -> Self;
fn to_f64(val: Self) -> f64;
}
impl NonUniformConvertible for f64 {
fn from_f64(val: f64) -> Self {
val
}
fn to_f64(val: Self) -> f64 {
val
}
}
impl NonUniformConvertible for f32 {
fn from_f64(val: f64) -> Self {
val as f32
}
fn to_f64(val: Self) -> f64 {
val as f64
}
}
impl NonUniformConvertible for i32 {
fn from_f64(val: f64) -> Self {
val.round() as i32
}
fn to_f64(val: Self) -> f64 {
val as f64
}
}
impl NonUniformConvertible for i64 {
fn from_f64(val: f64) -> Self {
val.round() as i64
}
fn to_f64(val: Self) -> f64 {
val as f64
}
}