genetic_algorithm 0.27.1

A genetic algorithm implementation
Documentation
use super::{Mutate, MutateEvent};
use crate::genotype::EvolveGenotype;
use crate::strategy::evolve::{EvolveConfig, EvolveState};
use crate::strategy::StrategyReporter;
use crate::strategy::{StrategyAction, StrategyState};
use rand::distributions::{Bernoulli, Distribution};
use rand::Rng;
use std::cmp::Ordering;
use std::marker::PhantomData;
use std::time::Instant;

/// Selects [Chromosomes](crate::chromosome::Chromosome) in the
/// [Population](crate::population::Population) with the dynamically updated mutation_probability.
/// Then mutates the selected chromosomes once, where the [Genotype](crate::genotype::Genotype)
/// determines whether this is random, relative or scaled. The mutation probability is dynamically
/// increased or decreased to achieve a target population cardinality
#[derive(Debug, Clone)]
pub struct SingleGeneDynamic<G: EvolveGenotype> {
    _phantom: PhantomData<G>,
    pub mutation_probability: f32,
    pub mutation_probability_step: f32,
    pub target_cardinality: usize,
}

impl<G: EvolveGenotype> Mutate for SingleGeneDynamic<G> {
    type Genotype = G;

    fn call<R: Rng, SR: StrategyReporter<Genotype = G>>(
        &mut self,
        genotype: &G,
        state: &mut EvolveState<G>,
        config: &EvolveConfig,
        reporter: &mut SR,
        rng: &mut R,
    ) {
        let now = Instant::now();

        if let Some(cardinality) = state.population_cardinality() {
            let changed = match cardinality.cmp(&self.target_cardinality) {
                Ordering::Greater => {
                    self.mutation_probability =
                        (self.mutation_probability - self.mutation_probability_step).max(0.0);
                    true
                }
                Ordering::Less => {
                    self.mutation_probability =
                        (self.mutation_probability + self.mutation_probability_step).min(1.0);
                    true
                }
                Ordering::Equal => false,
            };

            if changed {
                reporter.on_mutate_event(
                    MutateEvent(format!(
                        "ChangeMutationProbability, set to {:0.3}",
                        self.mutation_probability
                    )),
                    genotype,
                    state,
                    config,
                );
            }
        }

        let bool_sampler = Bernoulli::new(self.mutation_probability as f64).unwrap();
        for chromosome in state
            .population
            .chromosomes
            .iter_mut()
            .filter(|c| c.is_offspring())
        {
            if bool_sampler.sample(rng) {
                genotype.mutate_chromosome_genes(1, true, chromosome, rng);
            }
        }
        state.add_duration(StrategyAction::Mutate, now.elapsed());
    }
}

impl<G: EvolveGenotype> SingleGeneDynamic<G> {
    /// Create a new SingleGeneDynamic mutation strategy. Auto-adjusts mutation probability
    /// to maintain target population diversity (cardinality).
    /// * `mutation_probability_step` - step size for probability adjustment each generation
    /// * `target_cardinality` - target number of unique chromosomes in the population
    pub fn new(mutation_probability_step: f32, target_cardinality: usize) -> Self {
        Self {
            _phantom: PhantomData,
            mutation_probability: 0.0,
            mutation_probability_step,
            target_cardinality,
        }
    }
}