~ruther/ctu-fee-eoa

79c83af97c81934e0a00b46126f6cea215dc77dc — Rutherther 2 months ago 2ae2326
feat: add real fitness functions

Add linear, step, rastrigin, griewank and schwefel fitness functions
along with the tests for them.

Try using static vector where appropriate, but use OVector for those
where filling with zeros won't work. This is mostly a constrain for
the tests... rather than a real constrain. In the end the dimensions
will be given for each problem.
M env/src/fitness/mod.rs => env/src/fitness/mod.rs +1 -0
@@ 6,6 6,7 @@ pub mod labs;
pub mod one_max;
pub mod rosenbrock;
pub mod sphere;
pub mod real;

pub trait FitnessFunction {
    type In;

A env/src/fitness/real.rs => env/src/fitness/real.rs +278 -0
@@ 0,0 1,278 @@
use std::{convert::Infallible, f64::consts::PI, marker::PhantomData};

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

use super::FitnessFunction;

pub struct Linear<const LEN: usize> {
    a0: f64,
    a: SVector<f64, LEN>,
}

impl<const LEN: usize> Linear<LEN> {
    pub fn new(a0: f64, a: SVector<f64, LEN>) -> Self {
        Self {
            a0,
            a
        }
    }
}

impl<const LEN: usize> FitnessFunction for Linear<LEN> {
    type In = SVector<f64, LEN>;
    type Out = f64;
    type Err = Infallible;

    fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
        Ok(self.a0 + (self.a.transpose() * inp).x)
    }
}

pub struct Step<const LEN: usize> {
    a0: f64,
    a: SVector<f64, LEN>,
}

impl<const LEN: usize> Step<LEN> {
    pub fn new(a0: f64, a: SVector<f64, LEN>) -> Self {
        Self {
            a0,
            a
        }
    }
}

impl<const LEN: usize> FitnessFunction for Step<LEN> {
    type In = SVector<f64, LEN>;
    type Out = f64;
    type Err = Infallible;

    fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
        Ok(self.a0 + inp.component_mul(&self.a).map(|x| x.floor()).sum())
    }
}

pub struct Rastrigin<TDim: Dim> {
    _phantom: PhantomData<TDim>
}

impl<TDim: Dim> Rastrigin<TDim> {
    pub fn new() -> Self {
        Self {
            _phantom: PhantomData
        }
    }
}

impl<TDim: Dim> FitnessFunction for Rastrigin<TDim>
where
    DefaultAllocator: Allocator<TDim>
{
    type In = OVector<f64, TDim>;
    type Out = f64;
    type Err = Infallible;

    fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
        let dim = inp.len() as f64;
        Ok(10.0 * dim + inp.iter()
           .map(|x| x.powi(2) - 10.0 * (2.0 * PI * x).cos())
           .sum::<f64>())
    }
}

pub struct Griewank<TDim: Dim> {
    _phantom: PhantomData<TDim>
}

impl<TDim: Dim> Griewank<TDim> {
    pub fn new() -> Self {
        Self {
            _phantom: PhantomData
        }
    }
}

impl<TDim: Dim> FitnessFunction for Griewank<TDim>
where
    DefaultAllocator: Allocator<TDim>
{
    type In = OVector<f64, TDim>;
    type Out = f64;
    type Err = Infallible;

    fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
        Ok(1.0 + inp.map(|x| x.powi(2)).sum() / 4000.0 - inp
           .iter()
           .enumerate()
           .map(|(i, x)| (x / ((i + 1) as f64).sqrt()).cos())
           .product::<f64>())
    }
}

pub struct Schwefel<const LEN: usize>;

impl<const LEN: usize> Schwefel<LEN> {
    pub fn new() -> Self {
        Self
    }
}

impl<const LEN: usize> FitnessFunction for Schwefel<LEN> {
    type In = SVector<f64, LEN>;
    type Out = f64;
    type Err = Infallible;

    fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err> {
        Ok(-inp
            .map(|x| x * x.abs().sqrt().sin())
            .sum())
    }
}

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

    use crate::{fitness::{real::{Linear, Rastrigin, Schwefel, Step}, FitnessFunction}, test_infra::load_test_file};

    use super::Griewank;

    #[test]
    fn test_linear_1() {
        const MAX_LEN: usize = 10;

        let data = load_test_file::<f64, f64>("tests/linear_1.txt");
        let offset = SVector::repeat(1.0);
        let linear = Linear::new(1.0, offset);

        for test in data {
            let filled = test.inp.iter()
                .chain(vec![0f64; MAX_LEN - test.inp.len()].iter())
                .map(|x| *x)
                .collect::<Vec<_>>();
            let inp = SVector::<f64, MAX_LEN>::from_vec(filled);

            assert_eq!(
                linear.fit(&inp).unwrap(),
                test.out
            );
        }
    }

    #[test]
    fn test_linear_2() {
        const MAX_LEN: usize = 10;

        let data = load_test_file::<f64, f64>("tests/linear_2.txt");
        let offset = SVector::from_vec((2..=11).map(|x| x as f64).collect());
        let linear = Linear::new(1.0, offset);

        for test in data {
            let filled = test.inp.iter()
                .chain(vec![0f64; MAX_LEN - test.inp.len()].iter())
                .map(|x| *x)
                .collect::<Vec<_>>();
            let inp = SVector::<f64, MAX_LEN>::from_vec(filled);

            assert_eq!(
                linear.fit(&inp).unwrap(),
                test.out
            );
        }
    }

    #[test]
    fn test_step_1() {
        const MAX_LEN: usize = 10;

        let data = load_test_file::<f64, f64>("tests/step_1.txt");
        let offset = SVector::repeat(1.0);
        let linear = Step::new(1.0, offset);

        for test in data {
            let filled = test.inp.iter()
                .chain(vec![0f64; MAX_LEN - test.inp.len()].iter())
                .map(|x| *x)
                .collect::<Vec<_>>();
            let inp = SVector::<f64, MAX_LEN>::from_vec(filled);

            assert_eq!(
                linear.fit(&inp).unwrap(),
                test.out
            );
        }
    }

    #[test]
    fn test_step_2() {
        const MAX_LEN: usize = 10;

        let data = load_test_file::<f64, f64>("tests/step_2.txt");
        let offset = SVector::from_vec((2..=11).map(|x| x as f64).collect());
        let linear = Step::new(1.0, offset);

        for test in data {
            let filled = test.inp.iter()
                .chain(vec![0f64; MAX_LEN - test.inp.len()].iter())
                .map(|x| *x)
                .collect::<Vec<_>>();
            let inp = SVector::<f64, MAX_LEN>::from_vec(filled);

            assert_eq!(
                linear.fit(&inp).unwrap(),
                test.out
            );
        }
    }

    #[test]
    fn test_rastrigin() {
        let data = load_test_file::<f64, f64>("tests/rastrigin.txt");
        let linear = Rastrigin::<Dyn>::new();

        for test in data {
            let inp = OVector::<f64, Dyn>::from_vec(test.inp);

            assert_eq!(
                linear.fit(&inp).unwrap(),
                test.out
            );
        }
    }

    #[test]
    fn test_griewank() {
        let data = load_test_file::<f64, f64>("tests/griewank.txt");
        let griewank = Griewank::<Dyn>::new();

        for test in data {
            let inp = OVector::<f64, Dyn>::from_vec(test.inp);

            assert_eq!(
                griewank.fit(&inp).unwrap(),
                test.out
            );
        }
    }

    #[test]
    fn test_schwefel() {
        const MAX_LEN: usize = 10;

        let data = load_test_file::<f64, f64>("tests/schwefel.txt");
        let schwefel = Schwefel::new();

        for test in data {
            let filled = test.inp.iter()
                .chain(vec![0f64; MAX_LEN - test.inp.len()].iter())
                .map(|x| *x)
                .collect::<Vec<_>>();
            let inp = SVector::<f64, MAX_LEN>::from_vec(filled);

            assert_eq!(
                schwefel.fit(&inp).unwrap(),
                test.out
            );
        }
    }
}

