@@ 1,9 1,11 @@
+use std::convert::Infallible;
use std::ops::{AddAssign, Sub};
use std::{cmp::Ordering, error::Error};
use std::fmt::Debug;
use rand::RngCore;
+use crate::constraints::ConstraintFunction;
use crate::evolution::{evolution_algorithm_best_candidate, EvolutionCandidate, EvolutionStats};
use crate::population::{EvaluatedChromosome, EvaluatedPopulation};
use crate::{comparison::{BetterThanOperator, MinimizingOperator}, crossover::Crossover, evolution::{evolution_algorithm, EvolutionResult}, fitness::FitnessFunction, pairing::Pairing, perturbation::PerturbationOperator, population::Population, replacement::BestReplacement, selection::TournamentSelection};
@@ 17,6 19,16 @@ pub struct MOFitness<'a,
objectives: [Box<dyn FitnessFunction<In = TChromosome, Out = TOut, Err = TErr> + 'a>; OBJECTIVES]
}
+pub struct ConstrainedMOFitness<'a,
+ const OBJECTIVES: usize,
+ const CONSTRAINTS: usize,
+ TChromosome,
+ TOut,
+ TErr: Error> {
+ objectives: [Box<dyn FitnessFunction<In = TChromosome, Out = TOut, Err = TErr> + 'a>; OBJECTIVES],
+ constraints: [Box<dyn ConstraintFunction<Chromosome = TChromosome, Out = TOut, Err = Infallible>>; CONSTRAINTS]
+}
+
pub fn a_dominates_b<const OBJECTIVES: usize, TOut>(
a: &[TOut; OBJECTIVES], b: &[TOut; OBJECTIVES],
better_than: &(impl BetterThanOperator<TOut> + ?Sized)
@@ 24,6 36,31 @@ pub fn a_dominates_b<const OBJECTIVES: usize, TOut>(
a.iter().zip(b.iter()).all(|(a, b)| better_than.better_than(a, b) || better_than.equal(a, b))
}
+pub fn a_constraint_dominates_b<const OBJECTIVES: usize, const CONSTRAINTS: usize, TOut: PartialOrd>(
+ a: &ConstrainedMOEvaluation<OBJECTIVES, CONSTRAINTS, TOut>, b: &ConstrainedMOEvaluation<OBJECTIVES, CONSTRAINTS, TOut>,
+ better_than: &(impl BetterThanOperator<TOut> + ?Sized)
+) -> bool {
+ let a_feasible = a.is_feasible_full;
+ let b_feasible = b.is_feasible_full;
+
+ match (a_feasible, b_feasible) {
+ (true, true) => a_dominates_b(&a.evaluations, &b.evaluations, better_than),
+ (true, false) => true,
+ (false, true) => false,
+ (false, false) => {
+ (0..CONSTRAINTS).all(|i| {
+ if a.is_feasible[i] {
+ true
+ } else if b.is_feasible[i] {
+ false
+ } else {
+ a.constraints[i] < b.constraints[i]
+ }
+ })
+ }
+ }
+}
+
pub fn a_dominated_by_b<const OBJECTIVES: usize, TOut>(
a: &[TOut; OBJECTIVES],
b: &[TOut; OBJECTIVES],
@@ 32,6 69,13 @@ pub fn a_dominated_by_b<const OBJECTIVES: usize, TOut>(
a_dominates_b(b, a, better_than)
}
+pub fn a_constraint_dominated_by_b<const OBJECTIVES: usize, const CONSTRAINTS: usize, TOut: PartialOrd>(
+ a: &ConstrainedMOEvaluation<OBJECTIVES, CONSTRAINTS, TOut>, b: &ConstrainedMOEvaluation<OBJECTIVES, CONSTRAINTS, TOut>,
+ better_than: &(impl BetterThanOperator<TOut> + ?Sized)
+) -> bool {
+ a_constraint_dominates_b(b, a, better_than)
+}
+
impl<'a,
const OBJECTIVES: usize,
TChromosome,
@@ 65,6 109,69 @@ impl<'a,
}
}
+#[derive(Clone, PartialEq, Debug)]
+pub struct ConstrainedMOEvaluation<const OBJECTIVES: usize, const CONSTRAINTS: usize, TOut> {
+ evaluations: [TOut; OBJECTIVES],
+ constraints: [TOut; CONSTRAINTS],
+ is_feasible: [bool; CONSTRAINTS],
+ is_feasible_full: bool,
+}
+
+impl<'a,
+ const OBJECTIVES: usize,
+ const CONSTRAINTS: usize,
+ TChromosome,
+ TOut,
+ TErr: Error>
+ ConstrainedMOFitness<'a, OBJECTIVES, CONSTRAINTS, TChromosome, TOut, TErr>
+{
+ pub fn new(
+ objectives: [Box<dyn FitnessFunction<In = TChromosome, Out = TOut, Err = TErr> + 'a>; OBJECTIVES],
+ constraints: [Box<dyn ConstraintFunction<Chromosome = TChromosome, Out = TOut, Err = Infallible>>; CONSTRAINTS]
+ ) -> Self {
+ Self { objectives, constraints }
+ }
+}
+
+impl<'a,
+ const OBJECTIVES: usize,
+ const CONSTRAINTS: usize,
+ TChromosome,
+ TOut: Default + Copy,
+ TErr: Error + 'static>
+ FitnessFunction for ConstrainedMOFitness<'a, OBJECTIVES, CONSTRAINTS, TChromosome, TOut, TErr> {
+ type In = TChromosome;
+ type Out = ConstrainedMOEvaluation<OBJECTIVES, CONSTRAINTS, TOut>;
+ type Err = TErr;
+
+ fn fit(&self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
+ let mut evaluations = [Default::default(); OBJECTIVES];
+ let mut constraints = [Default::default(); CONSTRAINTS];
+ let mut is_feasible = [true; CONSTRAINTS];
+ let mut is_feasible_full = true;
+
+ for (i, objective) in self.objectives.iter().enumerate() {
+ evaluations[i] = objective.fit(inp)?;
+ }
+
+ for (i, constraint) in self.constraints.iter().enumerate() {
+ constraints[i] = constraint.evaluate(inp).unwrap();
+ if !constraint.is_feasible(inp).unwrap() {
+ is_feasible_full = false;
+ is_feasible[i] = false;
+ }
+ }
+
+ Ok(ConstrainedMOEvaluation {
+ evaluations,
+ constraints,
+ is_feasible,
+ is_feasible_full
+ })
+ }
+}
+
+
#[derive(Debug, Clone, PartialEq)]
pub struct NSGAEvaluation<TOut: PartialEq + Clone, const OBJECTIVES: usize> {
pub evaluations: [TOut; OBJECTIVES],
@@ 90,7 197,12 @@ pub struct NSGAFitness<'a, const OBJECTIVES: usize, TChromosome, TOut, TErr: Err
better_than: &'a dyn BetterThanOperator<TOut>
}
-pub fn get_nondominated_fronts<const OBJECTIVES: usize,TChromosome, TOut>(
+pub struct ConstrainedNSGAFitness<'a, const OBJECTIVES: usize, const CONSTRAINTS: usize, TChromosome, TOut, TErr: Error> {
+ mo_fitness: ConstrainedMOFitness<'a, OBJECTIVES, CONSTRAINTS, TChromosome, TOut, TErr>,
+ better_than: &'a dyn BetterThanOperator<TOut>
+}
+
+pub fn get_nondominated_fronts<const OBJECTIVES: usize, TChromosome, TOut>(
fitted: &[EvaluatedChromosome<TChromosome, [TOut; OBJECTIVES]>],
better_than: &(impl BetterThanOperator<TOut> + ?Sized)
) -> (Vec<usize>, Vec<Vec<usize>>) {
@@ 144,6 256,60 @@ pub fn get_nondominated_fronts<const OBJECTIVES: usize,TChromosome, TOut>(
(fronts, front_indices)
}
+pub fn get_constrained_nondominated_fronts<const OBJECTIVES: usize, const CONSTRAINTS: usize, TChromosome, TOut: PartialOrd>(
+ fitted: &[EvaluatedChromosome<TChromosome, ConstrainedMOEvaluation<OBJECTIVES, CONSTRAINTS, TOut>>],
+ better_than: &(impl BetterThanOperator<TOut> + ?Sized)
+) -> (Vec<usize>, Vec<Vec<usize>>) {
+ let mut remaining_indices = (0..fitted.len()).collect::<Vec<_>>();
+ let mut current_front = 0;
+ let mut fronts = vec![0; fitted.len()];
+ let mut front_indices = vec![];
+ let mut current_for_removal = vec![];
+
+ while !remaining_indices.is_empty() {
+ let mut current_front_indices = vec![];
+ 'outer: for (i, ¤t) in remaining_indices.iter().enumerate() {
+ let current_eval = &fitted[current].evaluation;
+ for &i in remaining_indices.iter() {
+ let i_eval = &fitted[i].evaluation;
+
+ if i == current {
+ continue;
+ }
+
+ if current_eval.evaluations
+ .iter()
+ .zip(i_eval.evaluations.iter())
+ .all(|(a, b)| better_than.equal(a, b)) {
+ continue;
+ }
+
+ if a_constraint_dominated_by_b(current_eval, i_eval, better_than) {
+ // At least one dominates current...
+ continue 'outer;
+ }
+ }
+
+ // None dominates current...
+ fronts[current] = current_front;
+ current_front_indices.push(current);
+ current_for_removal.push(i);
+ }
+
+ front_indices.push(current_front_indices);
+
+ for &for_removal in current_for_removal.iter().rev() {
+ remaining_indices.swap_remove(for_removal);
+ }
+ current_for_removal.clear();
+
+ current_front += 1;
+ }
+
+ // current_front is counting from backwards, so reverse it
+ (fronts, front_indices)
+}
+
pub fn get_front_crowding_distances<const NORMALIZE: bool,
const OBJECTIVES: usize,
TChromosome,
@@ 302,6 468,73 @@ impl<'a,
}
}
+impl<'a,
+ const OBJECTIVES: usize,
+ const CONSTRAINTS: usize,
+ TChromosome,
+ TOut: Copy + Into<f64>,
+ TErr: Error>
+ ConstrainedNSGAFitness<'a, OBJECTIVES, CONSTRAINTS, TChromosome, TOut, TErr> {
+ pub fn new(
+ objectives: [Box<dyn FitnessFunction<In = TChromosome, Out = TOut, Err = TErr> + 'a>; OBJECTIVES],
+ constraints: [Box<dyn ConstraintFunction<Chromosome = TChromosome, Out = TOut, Err = Infallible>>; CONSTRAINTS],
+ better_than: &'a impl BetterThanOperator<TOut>
+ ) -> Self {
+ Self {
+ mo_fitness: ConstrainedMOFitness::<'a, OBJECTIVES, CONSTRAINTS, _, _, _>::new(objectives, constraints),
+ better_than
+ }
+ }
+}
+
+impl<'a,
+ const OBJECTIVES: usize,
+ const CONSTRAINTS: usize,
+ TChromosome: Clone,
+ TOut: PartialEq + Default + Copy + PartialOrd + Into<f64>,
+ TErr: Error + 'static>
+ FitnessFunction for ConstrainedNSGAFitness<'a, OBJECTIVES, CONSTRAINTS, TChromosome, TOut, TErr> {
+ type In = TChromosome;
+ type Out = NSGAEvaluation<TOut, OBJECTIVES>;
+ type Err = TErr;
+
+ fn fit(&self, _: &Self::In) -> Result<Self::Out, Self::Err> {
+ panic!("NSGA fitness does not implement single fit. To get all the non dominated fronts informations, fit_population needs to be called.");
+ }
+
+ fn fit_population(&self, inp: Vec<Self::In>) -> Result<Vec<crate::population::EvaluatedChromosome<Self::In, Self::Out>>, Self::Err> {
+ let fitted = self.mo_fitness.fit_population(inp)?;
+
+ let (fronts, front_indices) = get_constrained_nondominated_fronts(&fitted, self.better_than);
+
+ let fitted =
+ fitted.into_iter().map(|individual|
+ EvaluatedChromosome {
+ chromosome: individual.chromosome,
+ evaluation: individual.evaluation.evaluations
+ }).collect::<Vec<_>>();
+
+ let cd = get_crowding_distances
+ ::<true, OBJECTIVES, TChromosome, TOut>(&fitted, &front_indices, self.better_than);
+
+ Ok(fitted
+ .into_iter()
+ .zip(fronts.into_iter())
+ .zip(cd.into_iter())
+ .map(|((individual, nondominated_front), crowding_distance)| {
+ EvaluatedChromosome {
+ chromosome: individual.chromosome,
+ evaluation: NSGAEvaluation {
+ evaluations: individual.evaluation,
+ nondominated_front,
+ crowding_distance
+ }
+ }
+ })
+ .collect())
+ }
+}
+
pub fn nsga_2<const OBJECTIVES: usize,
TChromosome: Clone,
TResult: Clone + Debug + PartialEq + Default + Copy + PartialOrd + Into<f64>,
@@ 354,6 587,61 @@ pub fn nsga_2<const OBJECTIVES: usize,
}))
}
+pub fn constrained_nsga_2<
+ const OBJECTIVES: usize,
+ const CONSTRAINTS: usize,
+ TChromosome: Clone,
+ TResult: Clone + Debug + PartialEq + Default + Copy + PartialOrd + Into<f64>,
+ TErr: Error + 'static,
+ const DParents: usize,
+ TPairing: Pairing<DParents, Chromosome = TChromosome, Out = NSGAEvaluation<TResult, OBJECTIVES>>,
+ TCrossover: Crossover<DParents, Chromosome = TChromosome, Out = NSGAEvaluation<TResult, OBJECTIVES>>,
+ TPerturbation: PerturbationOperator<Chromosome = TChromosome>> (
+ initial_population: Population<TChromosome>,
+ parents_count: usize,
+ // TODO: possibility for objectives with different outputs?
+ objectives: [Box<dyn FitnessFunction<In = TChromosome, Out = TResult, Err = TErr> + '_>; OBJECTIVES],
+ constraints: [Box<dyn ConstraintFunction<Chromosome = TChromosome, Out = TResult, Err = Infallible>>; CONSTRAINTS],
+ pairing: &mut TPairing,
+ crossover: &mut TCrossover,
+ perturbation: &mut TPerturbation,
+ // TODO: possibility for better_than different for different fitness functions
+ better_than: &impl BetterThanOperator<TResult>,
+ // TODO: termination condition
+ iterations: usize,
+ rng: &mut dyn RngCore,
+ mut evolutionary_strategy: impl FnMut(
+ usize,
+ &EvolutionStats<TChromosome, NSGAEvaluation<TResult, OBJECTIVES>>,
+ &EvaluatedPopulation<TChromosome, NSGAEvaluation<TResult, OBJECTIVES>>
+
+ // TODO
+ ),
+ better_than_stats: impl Fn(&TChromosome, &NSGAEvaluation<TResult, OBJECTIVES>, &Option<EvolutionCandidate<TChromosome, NSGAEvaluation<TResult, OBJECTIVES>>>) -> bool,
+) -> Result<EvolutionResult<TChromosome, [TResult; OBJECTIVES]>, Box<dyn Error>> {
+ let result = evolution_algorithm_best_candidate(
+ initial_population,
+ parents_count,
+ &mut ConstrainedNSGAFitness::new(objectives, constraints, better_than),
+ &mut TournamentSelection::new(5, 0.99),
+ pairing,
+ crossover,
+ perturbation,
+ &mut BestReplacement::new(),
+ &MinimizingOperator::new(),
+ iterations,
+ rng,
+ |iteration, stats, population, _, _, _, _, _, _| {
+ evolutionary_strategy(iteration, stats, population);
+ },
+ better_than_stats
+ )?;
+
+ Ok(result.map(|res| {
+ res.evaluations
+ }))
+}
+
#[cfg(test)]
pub mod test {
use std::str::FromStr;