use std::sync::Arc;
use crate::configuration::{CrossoverConfiguration, ProblemSolving};
use crate::error::GaError;
use crate::operations::crossover;
use crate::operations::mutation::ValueMutable;
use crate::rng::make_rng;
use crate::traits::MutationOperator;
use crate::traits::{FitnessFn, LinearChromosome};
use rand::Rng;
use super::configuration::AlpsConfiguration;
pub struct AlpsResult<U: LinearChromosome> {
pub layers: Vec<Vec<U>>,
pub best: U,
pub best_fitness: f64,
pub generations: usize,
}
pub struct AlpsEngine<U: LinearChromosome> {
config: AlpsConfiguration,
init_fn: Arc<dyn Fn(usize) -> Vec<U> + Send + Sync>,
fitness_fn: Arc<FitnessFn<U::Gene>>,
}
impl<U: LinearChromosome> AlpsEngine<U> {
pub fn new(
config: AlpsConfiguration,
init_fn: impl Fn(usize) -> Vec<U> + Send + Sync + 'static,
fitness_fn: impl Fn(&[U::Gene]) -> f64 + Send + Sync + 'static,
) -> Result<Self, GaError> {
if config.layer_size == 0 {
return Err(GaError::ConfigurationError(
"AlpsEngine: layer_size must be > 0".to_string(),
));
}
if config.n_layers == 0 {
return Err(GaError::ConfigurationError(
"AlpsEngine: n_layers must be > 0".to_string(),
));
}
Ok(Self {
config,
init_fn: Arc::new(init_fn),
fitness_fn: Arc::new(fitness_fn),
})
}
}
impl<U> AlpsEngine<U>
where
U: LinearChromosome + Clone + ValueMutable + crate::traits::RealValuedMutation + 'static,
{
pub fn run(&mut self) -> AlpsResult<U> {
let max_ages = self.config.max_ages();
let crossover_cfg = CrossoverConfiguration {
method: self.config.crossover,
..CrossoverConfiguration::default()
};
let mut layers: Vec<Vec<U>> = vec![vec![]; self.config.n_layers];
layers[0] = self.fresh_individuals(self.config.layer_size);
let mut best_fitness = layers[0]
.iter()
.map(|u| u.fitness())
.fold(
f64::NAN,
|acc, f| if self.is_better(f, acc) { f } else { acc },
);
let mut best = layers[0]
.iter()
.max_by(|a, b| {
if self.is_better(a.fitness(), b.fitness()) {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Less
}
})
.cloned()
.unwrap_or_else(|| layers[0][0].clone());
let mut rng = make_rng();
let mut generations = 0usize;
for gen in 0..self.config.max_generations {
for layer_idx in 0..self.config.n_layers {
if layers[layer_idx].is_empty() {
continue;
}
let elder_best: Option<U> = if layer_idx + 1 < self.config.n_layers {
self.find_best(&layers[layer_idx + 1])
.map(|i| layers[layer_idx + 1][i].clone())
} else {
None
};
let layer_len = layers[layer_idx].len();
let mut new_offspring: Vec<U> = Vec::with_capacity(layer_len);
for _ in 0..layer_len {
let a = rng.random_range(0..layer_len);
let parent_2 = if let Some(ref elder) = elder_best {
if rng.random::<f64>() < 0.2 {
elder.clone()
} else {
let mut b = rng.random_range(0..layer_len);
while b == a && layer_len > 1 {
b = rng.random_range(0..layer_len);
}
layers[layer_idx][b].clone()
}
} else {
let mut b = rng.random_range(0..layer_len);
while b == a && layer_len > 1 {
b = rng.random_range(0..layer_len);
}
layers[layer_idx][b].clone()
};
let mut offspring =
match crossover::factory(&layers[layer_idx][a], &parent_2, crossover_cfg) {
Ok(children) if !children.is_empty() => {
children.into_iter().next().unwrap()
}
_ => layers[layer_idx][a].clone(),
};
let _ = self
.config
.mutation
.mutate(&mut offspring, &self.config.mutation);
let f = (self.fitness_fn)(offspring.dna());
offspring.set_fitness(f);
offspring.set_age(0);
if self.is_better(f, best_fitness) {
best_fitness = f;
best = offspring.clone();
}
new_offspring.push(offspring);
}
layers[layer_idx].extend(new_offspring);
self.keep_best(&mut layers[layer_idx], self.config.layer_size);
}
for layer in &mut layers {
for ind in layer.iter_mut() {
ind.set_age(ind.age() + 1);
}
}
for layer_idx in 0..self.config.n_layers {
let max_age = max_ages[layer_idx];
let mut promoted: Vec<U> = Vec::new();
layers[layer_idx].retain(|ind| {
if ind.age() > max_age {
promoted.push(ind.clone());
false
} else {
true
}
});
if layer_idx + 1 < self.config.n_layers && !promoted.is_empty() {
layers[layer_idx + 1].extend(promoted);
self.keep_best(&mut layers[layer_idx + 1], self.config.layer_size * 2);
}
}
for layer in &layers {
for ind in layer {
if self.is_better(ind.fitness(), best_fitness) {
best_fitness = ind.fitness();
best = ind.clone();
}
}
}
if self.config.injection_interval > 0
&& gen > 0
&& gen % self.config.injection_interval == 0
{
layers[0] = self.fresh_individuals(self.config.layer_size);
for ind in &layers[0] {
if self.is_better(ind.fitness(), best_fitness) {
best_fitness = ind.fitness();
best = ind.clone();
}
}
}
generations += 1;
if let Some(target) = self.config.fitness_target {
if self.reached_target(best_fitness, target) {
break;
}
}
}
AlpsResult {
layers,
best,
best_fitness,
generations,
}
}
fn fresh_individuals(&self, n: usize) -> Vec<U> {
let mut inds = (self.init_fn)(n);
for ind in &mut inds {
let f = (self.fitness_fn)(ind.dna());
ind.set_fitness(f);
ind.set_age(0);
}
inds
}
fn find_best(&self, pop: &[U]) -> Option<usize> {
if pop.is_empty() {
return None;
}
let mut best_idx = 0;
let mut best_f = pop[0].fitness();
for (i, ind) in pop.iter().enumerate().skip(1) {
if self.is_better(ind.fitness(), best_f) {
best_f = ind.fitness();
best_idx = i;
}
}
Some(best_idx)
}
fn keep_best(&self, pop: &mut Vec<U>, k: usize) {
if pop.len() <= k {
return;
}
match self.config.problem_solving {
ProblemSolving::Minimization => {
pop.sort_unstable_by(|a, b| {
a.fitness()
.partial_cmp(&b.fitness())
.unwrap_or(std::cmp::Ordering::Equal)
});
}
_ => {
pop.sort_unstable_by(|a, b| {
b.fitness()
.partial_cmp(&a.fitness())
.unwrap_or(std::cmp::Ordering::Equal)
});
}
}
pop.truncate(k);
}
fn is_better(&self, candidate: f64, current: f64) -> bool {
if current.is_nan() {
return true;
}
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,
}
}
}