use crate::evolution::crossover::mutation::mutate_with_log;
use crate::evolution::{Chromosome, GenePool};
use crate::lineage::Lineage;
use crate::search::{EvalCandidate, SearchAlgorithm};
use crate::types::{Budget, EvolutionError, OracleVerdict, SearchStats};
use rand::rngs::StdRng;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HillClimbing {
current: Chromosome,
gene_pool: GenePool,
generation: u32,
eval_counter: u64,
best: Chromosome,
}
impl HillClimbing {
#[must_use]
pub fn new() -> Self {
Self {
current: Chromosome::new(vec![]),
gene_pool: GenePool::default_wafrift(),
generation: 0,
eval_counter: 0,
best: Chromosome::new(vec![]),
}
}
fn neighbor(&self, rng: &mut StdRng) -> Chromosome {
let mut child = self.current.clone();
let log = mutate_with_log(&mut child, &self.gene_pool, 0.25, rng);
child.lineage = Lineage::mutation(&self.current, log, self.generation);
child
}
}
impl Default for HillClimbing {
fn default() -> Self {
Self::new()
}
}
impl SearchAlgorithm for HillClimbing {
fn name(&self) -> &'static str {
"hill_climbing"
}
fn initialize(&mut self, population: Vec<Chromosome>, gene_pool: &GenePool, _rng: &mut StdRng) {
self.gene_pool = gene_pool.clone();
if let Some(best) = population.iter().max_by(|a, b| {
a.fitness
.partial_cmp(&b.fitness)
.unwrap_or(std::cmp::Ordering::Equal)
}) {
self.current = best.clone();
self.best = best.clone();
} else {
self.current = baseline(gene_pool);
self.best = self.current.clone();
}
}
fn request_evaluations(&mut self, n: usize, rng: &mut StdRng) -> Vec<EvalCandidate> {
let mut out = Vec::with_capacity(n);
for _ in 0..n {
self.eval_counter += 1;
out.push(EvalCandidate {
id: self.eval_counter,
chromosome: self.neighbor(rng),
});
}
out
}
fn submit_evaluations(&mut self, results: Vec<(u64, OracleVerdict)>) {
for (_id, verdict) in results {
let fitness = verdict.to_fitness();
if fitness >= self.current.fitness {
let mut next = self.current.clone();
next.record_verdict(&verdict);
self.current = next;
if self.current.fitness > self.best.fitness {
self.best = self.current.clone();
}
}
}
self.generation += 1;
}
fn should_terminate(&self, stats: &SearchStats, budget: &Budget) -> bool {
stats.evaluations >= budget.max_requests
|| stats.generation >= budget.max_generations
|| stats.stagnation_counter >= budget.stagnation_limit
}
fn best(&self) -> Option<&Chromosome> {
Some(&self.best)
}
fn checkpoint(&self) -> Result<Vec<u8>, EvolutionError> {
serde_json::to_vec(self).map_err(|e| EvolutionError::SerializationFailed(e.to_string()))
}
fn restore(&mut self, bytes: &[u8]) -> Result<(), EvolutionError> {
*self = serde_json::from_slice(bytes)
.map_err(|e| EvolutionError::DeserializationFailed(e.to_string()))?;
Ok(())
}
}
fn baseline(gene_pool: &GenePool) -> Chromosome {
let genes = gene_pool
.gene_names()
.into_iter()
.map(|name| (name.to_string(), String::from("None")))
.collect();
Chromosome::new(genes)
}