Trait Fitness

Source
pub trait Fitness:
    Clone
    + Send
    + Sync
    + Debug {
    type Genotype: Genotype;

    // Provided methods
    fn call_for_state_population<S: StrategyState<Self::Genotype>>(
        &mut self,
        state: &mut S,
        genotype: &Self::Genotype,
        thread_local: Option<&ThreadLocal<RefCell<Self>>>,
    ) { ... }
    fn call_for_state_chromosome<S: StrategyState<Self::Genotype>>(
        &mut self,
        state: &mut S,
        genotype: &Self::Genotype,
    ) { ... }
    fn call_for_population(
        &mut self,
        population: &mut FitnessPopulation<Self>,
        genotype: &Self::Genotype,
        thread_local: Option<&ThreadLocal<RefCell<Self>>>,
    ) { ... }
    fn call_for_chromosome(
        &mut self,
        chromosome: &mut FitnessChromosome<Self>,
        genotype: &Self::Genotype,
    ) { ... }
    fn calculate_for_population(
        &mut self,
        _population: &FitnessPopulation<Self>,
        _genotype: &Self::Genotype,
    ) -> Vec<Option<FitnessValue>> { ... }
    fn calculate_for_chromosome(
        &mut self,
        _chromosome: &FitnessChromosome<Self>,
        _genotype: &Self::Genotype,
    ) -> Option<FitnessValue> { ... }
}
Expand description

The fitness function, is implemented as a fitness method object.

Normally the fitness returns Some(FitnessValue) for the chromosome, which can be minimized or maximized in the search strategy (e.g. Evolve) by providing the FitnessOrdering.

If the fitness returns None, the chromosome is assumed invalid and taken last in the selection phase (also when minimizing).

§User implementation

There are two possible levels to implement. At least one level needs to be implemented:

  • calculate_for_chromosome(...) -> Option<FitnessValue>
    • The standard situation, suits all strategies. Implementable with all Genotypes.
    • Standard Genotypes have GenesOwner chromosomes. These chromosomes have a genes field, which can be read for the calculations.
    • non-standard Genotypes with GenesPointer chromosomes. These chromosomes have don’t have a genes field, so you need to retrieve the genes using genotype.genes_slice(&chromosome), which can then be read for the calculations. But for these types you usually don’t want to reach this call level, see other level below
  • calculate_for_population(...) -> Vec<Option<FitnessValue>>
    • Only overwrite for matrix Genotypes (designed for possible GPU acceleration)
    • If not overwritten, results in calling calculate_for_chromosome for each chromosome in the population. So it doesn’t have to be implemented by default, but it is a possible point to intercept with a custom implementation where the whole population data is available.
    • Only for Genotype with GenesPointer chromosomes. These chromosomes don’t have a genes field to read, but a row_id. The matrix Genotype has a contiguous memory data field with all the data, which can be calculated in one go.
    • The order and length of the rows in the genotype data matrix needs to be preserved in the returned vector as it matches the row_id on the chromosome
    • The order and length of the population does not matter at all and will most likely not align. The population is provided, because the full genotype matrix data also contains untainted chromosomes which already have a fitness_score (and will not be updated). The results for these chromosomes will be ignored, thus these do not have to be recalculated, so knowing which ones might be relevant (or not). The order of the results still need to align, so if the calculation is skipped, a None value should be inserted in the results to keep the order and length aligned.

The strategies use different levels of calls in Fitness. So you cannot always just intercept at calculate_for_population and be sure calculate_for_chromosome will not be called:

Therefore, additionally, you might need to implement calculate_for_chromosome for GenesPointer chromosomes. This is sometimes needed when testing out different strategies with different call levels. Problably no longer needed once settled on a strategy.

§Panics

calculate_for_chromosome has a default implementation which panics, because it doesn’t need to be implemented for genotypes which implement calculate_for_population. Will panic if reached and not implemented.

§Example (calculate_for_chromosome, standard GenesOwner chromosome):

use genetic_algorithm::fitness::prelude::*;

#[derive(Clone, Debug)]
pub struct CountTrue;
impl Fitness for CountTrue {
    type Genotype = BinaryGenotype;
    fn calculate_for_chromosome(
        &mut self,
        chromosome: &FitnessChromosome<Self>,
        _genotype: &FitnessGenotype<Self>
    ) -> Option<FitnessValue> {
        Some(chromosome.genes.iter().filter(|&value| *value).count() as FitnessValue)
    }
}

