@@ 0,0 1,217 @@
+use std::{collections::VecDeque, convert::Infallible, error::Error};
+
+use thiserror::Error;
+
+use crate::{comparison::BetterThanOperator, crossover::Crossover, evolution::EvolutionStats, fitness::FitnessFunction, pairing::Pairing, perturbation::PerturbationOperator, population::EvaluatedPopulation, replacement::Replacement, selection::Selection};
+
+pub trait ConstraintFunction {
+ type Chromosome;
+ type Out;
+ type Err: Error + 'static;
+
+ fn evaluate(&self, chromosome: &Self::Chromosome) -> Result<Self::Out, Self::Err>;
+ fn is_valid(&self, chromosome: &Self::Chromosome) -> Result<bool, Self::Err>;
+}
+
+pub struct LowerThanConstraintFunction<TChromosome, TOut> {
+ fun: Box<dyn Fn(&TChromosome) -> TOut>
+}
+
+impl<TChromosome, TOut> LowerThanConstraintFunction<TChromosome, TOut> {
+ pub fn new(fun: Box<dyn Fn(&TChromosome) -> TOut>) -> Self {
+ Self {
+ fun
+ }
+ }
+}
+
+impl<TChromosome, TOut: Default + PartialOrd> ConstraintFunction for LowerThanConstraintFunction<TChromosome, TOut> {
+ type Chromosome = TChromosome;
+ type Out = TOut;
+ type Err = Infallible;
+
+ fn evaluate(&self, chromosome: &Self::Chromosome) -> Result<Self::Out, Self::Err> {
+ Ok((self.fun)(chromosome))
+ }
+
+ fn is_valid(&self, chromosome: &Self::Chromosome) -> Result<bool, Self::Err> {
+ Ok(self.evaluate(chromosome)? <= Default::default())
+ }
+}
+
+pub struct ConstrainedFitnessFunction<'a,
+ TIn,
+ TOut,
+ TFitness: FitnessFunction<In = TIn, Out = TOut>,
+ TConstraint: ConstraintFunction<Chromosome = TIn, Out = TOut>> {
+ fitness: &'a TFitness,
+ constraints: Vec<&'a TConstraint>,
+ constraints_weight: TOut
+}
+
+#[derive(Error, Debug)]
+pub enum ConstrainedFitnessErr<T: Error, U: Error> {
+ #[error("An error that came from fitness function")]
+ FitnessErr(T),
+ #[error("An error that came from constraint function")]
+ ConstraintErr(U)
+}
+
+impl <'a,
+ TOut: std::ops::Mul<Output = TOut> + std::ops::AddAssign + Copy,
+ TIn,
+ TFitness: FitnessFunction<In = TIn, Out = TOut>,
+ TConstraint: ConstraintFunction<Chromosome = TIn, Out = TOut>>
+ FitnessFunction for ConstrainedFitnessFunction<'a, TIn, TOut, TFitness, TConstraint> {
+ type In = TFitness::In;
+ type Out = TOut;
+ type Err = ConstrainedFitnessErr<TFitness::Err, TConstraint::Err>;
+
+ fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
+ let mut fit = match self.fitness.fit(inp) {
+ Ok(fit) => fit,
+ Err(err) =>
+ return Err(ConstrainedFitnessErr::FitnessErr(err))
+ };
+
+ for &constraint in &self.constraints {
+ fit += self.constraints_weight * match constraint.evaluate(inp) {
+ Ok(constraint) => constraint,
+ Err(err) =>
+ return Err(ConstrainedFitnessErr::ConstraintErr(err))
+ };
+ }
+
+ Ok(fit)
+ }
+}
+
+// TODO: currently these functions do recalculate the constraints for each chromosome.
+// This is suboptimal. It could be solved by changing the result of fitness function to
+// a tuple, where the second element of the tuple would be evaluations of the constraints.
+// For this case, it would be the best if the number of constraints has been determined
+// by a generic. Then, no dynamic allocation is necessary for each element.
+
+pub fn evolve_constraint_penalty_weight_k
+ <TChromosome: Clone,
+ const DParents: usize,
+ TSelection: Selection<TChromosome, f64>,
+ TFitness: FitnessFunction<In = TChromosome, Out = f64>,
+ TConstraint: ConstraintFunction<Chromosome = TChromosome, Out = f64>,
+ TPairing: Pairing<DParents, Chromosome = TChromosome, Out = f64>,
+ TCrossover: Crossover<DParents, Chromosome = TChromosome, Out = f64>,
+ TReplacement: Replacement<TChromosome, f64>,
+ TPerturbation: PerturbationOperator<Chromosome = TChromosome>>(
+ k: usize,
+ n: usize,
+ beta_1: f64,
+ beta_2: f64,
+ better_than: &impl BetterThanOperator<f64>
+ ) -> impl FnMut(
+ usize,
+ &EvolutionStats<TChromosome, f64>,
+ &EvaluatedPopulation<TChromosome, f64>,
+
+ &mut ConstrainedFitnessFunction<TChromosome, f64, TFitness, TConstraint>,
+ &mut TSelection,
+ &mut TPairing,
+ &mut TCrossover,
+ &mut TPerturbation,
+ &mut TReplacement
+ ) {
+ let mut k_iters_feasible = VecDeque::with_capacity(k);
+ move |
+ iteration,
+ _,
+ population,
+ fitness,
+ _,
+ _,
+ _,
+ _,
+ _| {
+ let best_candidate = population.best_candidate(better_than);
+ let feasible = fitness.constraints
+ .iter()
+ .any(|c| c.is_valid(&best_candidate.chromosome)
+ .expect("Can verify candidates"));
+
+ // Change weight this iteration?
+ if iteration % n == 0 {
+ let all_feasible = k_iters_feasible.iter().all(|&f| f);
+
+ fitness.constraints_weight *= if all_feasible {
+ 1.0 / beta_1
+ } else {
+ beta_2
+ }
+ }
+
+ k_iters_feasible.push_back(feasible);
+
+ if k_iters_feasible.len() > k {
+ k_iters_feasible.pop_front();
+ }
+ }
+}
+
+pub fn evolve_constraint_penalty_weight_tau_target
+ <TChromosome: Clone,
+ const DParents: usize,
+ TSelection: Selection<TChromosome, f64>,
+ TFitness: FitnessFunction<In = TChromosome, Out = f64>,
+ TConstraint: ConstraintFunction<Chromosome = TChromosome, Out = f64>,
+ TPairing: Pairing<DParents, Chromosome = TChromosome, Out = f64>,
+ TCrossover: Crossover<DParents, Chromosome = TChromosome, Out = f64>,
+ TReplacement: Replacement<TChromosome, f64>,
+ TPerturbation: PerturbationOperator<Chromosome = TChromosome>>(
+ n: usize,
+ c: f64,
+ tau_target: f64,
+ better_than: &impl BetterThanOperator<f64>
+ ) -> impl FnMut(
+ usize,
+ &EvolutionStats<TChromosome, f64>,
+ &EvaluatedPopulation<TChromosome, f64>,
+
+ &mut ConstrainedFitnessFunction<TChromosome, f64, TFitness, TConstraint>,
+ &mut TSelection,
+ &mut TPairing,
+ &mut TCrossover,
+ &mut TPerturbation,
+ &mut TReplacement
+ ) {
+ move |
+ iteration,
+ _,
+ population,
+ fitness,
+ _,
+ _,
+ _,
+ _,
+ _| {
+ if iteration % n != 0 {
+ return;
+ }
+
+ let best_candidate = population.best_candidate(better_than);
+ let count_feasible = population.population
+ .iter()
+ .filter(|individual| {
+ fitness.constraints
+ .iter()
+ .all(|f| f
+ .is_valid(&individual.chromosome)
+ .expect("Can verify candidates"))
+ })
+ .count();
+ let tau = count_feasible as f64 / population.population.len() as f64;
+
+ if tau > tau_target {
+ fitness.constraints_weight *= 1.0 / c;
+ } else {
+ fitness.constraints_weight *= c;
+ }
+ }
+}