~ruther/ctu-fee-eoa

81dd1ec095657d49a49ca63c431af23f497b0b36 — Rutherther 25 days ago 070fb45
feat: add stochastic ranking
1 files changed, 137 insertions(+), 2 deletions(-)

M codes/eoa_lib/src/constraints.rs
M codes/eoa_lib/src/constraints.rs => codes/eoa_lib/src/constraints.rs +137 -2
@@ 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<const CONSTRAINTS: usize, TOut> {
    fitness: TOut,
    constraints: [TOut; CONSTRAINTS],
    weighted_sum: TOut
}

impl<const CONSTRAINTS: usize, TOut: PartialOrd> PartialOrd for ConstrainedEvaluation<CONSTRAINTS, TOut> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.weighted_sum.partial_cmp(&other.weighted_sum)
    }
}

pub struct ConstrainedEvalFitness<'a,
    const CONSTRAINTS: usize,
    TIn,
    TOut,
    TFitness: FitnessFunction<In = TIn, Out = TOut>,
    TConstraint: ConstraintFunction<Chromosome = TIn, Out = TOut>> {
    fitness: &'a TFitness,
    constraints: [&'a TConstraint; CONSTRAINTS],
    constraint_weights: Vec<TOut>
}

impl <'a,
      const CONSTRAINTS: usize,
      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 ConstrainedEvalFitness<'a, CONSTRAINTS, TIn, TOut, TFitness, TConstraint> {
    type In = TFitness::In;
    type Out = ConstrainedEvaluation<CONSTRAINTS, TOut>;
    type Err = ConstrainedFitnessErr<TFitness::Err, TConstraint::Err>;

    fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
        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<const CONSTRAINTS: usize, TIn, TOut: PartialOrd + Default>(
    evaluations: &[EvaluatedChromosome<TIn, ConstrainedEvaluation<CONSTRAINTS, TOut>>],
    N: usize,
    p: f64,
    better_than: &(impl BetterThanOperator<TOut> + ?Sized),
    rng: &mut dyn RngCore
) -> Vec<usize> {
    let mut indices = (0..evaluations.len()).collect::<Vec<_>>();
    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, &current_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<TSelection, TBetterThan> {
    N: usize,
    p: f64,
    selection: TSelection,
    better_than: TBetterThan
}

const MINIMIZING_OPERATOR: MinimizingOperator = MinimizingOperator;

impl<const CONSTRAINTS: usize,
     TChromosome,
     TResult: PartialOrd + Default,
     TSelection: Selection<(), usize>,
     TBetterThan: BetterThanOperator<TResult>>
    Selection<TChromosome, ConstrainedEvaluation<CONSTRAINTS, TResult>> for StochasticRankingSelection<TSelection, TBetterThan> {
    fn select(&self,
              count: usize,
              evaluations: &EvaluatedPopulation<TChromosome, ConstrainedEvaluation<CONSTRAINTS, TResult>>,
              _: &dyn BetterThanOperator<ConstrainedEvaluation<CONSTRAINTS, TResult>>,
              rng: &mut dyn RngCore
    ) -> impl Iterator<Item = usize> {
        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::<Vec<_>>()
            .into_iter()
    }
}