use std::fmt::Debug;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use solverforge_core::domain::PlanningSolution;
use solverforge_core::score::Score;
use super::Acceptor;
#[derive(Debug, Clone)]
pub struct SimulatedAnnealingAcceptor {
starting_temperature: f64,
current_temperature: f64,
decay_rate: f64,
rng: SmallRng,
level_count: usize,
}
impl SimulatedAnnealingAcceptor {
pub fn new(starting_temperature: f64, decay_rate: f64) -> Self {
Self {
starting_temperature,
current_temperature: starting_temperature,
decay_rate,
rng: SmallRng::from_rng(&mut rand::rng()),
level_count: 0,
}
}
pub fn with_seed(starting_temperature: f64, decay_rate: f64, seed: u64) -> Self {
Self {
starting_temperature,
current_temperature: starting_temperature,
decay_rate,
rng: SmallRng::seed_from_u64(seed),
level_count: 0,
}
}
pub fn auto_calibrate(decay_rate: f64) -> Self {
Self {
starting_temperature: 0.0, current_temperature: 0.0,
decay_rate,
rng: SmallRng::from_rng(&mut rand::rng()),
level_count: 0,
}
}
pub(crate) fn auto_calibrate_with_seed(decay_rate: f64, seed: u64) -> Self {
Self {
starting_temperature: 0.0, current_temperature: 0.0,
decay_rate,
rng: SmallRng::seed_from_u64(seed),
level_count: 0,
}
}
}
impl Default for SimulatedAnnealingAcceptor {
fn default() -> Self {
Self::auto_calibrate(0.999985)
}
}
fn score_delta_to_scalar(levels: &[i64]) -> f64 {
if levels.is_empty() {
return 0.0;
}
if levels.len() == 1 {
return levels[0] as f64;
}
let n = levels.len();
let mut scalar = 0.0f64;
for (i, &level) in levels.iter().enumerate() {
let weight = 10.0f64.powi(6 * (n - 1 - i) as i32);
scalar += level as f64 * weight;
}
scalar
}
impl<S: PlanningSolution> Acceptor<S> for SimulatedAnnealingAcceptor
where
S::Score: Score,
{
fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
if *move_score >= *last_step_score {
return true;
}
if self.current_temperature <= 0.0 {
return false;
}
let delta = move_score.to_scalar() - last_step_score.to_scalar();
let probability = (delta / self.current_temperature).exp();
let roll: f64 = self.rng.random::<f64>();
roll < probability
}
fn phase_started(&mut self, initial_score: &S::Score) {
self.level_count = S::Score::levels_count();
if self.starting_temperature == 0.0 {
let levels = initial_score.to_level_numbers();
let magnitude = score_delta_to_scalar(&levels).abs();
self.starting_temperature = if magnitude > 0.0 {
magnitude * 0.02
} else {
1.0
};
}
self.current_temperature = self.starting_temperature;
}
fn step_ended(&mut self, _step_score: &S::Score) {
self.current_temperature *= self.decay_rate;
}
}
#[cfg(test)]
#[path = "simulated_annealing_tests.rs"]
mod tests;