mahf 0.1.0

A framework for modular construction and evaluation of metaheuristics.
Documentation
//! Mutation components for Differential Evolution (DE).

use color_eyre::Section;
use eyre::{ensure, eyre};
use itertools::{multizip, Itertools};
use serde::{Deserialize, Serialize};

use crate::{
    component::ExecResult, components::Component, population::AsSolutionsMut,
    problems::VectorProblem, utils::with_index, State,
};

/// Applies the special DE mutation, similar to an arithmetic crossover.
///
/// Originally proposed for, and used as recombination in [`de`].
///
/// Requires a DE selection directly beforehand, e.g., [`DEBest`].
///
/// [`de`]: crate::heuristics::de
/// [`DEBest`]: crate::components::selection::de::DEBest
///
/// # Errors
///
/// Returns an `Err` if the population is not in the format generated by a DE selection.
#[derive(Clone, Serialize, Deserialize)]
pub struct DEMutation {
    /// Number of difference vectors ∈ {1, 2}.
    pub y: u32,
    /// Difference vector scaling ∈ (0, 2].
    pub f: f64,
}

impl DEMutation {
    pub fn from_params(y: u32, f: f64) -> ExecResult<Self> {
        ensure!(
            [1, 2].contains(&y),
            "`y` needs to be one of {{1, 2}}, but was {}",
            y
        );
        ensure!(
            (0.0..=2.0).contains(&f),
            "`f` must be in [0, 2], but was {}",
            f
        );
        Ok(Self { y, f })
    }

    pub fn new<P>(y: u32, f: f64) -> ExecResult<Box<dyn Component<P>>>
    where
        P: VectorProblem<Element = f64>,
    {
        Ok(Box::new(Self::from_params(y, f)?))
    }
}

impl<P> Component<P> for DEMutation
where
    P: VectorProblem<Element = f64>,
{
    fn execute(&self, _problem: &P, state: &mut State<P>) -> ExecResult<()> {
        let mut populations = state.populations_mut();
        let population = populations.current_mut();

        let size = (self.y * 2 + 1) as usize;

        if !population.len() % size == 0 {
            return Err(eyre!("the population must be in the format [`y` * 2 + 1]*, where the first individual is the base of the mutation"))
                .suggestion("try to use an appropriate selection method for this mutation");
        }

        let mut solutions = population.as_solutions_mut();

        for chunk in solutions.chunks_exact_mut(size) {
            match chunk {
                [ref mut base, remainder @ ..] => {
                    let pairs: Vec<[_; 2]> = remainder
                        .iter()
                        .chunks(2)
                        .into_iter()
                        .map(|pair| pair.collect_vec().try_into().unwrap())
                        .collect();

                    for [solution1, solution2] in pairs {
                        for (x, s1, s2) in
                            multizip((base.iter_mut(), solution1.iter(), solution2.iter()))
                        {
                            *x += self.f * (s1 - s2);
                        }
                    }
                }
                _ => unreachable!(),
            }
        }

        population.retain(with_index(|i, _| i % size == 0));

        Ok(())
    }
}