use crate::error::GaError;
use crate::traits::{LinearChromosome, LocalSearchOperator};
use rand::Rng;
use std::borrow::Cow;
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LocalSearch {
HillClimbing,
}
impl LocalSearchOperator for LocalSearch {
fn improve<U>(
&self,
individual: &mut U,
fitness_fn: &dyn Fn(&[U::Gene]) -> f64,
) -> Result<usize, GaError>
where
U: LinearChromosome + Send + Sync + 'static + Clone,
{
match self {
LocalSearch::HillClimbing => {
HillClimbingConfig::default().improve(individual, fitness_fn)
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HillClimbingConfig {
pub step_size: f64,
pub max_iterations: usize,
}
impl Default for HillClimbingConfig {
fn default() -> Self {
Self {
step_size: 0.1,
max_iterations: 20,
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LocalSearchApplicationStrategy {
#[default]
AllOffspring,
BestN {
n: usize,
},
Probabilistic {
probability: f64,
},
EveryNGenerations {
interval: usize,
},
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LocalSearchMode {
#[default]
Lamarckian,
Baldwinian,
}
pub fn factory(op: LocalSearch) -> LocalSearch {
op
}
pub fn factory_with_config(op: LocalSearch, config: HillClimbingConfig) -> HillClimbingConfig {
match op {
LocalSearch::HillClimbing => config,
}
}
impl LocalSearchOperator for HillClimbingConfig {
fn improve<U>(
&self,
individual: &mut U,
fitness_fn: &dyn Fn(&[U::Gene]) -> f64,
) -> Result<usize, GaError>
where
U: LinearChromosome + Send + Sync + 'static + Clone,
{
let any_ref: &dyn std::any::Any = individual;
if !any_ref.is::<crate::chromosomes::Range<f64>>() {
return Err(GaError::LocalSearchError(
"HillClimbing local search requires RangeChromosome<f64>".to_string(),
));
}
let step = self.step_size;
let mut rng = crate::rng::make_rng();
let mut improvements = 0usize;
for _ in 0..self.max_iterations {
let dim = individual.dna().len();
if dim == 0 {
break;
}
let j = rng.random_range(0..dim);
let delta: f64 = (rng.random::<f64>() * 2.0 - 1.0) * step;
let original_dna = individual.dna().to_vec();
let mut new_dna = individual.dna().to_vec();
unsafe {
use crate::genotypes::Range as RangeGene;
let ptr = new_dna.as_mut_ptr() as *mut RangeGene<f64>;
let gene = &mut *ptr.add(j);
let new_val = gene.value + delta;
if !gene.ranges.is_empty() {
gene.value = new_val.clamp(gene.ranges[0].0, gene.ranges[0].1);
} else {
gene.value = new_val;
}
}
individual.set_dna(Cow::Owned(new_dna));
let current_fitness = individual.fitness();
let new_fitness = fitness_fn(individual.dna());
if new_fitness < current_fitness {
individual.set_fitness(new_fitness);
improvements += 1;
} else {
individual.set_dna(Cow::Owned(original_dna));
}
}
Ok(improvements)
}
}