use super::{clamp, scale};
use crate::optimizers::{ConvergenceStatus, Objective, OptimizeResult};
use crate::rng::SplitMix64;
#[must_use]
pub fn simulated_annealing(
obj: &impl Objective,
bounds: &[(f64, f64)],
max_iter: usize,
rng: &mut SplitMix64,
) -> OptimizeResult {
let mut current: Vec<f64> = bounds.iter().map(|&(lo, hi)| scale(0.5, lo, hi)).collect();
let mut current_f = obj.value(¤t);
let mut best = current.clone();
let mut best_f = current_f;
let mut temp = 1.0_f64;
let mut iterations = 0;
let budget = max_iter.max(1);
let cooling = 1e-4_f64.powf(1.0 / f64::from(u32::try_from(budget).unwrap_or(u32::MAX)));
for step in 0..max_iter {
iterations = step + 1;
let proposal: Vec<f64> = current
.iter()
.zip(bounds)
.map(|(&xi, &(lo, hi))| {
let width = hi - lo;
clamp(
rng.standard_normal().mul_add(temp * width * 0.1, xi),
lo,
hi,
)
})
.collect();
let proposal_f = obj.value(&proposal);
let delta = proposal_f - current_f;
if delta < 0.0 || rng.next_f64() < (-delta / temp.max(1e-12)).exp() {
current = proposal;
current_f = proposal_f;
}
if current_f < best_f {
best_f = current_f;
best.clone_from(¤t);
}
temp *= cooling;
}
OptimizeResult {
x: best,
fx: best_f,
iterations,
status: ConvergenceStatus::MaxIterReached,
}
}