use super::{clamp, sample, Evaluator, Optimizer};
use crate::problem::{Bound, Problem};
use crate::rng::Rng;
use crate::solution::{Report, Solution};
use crate::termination::Termination;
#[derive(Debug, Clone, Copy)]
pub struct Sce {
pub complexes: usize,
pub seed: u64,
}
impl Default for Sce {
fn default() -> Self {
Sce {
complexes: 4,
seed: 42,
}
}
}
impl Optimizer for Sce {
fn with_seed(&self, seed: u64) -> Self {
Sce { seed, ..*self }
}
fn optimize(&self, problem: &dyn Problem, term: &Termination) -> Report {
crate::problem::validate(problem).unwrap_or_else(|e| panic!("Sce: invalid problem: {e}"));
let bounds = problem.bounds();
let n = bounds.len();
let complexes = self.complexes.max(1);
let m = 2 * n + 1; let q = n + 1; let beta = m; let pop_size = complexes * m;
let mut rng = Rng::new(self.seed);
let first_x = sample(bounds, &mut rng);
let first_v = problem.objective(&first_x);
let mut ev = Evaluator::new(
problem,
term,
Solution {
x: first_x.clone(),
value: finite_or_worst(first_v),
},
);
let mut pop: Vec<(Vec<f64>, f64)> = Vec::with_capacity(pop_size);
pop.push((first_x, finite_or_worst(first_v)));
for _ in 1..pop_size {
if ev.done() {
break;
}
let x = sample(bounds, &mut rng);
let s = finite_or_worst(ev.eval(&x));
pop.push((x, s));
}
let cmp = |a: &(Vec<f64>, f64), b: &(Vec<f64>, f64)| {
a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
};
while !ev.done() && pop.len() == pop_size {
pop.sort_by(cmp);
for k in 0..complexes {
let mut complex: Vec<(Vec<f64>, f64)> =
(0..m).map(|j| pop[k + j * complexes].clone()).collect();
for _ in 0..beta {
if ev.done() {
break;
}
complex.sort_by(cmp);
let parents = triangular_subset(m, q, &mut rng);
let worst_idx = *parents.last().unwrap();
let mut centroid = vec![0.0; n];
for &pi in &parents[..q - 1] {
for (c, &v) in centroid.iter_mut().zip(&complex[pi].0) {
*c += v;
}
}
let qm1 = (q - 1) as f64;
for c in &mut centroid {
*c /= qm1;
}
let worst = complex[worst_idx].0.clone();
let mut trial: Vec<f64> = (0..n)
.map(|d| centroid[d] + (centroid[d] - worst[d]))
.collect();
let in_bounds = trial
.iter()
.zip(bounds)
.all(|(&v, &(lo, hi))| v >= lo && v <= hi);
let mut tf;
if in_bounds {
tf = finite_or_worst(ev.eval(&trial));
if tf > complex[worst_idx].1 {
if ev.done() {
break;
}
trial = (0..n).map(|d| 0.5 * (centroid[d] + worst[d])).collect();
tf = finite_or_worst(ev.eval(&trial));
if tf > complex[worst_idx].1 {
if ev.done() {
break;
}
trial = sample_in_hull(&complex, bounds, &mut rng);
tf = finite_or_worst(ev.eval(&trial));
}
}
} else {
trial = sample_in_hull(&complex, bounds, &mut rng);
tf = finite_or_worst(ev.eval(&trial));
}
complex[worst_idx] = (trial, tf);
}
for (j, point) in complex.into_iter().enumerate() {
pop[k + j * complexes] = point;
}
}
}
ev.finish()
}
}
#[inline]
fn finite_or_worst(v: f64) -> f64 {
if v.is_finite() {
v
} else {
f64::INFINITY
}
}
fn triangular_subset(m: usize, q: usize, rng: &mut Rng) -> Vec<usize> {
let mut chosen = Vec::with_capacity(q);
let mf = m as f64;
while chosen.len() < q {
let u = rng.uniform();
let idx = ((mf + 0.5) - ((mf + 0.5).powi(2) - mf * (mf + 1.0) * u).sqrt()) as usize;
let idx = idx.min(m - 1);
if !chosen.contains(&idx) {
chosen.push(idx);
}
}
chosen.sort_unstable();
chosen
}
fn sample_in_hull(complex: &[(Vec<f64>, f64)], bounds: &[Bound], rng: &mut Rng) -> Vec<f64> {
let n = bounds.len();
(0..n)
.map(|d| {
let mut lo = complex[0].0[d];
let mut hi = lo;
for point in complex {
lo = lo.min(point.0[d]);
hi = hi.max(point.0[d]);
}
let v = rng.uniform_in(lo, hi);
clamp(v, bounds[d].0, bounds[d].1)
})
.collect()
}