#![allow(clippy::needless_range_loop)]
use super::Optimizer;
use crate::problem::Problem;
use crate::rng::Rng;
use crate::solution::{Report, Solution, StopReason};
use crate::termination::Termination;
#[derive(Debug, Clone, Copy)]
pub struct CmaEs {
pub population: Option<usize>,
pub sigma0: f64,
pub seed: u64,
}
impl Default for CmaEs {
fn default() -> Self {
CmaEs {
population: None,
sigma0: 0.3,
seed: 42,
}
}
}
impl Optimizer for CmaEs {
fn with_seed(&self, seed: u64) -> Self {
CmaEs { seed, ..*self }
}
fn optimize(&self, problem: &dyn Problem, term: &Termination) -> Report {
let bounds = problem.bounds();
let n = bounds.len();
let mut rng = Rng::new(self.seed);
let lambda = self
.population
.unwrap_or(4 + (3.0 * (n as f64).ln()) as usize)
.max(4);
let mu = lambda / 2;
let raw: Vec<f64> = (0..mu)
.map(|i| ((mu as f64) + 0.5).ln() - ((i + 1) as f64).ln())
.collect();
let wsum: f64 = raw.iter().sum();
let w: Vec<f64> = raw.iter().map(|&v| v / wsum).collect();
let mu_eff = 1.0 / w.iter().map(|&v| v * v).sum::<f64>();
let nf = n as f64;
let c_sigma = (mu_eff + 2.0) / (nf + mu_eff + 5.0);
let d_sigma = 1.0 + 2.0 * (((mu_eff - 1.0) / (nf + 1.0)).sqrt() - 1.0).max(0.0) + c_sigma;
let c_c = (4.0 + mu_eff / nf) / (nf + 4.0 + 2.0 * mu_eff / nf);
let c_1 = 2.0 / ((nf + 1.3).powi(2) + mu_eff);
let c_mu =
(1.0 - c_1).min(2.0 * (mu_eff - 2.0 + 1.0 / mu_eff) / ((nf + 2.0).powi(2) + mu_eff));
let e_n = nf.sqrt() * (1.0 - 1.0 / (4.0 * nf) + 1.0 / (21.0 * nf * nf));
let mut mean = vec![0.5; n];
let mut sigma = self.sigma0;
let mut cov = identity(n);
let mut p_sigma = vec![0.0; n];
let mut p_c = vec![0.0; n];
let mut generation = 0i32;
let mut best = Solution {
x: denormalize(&mean, bounds),
value: f64::INFINITY,
};
let mut evaluations = 0usize;
while term.reason(evaluations, best.value).is_none() {
let (eigvals, b) = jacobi_eigen(&cov);
let d: Vec<f64> = eigvals.iter().map(|&v| v.max(1e-20).sqrt()).collect();
let mut pop: Vec<(f64, Vec<f64>)> = Vec::with_capacity(lambda);
for _ in 0..lambda {
let z: Vec<f64> = (0..n).map(|_| rng.normal()).collect();
let dz: Vec<f64> = (0..n).map(|j| d[j] * z[j]).collect();
let y = matvec(&b, &dz); let u: Vec<f64> = (0..n).map(|i| mean[i] + sigma * y[i]).collect();
let x = denormalize_clamped(&u, bounds);
let f = problem.objective(&x);
evaluations += 1;
let f = if f.is_finite() { f } else { f64::INFINITY };
if f < best.value {
best = Solution { x, value: f };
}
pop.push((f, y));
}
pop.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
let mut y_w = vec![0.0; n];
for (i, wi) in w.iter().enumerate() {
for k in 0..n {
y_w[k] += wi * pop[i].1[k];
}
}
for k in 0..n {
mean[k] = (mean[k] + sigma * y_w[k]).clamp(0.0, 1.0);
}
let c_inv_sqrt_yw = c_inv_sqrt_mul(&b, &d, &y_w);
let cs_factor = (c_sigma * (2.0 - c_sigma) * mu_eff).sqrt();
for k in 0..n {
p_sigma[k] = (1.0 - c_sigma) * p_sigma[k] + cs_factor * c_inv_sqrt_yw[k];
}
let ps_norm = norm(&p_sigma);
generation += 1;
let hsig = if ps_norm / (1.0 - (1.0 - c_sigma).powi(2 * generation)).sqrt()
< (1.4 + 2.0 / (nf + 1.0)) * e_n
{
1.0
} else {
0.0
};
let cc_factor = (c_c * (2.0 - c_c) * mu_eff).sqrt();
for k in 0..n {
p_c[k] = (1.0 - c_c) * p_c[k] + hsig * cc_factor * y_w[k];
}
let delta_hsig = (1.0 - hsig) * c_c * (2.0 - c_c);
for a in 0..n {
for bcol in a..n {
let mut rank_mu = 0.0;
for (i, wi) in w.iter().enumerate() {
rank_mu += wi * pop[i].1[a] * pop[i].1[bcol];
}
let rank_one = p_c[a] * p_c[bcol];
let val = (1.0 - c_1 - c_mu) * cov[a][bcol]
+ c_1 * (rank_one + delta_hsig * cov[a][bcol])
+ c_mu * rank_mu;
cov[a][bcol] = val;
cov[bcol][a] = val; }
}
sigma *= ((c_sigma / d_sigma) * (ps_norm / e_n - 1.0)).exp();
if !sigma.is_finite() || sigma < 1e-300 {
break; }
}
let stop = term
.reason(evaluations, best.value)
.unwrap_or(StopReason::BudgetExhausted);
Report {
solution: best,
stop,
evaluations,
}
}
}
fn denormalize(u: &[f64], bounds: &[(f64, f64)]) -> Vec<f64> {
u.iter()
.zip(bounds)
.map(|(&ui, &(lo, hi))| lo + ui * (hi - lo))
.collect()
}
fn denormalize_clamped(u: &[f64], bounds: &[(f64, f64)]) -> Vec<f64> {
u.iter()
.zip(bounds)
.map(|(&ui, &(lo, hi))| lo + ui.clamp(0.0, 1.0) * (hi - lo))
.collect()
}
fn identity(n: usize) -> Vec<Vec<f64>> {
let mut m = vec![vec![0.0; n]; n];
for (i, row) in m.iter_mut().enumerate() {
row[i] = 1.0;
}
m
}
fn matvec(m: &[Vec<f64>], v: &[f64]) -> Vec<f64> {
m.iter()
.map(|row| row.iter().zip(v).map(|(a, b)| a * b).sum())
.collect()
}
fn norm(v: &[f64]) -> f64 {
v.iter().map(|x| x * x).sum::<f64>().sqrt()
}
fn c_inv_sqrt_mul(b: &[Vec<f64>], d: &[f64], v: &[f64]) -> Vec<f64> {
let n = v.len();
let mut a = vec![0.0; n];
for (j, aj) in a.iter_mut().enumerate() {
for i in 0..n {
*aj += b[i][j] * v[i];
}
*aj /= d[j];
}
matvec(b, &a)
}
fn jacobi_eigen(input: &[Vec<f64>]) -> (Vec<f64>, Vec<Vec<f64>>) {
let n = input.len();
let mut a: Vec<Vec<f64>> = input.to_vec();
let mut v = identity(n);
if n == 1 {
return (vec![a[0][0]], v);
}
for _ in 0..100 {
let mut off = 0.0;
for p in 0..n {
for q in p + 1..n {
off += a[p][q] * a[p][q];
}
}
if off.sqrt() < 1e-14 {
break;
}
for p in 0..n {
for q in p + 1..n {
if a[p][q].abs() < 1e-300 {
continue;
}
let theta = (a[q][q] - a[p][p]) / (2.0 * a[p][q]);
let t = theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt());
let c = 1.0 / (t * t + 1.0).sqrt();
let s = t * c;
for k in 0..n {
let akp = a[k][p];
let akq = a[k][q];
a[k][p] = c * akp - s * akq;
a[k][q] = s * akp + c * akq;
}
for k in 0..n {
let apk = a[p][k];
let aqk = a[q][k];
a[p][k] = c * apk - s * aqk;
a[q][k] = s * apk + c * aqk;
}
for k in 0..n {
let vkp = v[k][p];
let vkq = v[k][q];
v[k][p] = c * vkp - s * vkq;
v[k][q] = s * vkp + c * vkq;
}
}
}
}
let eigvals = (0..n).map(|i| a[i][i]).collect();
(eigvals, v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn jacobi_recovers_known_eigenpairs() {
let (vals, vecs) = jacobi_eigen(&[vec![2.0, 1.0], vec![1.0, 2.0]]);
let mut sorted = vals.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert!((sorted[0] - 1.0).abs() < 1e-9 && (sorted[1] - 3.0).abs() < 1e-9);
for j in 0..2 {
let col_norm = (0..2).map(|i| vecs[i][j] * vecs[i][j]).sum::<f64>().sqrt();
assert!((col_norm - 1.0).abs() < 1e-9);
}
}
}