From 41b0eb7ee7ae129303c1c0f7d818c52904edc001 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 17 Oct 2025 11:52:50 +0200 Subject: [PATCH] refactor: Use OVector instead of SVector in library --- env/src/binary_string.rs | 71 ++++++++++++--------- env/src/fitness/mod.rs | 30 ++++++--- env/src/fitness/real.rs | 116 ++++++++++++++++++++-------------- env/src/fitness/rosenbrock.rs | 34 +++++++--- env/src/fitness/sphere.rs | 33 +++++++--- env/src/local_search/mod.rs | 12 ++-- 6 files changed, 189 insertions(+), 107 deletions(-) diff --git a/env/src/binary_string.rs b/env/src/binary_string.rs index 6425b8594396565bda1f51949cbd904c3700a7cb..f2b5cf4dac32613df413b4abd7b7ffe65283e71c 100644 --- a/env/src/binary_string.rs +++ b/env/src/binary_string.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use nalgebra::{allocator::Allocator, Const, DefaultAllocator, Dim, DimName, Dynamic, OVector, SVector, U1}; use rand::{Rng, RngCore}; #[derive(Debug, Clone, PartialEq)] @@ -73,20 +74,20 @@ impl BinaryString { BinaryString::to_real_internal(self.vec.iter(), self.vec.len(), min, max) } - pub fn to_real(self: &Self, bounds: &Vec) -> Result, BinaryStringConversionError> { - if bounds.len() == 0 { - return Err(BinaryStringConversionError::NoBounds); - } - - let chunk_size = self.vec.len() / bounds.len(); - if self.vec.len() % bounds.len() != 0 { + pub fn to_real(self: &Self, min: &OVector, max: &OVector) -> Result, BinaryStringConversionError> + where + TDim: Dim, + DefaultAllocator: Allocator, + { + let chunk_size = self.vec.len() / min.len(); + if self.vec.len() % min.len() != 0 { return Err(BinaryStringConversionError::DimensionMismatch); } - Ok(self.vec.chunks(chunk_size) - .zip(bounds) - .map(|(chunk, bound)| BinaryString::to_real_internal(chunk.iter(), chunk_size, bound.min(), bound.max())) - .collect::>()) + let iter = self.vec.chunks(chunk_size) + .zip(min.iter().zip(max)) + .map(|(chunk, (&min, &max))| BinaryString::to_real_internal(chunk.iter(), chunk_size, min, max)); + Ok(OVector::::from_iterator_generic(min.shape_generic().0, min.shape_generic().1, iter)) } } @@ -121,7 +122,8 @@ impl<'a> IntoIterator for &'a BinaryString { #[cfg(test)] pub mod tests { - use crate::{binary_string::BinaryString, test_infra::{load_test_file, DataArrOfReals}}; + use crate::{binary_string::{BinaryString, BinaryStringConversionError}, test_infra::{load_test_file, DataArrOfReals}}; + use nalgebra::SVector; use super::Bounds; @@ -170,15 +172,12 @@ pub mod tests { ); } - #[cfg(test)] - fn test_binary_string_to_real(file_name: &str, bounds: Vec) { - use crate::binary_string::BinaryStringConversionError; - + fn test_binary_string_to_real(file_name: &str, min: SVector, max: SVector) { let data = load_test_file::(file_name); for test in data { let res = BinaryString::new(test.inp) - .to_real(&bounds); + .to_real(&min, &max); if !test.out.valid { assert_eq!( res, @@ -187,7 +186,7 @@ pub mod tests { } else { assert_eq!( - res.unwrap(), + res.unwrap().iter().map(|&x| x).collect::>(), test.out.vec ); } @@ -198,7 +197,8 @@ pub mod tests { fn test_binary_string_to_real_1d_1() { test_binary_string_to_real( "tests/Bin2Real_1D_1.txt", - vec![Bounds::new(0.0, 1.0)] + SVector::::new(0.0), + SVector::::new(1.0), ); } @@ -206,7 +206,8 @@ pub mod tests { fn test_binary_string_to_real_1d_2() { test_binary_string_to_real( "tests/Bin2Real_1D_2.txt", - vec![Bounds::new(0.0, 4095.0)] + SVector::::new(0.0), + SVector::::new(4095.0), ); } @@ -214,7 +215,8 @@ pub mod tests { fn test_binary_string_to_real_1d_3() { test_binary_string_to_real( "tests/Bin2Real_1D_3.txt", - vec![Bounds::new(-5.0, 5.0)] + SVector::::new(-5.0), + SVector::::new(5.0), ); } @@ -222,7 +224,8 @@ pub mod tests { fn test_binary_string_to_real_2d_1() { test_binary_string_to_real( "tests/Bin2Real_2D_1.txt", - vec![Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0)] + SVector::::new(0.0, 0.0), + SVector::::new(1.0, 1.0), ); } @@ -230,7 +233,8 @@ pub mod tests { fn test_binary_string_to_real_2d_2() { test_binary_string_to_real( "tests/Bin2Real_2D_2.txt", - vec![Bounds::new(0.0, 63.0), Bounds::new(-32.0, 31.0)] + SVector::::new(0.0, -32.0), + SVector::::new(63.0, 31.0), ); } @@ -238,7 +242,8 @@ pub mod tests { fn test_binary_string_to_real_2d_3() { test_binary_string_to_real( "tests/Bin2Real_2D_3.txt", - vec![Bounds::new(-5.0, 5.0), Bounds::new(0.0, 10.0)] + SVector::::new(-5.0, 0.0), + SVector::::new(5.0, 10.0), ); } @@ -246,7 +251,8 @@ pub mod tests { fn test_binary_string_to_real_3d_1() { test_binary_string_to_real( "tests/Bin2Real_3D_1.txt", - vec![Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0)] + SVector::::new(0.0, 0.0, 0.0), + SVector::::new(1.0, 1.0, 1.0), ); } @@ -254,7 +260,8 @@ pub mod tests { fn test_binary_string_to_real_3d_2() { test_binary_string_to_real( "tests/Bin2Real_3D_2.txt", - vec![Bounds::new(0.0, 15.0), Bounds::new(-8.0, 7.0), Bounds::new(-8.0, 8.0)] + SVector::::new(0.0, -8.0, -8.0), + SVector::::new(15.0, 7.0, 8.0), ); } @@ -262,7 +269,8 @@ pub mod tests { fn test_binary_string_to_real_4d_1() { test_binary_string_to_real( "tests/Bin2Real_4D_1.txt", - vec![Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0)] + SVector::::new(0.0, 0.0, 0.0, 0.0), + SVector::::new(1.0, 1.0, 1.0, 1.0), ); } @@ -270,7 +278,8 @@ pub mod tests { fn test_binary_string_to_real_4d_2() { test_binary_string_to_real( "tests/Bin2Real_4D_2.txt", - vec![Bounds::new(0.0, 7.0), Bounds::new(-4.0, 3.0), Bounds::new(-4.0, 4.0), Bounds::new(-8.0, 0.0)] + SVector::::new(0.0, -4.0, -4.0, -8.0), + SVector::::new(7.0, 3.0, 4.0, 0.0), ); } @@ -278,7 +287,8 @@ pub mod tests { fn test_binary_string_to_real_6d_1() { test_binary_string_to_real( "tests/Bin2Real_6D_1.txt", - vec![Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0), Bounds::new(0.0, 1.0)] + SVector::::zeros(), + SVector::::from_element(1.0), ); } @@ -286,7 +296,8 @@ pub mod tests { fn test_binary_string_to_real_6d_2() { test_binary_string_to_real( "tests/Bin2Real_6D_2.txt", - (0..=5).map(|x| Bounds::new(x as f64, (2 * (x + 1)) as f64)).collect::>() + SVector::::from_iterator((0..=5).map(|x| x as f64)), + SVector::::from_iterator((0..=5).map(|x| (2 * (x + 1)) as f64)), ); } } diff --git a/env/src/fitness/mod.rs b/env/src/fitness/mod.rs index 8effb0e3e61d47abc1510a7fef22245af1cc8aa6..e24623ed647c54eeb1788cd4de0f639be404c909 100644 --- a/env/src/fitness/mod.rs +++ b/env/src/fitness/mod.rs @@ -1,5 +1,7 @@ use std::convert::Infallible; +use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector, SVector}; + use crate::binary_string::{BinaryString, BinaryStringConversionError, Bounds}; pub mod labs; @@ -16,29 +18,41 @@ pub trait FitnessFunction { fn fit(self: &Self, inp: &Self::In) -> Result; } -pub struct BinaryFitnessWrapper { - bounds: Vec, +pub struct BinaryFitnessWrapper +where + D: Dim, + DefaultAllocator: Allocator +{ + min: OVector, + max: OVector, fitting_function: TFitness, } -impl BinaryFitnessWrapper { - pub fn new(fitting_function: TFitness, bounds: Vec) -> Self { +impl BinaryFitnessWrapper +where + D: Dim, + DefaultAllocator: Allocator +{ + pub fn new(fitting_function: TFitness, min: OVector, max: OVector) -> Self { BinaryFitnessWrapper { fitting_function, - bounds, + min, + max } } } -impl FitnessFunction for BinaryFitnessWrapper +impl FitnessFunction for BinaryFitnessWrapper where - TFitness: FitnessFunction, Out = f64, Err = Infallible> + D: Dim, + DefaultAllocator: Allocator, + TFitness: FitnessFunction, Out = f64, Err = Infallible> { type In = BinaryString; type Out = f64; type Err = BinaryStringConversionError; fn fit(self: &Self, inp: &BinaryString) -> Result { - Ok(self.fitting_function.fit(&inp.to_real(&self.bounds)?).unwrap()) + Ok(self.fitting_function.fit(&inp.to_real(&self.min, &self.max)?).unwrap()) } } diff --git a/env/src/fitness/real.rs b/env/src/fitness/real.rs index 2600e65fa0e138af34d20028fa4bad3f60cf212f..605db0528b75c034efbd43e1b5ceee2886c93f8a 100644 --- a/env/src/fitness/real.rs +++ b/env/src/fitness/real.rs @@ -1,16 +1,24 @@ use std::{convert::Infallible, f64::consts::PI, marker::PhantomData}; -use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector, SVector}; +use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector, SVector, U1}; use super::FitnessFunction; -pub struct Linear { +pub struct Linear +where + D: Dim, + DefaultAllocator: Allocator +{ a0: f64, - a: SVector, + a: OVector, } -impl Linear { - pub fn new(a0: f64, a: SVector) -> Self { +impl Linear +where + D: Dim, + DefaultAllocator: Allocator +{ + pub fn new(a0: f64, a: OVector) -> Self { Self { a0, a @@ -18,8 +26,13 @@ impl Linear { } } -impl FitnessFunction for Linear { - type In = SVector; +impl FitnessFunction for Linear +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator +{ + type In = OVector; type Out = f64; type Err = Infallible; @@ -28,13 +41,21 @@ impl FitnessFunction for Linear { } } -pub struct Step { +pub struct Step +where + D: Dim, + DefaultAllocator: Allocator, +{ a0: f64, - a: SVector, + a: OVector, } -impl Step { - pub fn new(a0: f64, a: SVector) -> Self { +impl Step +where + D: Dim, + DefaultAllocator: Allocator, +{ + pub fn new(a0: f64, a: OVector) -> Self { Self { a0, a @@ -42,8 +63,12 @@ impl Step { } } -impl FitnessFunction for Step { - type In = SVector; +impl FitnessFunction for Step +where + D: Dim, + DefaultAllocator: Allocator, +{ + type In = OVector; type Out = f64; type Err = Infallible; @@ -52,11 +77,11 @@ impl FitnessFunction for Step { } } -pub struct Rastrigin { - _phantom: PhantomData +pub struct Rastrigin { + _phantom: PhantomData } -impl Rastrigin { +impl Rastrigin { pub fn new() -> Self { Self { _phantom: PhantomData @@ -64,11 +89,11 @@ impl Rastrigin { } } -impl FitnessFunction for Rastrigin +impl FitnessFunction for Rastrigin where - DefaultAllocator: Allocator + DefaultAllocator: Allocator { - type In = OVector; + type In = OVector; type Out = f64; type Err = Infallible; @@ -80,8 +105,8 @@ where } } -pub struct Griewank { - _phantom: PhantomData +pub struct Griewank { + _phantom: PhantomData } impl Griewank { @@ -92,11 +117,11 @@ impl Griewank { } } -impl FitnessFunction for Griewank +impl FitnessFunction for Griewank where - DefaultAllocator: Allocator + DefaultAllocator: Allocator { - type In = OVector; + type In = OVector; type Out = f64; type Err = Infallible; @@ -109,16 +134,24 @@ where } } -pub struct Schwefel; +pub struct Schwefel { + _phantom: PhantomData +} -impl Schwefel { +impl Schwefel { pub fn new() -> Self { - Self + Self { + _phantom: PhantomData + } } } -impl FitnessFunction for Schwefel { - type In = SVector; +impl FitnessFunction for Schwefel +where + D: Dim, + DefaultAllocator: Allocator +{ + type In = OVector; type Out = f64; type Err = Infallible; @@ -139,18 +172,13 @@ pub mod tests { #[test] fn test_linear_1() { - const MAX_LEN: usize = 10; - let data = load_test_file::("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::>(); - let inp = SVector::::from_vec(filled); + let offset = OVector::::from_element(test.inp.len(), 1.0); + let inp = OVector::::from_vec(test.inp); + + let linear = Linear::new(1.0, offset); assert_eq!( linear.fit(&inp).unwrap(), @@ -161,18 +189,12 @@ pub mod tests { #[test] fn test_linear_2() { - const MAX_LEN: usize = 10; - let data = load_test_file::("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::>(); - let inp = SVector::::from_vec(filled); + let offset = OVector::::from_iterator(test.inp.len(), (2..=test.inp.len()+1).map(|x| x as f64)); + let linear = Linear::new(1.0, offset); + let inp = OVector::::from_vec(test.inp); assert_eq!( linear.fit(&inp).unwrap(), diff --git a/env/src/fitness/rosenbrock.rs b/env/src/fitness/rosenbrock.rs index 83e892b6a37adc518de86ca9be789d656c9b08e0..eb15cf48532ce1ff367b6be44656d99e87ddd947 100644 --- a/env/src/fitness/rosenbrock.rs +++ b/env/src/fitness/rosenbrock.rs @@ -1,21 +1,35 @@ -use std::convert::Infallible; +use std::{convert::Infallible, marker::PhantomData}; +use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector}; + use super::FitnessFunction; -pub struct Rosenbrock; +pub struct Rosenbrock { + _phantom: PhantomData +} -impl Rosenbrock { +impl Rosenbrock +where + D: Dim, + DefaultAllocator: Allocator +{ pub fn new() -> Self { - Rosenbrock + Rosenbrock { + _phantom: PhantomData + } } } -impl FitnessFunction for Rosenbrock { - type In = Vec; +impl FitnessFunction for Rosenbrock +where + D: Dim, + DefaultAllocator: Allocator +{ + type In = OVector; type Out = f64; type Err = Infallible; - fn fit(self: &Self, inp: &Vec) -> Result { - Ok(inp.windows(2) + fn fit(self: &Self, inp: &OVector) -> Result { + Ok(inp.as_slice().windows(2) .map(|xs| 100.0 * (xs[1] - xs[0].powi(2)).powi(2) + (1.0 - xs[0]).powi(2)) .sum()) } @@ -23,6 +37,8 @@ impl FitnessFunction for Rosenbrock { #[cfg(test)] pub mod tests { + use nalgebra::{Dyn, OVector}; + use crate::{fitness::{rosenbrock::Rosenbrock, FitnessFunction}, test_infra::load_test_file}; #[test] @@ -31,7 +47,7 @@ pub mod tests { for test in data { assert_eq!( - Rosenbrock::new().fit(&test.inp).unwrap(), + Rosenbrock::::new().fit(&OVector::<_, Dyn>::from_vec(test.inp)).unwrap(), test.out ) } diff --git a/env/src/fitness/sphere.rs b/env/src/fitness/sphere.rs index db7fd1d0f00cf081630637775f3f989055d6f69d..dfe2b9cfb4323e5b4bc58b83b2df9634cc751e1d 100644 --- a/env/src/fitness/sphere.rs +++ b/env/src/fitness/sphere.rs @@ -1,24 +1,38 @@ use std::convert::Infallible; +use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector, SVector}; + use super::FitnessFunction; -pub struct Sphere { - offset: Vec +pub struct Sphere +where + D: Dim, + DefaultAllocator: Allocator +{ + offset: OVector } -impl Sphere { - pub fn new(offset: Vec) -> Self { +impl Sphere +where + D: Dim, + DefaultAllocator: Allocator +{ + pub fn new(offset: OVector) -> Self { Sphere { offset } } } -impl FitnessFunction for Sphere { - type In = Vec; +impl FitnessFunction for Sphere +where + D: Dim, + DefaultAllocator: Allocator +{ + type In = OVector; type Out = f64; type Err = Infallible; - fn fit(self: &Self, chromosome: &Vec) -> Result { + fn fit(self: &Self, chromosome: &OVector) -> Result { Ok(chromosome .iter() .zip(&self.offset) @@ -29,6 +43,8 @@ impl FitnessFunction for Sphere { #[cfg(test)] pub mod tests { + use nalgebra::{Dyn, OVector}; + use crate::{fitness::{sphere::Sphere, FitnessFunction}, test_infra::load_test_file}; #[test] @@ -37,7 +53,8 @@ pub mod tests { for test in data { assert_eq!( - Sphere::new(vec![1.0; 10]).fit(&test.inp).unwrap(), + Sphere::::new(OVector::<_, Dyn>::from_vec(vec![1.0; test.inp.len()])) + .fit(&OVector::<_, Dyn>::from_vec(test.inp)).unwrap(), test.out ) } diff --git a/env/src/local_search/mod.rs b/env/src/local_search/mod.rs index b8eddd97753cceba88cee8b14f0f946cd719423e..952b13d83b8a4b09217cc2a1b73b96faa029d2e6 100644 --- a/env/src/local_search/mod.rs +++ b/env/src/local_search/mod.rs @@ -127,10 +127,11 @@ pub mod tests { fn test_local_search_sphere() { let optimum = BinaryString::new(vec![0, 0, 1, 0, 0, 0, 0, 1, 0, 0]); - let bounds = vec![Bounds::new(0.0, 31.0), Bounds::new(0.0, 31.0)]; - let optimum_real = optimum.to_real(&bounds).unwrap(); + let min = SVector::::from_element(0.0); + let max = SVector::::from_element(31.0); + let optimum_real = optimum.to_real(&min, &max).unwrap(); let sphere = Sphere::new(optimum_real); - let sphere_wrapped = BinaryFitnessWrapper::new(sphere, bounds); + let sphere_wrapped = BinaryFitnessWrapper::new(sphere, min, max); let result = local_search_first_improving( &sphere_wrapped, @@ -228,8 +229,9 @@ pub mod tests { let rosenbrock = Rosenbrock::new(); let optimum = BinaryString::new(vec![1, 0, 0, 0, 1, 1, 0, 0, 0, 1]); - let bounds = vec![Bounds::new(-16.0, 15.0), Bounds::new(-16.0, 15.0)]; - let rosenbrock_wrapped = BinaryFitnessWrapper::new(rosenbrock, bounds); + let min = SVector::::from_element(-16.0); + let max = SVector::::from_element(15.0); + let rosenbrock_wrapped = BinaryFitnessWrapper::new(rosenbrock, min, max); let result = local_search_first_improving( &rosenbrock_wrapped,