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 LShade {
pub init_pop: Option<usize>,
pub memory: usize,
pub p_best: f64,
pub archive_rate: f64,
pub seed: u64,
}
impl Default for LShade {
fn default() -> Self {
LShade {
init_pop: None,
memory: 6,
p_best: 0.11,
archive_rate: 2.6,
seed: 42,
}
}
}
const N_MIN: usize = 4;
impl Optimizer for LShade {
fn with_seed(&self, seed: u64) -> Self {
LShade { seed, ..*self }
}
fn optimize(&self, problem: &dyn Problem, term: &Termination) -> Report {
let bounds = problem.bounds();
let dim = bounds.len();
let mut rng = Rng::new(self.seed);
let n_init = self.init_pop.unwrap_or(18 * dim).max(N_MIN);
let h = self.memory.max(1);
let max_nfe = term.max_evaluations.max(1);
let eval = |x: &[f64], problem: &dyn Problem| -> f64 {
let v = problem.objective(x);
if v.is_finite() {
v
} else {
f64::INFINITY
}
};
let mut m_cr = vec![0.5f64; h];
let mut m_f = vec![0.5f64; h];
let mut k_pos = 0usize;
let mut pop: Vec<Vec<f64>> = Vec::with_capacity(n_init);
let mut fit: Vec<f64> = Vec::with_capacity(n_init);
let mut archive: Vec<Vec<f64>> = Vec::new();
let mut best = Solution {
x: vec![0.0; dim],
value: f64::INFINITY,
};
let mut nfe = 0usize;
for _ in 0..n_init {
let x: Vec<f64> = bounds
.iter()
.map(|&(lo, hi)| rng.uniform_in(lo, hi))
.collect();
let f = eval(&x, problem);
nfe += 1;
if f < best.value {
best = Solution {
x: x.clone(),
value: f,
};
}
pop.push(x);
fit.push(f);
}
let mut n = pop.len();
let mut trial = vec![0.0; dim];
'outer: while term.reason(nfe, best.value).is_none() {
let mut ranked: Vec<usize> = (0..n).collect();
ranked.sort_by(|&a, &b| {
fit[a]
.partial_cmp(&fit[b])
.unwrap_or(std::cmp::Ordering::Equal)
});
let p_num = ((self.p_best * n as f64).round() as usize).clamp(2, n);
let mut succ_cr: Vec<f64> = Vec::new();
let mut succ_f: Vec<f64> = Vec::new();
let mut delta: Vec<f64> = Vec::new();
for i in 0..n {
if term.reason(nfe, best.value).is_some() {
break 'outer;
}
let r = rng.index(h);
let cr = if m_cr[r].is_nan() {
0.0
} else {
(m_cr[r] + 0.1 * rng.normal()).clamp(0.0, 1.0)
};
let scale = loop {
let v = m_f[r] + 0.1 * cauchy(&mut rng);
if v > 0.0 {
break v.min(1.0);
}
};
let pbest = ranked[rng.index(p_num)];
let r1 = loop {
let z = rng.index(n);
if z != i {
break z;
}
};
let na = archive.len();
let r2 = loop {
let z = rng.index(n + na);
if z >= n || (z != i && z != r1) {
break z;
}
};
let x_r2: &[f64] = if r2 < n { &pop[r2] } else { &archive[r2 - n] };
let jrand = rng.index(dim);
for j in 0..dim {
let (lo, hi) = bounds[j];
if rng.uniform() <= cr || j == jrand {
let mut v = pop[i][j]
+ scale * (pop[pbest][j] - pop[i][j])
+ scale * (pop[r1][j] - x_r2[j]);
if v < lo {
v = (lo + pop[i][j]) / 2.0;
} else if v > hi {
v = (hi + pop[i][j]) / 2.0;
}
trial[j] = v;
} else {
trial[j] = pop[i][j];
}
}
let tf = eval(&trial, problem);
nfe += 1;
if tf < best.value {
best = Solution {
x: trial.clone(),
value: tf,
};
}
if tf <= fit[i] {
if tf < fit[i] {
succ_cr.push(cr);
succ_f.push(scale);
delta.push(fit[i] - tf);
archive.push(pop[i].clone());
}
pop[i].copy_from_slice(&trial);
fit[i] = tf;
}
}
let arc_size = (self.archive_rate * n as f64).round() as usize;
while archive.len() > arc_size {
let idx = rng.index(archive.len());
archive.swap_remove(idx);
}
let total: f64 = delta.iter().sum();
if !succ_f.is_empty() && total > 0.0 {
let w: Vec<f64> = delta.iter().map(|d| d / total).collect();
m_f[k_pos] = lehmer(&w, &succ_f);
let max_cr = succ_cr.iter().copied().fold(f64::NEG_INFINITY, f64::max);
m_cr[k_pos] = if m_cr[k_pos].is_nan() || max_cr == 0.0 {
f64::NAN
} else {
lehmer(&w, &succ_cr)
};
k_pos = (k_pos + 1) % h;
}
let target =
((N_MIN as f64 - n_init as f64) / max_nfe as f64) * nfe as f64 + n_init as f64;
let n_new = (target.round() as usize).clamp(N_MIN, n);
if n_new < n {
let mut ranked2: Vec<usize> = (0..n).collect();
ranked2.sort_by(|&a, &b| {
fit[a]
.partial_cmp(&fit[b])
.unwrap_or(std::cmp::Ordering::Equal)
});
ranked2.truncate(n_new);
let new_pop: Vec<Vec<f64>> = ranked2.iter().map(|&i| pop[i].clone()).collect();
let new_fit: Vec<f64> = ranked2.iter().map(|&i| fit[i]).collect();
pop = new_pop;
fit = new_fit;
n = n_new;
let arc2 = (self.archive_rate * n as f64).round() as usize;
while archive.len() > arc2 {
let idx = rng.index(archive.len());
archive.swap_remove(idx);
}
}
}
let stop = term
.reason(nfe, best.value)
.unwrap_or(StopReason::BudgetExhausted);
Report {
solution: best,
stop,
evaluations: nfe,
}
}
}
fn lehmer(w: &[f64], s: &[f64]) -> f64 {
let num: f64 = w.iter().zip(s).map(|(wk, sk)| wk * sk * sk).sum();
let den: f64 = w.iter().zip(s).map(|(wk, sk)| wk * sk).sum();
if den != 0.0 {
num / den
} else {
0.5
}
}
fn cauchy(rng: &mut Rng) -> f64 {
(std::f64::consts::PI * (rng.uniform() - 0.5)).tan()
}