use std::borrow::Cow;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use super::configuration::{DeAdaptive, DeConfiguration, DeMutationStrategy};
use super::crossover::crossover;
use super::mutation::{mutate, DeMutationParams, JadeState, LShadeState};
use crate::configuration::ProblemSolving;
use crate::rng::make_rng;
use crate::stats::GenerationStats;
use crate::traits::RealGene;
use crate::traits::{FitnessFn, LinearChromosome};
use rand::Rng;
pub struct DeResult<U: LinearChromosome> {
pub population: Vec<U>,
pub best: U,
pub best_fitness: f64,
pub generations: usize,
}
pub struct DeEngine<U: LinearChromosome>
where
U::Gene: RealGene,
{
config: DeConfiguration,
init_fn: Arc<dyn Fn(usize) -> Vec<U> + Send + Sync>,
fitness_fn: Arc<FitnessFn<U::Gene>>,
fitness_cache: Option<Arc<Mutex<crate::fitness::cache::FitnessCache>>>,
}
impl<U: LinearChromosome + Clone> DeEngine<U>
where
U::Gene: RealGene,
{
pub fn new(
config: DeConfiguration,
init_fn: impl Fn(usize) -> Vec<U> + Send + Sync + 'static,
fitness_fn: impl Fn(&[U::Gene]) -> f64 + Send + Sync + 'static,
) -> Self {
Self {
config,
init_fn: Arc::new(init_fn),
fitness_fn: Arc::new(fitness_fn),
fitness_cache: None,
}
}
pub fn run(&mut self) -> DeResult<U>
where
U::Gene: Debug,
{
let mut rng = make_rng();
let pop_size = self.config.population_size;
let is_maximization = matches!(self.config.problem_solving, ProblemSolving::Maximization);
if let Some(size) = self.config.fitness_cache_size {
if self.fitness_cache.is_none() {
let (wrapped_fn, cache_handle) =
crate::fitness::cache::wrap_with_cache(Arc::clone(&self.fitness_fn), size);
self.fitness_fn = wrapped_fn;
self.fitness_cache = Some(cache_handle);
}
}
let mut pop: Vec<U> = (self.init_fn)(pop_size);
for ind in &mut pop {
let f = (self.fitness_fn)(ind.dna());
ind.set_fitness(f);
}
let (mut best_idx, mut best_fitness) = self.find_best(&pop);
let mut best = pop[best_idx].clone();
let mut jade = JadeState::new();
let mut lshade = match &self.config.adaptive {
DeAdaptive::LShade { history_size } => Some(LShadeState::new(*history_size)),
_ => None,
};
let mut archive: Vec<U> = Vec::new();
let mut generations = 0usize;
let mut all_stats: Vec<GenerationStats> = Vec::with_capacity(self.config.max_generations);
let (mut prev_cache_hits, mut prev_cache_misses) = match &self.fitness_cache {
Some(ch) => {
let c = ch.lock().expect("fitness cache lock poisoned");
(c.hits(), c.misses())
}
None => (0, 0),
};
for _gen in 0..self.config.max_generations {
let eff_strategy = match &self.config.adaptive {
DeAdaptive::Jade { p, .. } => {
let _ = p; DeMutationStrategy::CurrentToBest1 }
_ => self.config.mutation_strategy.clone(),
};
for i in 0..pop_size {
let (f, cr) = match &self.config.adaptive {
DeAdaptive::None => (self.config.mutation_factor, self.config.crossover_rate),
DeAdaptive::Jade { .. } => (jade.draw_f(&mut rng), jade.draw_cr(&mut rng)),
DeAdaptive::LShade { .. } => {
let ls = lshade.as_ref().unwrap();
(ls.draw_f(&mut rng), ls.draw_cr(&mut rng))
}
};
let eff_best = match &self.config.adaptive {
DeAdaptive::Jade { p, .. } => {
let p_count = ((pop_size as f64 * p).ceil() as usize).max(1);
self.select_pbest(&pop, p_count, &mut rng)
}
_ => best_idx,
};
let arc_ref: Option<&[U]> = match &self.config.adaptive {
DeAdaptive::Jade { .. } if !archive.is_empty() => Some(&archive),
_ => None,
};
let mutant = mutate(
&DeMutationParams {
strategy: &eff_strategy,
target_idx: i,
best_idx: eff_best,
f,
},
&pop,
&mut rng,
arc_ref,
);
let trial_dna = crossover(
&self.config.crossover_mode,
pop[i].dna(),
&mutant,
cr,
&mut rng,
);
let trial_fitness = (self.fitness_fn)(&trial_dna);
let improved = self.is_better(trial_fitness, pop[i].fitness());
if improved {
if matches!(&self.config.adaptive, DeAdaptive::Jade { .. }) {
archive.push(pop[i].clone());
if archive.len() > pop_size {
let drop = rng.random_range(0..archive.len());
archive.swap_remove(drop);
}
jade.record_success(f, cr);
}
if let Some(ref mut ls) = lshade {
ls.record_success(f, cr);
}
let mut trial = pop[i].clone();
trial.set_dna(Cow::Owned(trial_dna));
trial.set_fitness(trial_fitness);
pop[i] = trial;
if self.is_better(trial_fitness, best_fitness) {
best_fitness = trial_fitness;
best_idx = i;
best = pop[i].clone();
}
}
}
match &self.config.adaptive {
DeAdaptive::Jade { c, .. } => jade.update(*c),
DeAdaptive::LShade { .. } => lshade.as_mut().unwrap().update(),
DeAdaptive::None => {}
}
generations += 1;
let fitness_values: Vec<f64> = pop.iter().map(|c| c.fitness()).collect();
let mut stats =
GenerationStats::from_fitness_values(generations, &fitness_values, is_maximization);
if let Some(ref ch) = self.fitness_cache {
let c = ch.lock().expect("fitness cache lock poisoned");
stats.cache_hits = Some(c.hits().saturating_sub(prev_cache_hits));
stats.cache_misses = Some(c.misses().saturating_sub(prev_cache_misses));
prev_cache_hits = c.hits();
prev_cache_misses = c.misses();
}
all_stats.push(stats);
let (bi, bf) = self.find_best(&pop);
if self.is_better(bf, best_fitness) {
best_fitness = bf;
best_idx = bi;
best = pop[bi].clone();
}
if let Some(target) = self.config.fitness_target {
if self.reached_target(best_fitness, target) {
break;
}
}
}
DeResult {
population: pop,
best,
best_fitness,
generations,
}
}
fn find_best(&self, pop: &[U]) -> (usize, f64) {
let mut best_idx = 0;
let mut best_fit = pop[0].fitness();
for (i, ind) in pop.iter().enumerate().skip(1) {
if self.is_better(ind.fitness(), best_fit) {
best_fit = ind.fitness();
best_idx = i;
}
}
(best_idx, best_fit)
}
fn select_pbest(&self, pop: &[U], p_count: usize, rng: &mut impl rand::Rng) -> usize {
let mut indexed: Vec<(usize, f64)> = pop
.iter()
.enumerate()
.map(|(i, u)| (i, u.fitness()))
.collect();
if matches!(self.config.problem_solving, ProblemSolving::Minimization) {
indexed.sort_unstable_by(|a, b| {
a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
});
} else {
indexed.sort_unstable_by(|a, b| {
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
});
}
let top = indexed[..p_count.min(indexed.len())].to_vec();
let pick = rng.random_range(0..top.len());
top[pick].0
}
fn is_better(&self, candidate: f64, current: f64) -> bool {
match self.config.problem_solving {
ProblemSolving::Minimization => candidate < current,
ProblemSolving::Maximization => candidate > current,
ProblemSolving::FixedFitness => {
if let Some(t) = self.config.fitness_target {
(candidate - t).abs() < (current - t).abs()
} else {
candidate < current
}
}
}
}
fn reached_target(&self, fitness: f64, target: f64) -> bool {
match self.config.problem_solving {
ProblemSolving::Minimization => fitness <= target,
ProblemSolving::Maximization => fitness >= target,
ProblemSolving::FixedFitness => (fitness - target).abs() < 1e-6,
}
}
}