use super::{clamp, scale};
use crate::optimizers::{ConvergenceStatus, Objective, OptimizeResult};
use crate::rng::SplitMix64;
const F: f64 = 0.8;
const CR: f64 = 0.9;
const POP: usize = 15;
#[must_use]
pub fn genetic(
obj: &impl Objective,
bounds: &[(f64, f64)],
generations: usize,
rng: &mut SplitMix64,
) -> OptimizeResult {
let dim = bounds.len();
let mut pop: Vec<Vec<f64>> = (0..POP)
.map(|_| {
bounds
.iter()
.map(|&(lo, hi)| scale(rng.next_f64(), lo, hi))
.collect()
})
.collect();
let mut fitness: Vec<f64> = pop.iter().map(|c| obj.value(c)).collect();
let mut iterations = 0;
for generation in 0..generations {
iterations = generation + 1;
for i in 0..POP {
let (a, b, c) = three_distinct(i, rng);
let trial = mutate_crossover(&pop, i, a, b, c, dim, bounds, rng);
let trial_f = obj.value(&trial);
if trial_f < *fitness.get(i).unwrap_or(&f64::INFINITY) {
if let Some(slot) = pop.get_mut(i) {
*slot = trial;
}
if let Some(slot) = fitness.get_mut(i) {
*slot = trial_f;
}
}
}
}
let (best_idx, &best_f) = fitness
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.total_cmp(b))
.unwrap_or((0, &f64::INFINITY));
let best = pop.get(best_idx).cloned().unwrap_or_default();
OptimizeResult {
x: best,
fx: best_f,
iterations,
status: ConvergenceStatus::MaxIterReached,
}
}
#[allow(clippy::too_many_arguments)] fn mutate_crossover(
pop: &[Vec<f64>],
target: usize,
a: usize,
b: usize,
c: usize,
dim: usize,
bounds: &[(f64, f64)],
rng: &mut SplitMix64,
) -> Vec<f64> {
let xa = pop.get(a).cloned().unwrap_or_default();
let xb = pop.get(b).cloned().unwrap_or_default();
let xc = pop.get(c).cloned().unwrap_or_default();
let xt = pop.get(target).cloned().unwrap_or_default();
let forced = bounded_index(rng, dim.max(1));
(0..dim)
.map(|j| {
let (lo, hi) = *bounds.get(j).unwrap_or(&(0.0, 1.0));
let donor = F.mul_add(
xb.get(j).unwrap_or(&0.0) - xc.get(j).unwrap_or(&0.0),
*xa.get(j).unwrap_or(&0.0),
);
if j == forced || rng.next_f64() < CR {
clamp(donor, lo, hi)
} else {
*xt.get(j).unwrap_or(&0.0)
}
})
.collect()
}
fn bounded_index(rng: &mut SplitMix64, modulus: usize) -> usize {
let m = u64::try_from(modulus).unwrap_or(1).max(1);
let r = rng.next_u64() % m;
usize::try_from(r).unwrap_or(0)
}
fn three_distinct(exclude: usize, rng: &mut SplitMix64) -> (usize, usize, usize) {
let pick = |rng: &mut SplitMix64| bounded_index(rng, POP);
let a = loop {
let v = pick(rng);
if v != exclude {
break v;
}
};
let b = loop {
let v = pick(rng);
if v != exclude && v != a {
break v;
}
};
let c = loop {
let v = pick(rng);
if v != exclude && v != a && v != b {
break v;
}
};
(a, b, c)
}