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; fn is_valid(&self, chromosome: &Self::Chromosome) -> Result; } pub struct LowerThanConstraintFunction { fun: Box TOut> } impl LowerThanConstraintFunction { pub fn new(fun: Box TOut>) -> Self { Self { fun } } } impl ConstraintFunction for LowerThanConstraintFunction { type Chromosome = TChromosome; type Out = TOut; type Err = Infallible; fn evaluate(&self, chromosome: &Self::Chromosome) -> Result { Ok((self.fun)(chromosome)) } fn is_valid(&self, chromosome: &Self::Chromosome) -> Result { Ok(self.evaluate(chromosome)? <= Default::default()) } } pub struct ConstrainedFitnessFunction<'a, TIn, TOut, TFitness: FitnessFunction, TConstraint: ConstraintFunction> { fitness: &'a TFitness, constraints: Vec<&'a TConstraint>, constraints_weight: TOut } #[derive(Error, Debug)] pub enum ConstrainedFitnessErr { #[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 + std::ops::AddAssign + Copy, TIn, TFitness: FitnessFunction, TConstraint: ConstraintFunction> FitnessFunction for ConstrainedFitnessFunction<'a, TIn, TOut, TFitness, TConstraint> { type In = TFitness::In; type Out = TOut; type Err = ConstrainedFitnessErr; fn fit(self: &Self, inp: &Self::In) -> Result { 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 , TFitness: FitnessFunction, TConstraint: ConstraintFunction, TPairing: Pairing, TCrossover: Crossover, TReplacement: Replacement, TPerturbation: PerturbationOperator>( k: usize, n: usize, beta_1: f64, beta_2: f64, better_than: &impl BetterThanOperator ) -> impl FnMut( usize, &EvolutionStats, &EvaluatedPopulation, &mut ConstrainedFitnessFunction, &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 , TFitness: FitnessFunction, TConstraint: ConstraintFunction, TPairing: Pairing, TCrossover: Crossover, TReplacement: Replacement, TPerturbation: PerturbationOperator>( n: usize, c: f64, tau_target: f64, better_than: &impl BetterThanOperator ) -> impl FnMut( usize, &EvolutionStats, &EvaluatedPopulation, &mut ConstrainedFitnessFunction, &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; } } }