use crate::core::candidate::Candidate;
use crate::core::population::Population;
use crate::core::problem::Problem;
use crate::core::result::OptimizationResult;
use crate::core::rng::rng_from_seed;
use crate::pareto::archive::ParetoArchive;
use crate::pareto::dominance::{Dominance, pareto_compare};
use crate::pareto::front::{best_candidate, pareto_front};
use crate::traits::{Initializer, Optimizer, Variation};
#[derive(Debug, Clone)]
pub struct PaesConfig {
pub iterations: usize,
pub archive_size: usize,
pub seed: u64,
}
impl Default for PaesConfig {
fn default() -> Self {
Self { iterations: 1000, archive_size: 100, seed: 42 }
}
}
#[derive(Debug, Clone)]
pub struct Paes<I, V> {
pub config: PaesConfig,
pub initializer: I,
pub variation: V,
}
impl<I, V> Paes<I, V> {
pub fn new(config: PaesConfig, initializer: I, variation: V) -> Self {
Self { config, initializer, variation }
}
}
impl<P, I, V> Optimizer<P> for Paes<I, V>
where
P: Problem,
I: Initializer<P::Decision>,
V: Variation<P::Decision>,
{
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
assert!(
self.config.archive_size > 0,
"PAES archive_size must be greater than 0",
);
let objectives = problem.objectives();
let mut rng = rng_from_seed(self.config.seed);
let mut initial = self.initializer.initialize(1, &mut rng);
assert!(
!initial.is_empty(),
"PAES initializer returned no decisions",
);
let mut current_decision = initial.remove(0);
let mut current_eval = problem.evaluate(¤t_decision);
let mut evaluations = 1usize;
let mut archive = ParetoArchive::new(objectives.clone());
archive.insert(Candidate::new(current_decision.clone(), current_eval.clone()));
for _ in 0..self.config.iterations {
let parents = vec![current_decision.clone()];
let children = self.variation.vary(&parents, &mut rng);
assert!(
!children.is_empty(),
"PAES variation returned no children",
);
let child_decision = children.into_iter().next().unwrap();
let child_eval = problem.evaluate(&child_decision);
evaluations += 1;
match pareto_compare(&child_eval, ¤t_eval, &objectives) {
Dominance::Dominates => {
current_decision = child_decision.clone();
current_eval = child_eval.clone();
}
Dominance::DominatedBy => {
}
Dominance::NonDominated | Dominance::Equal => {
current_decision = child_decision.clone();
current_eval = child_eval.clone();
}
}
archive.insert(Candidate::new(child_decision, child_eval));
archive.insert(Candidate::new(current_decision.clone(), current_eval.clone()));
archive.truncate(self.config.archive_size);
}
let members = archive.into_vec();
let front = pareto_front(&members, &objectives);
let best = best_candidate(&members, &objectives);
OptimizationResult::new(
Population::new(members),
front,
best,
evaluations,
self.config.iterations,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operators::{GaussianMutation, RealBounds};
use crate::tests_support::{SchafferN1, Sphere1D};
#[test]
fn produces_at_least_one_candidate() {
let mut opt = Paes::new(
PaesConfig { iterations: 50, archive_size: 16, seed: 1 },
RealBounds::new(vec![(-5.0, 5.0)]),
GaussianMutation { sigma: 0.3 },
);
let r = opt.run(&SchafferN1);
assert!(!r.population.is_empty());
assert!(!r.pareto_front.is_empty());
}
#[test]
fn archive_size_respected() {
let mut opt = Paes::new(
PaesConfig { iterations: 200, archive_size: 8, seed: 2 },
RealBounds::new(vec![(-5.0, 5.0)]),
GaussianMutation { sigma: 0.2 },
);
let r = opt.run(&SchafferN1);
assert!(r.population.len() <= 8);
}
#[test]
fn single_objective_returns_best() {
let mut opt = Paes::new(
PaesConfig { iterations: 200, archive_size: 8, seed: 3 },
RealBounds::new(vec![(-2.0, 2.0)]),
GaussianMutation { sigma: 0.1 },
);
let r = opt.run(&Sphere1D);
assert!(r.best.is_some());
}
}