~ruther/ctu-fee-eoa

c66539e12fa23155206cc9655202f90c39a42a42 — Rutherther a month ago 293627b
refactor: Add dimension generic to BinaryString
M env/src/binary_string.rs => env/src/binary_string.rs +54 -52
@@ 1,34 1,15 @@
use std::str::FromStr;
use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector};
use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, DimName, Dyn, OVector, U1};
use rand::{Rng, RngCore};
use thiserror::Error;

#[derive(Debug, Clone, PartialEq)]
pub struct Bounds {
    min: f64,
    max: f64,
}

impl Bounds {
    pub fn new(min: f64, max: f64) -> Self {
        Bounds {
            min,
            max
        }
    }

    pub fn min(&self) -> f64 {
        self.min
    }

    pub fn max(&self) -> f64 {
        self.max
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct BinaryString {
    pub vec: Vec<i8>
pub struct BinaryString<D>
where
    D: Dim,
    DefaultAllocator: Allocator<D>
{
    pub vec: OVector<i8, D>
}

#[derive(Error, Debug, Clone, PartialEq)]


@@ 37,24 18,40 @@ pub enum BinaryStringConversionError {
    DimensionMismatch,
}

impl BinaryString {
    pub fn new(vec: Vec<i8>) -> BinaryString {
impl<D> BinaryString<D>
where
    D: DimName,
    DefaultAllocator: Allocator<D>
{
    pub fn new(vec: Vec<i8>) -> Self {
        Self {
            vec: OVector::from_vec(vec)
        }
    }
}

impl BinaryString<Dyn>
{
    pub fn new_dyn(vec: Vec<i8>) -> Self {
        BinaryString {
            vec
            vec: OVector::from_vec_generic(Dyn(vec.len()), U1, vec)
        }
    }
}

    pub fn vec(&self) -> &Vec<i8> {
impl<D> BinaryString<D>
where
    D: Dim,
    DefaultAllocator: Allocator<D>
{
    pub fn vec(&self) -> &OVector<i8, D> {
        &self.vec
    }

    pub fn perturb(self: &Self, rng: &mut dyn RngCore, p: f64) -> Self
    pub fn perturb(mut self, rng: &mut dyn RngCore, p: f64) -> Self
    {
        BinaryString::new(
            self.into_iter()
                .map(|c| if rng.random::<f64>() <= p { 1 - *c } else { *c })
                .collect::<Vec<i8>>()
        )
        self.vec.apply(|c| *c = if rng.random::<f64>() <= p { 1 - *c } else { *c });
        self
    }

    fn to_real_internal<'a, T: DoubleEndedIterator<Item = &'a i8>>(vec: T, len: usize, min: f64, max: f64) -> f64


@@ 85,14 82,15 @@ impl BinaryString {
            return Err(BinaryStringConversionError::DimensionMismatch);
        }

        let iter = self.vec.chunks(chunk_size)
        let iter = self.vec.as_slice().chunks(chunk_size)
                .zip(min.iter().zip(max))
            .map(|(chunk, (&min, &max))| BinaryString::to_real_internal(chunk.iter(), chunk_size, min, max));
            .map(|(chunk, (&min, &max))| BinaryString::<D>::to_real_internal(chunk.iter(), chunk_size, min, max));
        Ok(OVector::<f64, TDim>::from_iterator_generic(min.shape_generic().0, min.shape_generic().1, iter))
    }
}

impl FromStr for BinaryString {
impl FromStr for BinaryString<Dyn>
{
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {


@@ 108,13 106,17 @@ impl FromStr for BinaryString {
            })
            .collect::<Result<Vec<i8>, Self::Err>>()?;

        Ok(BinaryString::new(binary_vec))
        Ok(BinaryString::new_dyn(binary_vec))
    }
}

impl<'a> IntoIterator for &'a BinaryString {
impl<'a, D> IntoIterator for &'a BinaryString<D>
where
    D: Dim,
    DefaultAllocator: nalgebra::allocator::Allocator<D>
{
    type Item = &'a i8;
    type IntoIter = std::slice::Iter<'a, i8>;
    type IntoIter = nalgebra::iter::MatrixIter<'a, i8, D, U1, <DefaultAllocator as nalgebra::allocator::Allocator<D>>::Buffer<i8>>;

    fn into_iter(self) -> Self::IntoIter {
        self.vec.iter()


@@ 124,48 126,48 @@ impl<'a> IntoIterator for &'a BinaryString {
#[cfg(test)]
pub mod tests {
    use crate::{binary_string::{BinaryString, BinaryStringConversionError}, test_infra::{load_test_file, DataArrOfReals}};
    use nalgebra::SVector;
    use nalgebra::{SVector, U1, U2, U5};

    #[test]
    fn test_binary_string_to_real_single() {
        assert_eq!(
            BinaryString::new(vec![1])
            BinaryString::<U1>::new(vec![1])
                .to_real_single(0.0, 32.0),
            32.0
        );
        assert_eq!(
            BinaryString::new(vec![1, 1])
            BinaryString::<U2>::new(vec![1, 1])
                .to_real_single(0.0, 32.0),
            32.0
        );
        assert_eq!(
            BinaryString::new(vec![0, 1])
            BinaryString::<U2>::new(vec![0, 1])
                .to_real_single(0.0, 32.0),
            32.0 / 3.0
        );
        assert_eq!(
            BinaryString::new(vec![0, 0])
            BinaryString::<U2>::new(vec![0, 0])
                .to_real_single(-16.0, 16.0),
            -16.0
        );
        assert_eq!(
            BinaryString::new(vec![0, 0, 0, 0, 1])
            BinaryString::<U5>::new(vec![0, 0, 0, 0, 1])
                .to_real_single(0.0, 31.0),
            1.0
        );
        assert_eq!(
            BinaryString::new(vec![1, 1, 1, 1, 1])
            BinaryString::<U5>::new(vec![1, 1, 1, 1, 1])
                .to_real_single(0.0, 31.0),
            31.0
        );
        assert_eq!(
            BinaryString::new(vec![0, 0, 0, 1, 0])
            BinaryString::<U5>::new(vec![0, 0, 0, 1, 0])
                .to_real_single(0.0, 31.0),
            2.0
        );

        assert_eq!(
            BinaryString::new(vec![1; 512])
            BinaryString::<U2>::new(vec![1; 512])
                .to_real_single(0.0, 31.0),
            31.0
        );


@@ 175,7 177,7 @@ pub mod tests {
        let data = load_test_file::<i8, DataArrOfReals>(file_name);

        for test in data {
            let res = BinaryString::new(test.inp)
            let res = BinaryString::new_dyn(test.inp)
                .to_real(&min, &max);
            if !test.out.valid {
                assert_eq!(

M env/src/fitness/labs.rs => env/src/fitness/labs.rs +30 -17
@@ 1,30 1,41 @@
use std::convert::Infallible;
use nalgebra::{allocator::Allocator, DefaultAllocator, Dim};

use crate::binary_string::BinaryString;
use super::FitnessFunction;
use std::marker::PhantomData;

pub struct LABS;
pub struct LABS<D> {
    _phantom: PhantomData<D>
}

impl LABS {
impl<D> LABS<D> {
    pub fn new() -> Self {
        LABS
    }

    fn ck(k: usize, s: &Vec<i32>) -> i32 {
        let d = s.len();
        s.iter()
            .take(d - k)
            .zip(s.iter().skip(k))
            .map(|(x, y)| x * y)
            .sum()
        Self {
            _phantom: PhantomData
        }
    }
}

impl FitnessFunction for LABS {
    type In = BinaryString;
impl<D> FitnessFunction for LABS<D>
where
    D: Dim,
    DefaultAllocator: Allocator<D>
{
    type In = BinaryString<D>;
    type Out = i32;
    type Err = Infallible;

    fn fit(self: &Self, chromosome: &BinaryString) -> Result<i32, Infallible> {
    fn fit(self: &Self, chromosome: &BinaryString<D>) -> Result<i32, Infallible> {
        fn ck(k: usize, s: &Vec<i32>) -> i32 {
            let d = s.len();
            s.iter()
                .take(d - k)
                .zip(s.iter().skip(k))
                .map(|(x, y)| x * y)
                .sum()
        }

        let s: Vec<i32> = chromosome
            .into_iter()
            .map(|c| (*c as i32) * 2 - 1)


@@ 32,13 43,15 @@ impl FitnessFunction for LABS {
        let d = s.len();

        Ok((1..=d-1)
           .map(|k| LABS::ck(k, &s).pow(2))
           .map(|k| ck(k, &s).pow(2))
           .sum())
    }
}

#[cfg(test)]
pub mod tests {
    use nalgebra::Dyn;

    use crate::{binary_string::BinaryString, fitness::{labs::LABS, FitnessFunction}, test_infra::load_test_file};

    #[test]


@@ 51,7 64,7 @@ pub mod tests {
                     .collect::<Vec<String>>()
                     .join(", "));
            assert_eq!(
                LABS::new().fit(&BinaryString::new(test.inp)).unwrap(),
                LABS::<Dyn>::new().fit(&BinaryString::new_dyn(test.inp)).unwrap(),
                test.out
            )
        }

M env/src/fitness/mod.rs => env/src/fitness/mod.rs +14 -8
@@ 1,4 1,4 @@
use std::{convert::Infallible, error::Error};
use std::{convert::Infallible, error::Error, marker::PhantomData};

use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector};



@@ 18,7 18,7 @@ pub trait FitnessFunction {
    fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err>;
}

pub struct BinaryFitnessWrapper<D, TFitness>
pub struct BinaryFitnessWrapper<D, DString, TFitness>
where
    D: Dim,
    DefaultAllocator: Allocator<D>


@@ 26,33 26,39 @@ where
    min: OVector<f64, D>,
    max: OVector<f64, D>,
    fitting_function: TFitness,
    _phantom: PhantomData<DString>
}

impl<D, TFitness> BinaryFitnessWrapper<D, TFitness>
impl<D, DString, TFitness> BinaryFitnessWrapper<D, DString, TFitness>
where
    DString: Dim,
    DefaultAllocator: Allocator<DString>,
    D: Dim,
    DefaultAllocator: Allocator<D>
{
    pub fn new(fitting_function: TFitness, min: OVector<f64, D>, max: OVector<f64, D>) -> Self {
        BinaryFitnessWrapper {
        Self {
            fitting_function,
            min,
            max
            max,
            _phantom: PhantomData
        }
    }
}

impl<D, TFitness> FitnessFunction for BinaryFitnessWrapper<D, TFitness>
impl<D, DString, TFitness> FitnessFunction for BinaryFitnessWrapper<D, DString, TFitness>
where
    DString: Dim,
    DefaultAllocator: Allocator<DString>,
    D: Dim,
    DefaultAllocator: Allocator<D>,
    TFitness: FitnessFunction<In = OVector<f64, D>, Out = f64, Err = Infallible>
{
    type In = BinaryString;
    type In = BinaryString<DString>;
    type Out = f64;
    type Err = BinaryStringConversionError;

    fn fit(self: &Self, inp: &BinaryString) -> Result<f64, BinaryStringConversionError> {
    fn fit(self: &Self, inp: &BinaryString<DString>) -> Result<f64, BinaryStringConversionError> {
        Ok(self.fitting_function.fit(&inp.to_real(&self.min, &self.max)?).unwrap())
    }
}

M env/src/fitness/one_max.rs => env/src/fitness/one_max.rs +19 -8
@@ 1,20 1,31 @@
use std::convert::Infallible;
use std::{convert::Infallible, marker::PhantomData};
use nalgebra::{allocator::Allocator, DefaultAllocator, Dim};

use crate::binary_string::BinaryString;
use super::FitnessFunction;

pub struct OneMax;
impl OneMax {
pub struct OneMax<D> {
    _phantom: PhantomData<D>
}

impl<D> OneMax<D> {
    pub fn new() -> Self {
        OneMax
        Self {
            _phantom: PhantomData
        }
    }
}

impl FitnessFunction for OneMax {
    type In = BinaryString;
impl<D> FitnessFunction for OneMax<D>
where
    D: Dim,
    DefaultAllocator: Allocator<D>
{
    type In = BinaryString<D>;
    type Out = i32;
    type Err = Infallible;

    fn fit(self: &Self, chromosome: &BinaryString) -> Result<i32, Infallible> {
    fn fit(self: &Self, chromosome: &BinaryString<D>) -> Result<i32, Infallible> {
        Ok(chromosome.into_iter()
           .map(|x| *x as i32)
           .sum())


@@ 33,7 44,7 @@ pub mod tests {

        for test in data {
            assert_eq!(
                OneMax::new().fit(&BinaryString::new(test.inp)).unwrap(),
                OneMax::new().fit(&BinaryString::new_dyn(test.inp)).unwrap(),
                test.out
            );
        }

M env/src/local_search/mod.rs => env/src/local_search/mod.rs +14 -9
@@ 7,7 7,8 @@ use crate::terminating::TerminatingCondition;
use crate::perturbation::PerturbationOperator;
use crate::comparison::BetterThanOperator;
use full_palette::BROWN;
use nalgebra::SVector;
use nalgebra::allocator::Allocator;
use nalgebra::{DefaultAllocator, Dim, SVector, U2};
use plotters::prelude::*;

// Functions


@@ 24,7 25,11 @@ pub struct LocalSearchStats<TInput, TResult> {
    stats: Vec<LocalSearchCandidate<TInput, TResult>>
}

impl<TResult> LocalSearchStats<BinaryString, TResult> {
impl<D, TResult> LocalSearchStats<BinaryString<D>, TResult>
where
    D: Dim,
    DefaultAllocator: Allocator<D>
{
    pub fn to_real(self, min: &SVector<f64, 2>, max: &SVector<f64, 2>) -> Result<LocalSearchStats<SVector<f64, 2>, TResult>, BinaryStringConversionError> {
        Ok(LocalSearchStats::<SVector<f64, 2>, TResult>::from_vec(
            self.stats


@@ 32,7 37,7 @@ impl<TResult> LocalSearchStats<BinaryString, TResult> {
                .map(|candidate|
                     Ok(LocalSearchCandidate {
                         fit: candidate.fit,
                         pos: candidate.pos.to_real(&min, &max)?,
                         pos: candidate.pos.to_real::<U2>(&min, &max)?,
                         cycle: candidate.cycle,
                     }))
                .collect::<Result<Vec<_>, _>>()?))


@@ 273,13 278,13 @@ pub fn plot_fitness_evolution(file: &str, stats: LocalSearchStats<SVector<f64, 2

#[cfg(test)]
pub mod tests {
    use nalgebra::SVector;
    use nalgebra::{SVector, U10};

    use crate::{binary_string::BinaryString, comparison::MinimizingOperator, evolutionary_strategy::{IdentityStrategy, OneToFiveStrategy}, fitness::{one_max::OneMax, real::Linear, rosenbrock::Rosenbrock, sphere::Sphere, BinaryFitnessWrapper}, local_search::{local_search_first_improving, local_search_first_improving_evolving, plot_fitness_evolution}, perturbation::{BinaryStringBitPerturbation, BoundedPerturbation, BoundedPerturbationStrategy, PatternPerturbation, RandomDistributionPerturbation}, terminating::{AndTerminatingConditions, CloserThanTerminatingCondition, EqualTerminatingCondition, NoBetterForCyclesTerminatingCondition}};

    #[test]
    fn test_local_search_sphere_binary() {
        let optimum = BinaryString::new(vec![0, 0, 1, 0, 0,
        let optimum = BinaryString::<U10>::new(vec![0, 0, 1, 0, 0,
                                             0, 0, 1, 0, 0]);
        let min = SVector::<f64, 2>::from_element(0.0);
        let max = SVector::<f64, 2>::from_element(31.0);


@@ 351,8 356,8 @@ pub mod tests {

    #[test]
    fn test_local_search_one_max() {
        let one_max = OneMax::new();
        let optimum = BinaryString::new(vec![0; 10]);
        let one_max = OneMax::<U10>::new();
        let optimum = BinaryString::<U10>::new(vec![0; 10]);

        let result = local_search_first_improving(
            &one_max,


@@ 365,7 370,7 @@ pub mod tests {
                ),
            &mut BinaryStringBitPerturbation::new(0.3),
            &MinimizingOperator::new(),
            &BinaryString::new(vec![1; 10]),
            &BinaryString::<U10>::new(vec![1; 10]),
        ).unwrap();

        println!("{:?}", result);


@@ 416,7 421,7 @@ pub mod tests {
    #[test]
    fn test_local_search_rosenbrock() {
        let rosenbrock = Rosenbrock::new();
        let optimum = BinaryString::new(vec![1, 0, 0, 0, 1, 1, 0, 0, 0, 1]);
        let optimum = BinaryString::<U10>::new(vec![1, 0, 0, 0, 1, 1, 0, 0, 0, 1]);

        let min = SVector::<f64, 2>::from_element(-16.0);
        let max = SVector::<f64, 2>::from_element(15.0);

M env/src/perturbation/mod.rs => env/src/perturbation/mod.rs +26 -11
@@ 1,4 1,6 @@
use nalgebra::SVector;
use std::marker::PhantomData;

use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, SVector};
use rand::{distr::Distribution, Rng, RngCore};
use rand_distr::{uniform, Normal, NormalError, Uniform};



@@ 10,25 12,31 @@ pub trait PerturbationOperator {
    fn perturb(self: &mut Self, chromosome: &Self::Chromosome) -> Self::Chromosome;
}

pub struct BinaryStringBitPerturbation {
pub struct BinaryStringBitPerturbation<D> {
    rng: Box<dyn RngCore>,
    p: f64,
    _phantom: PhantomData<D>
}

impl BinaryStringBitPerturbation {
impl<D> BinaryStringBitPerturbation<D> {
    pub fn new(p: f64) -> Self {
        Self {
            rng: Box::new(rand::rng()),
            p
            p,
            _phantom: PhantomData
        }
    }
}

impl PerturbationOperator for BinaryStringBitPerturbation {
    type Chromosome = BinaryString;
impl<D> PerturbationOperator for BinaryStringBitPerturbation<D>
where
    D: Dim,
    DefaultAllocator: Allocator<D>
{
    type Chromosome = BinaryString<D>;

    fn perturb(self: &mut Self, chromosome: &Self::Chromosome) -> Self::Chromosome {
        chromosome.perturb(&mut self.rng, self.p)
        chromosome.clone().perturb(&mut self.rng, self.p)
    }
}



@@ 211,16 219,23 @@ pub mod tests {
        let mut rng = rand::rng();

        assert_eq!(
            *BinaryString::new(vec![1, 1, 0, 0])
            *BinaryString::new_dyn(vec![1, 1, 0, 0])
                .perturb(&mut rng, 1.0)
                .vec(),
                .vec()
                .iter()
                .map(|&x| x)
                .collect::<Vec<_>>(),
            vec![0, 0, 1, 1]
        );


        assert_eq!(
            *BinaryString::new(vec![1, 1, 0, 0])
            *BinaryString::new_dyn(vec![1, 1, 0, 0])
                .perturb(&mut rng, 0.0)
                .vec(),
                .vec()
                .iter()
                .map(|&x| x)
                .collect::<Vec<_>>(),
            vec![1, 1, 0, 0]
        );
    }