§Example (calculate_for_population, static matrix calculation, GenesPointer chromosome):

use genetic_algorithm::fitness::prelude::*;
use genetic_algorithm::strategy::evolve::prelude::*;

#[derive(Clone, Debug)]
pub struct SumStaticMatrixGenes;
impl Fitness for SumStaticMatrixGenes {
    type Genotype = StaticMatrixGenotype<u16, 10, 100>;
    fn calculate_for_population(
        &mut self,
        population: &Population<StaticMatrixChromosome>,
        genotype: &FitnessGenotype<Self>,
    ) -> Vec<Option<FitnessValue>> {
        // pure matrix data calculation on [[T; N] M]
        // the order of the rows needs to be preserved as it matches the row_id on the chromosome
        // the order of the population does not matter at all and will most likely not align
        genotype
            .data
            .iter()
            .map(|genes| {
                genes
                    .iter()
                    .sum::<u16>() as FitnessValue
            })
            .map(Some)
            .collect()
    }
}

§Example (calculate_for_population, dynamic matrix calculation, GenesPointer chromosome):

use genetic_algorithm::fitness::prelude::*;
use genetic_algorithm::strategy::evolve::prelude::*;

#[derive(Clone, Debug)]
pub struct SumDynamicMatrixGenes;
impl Fitness for SumDynamicMatrixGenes {
    type Genotype = DynamicMatrixGenotype<u16>;
    fn calculate_for_population(
        &mut self,
        population: &Population<DynamicMatrixChromosome>,
        genotype: &FitnessGenotype<Self>,
    ) -> Vec<Option<FitnessValue>> {
        // pure matrix data calculation on Vec<T> with genes_size step
        // the order of the rows needs to be preserved as it matches the row_id on the chromosome
        // the order of the population does not matter at all and will most likely not align
        genotype
            .data
            .chunks(genotype.genes_size())
            .map(|genes| {
                genes
                    .iter()
                    .sum::<u16>() as FitnessValue
            })
            .map(Some)
            .collect()
    }
}

§Example (calculate_for_chromosome, matrix fall back, GenesPointer chromosome):

Note: For exploration purposes when switching stratgies a lot, not really used in final implementation

use genetic_algorithm::fitness::prelude::*;
use genetic_algorithm::strategy::hill_climb::prelude::*;

#[derive(Clone, Debug)]
pub struct SumStaticMatrixGenes;
impl Fitness for SumStaticMatrixGenes {
    type Genotype = StaticMatrixGenotype<u16, 10, 100>;
    fn calculate_for_chromosome(
        &mut self,
        chromosome: &FitnessChromosome<Self>,
        genotype: &Self::Genotype,
    ) -> Option<FitnessValue> {
        let score = genotype.genes_slice(chromosome).iter().sum::<u16>();
        Some(score as FitnessValue)
    }
}

Required Associated Types§

Provided Methods§

Source

fn call_for_state_population<S: StrategyState<Self::Genotype>>( &mut self, state: &mut S, genotype: &Self::Genotype, thread_local: Option<&ThreadLocal<RefCell<Self>>>, )

Source

fn call_for_state_chromosome<S: StrategyState<Self::Genotype>>( &mut self, state: &mut S, genotype: &Self::Genotype, )

Source

fn call_for_population( &mut self, population: &mut FitnessPopulation<Self>, genotype: &Self::Genotype, thread_local: Option<&ThreadLocal<RefCell<Self>>>, )

Pass thread_local for external control of fitness caching in multithreading

Source

fn call_for_chromosome( &mut self, chromosome: &mut FitnessChromosome<Self>, genotype: &Self::Genotype, )

Source

fn calculate_for_population( &mut self, _population: &FitnessPopulation<Self>, _genotype: &Self::Genotype, ) -> Vec<Option<FitnessValue>>

Optional interception point for client implementation.

The order and length of the results need to align with the order and length of the genotype data matrix. The order and length of the population does not matter at all and will most likely not align.

Source

fn calculate_for_chromosome( &mut self, _chromosome: &FitnessChromosome<Self>, _genotype: &Self::Genotype, ) -> Option<FitnessValue>

Optional interception point for client implementation

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§