A env/tests/griewank.txt => env/tests/griewank.txt +32 -0
@@ 0,0 1,32 @@
# Test data for Griewank function.
0 : 0.0
0.5 : 0.12247993810962732
1 : 0.4599476941318603
-0.5 : 0.12247993810962732
-5 : 0.7225878145367739
5 : 0.7225878145367739
0 0 : 0.0
-1 -1 : 0.5897380911762422
-1 1 : 0.5897380911762422
1 -1 : 0.5897380911762422
1 1 : 0.5897380911762422
-0.1 -0.2 : 0.014941804023654082
0.1 0.2 : 0.014941804023654082
-5 -5 : 1.2744346440216008
-5 5 : 1.2744346440216008
5 -5 : 1.2744346440216008
5 5 : 1.2744346440216008
0 0 0 : 0.0
1 1 1 : 0.656567738230001
-5 -5 -5 : 0.765274977211063
-5 -5 5 : 0.765274977211063
-5 5 -5 : 0.765274977211063
-5 5 5 : 0.765274977211063
5 -5 -5 : 0.765274977211063
5 -5 5 : 0.765274977211063
5 5 -5 : 0.765274977211063
5 5 5 : 0.765274977211063
-0.1 -1.2 -2.3 -3.4 -4.5 -5.6 -6.7 -7.8 -8.9 -9.1 : 1.088546150836955
0.1 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 9.1 : 1.088546150836955
0 0 0 0 0 0 0 0 0 0 : 0.0
1 1 1 1 1 1 1 1 1 1 : 0.8067591547236139

A env/tests/linear_1.txt => env/tests/linear_1.txt +32 -0
@@ 0,0 1,32 @@
# Test data for Linear function, with coeffs a_i= (1,1,...,1) and bias a_0 = 1
0 : 1
0.5 : 1.5
1 : 2
-0.5 : 0.5
-5 : -4
5 : 6
0 0 : 1
-1 -1 : -1
-1 1 : 1
1 -1 : 1
1 1 : 3
-0.1 -0.2 : 0.7
0.1 0.2 : 1.3
-5 -5 : -9
-5 5 : 1
5 -5 : 1
5 5 : 11
0 0 0 : 1
1 1 1 : 4
-5 -5 -5 : -14
-5 -5 5 : -4
-5 5 -5 : -4
-5 5 5 : 6
5 -5 -5 : -4
5 -5 5 : 6
5 5 -5 : 6
5 5 5 : 16
-0.1 -1.2 -2.3 -3.4 -4.5 -5.6 -6.7 -7.8 -8.9 -9.1 : -48.6
0.1 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 9.1 : 50.6
0 0 0 0 0 0 0 0 0 0 : 1
1 1 1 1 1 1 1 1 1 1 : 11

A env/tests/linear_2.txt => env/tests/linear_2.txt +32 -0
@@ 0,0 1,32 @@
# Test data for Linear function, with coeffs a_i = (2,3,...,D+1) and bias a_0 = 1
0 : 1
0.5 : 2.0
1 : 3
-0.5 : 0.0
-5 : -9
5 : 11
0 0 : 1
-1 -1 : -4
-1 1 : 2
1 -1 : 0
1 1 : 6
-0.1 -0.2 : 0.19999999999999996
0.1 0.2 : 1.8
-5 -5 : -24
-5 5 : 6
5 -5 : -4
5 5 : 26
0 0 0 : 1
1 1 1 : 10
-5 -5 -5 : -44
-5 -5 5 : -4
-5 5 -5 : -14
-5 5 5 : 26
5 -5 -5 : -24
5 -5 5 : 16
5 5 -5 : 6
5 5 5 : 46
-0.1 -1.2 -2.3 -3.4 -4.5 -5.6 -6.7 -7.8 -8.9 -9.1 : -408.1
0.1 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 9.1 : 410.1
0 0 0 0 0 0 0 0 0 0 : 1
1 1 1 1 1 1 1 1 1 1 : 66

A env/tests/rastrigin.txt => env/tests/rastrigin.txt +32 -0
@@ 0,0 1,32 @@
# Test data for Rastringin function.
0 : 0.0
0.5 : 20.25
1 : 1.0
-0.5 : 20.25
-5 : 25.0
5 : 25.0
0 0 : 0.0
-1 -1 : 2.0
-1 1 : 2.0
1 -1 : 2.0
1 1 : 2.0
-0.1 -0.2 : 8.869660112501052
0.1 0.2 : 8.869660112501052
-5 -5 : 50.0
-5 5 : 50.0
5 -5 : 50.0
5 5 : 50.0
0 0 0 : 0.0
1 1 1 : 3.0
-5 -5 -5 : 75.0
-5 -5 5 : 75.0
-5 5 -5 : 75.0
-5 5 5 : 75.0
5 -5 -5 : 75.0
5 -5 5 : 75.0
5 5 -5 : 75.0
5 5 5 : 75.0
-0.1 -1.2 -2.3 -3.4 -4.5 -5.6 -6.7 -7.8 -8.9 -9.1 : 439.5698300562505
0.1 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 9.1 : 439.5698300562505
0 0 0 0 0 0 0 0 0 0 : 0.0
1 1 1 1 1 1 1 1 1 1 : 10.0

