A env/src/binary_string.rs => env/src/binary_string.rs +285 -0
@@ 0,0 1,285 @@
+use std::str::FromStr;
+use crate::test_infra::{load_test_file, DataArrOfReals};
+
+use rand::Rng;
+
+#[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>
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum BinaryStringConversionError {
+ DimensionMismatch,
+ NoBounds
+}
+
+impl BinaryString {
+ pub fn new(vec: Vec<i8>) -> BinaryString {
+ BinaryString {
+ vec
+ }
+ }
+
+ pub fn vec(&self) -> &Vec<i8> {
+ &self.vec
+ }
+
+ pub fn perturb<TRng>(self: &Self, rng: &mut TRng, p: f64) -> Self
+ where TRng : Rng
+ {
+ BinaryString::new(
+ self.into_iter()
+ .map(|c| if rng.random::<f64>() <= p { 1 - *c } else { *c })
+ .collect::<Vec<i8>>()
+ )
+ }
+
+ fn to_real_internal<'a, T: DoubleEndedIterator<Item = &'a i8>>(vec: T, len: usize, min: f64, max: f64) -> f64
+ {
+ let diff = max - min;
+ let len = len as i32;
+ let max_represent_num = 2f64.powi(len) - 1.0;
+ let represented_num = vec
+ .rev()
+ .enumerate()
+ .map(|(bit, c)| diff * (*c as f64) * 2f64.powi(bit as i32))
+ .sum::<f64>();
+
+ min + (represented_num / max_represent_num)
+ }
+
+ pub fn to_real_single(self: &Self, min: f64, max: f64) -> f64 {
+ BinaryString::to_real_internal(self.vec.iter(), self.vec.len(), min, max)
+ }
+
+ pub fn to_real(self: &Self, bounds: &Vec<Bounds>) -> Result<Vec<f64>, BinaryStringConversionError> {
+ if bounds.len() == 0 {
+ return Err(BinaryStringConversionError::NoBounds);
+ }
+
+ let chunk_size = self.vec.len() / bounds.len();
+ if self.vec.len() % bounds.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::<Vec<f64>>())
+ }
+}
+
+impl FromStr for BinaryString {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let binary_vec: Vec<i8> = s
+ .chars()
+ // skip spaces
+ .filter(|c| *c != ' ')
+ // Map ones and zeros
+ .map(|c| match c {
+ '0' => Ok(0),
+ '1' => Ok(1),
+ _ => Err(format!("Invalid binary character: {}", c)),
+ })
+ .collect::<Result<Vec<i8>, Self::Err>>()?;
+
+ Ok(BinaryString::new(binary_vec))
+ }
+}
+
+impl<'a> IntoIterator for &'a BinaryString {
+ type Item = &'a i8;
+ type IntoIter = std::slice::Iter<'a, i8>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.vec.iter()
+ }
+}
+
+#[test]
+fn test_binary_string_to_real_single() {
+ assert_eq!(
+ BinaryString::new(vec![1])
+ .to_real_single(0.0, 32.0),
+ 32.0
+ );
+ assert_eq!(
+ BinaryString::new(vec![1, 1])
+ .to_real_single(0.0, 32.0),
+ 32.0
+ );
+ assert_eq!(
+ BinaryString::new(vec![0, 1])
+ .to_real_single(0.0, 32.0),
+ 32.0 / 3.0
+ );
+ assert_eq!(
+ BinaryString::new(vec![0, 0])
+ .to_real_single(-16.0, 16.0),
+ -16.0
+ );
+ assert_eq!(
+ BinaryString::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])
+ .to_real_single(0.0, 31.0),
+ 31.0
+ );
+ assert_eq!(
+ BinaryString::new(vec![0, 0, 0, 1, 0])
+ .to_real_single(0.0, 31.0),
+ 2.0
+ );
+
+ assert_eq!(
+ BinaryString::new(vec![1; 512])
+ .to_real_single(0.0, 31.0),
+ 31.0
+ );
+}
+
+fn test_binary_string_to_real(file_name: &str, bounds: Vec<Bounds>) {
+ let data = load_test_file::<i8, DataArrOfReals>(file_name);
+
+ for test in data {
+ let res = BinaryString::new(test.inp)
+ .to_real(&bounds);
+ if !test.out.valid {
+ assert_eq!(
+ res,
+ Err(BinaryStringConversionError::DimensionMismatch)
+ );
+ }
+ else {
+ assert_eq!(
+ res.unwrap(),
+ test.out.vec
+ );
+ }
+ }
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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)]
+ );
+}
+
+#[test]
+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::<Vec<_>>()
+ );
+}
A env/src/comparison/mod.rs => env/src/comparison/mod.rs +18 -0
@@ 0,0 1,18 @@
+pub trait BetterThanOperator<T> {
+ fn better_than(self: &Self, a: &T, b: &T) -> bool;
+}
+
+pub struct MinimizingOperator;
+impl MinimizingOperator {
+ pub fn new() -> Self {
+ Self
+ }
+}
+
+impl<T> BetterThanOperator<T> for MinimizingOperator
+ where T: PartialOrd
+{
+ fn better_than(self: &Self, a: &T, b: &T) -> bool {
+ a < b
+ }
+}
A env/src/fitness/labs.rs => env/src/fitness/labs.rs +54 -0
@@ 0,0 1,54 @@
+use std::convert::Infallible;
+use crate::{binary_string::BinaryString, test_infra::load_test_file};
+use super::FitnessFunction;
+
+pub struct LABS;
+
+impl LABS {
+ 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()
+ }
+}
+
+impl FitnessFunction for LABS {
+ type In = BinaryString;
+ type Out = i32;
+ type Err = Infallible;
+
+ fn fit(self: &Self, chromosome: &BinaryString) -> Result<i32, Infallible> {
+ let S: Vec<i32> = chromosome
+ .into_iter()
+ .map(|c| (*c as i32) * 2 - 1)
+ .collect();
+ let D = S.len();
+
+ Ok((1..=D-1)
+ .map(|k| LABS::Ck(k, &S).pow(2))
+ .sum())
+ }
+}
+
+#[test]
+fn test_LABS() {
+ let data = load_test_file::<i8, i32>("tests/labs.txt");
+
+ for test in data {
+ println!("Test vector {}", test.inp.iter()
+ .map(|x| x.to_string())
+ .collect::<Vec<String>>()
+ .join(", "));
+ assert_eq!(
+ LABS::new().fit(&BinaryString::new(test.inp)).unwrap(),
+ test.out
+ )
+ }
+}
A env/src/fitness/mod.rs => env/src/fitness/mod.rs +43 -0
@@ 0,0 1,43 @@
+use std::convert::Infallible;
+
+use crate::binary_string::{BinaryString, BinaryStringConversionError, Bounds};
+
+pub mod labs;
+pub mod one_max;
+pub mod rosenbrock;
+pub mod sphere;
+
+pub trait FitnessFunction {
+ type In;
+ type Out;
+ type Err;
+
+ fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err>;
+}
+
+pub struct BinaryFitnessWrapper<TFitness> {
+ bounds: Vec<Bounds>,
+ fitting_function: TFitness,
+}
+
+impl<TFitness> BinaryFitnessWrapper<TFitness> {
+ pub fn new(fitting_function: TFitness, bounds: Vec<Bounds>) -> Self {
+ BinaryFitnessWrapper {
+ fitting_function,
+ bounds,
+ }
+ }
+}
+
+impl<TFitness> FitnessFunction for BinaryFitnessWrapper<TFitness>
+where
+ TFitness: FitnessFunction<In = Vec<f64>, Out = f64, Err = Infallible>
+{
+ type In = BinaryString;
+ type Out = f64;
+ type Err = BinaryStringConversionError;
+
+ fn fit(self: &Self, inp: &BinaryString) -> Result<f64, BinaryStringConversionError> {
+ Ok(self.fitting_function.fit(&inp.to_real(&self.bounds)?).unwrap())
+ }
+}
A env/src/fitness/one_max.rs => env/src/fitness/one_max.rs +36 -0
@@ 0,0 1,36 @@
+use std::convert::Infallible;
+
+use crate::{binary_string::BinaryString, test_infra::load_test_file};
+
+use super::FitnessFunction;
+
+pub struct OneMax;
+impl OneMax {
+ pub fn new() -> Self {
+ OneMax
+ }
+}
+
+impl FitnessFunction for OneMax {
+ type In = BinaryString;
+ type Out = i32;
+ type Err = Infallible;
+
+ fn fit(self: &Self, chromosome: &BinaryString) -> Result<i32, Infallible> {
+ Ok(chromosome.into_iter()
+ .map(|x| *x as i32)
+ .sum())
+ }
+}
+
+#[test]
+fn test_one_max() {
+ let data = load_test_file::<i8, i32>("tests/onemax.txt");
+
+ for test in data {
+ assert_eq!(
+ OneMax::new().fit(&BinaryString::new(test.inp)).unwrap(),
+ test.out
+ );
+ }
+}
A env/src/fitness/rosenbrock.rs => env/src/fitness/rosenbrock.rs +36 -0
@@ 0,0 1,36 @@
+use std::convert::Infallible;
+use crate::test_infra::load_test_file;
+
+use super::FitnessFunction;
+
+pub struct Rosenbrock;
+
+impl Rosenbrock {
+ pub fn new() -> Self {
+ Rosenbrock
+ }
+}
+
+impl FitnessFunction for Rosenbrock {
+ type In = Vec<f64>;
+ type Out = f64;
+ type Err = Infallible;
+
+ fn fit(self: &Self, inp: &Vec<f64>) -> Result<f64, Infallible> {
+ Ok(inp.windows(2)
+ .map(|xs| 100.0 * (xs[1] - xs[0].powi(2)).powi(2) + (1.0 - xs[0]).powi(2))
+ .sum())
+ }
+}
+
+#[test]
+fn test_rosenbrock() {
+ let data = load_test_file::<f64, f64>("tests/rosenbrock.txt");
+
+ for test in data {
+ assert_eq!(
+ Rosenbrock::new().fit(&test.inp).unwrap(),
+ test.out
+ )
+ }
+}
A env/src/fitness/sphere.rs => env/src/fitness/sphere.rs +42 -0
@@ 0,0 1,42 @@
+use std::convert::Infallible;
+use crate::test_infra::load_test_file;
+
+use super::FitnessFunction;
+
+pub struct Sphere {
+ offset: Vec<f64>
+}
+
+impl Sphere {
+ pub fn new(offset: Vec<f64>) -> Self {
+ Sphere {
+ offset
+ }
+ }
+}
+
+impl FitnessFunction for Sphere {
+ type In = Vec<f64>;
+ type Out = f64;
+ type Err = Infallible;
+
+ fn fit(self: &Self, chromosome: &Vec<f64>) -> Result<f64, Infallible> {
+ Ok(chromosome
+ .iter()
+ .zip(&self.offset)
+ .map(|(x, o)| (x - o).powi(2))
+ .sum())
+ }
+}
+
+#[test]
+fn test_sphere() {
+ let data = load_test_file::<f64, f64>("tests/sphere.txt");
+
+ for test in data {
+ assert_eq!(
+ Sphere::new(vec![1.0; 10]).fit(&test.inp).unwrap(),
+ test.out
+ )
+ }
+}
A env/src/local_search/mod.rs => env/src/local_search/mod.rs +147 -0
@@ 0,0 1,147 @@
+use crate::binary_string::{BinaryString, Bounds};
+use crate::fitness::one_max::OneMax;
+use crate::fitness::sphere::Sphere;
+use crate::terminating::{AndTerminatingConditions, EqualTerminatingCondition, NoBetterForCyclesTerminatingCondition, TerminatingCondition};
+use crate::perturbation::{BinaryStringBitPerturbation, PerturbationOperator};
+use crate::comparison::{BetterThanOperator, MinimizingOperator};
+use crate::fitness::{FitnessFunction, BinaryFitnessWrapper};
+
+// Functions
+#[derive(Debug, Clone, PartialEq)]
+pub struct LocalSearchCandidate<T>
+ where T: Clone
+{
+ pub fit: T,
+ pub pos: BinaryString,
+ pub cycle: usize
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct LocalSearchResult<T>
+ where T: Clone
+{
+ pub best_candidate: LocalSearchCandidate<T>,
+
+ // How many cycles there were
+ pub cycles: usize,
+
+ // statistics
+ pub best_candidates: Vec<LocalSearchCandidate<T>>
+}
+
+fn local_search_first_improving<
+ T, TErr, TFit, TTerminatingCondition, TPerturbationOperator, TBetterThanOperator>(
+ fit: &TFit,
+ terminating_condition: &mut TTerminatingCondition,
+ perturbation_operator: &mut TPerturbationOperator,
+ better_than_operator: &TBetterThanOperator,
+ initial: &BinaryString
+) -> Result<LocalSearchResult<T>, TErr>
+where
+ T: Clone,
+ TFit: FitnessFunction<In = BinaryString, Out = T, Err = TErr>,
+ TTerminatingCondition: TerminatingCondition<T>,
+ TPerturbationOperator: PerturbationOperator<Chromosome = BinaryString>,
+ TBetterThanOperator: BetterThanOperator<T>,
+{
+ let mut best_candidate = LocalSearchCandidate {
+ pos: initial.clone(),
+ fit: fit.fit(&initial)?,
+ cycle: 0
+ };
+
+ let mut stats: Vec<LocalSearchCandidate<T>> = vec![];
+ let mut cycle: usize = 0;
+
+ while !terminating_condition.should_terminate(&best_candidate, &stats, cycle) {
+ let perturbed = perturbation_operator.perturb(&best_candidate.pos);
+ let perturbed_fit = fit.fit(&perturbed)?;
+
+ // Minimize
+ if better_than_operator.better_than(&perturbed_fit, &best_candidate.fit) {
+ best_candidate = LocalSearchCandidate {
+ pos: perturbed.clone(),
+ fit: perturbed_fit,
+ cycle
+ };
+
+ stats.push(best_candidate.clone());
+ }
+
+ cycle += 1;
+ }
+
+ Ok(LocalSearchResult {
+ best_candidate,
+ best_candidates: stats,
+ cycles: cycle
+ })
+}
+
+#[test]
+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 sphere = Sphere::new(optimum_real);
+ let sphere_wrapped = BinaryFitnessWrapper::new(sphere, bounds);
+
+ let result = local_search_first_improving(
+ &sphere_wrapped,
+ &mut
+ AndTerminatingConditions::new(
+ vec![
+ &mut EqualTerminatingCondition::new_remembered(optimum.clone()),
+ &mut NoBetterForCyclesTerminatingCondition::new(100)
+ ]
+ ),
+ &mut BinaryStringBitPerturbation::new(0.3),
+ &MinimizingOperator::new(),
+ &BinaryString::new(vec![1; 10]),
+ ).unwrap();
+
+ println!("{:?}", result);
+
+ assert_eq!(
+ result.best_candidate.fit,
+ 0.0
+ );
+
+ assert_eq!(
+ result.best_candidate.pos,
+ optimum
+ );
+}
+
+#[test]
+fn test_local_search_one_max() {
+ let one_max = OneMax::new();
+ let optimum = BinaryString::new(vec![0; 10]);
+
+ let result = local_search_first_improving(
+ &one_max,
+ &mut
+ AndTerminatingConditions::new(
+ vec![
+ &mut EqualTerminatingCondition::new_remembered(optimum.clone()),
+ &mut NoBetterForCyclesTerminatingCondition::new(100)
+ ]
+ ),
+ &mut BinaryStringBitPerturbation::new(0.3),
+ &MinimizingOperator::new(),
+ &BinaryString::new(vec![1; 10]),
+ ).unwrap();
+
+ println!("{:?}", result);
+
+ assert_eq!(
+ result.best_candidate.fit,
+ 0
+ );
+
+ assert_eq!(
+ result.best_candidate.pos,
+ optimum
+ );
+}
M env/src/main.rs => env/src/main.rs +8 -844
@@ 1,848 1,12 @@
-use std::{convert::Infallible, fmt::{Binary, Debug}, fs::File, io::{BufRead, BufReader}, marker::PhantomData, num::ParseFloatError, str::FromStr};
-use rand::Rng;
+pub mod fitness;
+pub mod terminating;
+pub mod perturbation;
+pub mod comparison;
+pub mod local_search;
+pub mod binary_string;
-fn add(a: u16, b: u16) -> u16 {
- a + b
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct BinaryString {
- vec: Vec<i8>
-}
-
-struct Bounds {
- min: f64,
- max: f64,
-}
-
-impl Bounds {
- pub fn new(min: f64, max: f64) -> Self {
- Bounds {
- min,
- max
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-enum BinaryStringConversionError {
- DimensionMismatch,
- NoBounds
-}
-
-impl BinaryString {
- pub fn new(vec: Vec<i8>) -> BinaryString {
- BinaryString {
- vec
- }
- }
-
- pub fn perturb<TRng>(self: &Self, rng: &mut TRng, p: f64) -> Self
- where TRng : Rng
- {
- BinaryString::new(
- self.into_iter()
- .map(|c| if rng.random::<f64>() <= p { 1 - *c } else { *c })
- .collect::<Vec<i8>>()
- )
- }
-
- fn to_real_internal<'a, T: DoubleEndedIterator<Item = &'a i8>>(vec: T, len: usize, min: f64, max: f64) -> f64
- {
- let diff = max - min;
- let len = len as i32;
- let max_represent_num = 2f64.powi(len) - 1.0;
- let represented_num = vec
- .rev()
- .enumerate()
- .map(|(bit, c)| diff * (*c as f64) * 2f64.powi(bit as i32))
- .sum::<f64>();
-
- min + (represented_num / max_represent_num)
- }
-
- pub fn to_real_single(self: &Self, min: f64, max: f64) -> f64 {
- BinaryString::to_real_internal(self.vec.iter(), self.vec.len(), min, max)
- }
-
- pub fn to_real(self: &Self, bounds: &Vec<Bounds>) -> Result<Vec<f64>, BinaryStringConversionError> {
- if bounds.len() == 0 {
- return Err(BinaryStringConversionError::NoBounds);
- }
-
- let chunk_size = self.vec.len() / bounds.len();
- if self.vec.len() % bounds.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::<Vec<f64>>())
- }
-}
-
-impl FromStr for BinaryString {
- type Err = String;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let binary_vec: Vec<i8> = s
- .chars()
- // skip spaces
- .filter(|c| *c != ' ')
- // Map ones and zeros
- .map(|c| match c {
- '0' => Ok(0),
- '1' => Ok(1),
- _ => Err(format!("Invalid binary character: {}", c)),
- })
- .collect::<Result<Vec<i8>, Self::Err>>()?;
-
- Ok(BinaryString::new(binary_vec))
- }
-}
-
-impl<'a> IntoIterator for &'a BinaryString {
- type Item = &'a i8;
- type IntoIter = std::slice::Iter<'a, i8>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.vec.iter()
- }
-}
-
-trait FittingFunction {
- type In;
- type Out;
- type Err;
-
- fn fit(self: &Self, inp: &Self::In) -> Result<Self::Out, Self::Err>;
-}
-
-// Functions
-struct OneMax;
-impl OneMax {
- pub fn new() -> Self {
- OneMax
- }
-}
-impl FittingFunction for OneMax {
- type In = BinaryString;
- type Out = i32;
- type Err = Infallible;
-
- fn fit(self: &Self, chromosome: &BinaryString) -> Result<i32, Infallible> {
- Ok(chromosome.into_iter()
- .map(|x| *x as i32)
- .sum())
- }
-}
-struct LABS;
-
-impl LABS {
- 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()
- }
-}
-
-impl FittingFunction for LABS {
- type In = BinaryString;
- type Out = i32;
- type Err = Infallible;
-
- fn fit(self: &Self, chromosome: &BinaryString) -> Result<i32, Infallible> {
- let S: Vec<i32> = chromosome
- .into_iter()
- .map(|c| (*c as i32) * 2 - 1)
- .collect();
- let D = S.len();
-
- Ok((1..=D-1)
- .map(|k| LABS::Ck(k, &S).pow(2))
- .sum())
- }
-}
-
-struct Sphere {
- offset: Vec<f64>
-}
-
-impl Sphere {
- pub fn new(offset: Vec<f64>) -> Self {
- Sphere {
- offset
- }
- }
-}
-impl FittingFunction for Sphere {
- type In = Vec<f64>;
- type Out = f64;
- type Err = Infallible;
-
- fn fit(self: &Self, chromosome: &Vec<f64>) -> Result<f64, Infallible> {
- Ok(chromosome
- .iter()
- .zip(&self.offset)
- .map(|(x, o)| (x - o).powi(2))
- .sum())
- }
-}
-
-struct Rosenbrock;
-
-impl Rosenbrock {
- pub fn new() -> Self {
- Rosenbrock
- }
-}
-
-impl FittingFunction for Rosenbrock {
- type In = Vec<f64>;
- type Out = f64;
- type Err = Infallible;
-
- fn fit(self: &Self, inp: &Vec<f64>) -> Result<f64, Infallible> {
- Ok(inp.windows(2)
- .map(|xs| 100.0 * (xs[1] - xs[0].powi(2)).powi(2) + (1.0 - xs[0]).powi(2))
- .sum())
- }
-}
-
-struct BinaryFittingWrapper<TFitting> {
- bounds: Vec<Bounds>,
- fitting_function: TFitting,
-}
-
-impl<TFitting> BinaryFittingWrapper<TFitting> {
- pub fn new(fitting_function: TFitting, bounds: Vec<Bounds>) -> Self {
- BinaryFittingWrapper {
- fitting_function,
- bounds,
- }
- }
-}
-
-impl<TFitting> FittingFunction for BinaryFittingWrapper<TFitting>
-where
- TFitting: FittingFunction<In = Vec<f64>, Out = f64, Err = Infallible>
-{
- type In = BinaryString;
- type Out = f64;
- type Err = BinaryStringConversionError;
-
- fn fit(self: &Self, inp: &BinaryString) -> Result<f64, BinaryStringConversionError> {
- Ok(self.fitting_function.fit(&inp.to_real(&self.bounds)?).unwrap())
- }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct LocalSearchCandidate<T>
- where T: Clone
-{
- fit: T,
- pos: BinaryString,
- cycle: usize
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct LocalSearchResult<T>
- where T: Clone
-{
- best_candidate: LocalSearchCandidate<T>,
-
- // How many cycles there were
- cycles: usize,
-
- // statistics
- best_candidates: Vec<LocalSearchCandidate<T>>
-}
-
-trait TerminatingCondition<T>
- where T: Clone
-{
- fn should_terminate(
- self: &mut Self,
- candidate: &LocalSearchCandidate<T>,
- stats: &Vec<LocalSearchCandidate<T>>,
- cycle: usize
- ) -> bool;
-}
-
-struct EqualTerminatingCondition {
- target: BinaryString,
- remember_match: bool,
- matched: bool,
-}
-
-impl EqualTerminatingCondition {
- pub fn new(target: BinaryString) -> Self {
- Self {
- target,
- remember_match: false,
- matched: false,
- }
- }
-
- pub fn new_remembered(target: BinaryString) -> Self {
- Self {
- target,
- remember_match: true,
- matched: false,
- }
- }
-
- pub fn reset_match(self: &mut Self) {
- self.matched = false;
- }
-}
-
-impl<T: Clone> TerminatingCondition<T> for EqualTerminatingCondition {
- fn should_terminate(
- self: &mut Self,
- candidate: &LocalSearchCandidate<T>,
- _: &Vec<LocalSearchCandidate<T>>,
- _: usize
- ) -> bool {
- let matched = candidate.pos == self.target;
-
- if matched && self.remember_match {
- self.matched = true;
- }
-
- matched || self.matched
- }
-}
-
-struct NoBetterForCyclesTerminatingCondition {
- cycles: usize
-}
-
-impl NoBetterForCyclesTerminatingCondition {
- pub fn new(cycles: usize) -> Self {
- Self {
- cycles
- }
- }
-}
-
-struct AndTerminatingConditions<'a, T: Clone> {
- terminating_conditions: Vec<&'a mut dyn TerminatingCondition<T>>
-}
-
-impl<'a, T: Clone> AndTerminatingConditions<'a, T> {
- pub fn new(terminating_conditions: Vec<&'a mut dyn TerminatingCondition<T>>) -> Self {
- Self {
- terminating_conditions
- }
- }
-}
-
-impl<'a, T: Clone> TerminatingCondition<T> for AndTerminatingConditions<'a, T> {
- fn should_terminate(
- self: &mut Self,
- candidate: &LocalSearchCandidate<T>,
- stats: &Vec<LocalSearchCandidate<T>>,
- cycle: usize
- ) -> bool {
- return self.terminating_conditions.iter_mut()
- .all(
- |cond| cond.should_terminate(candidate, stats, cycle)
- )
- }
-}
-
-impl<T: Clone> TerminatingCondition<T> for NoBetterForCyclesTerminatingCondition {
- fn should_terminate (
- self: &mut Self,
- candidate: &LocalSearchCandidate<T>,
- _: &Vec<LocalSearchCandidate<T>>,
- cycle: usize
- ) -> bool {
- (cycle - candidate.cycle) > self.cycles
- }
-}
-
-trait PerturbationOperator {
- type Chromosome;
-
- fn perturb(self: &mut Self, chromosome: &Self::Chromosome) -> Self::Chromosome;
-}
-
-struct BinaryStringBitPerturbation<TRng: Rng> {
- rng: TRng,
- p: f64,
-}
-
-impl BinaryStringBitPerturbation<rand::rngs::ThreadRng> {
- pub fn new(p: f64) -> Self {
- Self {
- rng: rand::rng(),
- p
- }
- }
-}
-
-impl<TRng: Rng> PerturbationOperator for BinaryStringBitPerturbation<TRng> {
- type Chromosome = BinaryString;
-
- fn perturb(self: &mut Self, chromosome: &Self::Chromosome) -> Self::Chromosome {
- chromosome.perturb(&mut self.rng, self.p)
- }
-}
-
-trait BetterThanOperator<T> {
- fn better_than(self: &Self, a: &T, b: &T) -> bool;
-}
-
-struct DefaultBetterThan;
-impl DefaultBetterThan {
- pub fn new() -> Self {
- Self
- }
-}
-
-impl<T> BetterThanOperator<T> for DefaultBetterThan
- where T: PartialOrd
-{
- fn better_than(self: &Self, a: &T, b: &T) -> bool {
- a < b
- }
-}
-
-fn local_search_first_improving<
- T, TErr, TFit, TTerminatingCondition, TPerturbationOperator, TBetterThanOperator>(
- fit: &TFit,
- terminating_condition: &mut TTerminatingCondition,
- perturbation_operator: &mut TPerturbationOperator,
- better_than_operator: &TBetterThanOperator,
- initial: &BinaryString
-) -> Result<LocalSearchResult<T>, TErr>
-where
- T: Clone,
- TFit: FittingFunction<In = BinaryString, Out = T, Err = TErr>,
- TTerminatingCondition: TerminatingCondition<T>,
- TPerturbationOperator: PerturbationOperator<Chromosome = BinaryString>,
- TBetterThanOperator: BetterThanOperator<T>,
-{
- let mut best_candidate = LocalSearchCandidate {
- pos: initial.clone(),
- fit: fit.fit(&initial)?,
- cycle: 0
- };
-
- let mut stats: Vec<LocalSearchCandidate<T>> = vec![];
- let mut cycle: usize = 0;
-
- while !terminating_condition.should_terminate(&best_candidate, &stats, cycle) {
- let perturbed = perturbation_operator.perturb(&best_candidate.pos);
- let perturbed_fit = fit.fit(&perturbed)?;
-
- // Minimize
- if better_than_operator.better_than(&perturbed_fit, &best_candidate.fit) {
- best_candidate = LocalSearchCandidate {
- pos: perturbed.clone(),
- fit: perturbed_fit,
- cycle
- };
-
- stats.push(best_candidate.clone());
- }
-
- cycle += 1;
- }
-
- Ok(LocalSearchResult {
- best_candidate,
- best_candidates: stats,
- cycles: cycle
- })
-}
-
-#[test]
-fn test_local_search_one_max() {
- let one_max = OneMax::new();
- let optimum = BinaryString::new(vec![0; 10]);
-
- let result = local_search_first_improving(
- &one_max,
- &mut
- AndTerminatingConditions::new(
- vec![
- &mut EqualTerminatingCondition::new_remembered(optimum.clone()),
- &mut NoBetterForCyclesTerminatingCondition::new(100)
- ]
- ),
- &mut BinaryStringBitPerturbation::new(0.3),
- &DefaultBetterThan::new(),
- &BinaryString::new(vec![1; 10]),
- ).unwrap();
-
- println!("{:?}", result);
-
- assert_eq!(
- result.best_candidate.fit,
- 0
- );
-
- assert_eq!(
- result.best_candidate.pos,
- optimum
- );
-}
-
-#[test]
-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 sphere = Sphere::new(optimum_real);
- let sphere_wrapped = BinaryFittingWrapper::new(sphere, bounds);
-
- let result = local_search_first_improving(
- &sphere_wrapped,
- &mut
- AndTerminatingConditions::new(
- vec![
- &mut EqualTerminatingCondition::new_remembered(optimum.clone()),
- &mut NoBetterForCyclesTerminatingCondition::new(100)
- ]
- ),
- &mut BinaryStringBitPerturbation::new(0.3),
- &DefaultBetterThan::new(),
- &BinaryString::new(vec![1; 10]),
- ).unwrap();
-
- println!("{:?}", result);
-
- assert_eq!(
- result.best_candidate.fit,
- 0.0
- );
-
- assert_eq!(
- result.best_candidate.pos,
- optimum
- );
-}
-
-#[test]
-fn test_perturb() {
- let mut rng = rand::rng();
-
- assert_eq!(
- BinaryString::new(vec![1, 1, 0, 0])
- .perturb(&mut rng, 1.0)
- .vec,
- vec![0, 0, 1, 1]
- );
-
- assert_eq!(
- BinaryString::new(vec![1, 1, 0, 0])
- .perturb(&mut rng, 0.0)
- .vec,
- vec![1, 1, 0, 0]
- );
-}
-
-#[test]
-fn test_binary_string_to_real_single() {
- assert_eq!(
- BinaryString::new(vec![1])
- .to_real_single(0.0, 32.0),
- 32.0
- );
- assert_eq!(
- BinaryString::new(vec![1, 1])
- .to_real_single(0.0, 32.0),
- 32.0
- );
- assert_eq!(
- BinaryString::new(vec![0, 1])
- .to_real_single(0.0, 32.0),
- 32.0 / 3.0
- );
- assert_eq!(
- BinaryString::new(vec![0, 0])
- .to_real_single(-16.0, 16.0),
- -16.0
- );
- assert_eq!(
- BinaryString::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])
- .to_real_single(0.0, 31.0),
- 31.0
- );
- assert_eq!(
- BinaryString::new(vec![0, 0, 0, 1, 0])
- .to_real_single(0.0, 31.0),
- 2.0
- );
-
- assert_eq!(
- BinaryString::new(vec![1; 512])
- .to_real_single(0.0, 31.0),
- 31.0
- );
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct DataArrOfReals {
- vec: Vec<f64>,
- valid: bool
-}
-
-impl FromStr for DataArrOfReals {
- type Err = <f64 as FromStr>::Err;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- // TODO: maybe better handling, as an error?
- // this would mean also reimplementing load_test_file to be able to supply
- // out the error, but only for the output...
- if !s.starts_with('[') {
- return Ok(
- DataArrOfReals {
- valid: false,
- vec: vec![]
- }
- )
- }
-
- let trimmed = &s[1..s.len()-1];
-
- Ok(DataArrOfReals {
- valid: true,
- vec: trimmed.split(',')
- .map(|x| x.trim().parse::<f64>())
- .collect::<Result<Vec<f64>, _>>()?
- })
- }
-}
-
-fn test_binary_string_to_real(file_name: &str, bounds: Vec<Bounds>) {
- let data = load_test_file::<i8, DataArrOfReals>(file_name);
-
- for test in data {
- let res = BinaryString::new(test.inp)
- .to_real(&bounds);
- if !test.out.valid {
- assert_eq!(
- res,
- Err(BinaryStringConversionError::DimensionMismatch)
- );
- }
- else {
- assert_eq!(
- res.unwrap(),
- test.out.vec
- );
- }
- }
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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)]
- );
-}
-
-#[test]
-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::<Vec<_>>()
- );
-}
-
-#[test]
-fn test_one_max() {
- let data = load_test_file::<i8, i32>("tests/onemax.txt");
-
- for test in data {
- assert_eq!(
- OneMax::new().fit(&BinaryString::new(test.inp)).unwrap(),
- test.out
- );
- }
-}
-
-#[test]
-fn test_LABS() {
- let data = load_test_file::<i8, i32>("tests/labs.txt");
-
- for test in data {
- println!("Test vector {}", test.inp.iter()
- .map(|x| x.to_string())
- .collect::<Vec<String>>()
- .join(", "));
- assert_eq!(
- LABS::new().fit(&BinaryString::new(test.inp)).unwrap(),
- test.out
- )
- }
-}
-
-#[test]
-fn test_sphere() {
- let data = load_test_file::<f64, f64>("tests/sphere.txt");
-
- for test in data {
- assert_eq!(
- Sphere::new(vec![1.0; 10]).fit(&test.inp).unwrap(),
- test.out
- )
- }
-}
-
-#[test]
-fn test_rosenbrock() {
- let data = load_test_file::<f64, f64>("tests/rosenbrock.txt");
-
- for test in data {
- assert_eq!(
- Rosenbrock::new().fit(&test.inp).unwrap(),
- test.out
- )
- }
-}
-
-// Test infra
-struct TestVector<TIn, TOut> {
- inp: Vec<TIn>,
- out: TOut
-}
-
-fn load_test_file<TIn, TOut>(file: &str) -> Vec<TestVector::<TIn, TOut>>
-where
- TIn : FromStr + Debug,
- TIn::Err: Debug,
- TOut : FromStr + Debug,
- TOut::Err: Debug,
-{
- let mut vectors: Vec<TestVector::<TIn, TOut>> = vec![];
-
- let file = File::open(file).expect("Could not read test data!");
- let reader = BufReader::new(file);
-
- for (_, line) in reader.lines().enumerate() {
- let line = line.expect("Could not read a line!");
-
- if line.starts_with('#') || line.len() == 0 {
- continue;
- }
-
- let (inp_str, out_str) = line.split_once(":").unwrap();
-
- let out: TOut = out_str.trim().parse::<TOut>().unwrap();
- let inp: Vec<TIn> = inp_str.split(' ')
- .filter(|num| num.len() > 0)
- .map(|num| num.trim().parse().unwrap())
- .collect();
-
- vectors.push(TestVector::<TIn, TOut> {
- inp,
- out
- });
- }
-
- vectors
-}
+mod test_infra;
fn main() {
- println!("Hello, world! {}", add(1, 2));
+ println!("Hello, world!");
}
A env/src/perturbation/mod.rs => env/src/perturbation/mod.rs +50 -0
@@ 0,0 1,50 @@
+use rand::Rng;
+
+use crate::binary_string::BinaryString;
+
+pub trait PerturbationOperator {
+ type Chromosome;
+
+ fn perturb(self: &mut Self, chromosome: &Self::Chromosome) -> Self::Chromosome;
+}
+
+pub struct BinaryStringBitPerturbation<TRng: Rng> {
+ rng: TRng,
+ p: f64,
+}
+
+impl BinaryStringBitPerturbation<rand::rngs::ThreadRng> {
+ pub fn new(p: f64) -> Self {
+ Self {
+ rng: rand::rng(),
+ p
+ }
+ }
+}
+
+impl<TRng: Rng> PerturbationOperator for BinaryStringBitPerturbation<TRng> {
+ type Chromosome = BinaryString;
+
+ fn perturb(self: &mut Self, chromosome: &Self::Chromosome) -> Self::Chromosome {
+ chromosome.perturb(&mut self.rng, self.p)
+ }
+}
+
+#[test]
+fn test_perturb() {
+ let mut rng = rand::rng();
+
+ assert_eq!(
+ *BinaryString::new(vec![1, 1, 0, 0])
+ .perturb(&mut rng, 1.0)
+ .vec(),
+ vec![0, 0, 1, 1]
+ );
+
+ assert_eq!(
+ *BinaryString::new(vec![1, 1, 0, 0])
+ .perturb(&mut rng, 0.0)
+ .vec(),
+ vec![1, 1, 0, 0]
+ );
+}
A env/src/terminating/mod.rs => env/src/terminating/mod.rs +106 -0
@@ 0,0 1,106 @@
+use crate::{binary_string::BinaryString, local_search::LocalSearchCandidate};
+
+pub trait TerminatingCondition<T>
+ where T: Clone
+{
+ fn should_terminate(
+ self: &mut Self,
+ candidate: &LocalSearchCandidate<T>,
+ stats: &Vec<LocalSearchCandidate<T>>,
+ cycle: usize
+ ) -> bool;
+}
+
+pub struct EqualTerminatingCondition {
+ target: BinaryString,
+ remember_match: bool,
+ matched: bool,
+}
+
+impl EqualTerminatingCondition {
+ pub fn new(target: BinaryString) -> Self {
+ Self {
+ target,
+ remember_match: false,
+ matched: false,
+ }
+ }
+
+ pub fn new_remembered(target: BinaryString) -> Self {
+ Self {
+ target,
+ remember_match: true,
+ matched: false,
+ }
+ }
+
+ pub fn reset_match(self: &mut Self) {
+ self.matched = false;
+ }
+}
+
+impl<T: Clone> TerminatingCondition<T> for EqualTerminatingCondition {
+ fn should_terminate(
+ self: &mut Self,
+ candidate: &LocalSearchCandidate<T>,
+ _: &Vec<LocalSearchCandidate<T>>,
+ _: usize
+ ) -> bool {
+ let matched = candidate.pos == self.target;
+
+ if matched && self.remember_match {
+ self.matched = true;
+ }
+
+ matched || self.matched
+ }
+}
+
+pub struct NoBetterForCyclesTerminatingCondition {
+ cycles: usize
+}
+
+impl NoBetterForCyclesTerminatingCondition {
+ pub fn new(cycles: usize) -> Self {
+ Self {
+ cycles
+ }
+ }
+}
+
+pub struct AndTerminatingConditions<'a, T: Clone> {
+ terminating_conditions: Vec<&'a mut dyn TerminatingCondition<T>>
+}
+
+impl<'a, T: Clone> AndTerminatingConditions<'a, T> {
+ pub fn new(terminating_conditions: Vec<&'a mut dyn TerminatingCondition<T>>) -> Self {
+ Self {
+ terminating_conditions
+ }
+ }
+}
+
+impl<'a, T: Clone> TerminatingCondition<T> for AndTerminatingConditions<'a, T> {
+ fn should_terminate(
+ self: &mut Self,
+ candidate: &LocalSearchCandidate<T>,
+ stats: &Vec<LocalSearchCandidate<T>>,
+ cycle: usize
+ ) -> bool {
+ return self.terminating_conditions.iter_mut()
+ .all(
+ |cond| cond.should_terminate(candidate, stats, cycle)
+ )
+ }
+}
+
+impl<T: Clone> TerminatingCondition<T> for NoBetterForCyclesTerminatingCondition {
+ fn should_terminate (
+ self: &mut Self,
+ candidate: &LocalSearchCandidate<T>,
+ _: &Vec<LocalSearchCandidate<T>>,
+ cycle: usize
+ ) -> bool {
+ (cycle - candidate.cycle) > self.cycles
+ }
+}
A env/src/test_infra.rs => env/src/test_infra.rs +78 -0
@@ 0,0 1,78 @@
+use std::fs::File;
+use std::io::{BufRead,BufReader};
+use std::str::FromStr;
+use std::fmt::Debug;
+
+pub struct TestVector<TIn, TOut> {
+ pub inp: Vec<TIn>,
+ pub out: TOut
+}
+
+pub fn load_test_file<TIn, TOut>(file: &str) -> Vec<TestVector::<TIn, TOut>>
+where
+ TIn : FromStr + Debug,
+ TIn::Err: Debug,
+ TOut : FromStr + Debug,
+ TOut::Err: Debug,
+{
+ let mut vectors: Vec<TestVector::<TIn, TOut>> = vec![];
+
+ let file = File::open(file).expect("Could not read test data!");
+ let reader = BufReader::new(file);
+
+ for (_, line) in reader.lines().enumerate() {
+ let line = line.expect("Could not read a line!");
+
+ if line.starts_with('#') || line.len() == 0 {
+ continue;
+ }
+
+ let (inp_str, out_str) = line.split_once(":").unwrap();
+
+ let out: TOut = out_str.trim().parse::<TOut>().unwrap();
+ let inp: Vec<TIn> = inp_str.split(' ')
+ .filter(|num| num.len() > 0)
+ .map(|num| num.trim().parse().unwrap())
+ .collect();
+
+ vectors.push(TestVector::<TIn, TOut> {
+ inp,
+ out
+ });
+ }
+
+ vectors
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct DataArrOfReals {
+ pub vec: Vec<f64>,
+ pub valid: bool
+}
+
+impl FromStr for DataArrOfReals {
+ type Err = <f64 as FromStr>::Err;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ // TODO: maybe better handling, as an error?
+ // this would mean also reimplementing load_test_file to be able to supply
+ // out the error, but only for the output...
+ if !s.starts_with('[') {
+ return Ok(
+ DataArrOfReals {
+ valid: false,
+ vec: vec![]
+ }
+ )
+ }
+
+ let trimmed = &s[1..s.len()-1];
+
+ Ok(DataArrOfReals {
+ valid: true,
+ vec: trimmed.split(',')
+ .map(|x| x.trim().parse::<f64>())
+ .collect::<Result<Vec<f64>, _>>()?
+ })
+ }
+}