use crate::error::{GeneticError, Result};
use crate::evolution::Challenge;
use crate::local_search::application_stategy::LocalSearchApplicationStrategy;
use crate::local_search::LocalSearch;
use crate::phenotype::Phenotype;
use std::marker::PhantomData;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct LocalSearchManager<P, L, A, C>
where
P: Phenotype,
L: LocalSearch<P, C> + Clone,
A: LocalSearchApplicationStrategy<P> + Clone,
C: Challenge<P> + Clone,
{
algorithm: L,
application_strategy: A,
#[cfg_attr(feature = "serde", serde(skip))]
_marker: PhantomData<(P, C)>,
}
impl<P, L, A, C> LocalSearchManager<P, L, A, C>
where
P: Phenotype,
L: LocalSearch<P, C> + Clone,
A: LocalSearchApplicationStrategy<P> + Clone,
C: Challenge<P> + Clone,
{
pub fn new(algorithm: L, application_strategy: A) -> Self {
Self {
algorithm,
application_strategy,
_marker: PhantomData,
}
}
fn validate_inputs(&self, population: &[P], fitness: &[f64]) -> Result<()> {
if population.is_empty() {
return Err(GeneticError::EmptyPopulation);
}
if population.len() != fitness.len() {
return Err(GeneticError::Configuration(format!(
"Population size ({}) does not match fitness vector size ({})",
population.len(),
fitness.len()
)));
}
Ok(())
}
pub fn apply(&self, population: &mut [P], fitness: &[f64], challenge: &C) -> Result<Vec<bool>> {
self.validate_inputs(population, fitness)?;
let indices = self
.application_strategy
.select_for_local_search(population, fitness)?;
let mut improvements = vec![false; population.len()];
for idx in indices {
improvements[idx] = self.algorithm.search(&mut population[idx], challenge);
}
Ok(improvements)
}
pub fn algorithm(&self) -> &L {
&self.algorithm
}
pub fn application_strategy(&self) -> &A {
&self.application_strategy
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::local_search::application_stategy::{AllIndividualsStrategy, TopNStrategy};
use crate::local_search::HillClimbing;
use crate::rng::RandomNumberGenerator;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Clone, Debug)]
struct TestPhenotype {
value: f64,
}
impl Phenotype for TestPhenotype {
fn crossover(&mut self, other: &Self) {
self.value = (self.value + other.value) / 2.0;
}
fn mutate(&mut self, _rng: &mut RandomNumberGenerator) {
self.value += 0.1;
}
}
#[derive(Clone, Debug)]
struct TestChallenge {
target: f64,
evaluations: Arc<AtomicUsize>,
}
impl TestChallenge {
fn new(target: f64) -> Self {
Self {
target,
evaluations: Arc::new(AtomicUsize::new(0)),
}
}
fn get_evaluations(&self) -> usize {
self.evaluations.load(Ordering::SeqCst)
}
}
impl Challenge<TestPhenotype> for TestChallenge {
fn score(&self, phenotype: &TestPhenotype) -> f64 {
self.evaluations.fetch_add(1, Ordering::SeqCst);
-((phenotype.value - self.target).abs()) }
}
#[test]
fn test_local_search_manager_all_individuals() {
let mut population = vec![
TestPhenotype { value: 1.0 },
TestPhenotype { value: 2.0 },
TestPhenotype { value: 3.0 },
];
let fitness = vec![-1.0, -2.0, -3.0]; let challenge = TestChallenge::new(0.0);
let hill_climbing = HillClimbing::new(10, 10).unwrap();
let all_strategy = AllIndividualsStrategy::new();
let manager = LocalSearchManager::new(hill_climbing, all_strategy);
let result = manager
.apply(&mut population, &fitness, &challenge)
.unwrap();
assert_eq!(result.len(), 3);
assert!(challenge.get_evaluations() > 0);
}
#[test]
fn test_local_search_manager_top_n() {
let mut population = vec![
TestPhenotype { value: 1.0 },
TestPhenotype { value: 2.0 },
TestPhenotype { value: 3.0 },
];
let fitness = vec![-1.0, -2.0, -3.0]; let challenge = TestChallenge::new(0.0);
let hill_climbing = HillClimbing::new(10, 10).unwrap();
let top_strategy = TopNStrategy::new_minimizing(1); let manager = LocalSearchManager::new(hill_climbing, top_strategy);
let result = manager
.apply(&mut population, &fitness, &challenge)
.unwrap();
assert_eq!(result.len(), 3);
assert!(challenge.get_evaluations() > 0);
}
#[test]
fn test_local_search_manager_empty_population() {
let mut population: Vec<TestPhenotype> = vec![];
let fitness: Vec<f64> = vec![];
let challenge = TestChallenge::new(0.0);
let hill_climbing = HillClimbing::new(10, 10).unwrap();
let all_strategy = AllIndividualsStrategy::new();
let manager = LocalSearchManager::new(hill_climbing, all_strategy);
let result = manager.apply(&mut population, &fitness, &challenge);
assert!(result.is_err());
}
#[test]
fn test_local_search_manager_mismatched_lengths() {
let mut population = vec![TestPhenotype { value: 1.0 }, TestPhenotype { value: 2.0 }];
let fitness = vec![-1.0, -2.0, -3.0]; let challenge = TestChallenge::new(0.0);
let hill_climbing = HillClimbing::new(10, 10).unwrap();
let all_strategy = AllIndividualsStrategy::new();
let manager = LocalSearchManager::new(hill_climbing, all_strategy);
let result = manager.apply(&mut population, &fitness, &challenge);
assert!(result.is_err());
}
}