A env/tests/schwefel.txt => env/tests/schwefel.txt +32 -0
@@ 0,0 1,32 @@
# Test data for Schwefel function.
0 : -0.0
0.5 : -0.32481846954003124
1 : -0.8414709848078965
-0.5 : 0.32481846954003124
-5 : 3.93374565773607
5 : -3.93374565773607
0 0 : -0.0
-1 -1 : 1.682941969615793
-1 1 : -0.0
1 -1 : -0.0
1 1 : -1.682941969615793
-0.1 -0.2 : 0.11758932708149258
0.1 0.2 : -0.11758932708149258
-5 -5 : 7.86749131547214
-5 5 : -0.0
5 -5 : -0.0
5 5 : -7.86749131547214
0 0 0 : -0.0
1 1 1 : -2.5244129544236893
-5 -5 -5 : 11.80123697320821
-5 -5 5 : 3.93374565773607
-5 5 -5 : 3.93374565773607
-5 5 5 : -3.93374565773607
5 -5 -5 : 3.93374565773607
5 -5 5 : -3.93374565773607
5 5 -5 : -3.93374565773607
5 5 5 : -11.80123697320821
-0.1 -1.2 -2.3 -3.4 -4.5 -5.6 -6.7 -7.8 -8.9 -9.1 : 23.145593064008178
0.1 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 9.1 : -23.145593064008178
0 0 0 0 0 0 0 0 0 0 : -0.0
1 1 1 1 1 1 1 1 1 1 : -8.414709848078965

A env/tests/step_1.txt => env/tests/step_1.txt +32 -0
@@ 0,0 1,32 @@
# Test data for Step function, with coeffs a_i= (1,1,...,1) and bias a_0 = 1
0 : 1
0.5 : 1
1 : 2
-0.5 : 0
-5 : -4
5 : 6
0 0 : 1
-1 -1 : -1
-1 1 : 1
1 -1 : 1
1 1 : 3
-0.1 -0.2 : -1
0.1 0.2 : 1
-5 -5 : -9
-5 5 : 1
5 -5 : 1
5 5 : 11
0 0 0 : 1
1 1 1 : 4
-5 -5 -5 : -14
-5 -5 5 : -4
-5 5 -5 : -4
-5 5 5 : 6
5 -5 -5 : -4
5 -5 5 : 6
5 5 -5 : 6
5 5 5 : 16
-0.1 -1.2 -2.3 -3.4 -4.5 -5.6 -6.7 -7.8 -8.9 -9.1 : -54
0.1 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 9.1 : 46
0 0 0 0 0 0 0 0 0 0 : 1
1 1 1 1 1 1 1 1 1 1 : 11

A env/tests/step_2.txt => env/tests/step_2.txt +32 -0
@@ 0,0 1,32 @@
# Test data for Step function, with coeffs a_i = (2,3,...,D+1) and bias a_0 = 1
0 : 1
0.5 : 2
1 : 3
-0.5 : 0
-5 : -9
5 : 11
0 0 : 1
-1 -1 : -4
-1 1 : 2
1 -1 : 0
1 1 : 6
-0.1 -0.2 : -1
0.1 0.2 : 1
-5 -5 : -24
-5 5 : 6
5 -5 : -4
5 5 : 26
0 0 0 : 1
1 1 1 : 10
-5 -5 -5 : -44
-5 -5 5 : -4
-5 5 -5 : -14
-5 5 5 : 26
5 -5 -5 : -24
5 -5 5 : 16
5 5 -5 : 6
5 5 5 : 46
-0.1 -1.2 -2.3 -3.4 -4.5 -5.6 -6.7 -7.8 -8.9 -9.1 : -413
0.1 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 9.1 : 408
0 0 0 0 0 0 0 0 0 0 : 1
1 1 1 1 1 1 1 1 1 1 : 66