use std::{collections::VecDeque, convert::Infallible, error::Error}; use rand::{Rng, RngCore}; use thiserror::Error; use crate::{comparison::{BetterThanOperator, MinimizingOperator}, crossover::Crossover, evolution::EvolutionStats, fitness::FitnessFunction, pairing::Pairing, perturbation::PerturbationOperator, population::{EvaluatedChromosome, EvaluatedPopulation, Population}, replacement::Replacement, selection::Selection}; pub trait ConstraintFunction { type Chromosome; type Out; type Err: Error + 'static; fn evaluate(&self, chromosome: &Self::Chromosome) -> Result; fn is_feasible(&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_feasible(&self, chromosome: &Self::Chromosome) -> Result { Ok(self.evaluate(chromosome)? <= Default::default()) } } pub struct ConstrainedFitnessFunction<'a, const CONSTRAINTS: usize, TIn, TOut, TFitness: FitnessFunction, TConstraint: ConstraintFunction> { fitness: &'a TFitness, constraints: [&'a TConstraint; CONSTRAINTS], constraint_weights: Vec } #[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, const CONSTRAINTS: usize, TOut: std::ops::Mul + std::ops::AddAssign + Copy, TIn, TFitness: FitnessFunction, TConstraint: ConstraintFunction> FitnessFunction for ConstrainedFitnessFunction<'a, CONSTRAINTS, 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, weight) in self.constraints.iter().zip(self.constraint_weights.iter()) { fit += weight.clone() * 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_feasible(&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); for constraint_weight in fitness.constraint_weights.iter_mut() { *constraint_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 count_feasible = population.population .iter() .filter(|individual| { fitness.constraints .iter() .all(|f| f .is_feasible(&individual.chromosome) .expect("Can verify candidates")) }) .count(); let tau = count_feasible as f64 / population.population.len() as f64; for constraint_weight in fitness.constraint_weights.iter_mut() { if tau > tau_target { *constraint_weight *= 1.0 / c; } else { *constraint_weight *= c; } } } } #[derive(PartialEq, Debug)] pub struct ConstrainedEvaluation { fitness: TOut, constraints: [TOut; CONSTRAINTS], weighted_sum: TOut } impl PartialOrd for ConstrainedEvaluation { fn partial_cmp(&self, other: &Self) -> Option { self.weighted_sum.partial_cmp(&other.weighted_sum) } } pub struct ConstrainedEvalFitness<'a, const CONSTRAINTS: usize, TIn, TOut, TFitness: FitnessFunction, TConstraint: ConstraintFunction> { fitness: &'a TFitness, constraints: [&'a TConstraint; CONSTRAINTS], constraint_weights: Vec } impl <'a, const CONSTRAINTS: usize, TOut: std::ops::Mul + std::ops::AddAssign + Copy, TIn, TFitness: FitnessFunction, TConstraint: ConstraintFunction> FitnessFunction for ConstrainedEvalFitness<'a, CONSTRAINTS, TIn, TOut, TFitness, TConstraint> { type In = TFitness::In; type Out = ConstrainedEvaluation; type Err = ConstrainedFitnessErr; fn fit(self: &Self, inp: &Self::In) -> Result { let fit = match self.fitness.fit(inp) { Ok(fit) => fit, Err(err) => return Err(ConstrainedFitnessErr::FitnessErr(err)) }; let mut weighted_sum = fit; let mut constraints = [fit; CONSTRAINTS]; for (i, (constraint, weight)) in self.constraints.iter().zip(self.constraint_weights.iter()).enumerate() { let constraint = match constraint.evaluate(inp) { Ok(constraint) => constraint, Err(err) => return Err(ConstrainedFitnessErr::ConstraintErr(err)) }; constraints[i] = constraint; weighted_sum += weight.clone() * constraint; } Ok(ConstrainedEvaluation { fitness: fit, constraints, weighted_sum }) } } fn stochastic_ranking_sort( evaluations: &[EvaluatedChromosome>], N: usize, p: f64, better_than: &(impl BetterThanOperator + ?Sized), rng: &mut dyn RngCore ) -> Vec { let mut indices = (0..evaluations.len()).collect::>(); for _ in 0..N { for j in 0..evaluations.len()-1 { let u = rng.random_range(0.0..=1.0); let current_evaluation = &evaluations[indices[j]].evaluation; let next_evaluation = &evaluations[indices[j + 1]].evaluation; if (current_evaluation.weighted_sum == Default::default() && next_evaluation.weighted_sum == Default::default()) || u < p { if better_than.better_than(&next_evaluation.fitness, ¤t_evaluation.fitness) { indices.swap(j, j + 1); } } else { if current_evaluation.weighted_sum > next_evaluation.weighted_sum { indices.swap(j, j + 1); } } } } indices } pub struct StochasticRankingSelection { N: usize, p: f64, selection: TSelection, better_than: TBetterThan } const MINIMIZING_OPERATOR: MinimizingOperator = MinimizingOperator; impl, TBetterThan: BetterThanOperator> Selection> for StochasticRankingSelection { fn select(&self, count: usize, evaluations: &EvaluatedPopulation>, _: &dyn BetterThanOperator>, rng: &mut dyn RngCore ) -> impl Iterator { let sorted_indices = stochastic_ranking_sort( evaluations.population.as_slice(), self.N, self.p, &self.better_than, rng ); let mut rankings = vec![EvaluatedChromosome { chromosome: (), evaluation: 0 }; evaluations.population.len()]; for (ranking, index) in sorted_indices.into_iter().enumerate() { rankings[index] = EvaluatedChromosome { chromosome: (), evaluation: ranking }; } // Replace with this better than self.selection.select( count, &EvaluatedPopulation::from_vec(rankings), &MINIMIZING_OPERATOR, rng) .collect::>() .into_iter() } }