use std::{collections::VecDeque, convert::Infallible, error::Error}; use rand::{Rng, RngCore}; use thiserror::Error; use crate::{comparison::{BetterThanOperator, MinimizingOperator}, crossover::Crossover, evolution::{evolution_algorithm_best_candidate, EvolutionCandidate, EvolutionResult, 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, Clone, Debug)] pub struct ConstrainedEvaluation { fitness: TOut, constraints: [TOut; CONSTRAINTS], weighted_sum: TOut, is_feasible: bool, } 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: [TOut; CONSTRAINTS] } impl <'a, const CONSTRAINTS: usize, TOut: std::ops::Mul + std::ops::AddAssign + Copy + Default, 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]; let mut is_feasible = true; for (i, (constraint, weight)) in self.constraints.iter().zip(self.constraint_weights.iter()).enumerate() { let constraint_eval = match constraint.evaluate(inp) { Ok(constraint) => constraint, Err(err) => return Err(ConstrainedFitnessErr::ConstraintErr(err)) }; constraints[i] = constraint_eval; weighted_sum += match constraint.is_feasible(inp) { Ok(true) => weight.clone() * constraint_eval, Ok(false) => { is_feasible = false; Default::default() }, Err(err) => return Err(ConstrainedFitnessErr::ConstraintErr(err)) }; } Ok(ConstrainedEvaluation { fitness: fit, constraints, weighted_sum, is_feasible }) } } 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<'a, TSelection, TBetterThan> { N: usize, p: f64, selection: &'a mut TSelection, better_than: &'a TBetterThan } const MINIMIZING_OPERATOR: MinimizingOperator = MinimizingOperator; impl<'a, const CONSTRAINTS: usize, TChromosome, TResult: PartialOrd + Default, TSelection: Selection<(), usize>, TBetterThan: BetterThanOperator> Selection> for StochasticRankingSelection<'a, TSelection, TBetterThan> { 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() } } pub fn stochastic_ranking_evolution_algorithm + std::ops::AddAssign + Copy + Default, const DParents: usize, const CONSTRAINTS: usize, TFitness: FitnessFunction, TSelection: Selection<(), usize>, TPairing: Pairing>, TCrossover: Crossover>, TReplacement: Replacement>, TConstraint: ConstraintFunction, TPerturbation: PerturbationOperator>( initial_population: Population, parents_count: usize, N: usize, p: f64, fitness: &mut TFitness, constraints: [&TConstraint; CONSTRAINTS], constraint_weights: [TResult; CONSTRAINTS], pairing: &mut TPairing, selection: &mut TSelection, crossover: &mut TCrossover, perturbation: &mut TPerturbation, replacement: &mut TReplacement, better_than: &(impl BetterThanOperator> + BetterThanOperator), // TODO: termination condition iterations: usize, rng: &mut dyn RngCore, // mut evolutionary_strategy: impl FnMut( // usize, // &EvolutionStats, // &EvaluatedPopulation, // &mut TPairing, // &mut TCrossover, // &mut TPerturbation, // &mut TReplacement, // &mut ConstrainedEvalFitness, // ), ) -> Result<(EvolutionResult, Vec), Box> { let mut constrained_fitness = ConstrainedEvalFitness { fitness, constraints, constraint_weights, }; let mut stochastic_ranking_selection = StochasticRankingSelection { N, p, selection, better_than }; let mut feasible_fractions = Vec::with_capacity(iterations); evolution_algorithm_best_candidate( initial_population, parents_count, &mut constrained_fitness, &mut stochastic_ranking_selection, pairing, crossover, perturbation, replacement, better_than, iterations, rng, |iteration, stats, population, fitness, selection, pairing, crossover, perturbation, replacement| { let feasible_fraction = population.population .iter() .map(|x| x.evaluation.is_feasible) .count() as f64 / population.population.len() as f64; feasible_fractions.push(feasible_fraction); // evolutionary_strategy( // iteration, // stats, // population, // fitness // ) }, |_, evaluation, best_candidate| { // Do not save enfeasible solutions! if !evaluation.is_feasible { return false; } if best_candidate.is_none() { return true; } better_than.better_than( evaluation, &best_candidate.as_ref().unwrap().evaluated_chromosome.evaluation ) }).map(|res| ( res.map(|evaluation| evaluation.fitness), feasible_fractions )) }