use std::fmt::Debug;
use solverforge_config::AcceptorConfig;
use solverforge_core::domain::PlanningSolution;
use solverforge_core::score::{ParseableScore, Score};
use crate::phase::localsearch::{
Acceptor, GreatDelugeAcceptor, HillClimbingAcceptor, LateAcceptanceAcceptor,
SimulatedAnnealingAcceptor, TabuSearchAcceptor,
};
#[allow(clippy::large_enum_variant)]
pub enum AnyAcceptor<S: PlanningSolution> {
HillClimbing(HillClimbingAcceptor),
TabuSearch(TabuSearchAcceptor<S>),
SimulatedAnnealing(SimulatedAnnealingAcceptor),
LateAcceptance(LateAcceptanceAcceptor<S>),
GreatDeluge(GreatDelugeAcceptor<S>),
}
impl<S: PlanningSolution> Debug for AnyAcceptor<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HillClimbing(a) => write!(f, "AnyAcceptor::HillClimbing({a:?})"),
Self::TabuSearch(a) => write!(f, "AnyAcceptor::TabuSearch({a:?})"),
Self::SimulatedAnnealing(a) => write!(f, "AnyAcceptor::SimulatedAnnealing({a:?})"),
Self::LateAcceptance(a) => write!(f, "AnyAcceptor::LateAcceptance({a:?})"),
Self::GreatDeluge(a) => write!(f, "AnyAcceptor::GreatDeluge({a:?})"),
}
}
}
impl<S: PlanningSolution> Clone for AnyAcceptor<S>
where
S::Score: Clone,
{
fn clone(&self) -> Self {
match self {
Self::HillClimbing(a) => Self::HillClimbing(a.clone()),
Self::TabuSearch(a) => Self::TabuSearch(a.clone()),
Self::SimulatedAnnealing(a) => Self::SimulatedAnnealing(a.clone()),
Self::LateAcceptance(a) => Self::LateAcceptance(a.clone()),
Self::GreatDeluge(a) => Self::GreatDeluge(a.clone()),
}
}
}
impl<S: PlanningSolution> Acceptor<S> for AnyAcceptor<S>
where
S::Score: Score,
{
fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
match self {
Self::HillClimbing(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
Self::TabuSearch(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
Self::SimulatedAnnealing(a) => {
Acceptor::<S>::is_accepted(a, last_step_score, move_score)
}
Self::LateAcceptance(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
Self::GreatDeluge(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
}
}
fn phase_started(&mut self, initial_score: &S::Score) {
match self {
Self::HillClimbing(a) => Acceptor::<S>::phase_started(a, initial_score),
Self::TabuSearch(a) => Acceptor::<S>::phase_started(a, initial_score),
Self::SimulatedAnnealing(a) => Acceptor::<S>::phase_started(a, initial_score),
Self::LateAcceptance(a) => Acceptor::<S>::phase_started(a, initial_score),
Self::GreatDeluge(a) => Acceptor::<S>::phase_started(a, initial_score),
}
}
fn phase_ended(&mut self) {
match self {
Self::HillClimbing(a) => Acceptor::<S>::phase_ended(a),
Self::TabuSearch(a) => Acceptor::<S>::phase_ended(a),
Self::SimulatedAnnealing(a) => Acceptor::<S>::phase_ended(a),
Self::LateAcceptance(a) => Acceptor::<S>::phase_ended(a),
Self::GreatDeluge(a) => Acceptor::<S>::phase_ended(a),
}
}
fn step_started(&mut self) {
match self {
Self::HillClimbing(a) => Acceptor::<S>::step_started(a),
Self::TabuSearch(a) => Acceptor::<S>::step_started(a),
Self::SimulatedAnnealing(a) => Acceptor::<S>::step_started(a),
Self::LateAcceptance(a) => Acceptor::<S>::step_started(a),
Self::GreatDeluge(a) => Acceptor::<S>::step_started(a),
}
}
fn step_ended(&mut self, step_score: &S::Score) {
match self {
Self::HillClimbing(a) => Acceptor::<S>::step_ended(a, step_score),
Self::TabuSearch(a) => Acceptor::<S>::step_ended(a, step_score),
Self::SimulatedAnnealing(a) => Acceptor::<S>::step_ended(a, step_score),
Self::LateAcceptance(a) => Acceptor::<S>::step_ended(a, step_score),
Self::GreatDeluge(a) => Acceptor::<S>::step_ended(a, step_score),
}
}
}
pub struct AcceptorBuilder;
impl AcceptorBuilder {
pub fn build<S: PlanningSolution>(config: &AcceptorConfig) -> AnyAcceptor<S>
where
S::Score: Score + ParseableScore,
{
match config {
AcceptorConfig::HillClimbing => AnyAcceptor::HillClimbing(HillClimbingAcceptor::new()),
AcceptorConfig::TabuSearch(tabu_config) => {
let tabu_size = tabu_config
.entity_tabu_size
.or(tabu_config.move_tabu_size)
.unwrap_or(7);
AnyAcceptor::TabuSearch(TabuSearchAcceptor::<S>::new(tabu_size))
}
AcceptorConfig::SimulatedAnnealing(sa_config) => {
let starting_temp = sa_config
.starting_temperature
.as_ref()
.map(|s| {
s.parse::<f64>()
.ok()
.or_else(|| S::Score::parse(s).ok().map(|score| score.to_scalar().abs()))
.unwrap_or_else(|| {
panic!("Invalid starting_temperature '{}': expected scalar or score string", s)
})
})
.unwrap_or(0.0);
AnyAcceptor::SimulatedAnnealing(SimulatedAnnealingAcceptor::new(
starting_temp,
0.999985,
))
}
AcceptorConfig::LateAcceptance(la_config) => {
let size = la_config.late_acceptance_size.unwrap_or(400);
AnyAcceptor::LateAcceptance(LateAcceptanceAcceptor::<S>::new(size))
}
AcceptorConfig::GreatDeluge(gd_config) => {
let rain_speed = gd_config.water_level_increase_ratio.unwrap_or(0.001);
AnyAcceptor::GreatDeluge(GreatDelugeAcceptor::<S>::new(rain_speed))
}
}
}
pub fn hill_climbing<S: PlanningSolution>() -> HillClimbingAcceptor {
HillClimbingAcceptor::new()
}
pub fn tabu_search<S: PlanningSolution>(tabu_size: usize) -> TabuSearchAcceptor<S> {
TabuSearchAcceptor::<S>::new(tabu_size)
}
pub fn simulated_annealing(starting_temp: f64, decay_rate: f64) -> SimulatedAnnealingAcceptor {
SimulatedAnnealingAcceptor::new(starting_temp, decay_rate)
}
pub fn late_acceptance<S: PlanningSolution>(size: usize) -> LateAcceptanceAcceptor<S> {
LateAcceptanceAcceptor::<S>::new(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use solverforge_config::{
AcceptorConfig, LateAcceptanceConfig, SimulatedAnnealingConfig, TabuSearchConfig,
};
use solverforge_core::score::SoftScore;
#[derive(Clone, Debug)]
struct TestSolution {
score: Option<SoftScore>,
}
impl PlanningSolution for TestSolution {
type Score = SoftScore;
fn score(&self) -> Option<Self::Score> {
self.score
}
fn set_score(&mut self, score: Option<Self::Score>) {
self.score = score;
}
}
#[test]
fn test_acceptor_builder_hill_climbing() {
let config = AcceptorConfig::HillClimbing;
let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
}
#[test]
fn test_acceptor_builder_tabu_search() {
let config = AcceptorConfig::TabuSearch(TabuSearchConfig {
entity_tabu_size: Some(10),
..Default::default()
});
let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
}
#[test]
fn test_acceptor_builder_simulated_annealing() {
let config = AcceptorConfig::SimulatedAnnealing(SimulatedAnnealingConfig {
starting_temperature: Some("2".to_string()),
});
let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
}
#[test]
fn test_acceptor_builder_simulated_annealing_accepts_fractional_scalar() {
let config = AcceptorConfig::SimulatedAnnealing(SimulatedAnnealingConfig {
starting_temperature: Some("2.5".to_string()),
});
let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
}
#[test]
fn test_acceptor_builder_late_acceptance() {
let config = AcceptorConfig::LateAcceptance(LateAcceptanceConfig {
late_acceptance_size: Some(500),
});
let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
}
}