use crate::error::{GeneticError, Result};
use crate::evolution::Challenge;
use crate::phenotype::Phenotype;
use crate::rng::RandomNumberGenerator;
use super::LocalSearch;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct HillClimbing {
max_iterations: usize,
max_neighbors: usize,
restart_probability: f64,
}
impl HillClimbing {
pub fn new(max_iterations: usize, max_neighbors: usize) -> Result<Self> {
if max_iterations == 0 {
return Err(GeneticError::Configuration(
"Maximum iterations must be greater than 0".to_string(),
));
}
if max_neighbors == 0 {
return Err(GeneticError::Configuration(
"Maximum neighbors must be greater than 0".to_string(),
));
}
Ok(Self {
max_iterations,
max_neighbors,
restart_probability: 0.05,
})
}
pub fn with_restart_probability(mut self, restart_probability: f64) -> Result<Self> {
if !(0.0..=1.0).contains(&restart_probability) {
return Err(GeneticError::Configuration(
"Restart probability must be between 0.0 and 1.0".to_string(),
));
}
self.restart_probability = restart_probability;
Ok(self)
}
}
impl<P, C> LocalSearch<P, C> for HillClimbing
where
P: Phenotype,
C: Challenge<P>,
{
fn search(&self, phenotype: &mut P, challenge: &C) -> bool {
let initial_score = challenge.score(phenotype);
let mut current_solution = phenotype.clone();
let mut current_score = initial_score;
let mut improved = false;
let mut rng = RandomNumberGenerator::new();
for _ in 0..self.max_iterations {
let mut best_neighbor = None;
let mut best_neighbor_score = current_score;
for _ in 0..self.max_neighbors {
let mut neighbor = current_solution.clone();
neighbor.mutate(&mut rng);
let neighbor_score = challenge.score(&neighbor);
if neighbor_score > best_neighbor_score {
best_neighbor = Some(neighbor);
best_neighbor_score = neighbor_score;
}
}
if let Some(best) = best_neighbor {
current_solution = best;
current_score = best_neighbor_score;
improved = true;
} else {
let should_restart: f64 = match rng.fetch_uniform(0.0, 1.0, 1).front() {
Some(value) => *value as f64,
None => {
return false;
}
};
if should_restart < self.restart_probability {
current_solution.mutate(&mut rng);
current_score = challenge.score(¤t_solution);
} else {
break;
}
}
}
if improved {
*phenotype = current_solution;
}
improved
}
}