use std::fmt::{Debug, Display};
use std::str::FromStr;
use crate::algorithms::checkpoint::StepStateCheckpoint;
use crate::algorithms::objective::is_better;
use crate::algorithms::runtime::ExecutionContext;
use crate::algorithms::termination::{ExecutionStateSnapshot, TerminationCriteria};
use crate::algorithms::traits::Algorithm;
use crate::experiment::traits::{CaseParameter, ExperimentalCase};
use crate::observer::traits::AlgorithmObserver;
use crate::observer::Observable;
use crate::operator::traits::MutationOperator;
use crate::problem::traits::Problem;
use crate::solution::Solution;
use crate::solution_set::implementations::vector_solution_set::VectorSolutionSet;
use crate::solution_set::traits::SolutionSet;
use crate::utils::random::{seed_from_time, Random};
#[derive(Clone)]
pub struct HillClimbingParameters<T, M>
where
T: Clone,
M: MutationOperator<T>,
{
pub mutation_operator: M,
pub mutation_probability: f64,
pub termination_criteria: TerminationCriteria,
pub random_seed: Option<u64>,
_phantom: std::marker::PhantomData<T>,
}
impl<T, M> HillClimbingParameters<T, M>
where
T: Clone,
M: MutationOperator<T>,
{
pub fn new(
mutation_operator: M,
mutation_probability: f64,
termination_criteria: TerminationCriteria,
) -> Self {
Self {
mutation_operator,
mutation_probability,
termination_criteria,
random_seed: None,
_phantom: std::marker::PhantomData,
}
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.random_seed = Some(seed);
self
}
}
pub struct HillClimbing<T, M>
where
T: Clone,
M: MutationOperator<T>,
{
parameters: HillClimbingParameters<T, M>,
solution_set: Option<VectorSolutionSet<T>>,
observers: Vec<Box<dyn AlgorithmObserver<T>>>,
}
impl<T, M> HillClimbing<T, M>
where
T: Clone,
M: MutationOperator<T>,
{
}
pub struct HillClimbingState<T>
where
T: Clone,
{
current: Solution<T>,
rng: Random,
iteration: usize,
evaluations: usize,
}
impl<T> StepStateCheckpoint<T, f64> for HillClimbingState<T>
where
T: Clone + Display + FromStr + Debug,
{
fn random_seed(&self) -> u64 {
self.rng.state()
}
fn to_payload(&self) -> String {
format!(
"iter={};eval={};seed={};state={}",
self.iteration(),
self.evaluations(),
self.random_seed(),
self.current.encode()
)
}
fn from_payload(payload: &str) -> Self {
let parts: std::collections::HashMap<&str, &str> = payload
.split(';')
.filter_map(|s| {
let mut kv = s.splitn(2, '=');
Some((kv.next()?, kv.next()?))
})
.collect();
let iteration = parts.get("iter").and_then(|s| s.parse().ok()).unwrap_or(0);
let evaluations = parts.get("eval").and_then(|s| s.parse().ok()).unwrap_or(0);
let random_seed = parts
.get("seed")
.and_then(|s| s.parse().ok())
.unwrap_or_else(seed_from_time);
let current = parts
.get("state")
.and_then(|s| Solution::decode(s).ok())
.expect("Critical error: Could not decode the current state from payload");
Self {
current,
rng: Random::new(random_seed),
iteration,
evaluations,
}
}
fn iteration(&self) -> usize {
self.iteration
}
fn evaluations(&self) -> usize {
self.evaluations
}
}
impl<T, M> Observable<T> for HillClimbing<T, M>
where
T: Clone + Send + 'static,
M: MutationOperator<T>,
{
fn add_observer(&mut self, observer: Box<dyn AlgorithmObserver<T>>) {
self.observers.push(observer);
}
fn clear_observers(&mut self) {
self.observers.clear();
}
}
impl<T, M> Algorithm<T> for HillClimbing<T, M>
where
T: Clone + Send + Sync + 'static + Display + FromStr + Debug,
M: MutationOperator<T> + Send + Sync,
{
type SolutionSet = VectorSolutionSet<T>;
type Parameters = HillClimbingParameters<T, M>;
type StepState = HillClimbingState<T>;
fn new(parameters: HillClimbingParameters<T, M>) -> Self {
Self {
parameters,
solution_set: None,
observers: Vec::new(),
}
}
fn algorithm_name(&self) -> &str {
"HillClimbing"
}
fn termination_criteria(&self) -> TerminationCriteria {
self.parameters.termination_criteria.clone()
}
fn observers_mut(&mut self) -> &mut Vec<Box<dyn AlgorithmObserver<T>>> {
&mut self.observers
}
fn set_solution_set(&mut self, solution_set: Self::SolutionSet) {
self.solution_set = Some(solution_set);
}
fn validate_parameters(&self) -> Result<(), String> {
if self.parameters.termination_criteria.is_empty() {
return Err("termination_criteria must not be empty".to_string());
}
if !(0.0..=1.0).contains(&self.parameters.mutation_probability) {
return Err("mutation_probability must be in [0,1]".to_string());
}
Ok(())
}
fn get_solution_set(&self) -> Option<&Self::SolutionSet> {
self.solution_set.as_ref()
}
fn initialize_step_state(&self, problem: &(impl Problem<T> + Sync)) -> Self::StepState {
let mut rng = Random::new(self.parameters.random_seed.unwrap_or_else(seed_from_time));
let mut current = problem.create_solution(&mut rng);
problem.evaluate(&mut current);
HillClimbingState {
current,
rng,
iteration: 0,
evaluations: 1,
}
}
fn step(
&self,
problem: &(impl Problem<T> + Sync),
state: &mut Self::StepState,
_context: &ExecutionContext<T>,
) {
state.iteration += 1;
let mut neighbor = state.current.copy();
self.parameters.mutation_operator.execute(
&mut neighbor,
self.parameters.mutation_probability,
&mut state.rng,
);
problem.evaluate(&mut neighbor);
state.evaluations += 1;
let improved = is_better(
neighbor.quality_value(),
state.current.quality_value(),
problem.get_improvement_direction(),
);
if improved {
state.current = neighbor;
}
}
fn build_snapshot(&self, state: &Self::StepState) -> ExecutionStateSnapshot<T> {
let fit = state.current.quality_value();
ExecutionStateSnapshot {
iteration: state.iteration,
evaluations: state.evaluations,
best_solution: state.current.copy(),
best_fitness: fit,
worst_fitness: fit,
average_fitness: fit,
}
}
fn finalize_step_state(&self, state: Self::StepState) -> Self::SolutionSet {
let mut result = VectorSolutionSet::new();
result.add_solution(state.current);
result
}
}
impl<T, M, P> ExperimentalCase<T, f64, P> for HillClimbingParameters<T, M>
where
T: Clone + Send + Sync + 'static + Display + FromStr + Debug,
M: MutationOperator<T> + Clone + Send + Sync + 'static,
P: Problem<T, f64> + Sync,
{
fn algorithm_name(&self) -> &str {
"HillClimbing"
}
fn case_name(&self) -> String {
format!(
"{}(mutation_prob={:.4})",
"HillClimbing", self.mutation_probability,
)
}
fn parameters(&self) -> Vec<CaseParameter> {
vec![
CaseParameter::new("mutation_operator", self.mutation_operator.name()),
CaseParameter::new(
"mutation_probability",
format!("{:.6}", self.mutation_probability),
),
CaseParameter::new(
"termination_criteria",
format!("{:?}", self.termination_criteria),
),
]
}
fn run(&self, problem: &P) -> Result<Box<dyn SolutionSet<T, f64>>, String> {
let result = HillClimbing::new(self.clone()).run(problem)?;
Ok(Box::new(result))
}
}