From 81dd1ec095657d49a49ca63c431af23f497b0b36 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Sun, 16 Nov 2025 19:54:48 +0100 Subject: [PATCH] feat: add stochastic ranking --- codes/eoa_lib/src/constraints.rs | 139 ++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/codes/eoa_lib/src/constraints.rs b/codes/eoa_lib/src/constraints.rs index 18706f5d31ae9d198513280d3c58819ba7fe2e7a..454bc9d7608124e32555eb76689601ea2dc5a11f 100644 --- a/codes/eoa_lib/src/constraints.rs +++ b/codes/eoa_lib/src/constraints.rs @@ -1,8 +1,9 @@ use std::{collections::VecDeque, convert::Infallible, error::Error}; +use rand::{Rng, RngCore}; use thiserror::Error; -use crate::{comparison::BetterThanOperator, crossover::Crossover, evolution::EvolutionStats, fitness::FitnessFunction, pairing::Pairing, perturbation::PerturbationOperator, population::EvaluatedPopulation, replacement::Replacement, selection::Selection}; +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; @@ -223,8 +224,142 @@ pub fn evolve_constraint_penalty_weight_tau_target } } +#[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 { - fitness.constraints_weight *= c; + 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() + } }