From 67862b0cb98557fe3aa278b0c7e90091dfb56cfe Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 19 Oct 2023 15:51:01 +0200 Subject: [PATCH 01/27] Add measure variations for step size --- src/components/archive.rs | 69 +++++- src/components/{ => measures}/diversity.rs | 0 src/components/measures/mod.rs | 2 + src/components/measures/stepsize.rs | 274 +++++++++++++++++++++ src/components/mod.rs | 2 +- 5 files changed, 341 insertions(+), 6 deletions(-) rename src/components/{ => measures}/diversity.rs (100%) create mode 100644 src/components/measures/mod.rs create mode 100644 src/components/measures/stepsize.rs diff --git a/src/components/archive.rs b/src/components/archive.rs index 9ef4564..a3f5f5a 100644 --- a/src/components/archive.rs +++ b/src/components/archive.rs @@ -1,12 +1,9 @@ -//! Elitist archive. +//! Archive for specified parts of population. use better_any::{Tid, TidAble}; use serde::{Deserialize, Serialize}; -use crate::{ - component::ExecResult, components::Component, problems::SingleObjectiveProblem, - state::StateReq, CustomState, Individual, State, -}; +use crate::{component::ExecResult, components::Component, problems::SingleObjectiveProblem, state::StateReq, CustomState, Individual, State, Problem}; /// An archive for storing elitist individuals. #[derive(Default, Tid)] @@ -118,3 +115,65 @@ where Ok(()) } } + +/// An archive for storing individuals between operators, e.g. for subsequent calculation of measures. +#[derive(Default, Tid)] +pub struct IntermediateArchive(Vec>); + +impl CustomState<'_> for IntermediateArchive

{} + +impl IntermediateArchive

{ + /// Creates a new, empty `IntermediateArchive`. + fn new() -> Self { + Self(Vec::new()) + } + + /// Updates the archive using the `population`, keeping all individuals at the current step of the algorithm. + fn update(&mut self, population: &[Individual

]) { + self.0 = Vec::from(population.clone()); + } + + /// Returns a reference to the archived population. + pub fn archived_population(&self) -> &[Individual

] { + &self.0 + } + + /// Returns a mutable reference to the archived population. + pub fn archived_population_mut(&mut self) -> &mut [Individual

] { + &mut self.0 + } +} + +/// Updates the [`IntermediateArchive`] with the current population. +#[derive(Clone, Serialize, Deserialize)] +pub struct IntermediateArchiveUpdate; + +impl IntermediateArchiveUpdate { + pub fn from_params() -> Self { + Self {} + } + + pub fn new

() -> Box> + where + P: Problem, + { + Box::new(Self::from_params()) + } +} + +impl

Component

for IntermediateArchiveUpdate + where + P: Problem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(IntermediateArchive::

::new()); + Ok(()) + } + + fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state + .borrow_mut::>() + .update(state.populations().current()); + Ok(()) + } +} \ No newline at end of file diff --git a/src/components/diversity.rs b/src/components/measures/diversity.rs similarity index 100% rename from src/components/diversity.rs rename to src/components/measures/diversity.rs diff --git a/src/components/measures/mod.rs b/src/components/measures/mod.rs new file mode 100644 index 0000000..3b1f5bc --- /dev/null +++ b/src/components/measures/mod.rs @@ -0,0 +1,2 @@ +pub mod diversity; +pub mod stepsize; \ No newline at end of file diff --git a/src/components/measures/stepsize.rs b/src/components/measures/stepsize.rs new file mode 100644 index 0000000..2f8f49d --- /dev/null +++ b/src/components/measures/stepsize.rs @@ -0,0 +1,274 @@ +//! Step size measures for changes caused by operators. +//! +//! # References +//! +//! \[1\] + + +use std::any::type_name; +use std::marker::PhantomData; +use better_any::{Tid, TidAble}; +use derivative::Derivative; +use serde::Serialize; +use crate::component::AnyComponent; +use crate::{Component, CustomState, ExecResult, Problem, State}; +use crate::components::archive; +use crate::lens::{AnyLens, Lens, LensMap}; +use crate::logging::extractor::{EntryExtractor, EntryName}; +use crate::population::AsSolutions; +use crate::problems::VectorProblem; +use crate::utils::{SerializablePhantom, squared_euclidean}; + +/// Trait for representing a component that measures the step size of the change caused by an operator. +pub trait StepSizeMeasure: AnyComponent { + /// Calculates the step size between two `solutions`. + fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> Vec; +} + +/// A default implementation of [`Component::execute`] for types implementing [`StepSizeMeasure`]. +pub fn step_size_measure(component: &T, problem: &P, state: &mut State

) -> ExecResult<()> +where + P: Problem, + T: StepSizeMeasure

+ 'static, +{ + let populations = state.populations(); + let current_pop = populations.current(); + let archive = state.borrow_mut::>(); + let previous_pop= archive.archived_population(); + let mut step_size = state.borrow_mut::>(); + + if current_pop.is_empty() || previous_pop.is_empty() { + step_size.update(vec![0.0]); + } else { + step_size.update(component.measure(problem, &previous_pop.as_solutions(), ¤t_pop.as_solutions())); + } + + Ok(()) +} + +/// The step size between two snapshots of the population as measured by the component `I`. +#[derive(Tid)] +pub struct StepSize { + /// Mean over all solutions. + pub step_size: f64, + /// Individual step sizes depending on aspect of interest. + pub all_steps: Vec, + marker: PhantomData, +} + +impl StepSize { + /// Creates a new `StepSize` with initial value of 0 and an empty vector. + pub fn new() -> Self { + Self { + step_size: 0., + all_steps: Vec::new(), + marker: PhantomData, + } + } + + /// Updates the step size using the step size vector. + pub fn update(&mut self, all_steps: Vec) { + self.all_steps = all_steps.clone(); + self.step_size = all_steps.iter().sum::() / all_steps.len() as f64; + } +} + +impl Default for StepSize { + fn default() -> Self { Self::new() } +} + +impl CustomState<'_> for StepSize {} + +/// Lens for accessing the individual step sizes of [`StepSize`]. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct IndividualStepSizeLens(SerializablePhantom); + +impl AnyLens for IndividualStepSizeLens { + type Target = Vec; +} + +impl EntryName for IndividualStepSizeLens { + fn entry_name() -> &'static str { type_name::() } +} + +impl IndividualStepSizeLens { + /// Construct the lens. + pub fn new() -> Self { Self(SerializablePhantom::default()) } + + /// Constructs the lens for logging. + pub fn entry

() -> Box> + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, { Box::::default() } +} + +impl LensMap for IndividualStepSizeLens { + type Source = StepSize; + + fn map(&self, source: &Self::Source) -> Self::Target { source.all_steps.clone() } +} + +/// Lens for accessing the mean step size of [`StepSize`]. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct MeanStepSizeLens(SerializablePhantom); + +impl AnyLens for MeanStepSizeLens { + type Target = f64; +} + +impl EntryName for MeanStepSizeLens { + fn entry_name() -> &'static str { type_name::() } +} + +impl MeanStepSizeLens { + /// Construct the lens. + pub fn new() -> Self { Self(SerializablePhantom::default()) } + + /// Constructs the lens for logging. + pub fn entry

() -> Box> + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, { Box::::default() } +} + +impl LensMap for MeanStepSizeLens { + type Source = StepSize; + + fn map(&self, source: &Self::Source) -> Self::Target { source.step_size } +} + +/// Measures the step size in terms of the Euclidean distance between two solutions. +/// +/// The value is stored in the [`StepSize`] state. +#[derive(Clone, Serialize)] +pub struct EuclideanStepSize; + +impl EuclideanStepSize { + pub fn from_params() -> Self { Self } + + pub fn new

() -> Box> + where + P: VectorProblem, { Box::new(Self::from_params()) } +} + +impl

StepSizeMeasure

for EuclideanStepSize +where + P: VectorProblem, +{ + fn measure(&self, _problem: &P, previous: &[&Vec], current: &[&Vec]) -> Vec { + previous + .iter() + .zip(current.iter()) + .map(|(p, q)| squared_euclidean(p, q).sqrt()) + .collect() + } +} + +impl

Component

for EuclideanStepSize +where + P: VectorProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(StepSize::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + step_size_measure(self, problem, state) + } +} + +/// Measures the step size by calculating the mean distance of the values at the same positions of two solutions. +/// +/// The value is stored in the [`StepSize`] state. +#[derive(Clone, Serialize)] +pub struct PositionalStepSize; + +impl PositionalStepSize { + pub fn from_params() -> Self { Self } + + pub fn new

() -> Box> + where + P: VectorProblem, { Box::new(Self::from_params()) } +} + +impl

StepSizeMeasure

for PositionalStepSize +where + P: VectorProblem, +{ + fn measure(&self, _problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> Vec { + previous + .iter() + .zip(current.iter()) + .map(|(p, q)| + p.iter().zip(q.iter()).map(|(v, w)| (v - w).abs()).sum::() / p.len() as f64) + .collect() + } +} + +impl

Component

for PositionalStepSize +where + P: VectorProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(StepSize::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + step_size_measure(self, problem, state) + } +} + +/// Measures the step size by calculating the mean difference per dimension of all solutions. +/// +/// The value is stored in the [`StepSize`] state. +#[derive(Clone, Serialize)] +pub struct DimensionalStepSize; + +impl DimensionalStepSize { + pub fn from_params() -> Self { Self } + + pub fn new

() -> Box> + where + P: VectorProblem, { Box::new(Self::from_params()) } +} + +impl

StepSizeMeasure

for DimensionalStepSize +where + P: VectorProblem, +{ + fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> Vec { + let mut steps: Vec = vec![]; + let dims = problem.dimension(); + for d in 0..dims { + let summed = previous + .iter() + .zip(current.iter()) + .map(|(p, q)| (p[d] - q[d]).abs()) + .sum::(); + steps.push(summed / previous.len() as f64 ) + } + steps + } +} + +impl

Component

for DimensionalStepSize +where + P: VectorProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(StepSize::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + step_size_measure(self, problem, state) + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 94a6770..8c8b7a9 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -13,11 +13,11 @@ use crate::{ pub mod archive; pub mod boundary; pub mod control_flow; -pub mod diversity; pub mod evaluation; pub mod generative; pub mod initialization; pub mod mapping; +pub mod measures; pub mod misc; pub mod mutation; pub mod recombination; From 0254631638fa19dc8981727d106e6179ef8bae31 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Wed, 8 Nov 2023 11:46:14 +0100 Subject: [PATCH 02/27] Add variance to step size measures --- Cargo.toml | 3 +- src/components/measures/stepsize.rs | 131 +++++++++++++++++++++++----- src/utils.rs | 5 ++ 3 files changed, 118 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fbadcf7..ced17ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,9 @@ test-case = "3.1.0" float_eq = "1.0.1" contracts = "0.6.3" itertools = "0.10.5" -ron = "0.8.0" +ron = "=0.8.0" indicatif = { version = "0.17.4", features = ["rayon"] } +statrs = "0.16" [dev-dependencies] criterion = "0.5.1" diff --git a/src/components/measures/stepsize.rs b/src/components/measures/stepsize.rs index 2f8f49d..969710b 100644 --- a/src/components/measures/stepsize.rs +++ b/src/components/measures/stepsize.rs @@ -10,6 +10,7 @@ use std::marker::PhantomData; use better_any::{Tid, TidAble}; use derivative::Derivative; use serde::Serialize; +use statrs::statistics::Statistics; use crate::component::AnyComponent; use crate::{Component, CustomState, ExecResult, Problem, State}; use crate::components::archive; @@ -22,7 +23,7 @@ use crate::utils::{SerializablePhantom, squared_euclidean}; /// Trait for representing a component that measures the step size of the change caused by an operator. pub trait StepSizeMeasure: AnyComponent { /// Calculates the step size between two `solutions`. - fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> Vec; + fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> (Vec, Vec); } /// A default implementation of [`Component::execute`] for types implementing [`StepSizeMeasure`]. @@ -38,7 +39,7 @@ where let mut step_size = state.borrow_mut::>(); if current_pop.is_empty() || previous_pop.is_empty() { - step_size.update(vec![0.0]); + step_size.update((vec![0.0], vec![0.0])); } else { step_size.update(component.measure(problem, &previous_pop.as_solutions(), ¤t_pop.as_solutions())); } @@ -51,8 +52,12 @@ where pub struct StepSize { /// Mean over all solutions. pub step_size: f64, + /// Variance over all solutions. + pub variance: f64, /// Individual step sizes depending on aspect of interest. pub all_steps: Vec, + /// Variance of individual step sizes. Not applicable for EuclideanStepSize. + pub all_var: Vec, marker: PhantomData, } @@ -61,15 +66,20 @@ impl StepSize { pub fn new() -> Self { Self { step_size: 0., + variance: 0., all_steps: Vec::new(), + all_var: Vec::new(), marker: PhantomData, } } /// Updates the step size using the step size vector. - pub fn update(&mut self, all_steps: Vec) { - self.all_steps = all_steps.clone(); - self.step_size = all_steps.iter().sum::() / all_steps.len() as f64; + pub fn update(&mut self, all_steps: (Vec, Vec)) { + let (a, b) = all_steps; + self.all_steps = a.clone(); + self.all_var = b.clone(); + self.variance = a.clone().variance(); + self.step_size = a.mean(); } } @@ -111,6 +121,38 @@ impl LensMap for IndividualStepSizeLens { fn map(&self, source: &Self::Source) -> Self::Target { source.all_steps.clone() } } +/// Lens for accessing the individual variances of [`StepSize`]. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct IndividualVarianceLens(SerializablePhantom); + +impl AnyLens for IndividualVarianceLens { + type Target = Vec; +} + +impl EntryName for IndividualVarianceLens { + fn entry_name() -> &'static str { "Individual Variances" } +} + +impl IndividualVarianceLens { + /// Construct the lens. + pub fn new() -> Self { Self(SerializablePhantom::default()) } + + /// Constructs the lens for logging. + pub fn entry

() -> Box> + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, { Box::::default() } +} + +impl LensMap for IndividualVarianceLens { + type Source = StepSize; + + fn map(&self, source: &Self::Source) -> Self::Target { source.all_var.clone() } +} + /// Lens for accessing the mean step size of [`StepSize`]. #[derive(Serialize, Derivative)] #[serde(bound = "")] @@ -122,7 +164,7 @@ impl AnyLens for MeanStepSizeLens { } impl EntryName for MeanStepSizeLens { - fn entry_name() -> &'static str { type_name::() } + fn entry_name() -> &'static str { "Mean Step Size" } } impl MeanStepSizeLens { @@ -143,6 +185,38 @@ impl LensMap for MeanStepSizeLens { fn map(&self, source: &Self::Source) -> Self::Target { source.step_size } } +/// Lens for accessing the variance of [`StepSize`]. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct StepSizeVarianceLens(SerializablePhantom); + +impl AnyLens for StepSizeVarianceLens { + type Target = f64; +} + +impl EntryName for StepSizeVarianceLens { + fn entry_name() -> &'static str { "Step Size Variance" } +} + +impl StepSizeVarianceLens { + /// Construct the lens. + pub fn new() -> Self { Self(SerializablePhantom::default()) } + + /// Constructs the lens for logging. + pub fn entry

() -> Box> + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, { Box::::default() } +} + +impl LensMap for StepSizeVarianceLens { + type Source = StepSize; + + fn map(&self, source: &Self::Source) -> Self::Target { source.variance } +} + /// Measures the step size in terms of the Euclidean distance between two solutions. /// /// The value is stored in the [`StepSize`] state. @@ -161,12 +235,15 @@ impl

StepSizeMeasure

for EuclideanStepSize where P: VectorProblem, { - fn measure(&self, _problem: &P, previous: &[&Vec], current: &[&Vec]) -> Vec { - previous + fn measure(&self, _problem: &P, previous: &[&Vec], current: &[&Vec]) -> (Vec, Vec) { + + let steps: Vec = previous .iter() .zip(current.iter()) .map(|(p, q)| squared_euclidean(p, q).sqrt()) - .collect() + .collect(); + let vars = vec![0.0; steps.len()]; + (steps, vars) } } @@ -202,13 +279,21 @@ impl

StepSizeMeasure

for PositionalStepSize where P: VectorProblem, { - fn measure(&self, _problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> Vec { - previous + fn measure(&self, _problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> (Vec, Vec) { + let diffs: Vec> = previous .iter() .zip(current.iter()) .map(|(p, q)| - p.iter().zip(q.iter()).map(|(v, w)| (v - w).abs()).sum::() / p.len() as f64) - .collect() + p.iter().zip(q.iter()).map(|(v, w)| (v - w).abs()).collect()) + .collect(); + + let mut steps: Vec = vec![]; + let mut vars: Vec = vec![]; + for i in diffs { + steps.push(i.clone().mean()); + vars.push(i.variance()); + } + (steps, vars) } } @@ -244,18 +329,24 @@ impl

StepSizeMeasure

for DimensionalStepSize where P: VectorProblem, { - fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> Vec { - let mut steps: Vec = vec![]; + fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> (Vec ,Vec) { + let mut diffs: Vec> = vec![]; let dims = problem.dimension(); for d in 0..dims { - let summed = previous + let summed: Vec = previous .iter() .zip(current.iter()) - .map(|(p, q)| (p[d] - q[d]).abs()) - .sum::(); - steps.push(summed / previous.len() as f64 ) + .map(|(p, q)| (p[d] - q[d]).abs()).collect(); + diffs.push(summed); + } + + let mut steps: Vec = vec![]; + let mut vars: Vec = vec![]; + for i in diffs { + steps.push(i.clone().mean()); + vars.push(i.variance()); } - steps + (steps, vars) } } diff --git a/src/utils.rs b/src/utils.rs index 5a6d91d..b7eb6ed 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -44,3 +44,8 @@ impl serde::Serialize for SerializablePhantom { serializer.serialize_unit_struct(std::any::type_name::()) } } + +/// Calculates squared Euclidean distance between two vectors. +pub fn squared_euclidean(a: &Vec, b:&Vec) -> f64 { + a.iter().zip(b.iter()).map(|(p, q)| (q - p).powf(2.0)).sum::() +} \ No newline at end of file From 5757b503b3d014eb55ef5935efa6273f36452fab Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Fri, 24 Nov 2023 15:03:57 +0100 Subject: [PATCH 03/27] Add improvement measure and population lenses --- src/components/measures/improvement.rs | 232 +++++++++++++++++++++++++ src/components/measures/mod.rs | 3 +- src/components/measures/stepsize.rs | 2 +- src/lens/common.rs | 86 +++++++++ 4 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 src/components/measures/improvement.rs diff --git a/src/components/measures/improvement.rs b/src/components/measures/improvement.rs new file mode 100644 index 0000000..2cd23e3 --- /dev/null +++ b/src/components/measures/improvement.rs @@ -0,0 +1,232 @@ +//! Improvement measures for changes by operators. +//! +//! # References +//! +//! \[1\] + + +use std::any::{type_name}; +use std::array::from_mut; +use std::marker::PhantomData; +use better_any::{Tid, TidAble}; +use derivative::Derivative; +use serde::Serialize; +use crate::component::AnyComponent; +use crate::{Component, CustomState, ExecResult, Individual, Problem, SingleObjectiveProblem, State}; +use crate::components::archive; +use crate::lens::{AnyLens, Lens, LensMap}; +use crate::logging::extractor::{EntryExtractor, EntryName}; +use crate::problems::{LimitedVectorProblem, VectorProblem}; +use crate::state::common; +use crate::state::common::Evaluator; +use crate::utils::SerializablePhantom; + +/// Trait for representing a component that measures the improvement of the solutions an operator caused. +pub trait ImprovementMeasure: AnyComponent { + /// Calculates the amount of improvement between two `solutions`. + fn measure(&self, problem: &P, previous: &[Individual

], current: &[Individual

]) -> (Vec, Vec); +} + +/// A default implementation of [`Component::execute`] for types implementing [`ImprovementMeasure`]. +/// +/// Note that, if called between or directly after operators, solutions will be evaluated here. +pub fn improvement_measure(component: &T, problem: &P, state: &mut State

) -> ExecResult<()> +where + P: Problem, + T: ImprovementMeasure

+ 'static, +{ + let current_pop = state.populations_mut().pop(); + + let mut cur = current_pop.clone(); + for i in 0..current_pop.len() { + if !cur[i].is_evaluated() { + state.holding::>( + |evaluator: &mut Evaluator

, state| { + evaluator.as_inner_mut().evaluate( + problem, + state, + from_mut(&mut cur[i]), + ); + Ok(()) + }, + ).expect("TODO: panic message"); + *state.borrow_value_mut::() += 1; + } + } + state.populations_mut().push(cur); + + let archive = state.borrow_mut::>(); + let previous_pop= archive.archived_population(); + let mut improvement = state.borrow_mut::>(); + + + if previous_pop.is_empty() { + improvement.update((vec![0.0], vec![0.0])); + } else { + improvement.update(component.measure(problem, &previous_pop, ¤t_pop)); + } + + Ok(()) +} + +/// The improvement between two snapshots of the population as measured by the component `I`. +#[derive(Tid)] +pub struct Improvement { + /// Percentages better than previous solutions. + pub percent_improvement: Vec, + /// Amounts better than previous solutions. + pub total_improvement: Vec, + marker: PhantomData, +} + +impl Improvement { + /// Creates a new `Improvement` with empty vectors. + pub fn new() -> Self { + Self { + percent_improvement: Vec::new(), + total_improvement: Vec::new(), + marker: PhantomData, + } + } + + /// Updates the improvement using the total and the percentage vectors. + pub fn update(&mut self, improvement: (Vec, Vec)) { + let (a, b) = improvement; + self.percent_improvement = a.clone(); + self.total_improvement = b.clone(); + } +} + +impl Default for Improvement { + fn default() -> Self { Self::new() } +} + +impl CustomState<'_> for Improvement {} + +/// Lens for accessing the improvement values of [`Improvement`]. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct TotalImprovementLens(SerializablePhantom); + +impl AnyLens for TotalImprovementLens { + type Target = Vec; +} + +impl EntryName for TotalImprovementLens { + fn entry_name() -> &'static str { type_name::() } +} + +impl TotalImprovementLens { + /// Construct the lens. + pub fn new() -> Self { Self(SerializablePhantom::default()) } + + /// Constructs the lens for logging. + pub fn entry

() -> Box> + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, { Box::::default() } +} + +impl LensMap for TotalImprovementLens { + type Source = Improvement; + + fn map(&self, source: &Self::Source) -> Self::Target { source.total_improvement.clone() } +} + +/// Lens for accessing the improvement percentages of [`Improvement`]. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct PercentageImprovementLens(SerializablePhantom); + +impl AnyLens for PercentageImprovementLens { + type Target = Vec; +} + +impl EntryName for PercentageImprovementLens { + fn entry_name() -> &'static str { "Improvement in percent" } +} + +impl PercentageImprovementLens { + /// Construct the lens. + pub fn new() -> Self { Self(SerializablePhantom::default()) } + + /// Constructs the lens for logging. + pub fn entry

() -> Box> + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, { Box::::default() } +} + +impl LensMap for PercentageImprovementLens { + type Source = Improvement; + + fn map(&self, source: &Self::Source) -> Self::Target { source.percent_improvement.clone() } +} + +/// Measures the improvement by calculating the total and percentual difference between a solution +/// before and after the application of an operator. +/// +/// Note that the results are flawed if the operator shuffles the population. +/// +/// The values are stored in the [`Improvement`] state. +#[derive(Clone, Serialize)] +pub struct FitnessImprovement; + +impl FitnessImprovement { + pub fn from_params() -> Self { Self } + + pub fn new_with_id

() -> Box> + where + P: LimitedVectorProblem, + P: SingleObjectiveProblem, + { + Box::new(Self::from_params()) + } +} + +impl FitnessImprovement { + pub fn new

() -> Box> + where + P: VectorProblem, + P: SingleObjectiveProblem, + { Box::new(Self::from_params()) } +} + +impl

ImprovementMeasure

for FitnessImprovement +where + P: VectorProblem, + P: SingleObjectiveProblem, +{ + fn measure(&self, _problem: &P, previous: &[Individual

], current: &[Individual

]) -> (Vec, Vec) { + let mut diffs = vec![]; + let mut percents = vec![]; + + for u in 0..current.len() { + let diff = previous[u].objective().value() - current[u].objective().value(); + let frac = (previous[u].objective().value() / current[u].objective().value()) * 100.0; + diffs.push(diff); + percents.push(frac); + + } + (percents, diffs) + } +} + +impl

Component

for FitnessImprovement +where + P: VectorProblem, + P: SingleObjectiveProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(Improvement::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + improvement_measure(self, problem, state) + } +} \ No newline at end of file diff --git a/src/components/measures/mod.rs b/src/components/measures/mod.rs index 3b1f5bc..f3e9c89 100644 --- a/src/components/measures/mod.rs +++ b/src/components/measures/mod.rs @@ -1,2 +1,3 @@ pub mod diversity; -pub mod stepsize; \ No newline at end of file +pub mod improvement; +pub mod stepsize; diff --git a/src/components/measures/stepsize.rs b/src/components/measures/stepsize.rs index 969710b..6ad263b 100644 --- a/src/components/measures/stepsize.rs +++ b/src/components/measures/stepsize.rs @@ -62,7 +62,7 @@ pub struct StepSize { } impl StepSize { - /// Creates a new `StepSize` with initial value of 0 and an empty vector. + /// Creates a new `StepSize` with initial values of 0 and empty vectors. pub fn new() -> Self { Self { step_size: 0., diff --git a/src/lens/common.rs b/src/lens/common.rs index c9bc874..310b1cd 100644 --- a/src/lens/common.rs +++ b/src/lens/common.rs @@ -346,3 +346,89 @@ impl LensRef

for BestObjectiveValueLens

{ .map_err(|_| eyre!("no best individual found yet")) } } + +/// Lens for extracting the solutions of the whole population. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct PopulationLens

(#[serde(skip)] PhantomData P>); + +impl

PopulationLens

+where + P: Problem, + Self: Lens, +{ + /// Constructs the lens. + pub fn new() -> Self { Self(PhantomData) } +} + +impl AnyLens for PopulationLens

{ + type Target = Vec; +} + +impl

EntryName for PopulationLens

{ + fn entry_name() -> &'static str { "Population" } +} + +impl

PopulationLens

+where + P: SingleObjectiveProblem, + P::Encoding: Clone, + Self: Lens

, + ::Target: Serialize + Send, +{ + /// Constructs the lens for logging entries. + pub fn entry() -> Box> { Box::::default() } +} + +impl LensMap for PopulationLens

{ + type Source = Populations

; + + fn map(&self, source: &Self::Source) -> Self::Target { + let pop = source.current().clone(); + pop.into_iter().map(|i| i.solution().to_owned()).collect() + } +} + +/// Lens for extracting the objective values of the whole population. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct ObjectiveValuesLens

(#[serde(skip)] PhantomData P>); + +impl

ObjectiveValuesLens

+ where + P: Problem, + Self: Lens, +{ + /// Constructs the lens. + pub fn new() -> Self { Self(PhantomData) } +} + +impl AnyLens for ObjectiveValuesLens

{ + type Target = Vec; +} + +impl

EntryName for ObjectiveValuesLens

{ + fn entry_name() -> &'static str { "Population Objective Values" } +} + +impl

ObjectiveValuesLens

+ where + P: SingleObjectiveProblem, + P::Encoding: Clone, + Self: Lens

, + ::Target: Serialize + Send, +{ + /// Constructs the lens for logging entries. + pub fn entry() -> Box> { Box::::default() } +} + +impl LensMap for ObjectiveValuesLens

{ + type Source = Populations

; + + fn map(&self, source: &Self::Source) -> Self::Target { + let pop = source.current().clone(); + pop.into_iter().map(|i| i.objective().to_owned()).collect() + } +} \ No newline at end of file From 71576ab3a7c17bcede62c318e562a39f07bde904 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Mon, 4 Dec 2023 10:24:06 +0100 Subject: [PATCH 04/27] Fix improvement measure --- src/components/measures/improvement.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/measures/improvement.rs b/src/components/measures/improvement.rs index 2cd23e3..473e1e9 100644 --- a/src/components/measures/improvement.rs +++ b/src/components/measures/improvement.rs @@ -39,19 +39,19 @@ where let mut cur = current_pop.clone(); for i in 0..current_pop.len() { - if !cur[i].is_evaluated() { - state.holding::>( - |evaluator: &mut Evaluator

, state| { - evaluator.as_inner_mut().evaluate( - problem, - state, - from_mut(&mut cur[i]), - ); - Ok(()) - }, - ).expect("TODO: panic message"); - *state.borrow_value_mut::() += 1; - } + //if !cur[i].is_evaluated() { + state.holding::>( + |evaluator: &mut Evaluator

, state| { + evaluator.as_inner_mut().evaluate( + problem, + state, + from_mut(&mut cur[i]), + ); + Ok(()) + }, + ).expect("TODO: panic message"); + *state.borrow_value_mut::() += 1; + //} } state.populations_mut().push(cur); From c553016922652c9cbb37ea9680194b5cb6c46c66 Mon Sep 17 00:00:00 2001 From: Jonathan Wurth Date: Fri, 8 Sep 2023 00:37:40 +0200 Subject: [PATCH 05/27] Add contribution guide --- CONTRIBUTING.md | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2e301e7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,166 @@ +# MAHF Contribution Guidelines + +Thank you for your interest in contributing to the MAHF ecosystem! We welcome contributions from the community to help +make this project better. + +Before you get started, please take a moment to read through these contribution guidelines to ensure a smooth and +collaborative experience. + +## Table of Contents + +* [Contributing](#contributing) + * [Reporting Bugs](#reporting-bugs) + * [Suggesting Enhancements](#suggesting-enhancements) + * [Documentation](#documentation) + * [Code Contributions](#code-contributions) +* [Pull Requests](#pull-requests) +* [Code Guidelines](#code-guidelines) + * [Code Style](#code-style) + * [Testing](#testing) + * [Documenting](#documenting) +* [License](#license) + +## Contributing + +We welcome contributions in various forms, including bug reports, feature requests, code contributions, and improvements +to documentation. + +In general, if you find an issue that addresses the problem you're having, please add your own reproduction information +to the existing issue rather than creating a new one. Adding +a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be +indicating to our maintainers that a particular problem is affecting more than just the reporter. + +### Reporting Bugs + +If you encounter a bug, please [open an issue](https://github.com/mahf-opt/mahf/issues) with a detailed description of +the problem, steps to reproduce it, and any relevant error messages. +Additionally, please annotate your issue with the [bug](https://github.com/mahf-opt/mahf/issues/labels) label. + +### Suggesting Enhancements + +If you have an idea for an enhancement or a new feature, please [open an issue](https://github.com/mahf-opt/mahf/issues) +with a clear description of the proposed change and its potential benefits. +Additionally, please annotate your issue with the [enhancement](https://github.com/mahf-opt/mahf/issues/labels) label. + +### Documentation + +Improvements to documentation are always appreciated. If you find any errors or areas that need clarification, please +submit a pull request with your changes. + +### Code Contributions + +In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr) + +1. Fork the repository to your own GitHub account. +2. Clone the project to your machine. +3. Create a branch locally with a succinct but descriptive name. +4. Commit changes to the branch + following [commit message guidelines](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +5. Ensure your code follows our [code guidelines](#code-guidelines). +6. Push changes to your fork. +7. Open a PR in our repository and describe the changes you've made. + +## Pull Requests + +PRs to our projects are always welcome and can be a quick way to get your fix or improvement slated for the next +release. +When submitting a PR, please: + +1. Provide a clear and concise title and description. +2. Reference any related issues in your pull request description. +3. Ensure that your code passes the [CI](#code-guidelines). +4. Be prepared to address feedback and make necessary changes. + +For changes that address core functionality or would require breaking changes (e.g. a major release), it's best to open +an issue to discuss your proposal first. This is not required but can save time creating and reviewing changes. + +## Code Guidelines + +Our projects have strict guidelines for styling, testing, and documenting code. + +Note that the [CI](./.github/workflows) checks `master` and PRs into `master` for compliance to the guidelines. +If the CI fails on your PR, please check and fix the errors. + +### Code Style + +#### Clippy + +Clippy with the `-D warnings` flag is used to perform linting checks. + +

+ Run Clippy + + ```shell + $ cargo clippy --workspace --all-targets --all-features -- -D warnings + ``` + +
+ +#### Rustfmt + +Rustfmt is used to enforce consistent code formatting with the options specified in [`rustfmt.toml`](rustfmt.toml). + +Some options require running rustfmt with +the [nightly toolchain](https://rust-lang.github.io/rustup/concepts/channels.html). +Note that the nightly toolchain is only required for rustfmt, and **not** for building. + +
+ Run Rustfmt + + ```shell + $ cargo +nightly fmt --all -- --check --verbose + ``` + +
+ +### Testing + +Testing is a critical aspect of maintaining code quality. + +If your changes affect existing tests, please update them to accommodate for the new behaviour. +If you add a new feature that can be tested, add unit tests to check for correct behaviour. + +
+ Run tests + + ```shell + $ cargo test --workspace --all-features --lib --bins --tests --examples --verbose + ``` + +
+ +### Documenting + +Please add or update the documentation for all public items affected by your changes. + +#### Docs + +The documentation should build without any errors or warnings. + +
+ Build docs + + ```shell + $ export RUSTDOCFLAGS = -D warnings + $ cargo doc --no-deps --verbose + ``` + +
+ +#### Doctests + +Please add examples for all public methods. + +
+ Run doctests + + ```shell + $ cargo test --workspace --workspace --all-features --doc --verbose + ``` + +
+ +## License + +By contributing to this project, you agree to license your contributions under +the [GNU General Public License v3.0](https://github.com/mahf-opt/mahf/blob/master/LICENSE). diff --git a/README.md b/README.md index 9d16c7f..da9fa0a 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ MAHF has extensive [documentation](https://docs.rs/mahf/latest/mahf/), which sho ## Contributing We welcome contributions from the community and appreciate your interest in improving this project. -A contribution guide will follow shortly. +Please take a look at our [contribution guide](CONTRIBUTING.md). ## License From 9c7bd17ba0508ac11f111029434ca14866a25cc5 Mon Sep 17 00:00:00 2001 From: Jonathan Wurth Date: Fri, 8 Sep 2023 00:41:47 +0200 Subject: [PATCH 06/27] Fix formatting --- CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e301e7..545debf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ submit a pull request with your changes. ### Code Contributions -In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr) +In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr): 1. Fork the repository to your own GitHub account. 2. Clone the project to your machine. @@ -64,6 +64,7 @@ In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susa PRs to our projects are always welcome and can be a quick way to get your fix or improvement slated for the next release. + When submitting a PR, please: 1. Provide a clear and concise title and description. @@ -155,7 +156,7 @@ Please add examples for all public methods. Run doctests ```shell - $ cargo test --workspace --workspace --all-features --doc --verbose + $ cargo test --workspace --all-features --doc --verbose ``` From 6eadd363201cbfbe0ff3656b01831a28ebd99130 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 31 Aug 2023 15:14:15 +0200 Subject: [PATCH 07/27] Implement firefly algorithm --- src/components/swarm.rs | 124 ++++++++++++++++++++++++++++++++++++++++ src/heuristics/fa.rs | 103 +++++++++++++++++++++++++++++++++ src/heuristics/mod.rs | 1 + 3 files changed, 228 insertions(+) create mode 100644 src/heuristics/fa.rs diff --git a/src/components/swarm.rs b/src/components/swarm.rs index 3e5fc97..d81e49a 100644 --- a/src/components/swarm.rs +++ b/src/components/swarm.rs @@ -423,3 +423,127 @@ impl ParticleSwarmUpdate { Self::new_with_id() } } + +/// Updates the and firefly positions. +/// +/// Originally proposed for, and used as operator in [`fa`]. +/// +/// Uses the [`RandomizationParameter`]. +/// +/// [`fa`]: crate::heuristics::fa +#[derive(Clone, Serialize)] +pub struct FireflyPositionsUpdate { + pub alpha: f64, + pub beta: f64, + pub gamma: f64, + id: PhantomId, +} + +impl FireflyPositionsUpdate { + pub fn from_params(alpha: f64, beta: f64, gamma: f64) -> Self { + Self { + alpha, + beta, + gamma, + id: PhantomId::default(), + } + } + + pub fn new_with_id

( + alpha: f64, + beta: f64, + gamma: f64, + ) -> Box> + where + P: LimitedVectorProblem, + { + Box::new(Self::from_params(alpha, beta, gamma)) + } +} + +impl FireflyPositionsUpdate { + pub fn new

(alpha: f64, beta: f64, gamma: f64) -> Box> + where + P: LimitedVectorProblem, + { + Self::new_with_id(alpha, beta, gamma) + } +} + +impl Component

for FireflyPositionsUpdate + where + P: LimitedVectorProblem, + I: Identifier, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(RandomizationParameter(self.alpha)); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + let mut populations = state.populations_mut(); + let mut rng = state.random_mut(); + + // Prepare parameters + let &Self { + beta, gamma, .. + } = self; + let a = state.get_value::(); + + // Get necessary state + let mut jj: Vec> = populations.current().into_iter().cloned().collect(); + let mut ii: Vec> = populations.current().into_iter().cloned().collect(); + let mut x: Vec<&mut Vec> = populations.current_mut().as_solutions_mut(); + + // scale for adapting to problem domain; at the moment only if domain in each dim is the same + let scale = (problem.domain()[0].end - problem.domain()[0].start).abs(); + + // Perform the update step. + // compare all individuals + for (u, i) in ii.iter_mut().enumerate() { + for j in jj.iter_mut() { + // if individual j is "more attractive" (i.e. has lower fitness), move towards j + if i.get_objective() > j.get_objective() { + // draw random values from uniform distribution between 0 and 1 + // according to paper: also possible to use normal distribution, depending on problem + let rand: Vec = (0..problem.dimension()).map(|_| rng.gen_range(0.0..1.0)).collect(); + // calculate distance between i and j; without .sqrt() as it has to be squared again in the next step + let r = i.solution_mut() + .into_iter() + .zip(j.solution_mut()) + .map(|(p, q)| (p.clone() - q.clone()).powf(2.0)) + .sum::(); + // calculate "attractiveness" + let b = beta * (- gamma * r).exp(); + // calculate difference of solutions j and i + let diff = i.solution_mut() + .into_iter() + .zip(j.solution_mut()) + .map(|(p, q)| q.clone() - p.clone()) + .collect::>(); + // calculate values that should be added to current position + let pos = diff + .into_iter() + .zip(rand) + .map(|(p, q)| b * p + a * (q - 0.5) * scale) + .collect::>(); + // Add values to firefly position + for s in 0..i.solution_mut().len() { + x[u][s] += pos[s]; + } + } + } + } + Ok(()) + } +} + +/// The randomization parameter used to update the firefly positions. +#[derive(Deref, DerefMut, Tid)] +pub struct RandomizationParameter( + #[deref] + #[deref_mut] + pub f64, +); + +impl CustomState<'_> for RandomizationParameter {} \ No newline at end of file diff --git a/src/heuristics/fa.rs b/src/heuristics/fa.rs new file mode 100644 index 0000000..17649bf --- /dev/null +++ b/src/heuristics/fa.rs @@ -0,0 +1,103 @@ +//! Firefly Algorithm (FA). +//! +//! # References +//! +//! \[1\] Xin-She Yang. 2009. +//! Firefly algorithms for Multimodal Optimization. +//! In Watanabe, O.; Zeugmann, T. (eds) Stochastic Algorithms: Foundations and Applications. +//! SAGA 2009. Lecture Notes in Computer Science, vol 5792. Springer, Berlin, Heidelberg. +//! DOI: + + +use crate::{ + component::ExecResult, + components::{boundary, initialization, mapping, swarm}, + conditions::Condition, + configuration::Configuration, + identifier::{Global, Identifier}, + lens::ValueOf, + logging::Logger, + problems::{LimitedVectorProblem, SingleObjectiveProblem}, + Component, +}; + +/// Parameters for [`real_fa`]. +pub struct RealProblemParameters { + pub pop_size: u32, + pub alpha: f64, + pub beta: f64, + pub gamma: f64, + pub delta: f64, +} + +/// An example single-objective FA operating on a real search space. +/// +/// Uses the [`fa`] component internally. +pub fn real_fa

( + params: RealProblemParameters, + condition: Box>, +) -> ExecResult> + where + P: SingleObjectiveProblem + LimitedVectorProblem, +{ + let RealProblemParameters { + pop_size, + alpha, + beta, + gamma, + delta, + } = params; + + Ok(Configuration::builder() + .do_(initialization::RandomSpread::new(pop_size)) + .evaluate() + .update_best_individual() + .do_(fa::( + Parameters { + firefly_update: swarm::FireflyPositionsUpdate::new( + alpha, + beta, + gamma, + ), + constraints: boundary::Saturation::new(), + alpha_update: Box::from(mapping::sa::GeometricCooling::new( + delta, + ValueOf::::new(), + )), + }, + condition, + )) + .build()) +} + +/// Basic building blocks of [`fa`]. +pub struct Parameters

{ + pub firefly_update: Box>, + pub constraints: Box>, + pub alpha_update: Box>, +} + +/// A generic single-objective Firefly Algorithm (FA) template. +pub fn fa(params: Parameters

, condition: Box>) -> Box> + where + P: SingleObjectiveProblem, + I: Identifier, +{ + let Parameters { + firefly_update, + constraints, + alpha_update, + } = params; + + Configuration::builder() + .while_(condition, |builder| { + builder + .do_(firefly_update) + .do_(constraints) + .evaluate_with::() + .update_best_individual() + .do_(alpha_update) + .do_(Logger::new()) + }) + .build_component() +} \ No newline at end of file diff --git a/src/heuristics/mod.rs b/src/heuristics/mod.rs index b498efa..51320c3 100644 --- a/src/heuristics/mod.rs +++ b/src/heuristics/mod.rs @@ -11,6 +11,7 @@ pub mod aco; pub mod cro; pub mod de; pub mod es; +pub mod fa; pub mod ga; pub mod ils; pub mod iwo; From 739889c75821f8cdbd59e16f889fa61dfae9fc29 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 14 Sep 2023 15:08:42 +0200 Subject: [PATCH 08/27] Rebase --- src/components/replacement/bh.rs | 49 ++++++ src/components/replacement/mod.rs | 1 + src/components/replacement/sa.rs | 1 + src/components/swarm/fa.rs | 142 ++++++++++++++++ src/components/swarm/mod.rs | 2 + src/components/{swarm.rs => swarm/pso.rs} | 190 ++++------------------ src/heuristics/bh.rs | 96 +++++++++++ src/heuristics/fa.rs | 4 +- src/heuristics/mod.rs | 1 + src/heuristics/pso.rs | 8 +- src/utils.rs | 8 +- 11 files changed, 336 insertions(+), 166 deletions(-) create mode 100644 src/components/replacement/bh.rs create mode 100644 src/components/swarm/fa.rs create mode 100644 src/components/swarm/mod.rs rename src/components/{swarm.rs => swarm/pso.rs} (71%) create mode 100644 src/heuristics/bh.rs diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs new file mode 100644 index 0000000..71f8a9c --- /dev/null +++ b/src/components/replacement/bh.rs @@ -0,0 +1,49 @@ +//! Replacement components for the Black Hole algorithm (BH). + +use rand::Rng; +use serde::{Deserialize, Serialize}; +use crate::{Component, ExecResult, Individual, SingleObjectiveProblem, State}; +use crate::problems::LimitedVectorProblem; +use crate::utils::squared_euclidean; + +#[derive(Clone, Serialize, Deserialize)] +pub struct EventHorizon; + +impl EventHorizon { + pub fn new() -> Box> + where + P: LimitedVectorProblem, { + Box::new(Self) + } +} + +impl

Component

for EventHorizon + where + P: LimitedVectorProblem, +{ + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + let mut populations = state.populations_mut(); + let mut offspring = populations.pop(); + + let f_bh = state.best_objective_value().unwrap().value(); + + let fitness_sum = offspring.iter().map(|x| *x.objective().value()).sum::(); + let radius = f_bh / fitness_sum; + + let rng = &mut state.random_mut(); + + let best = state.best_individual().solution(); + let distances = offspring.iter().map(|o| squared_euclidean(*o.as_solution(), best).sqrt()).collect::>(); + + for (u, mut i) in offspring.iter().enumerate() { + if distances[u] < radius { + let rand: Vec = (0..problem.dimension()).map(|_| rng.gen_range(problem.domain()[0].clone())).collect(); + let j = Individual::new_unevaluated(rand); + i = &j; + } + } + populations.push(offspring); + Ok(()) + } +} \ No newline at end of file diff --git a/src/components/replacement/mod.rs b/src/components/replacement/mod.rs index da5716f..14d203f 100644 --- a/src/components/replacement/mod.rs +++ b/src/components/replacement/mod.rs @@ -9,6 +9,7 @@ use crate::{ }; pub mod common; +pub mod bh; pub mod sa; pub use common::{ diff --git a/src/components/replacement/sa.rs b/src/components/replacement/sa.rs index 1ac486e..abed9ba 100644 --- a/src/components/replacement/sa.rs +++ b/src/components/replacement/sa.rs @@ -49,6 +49,7 @@ impl Component

for ExponentialAnnealingAcceptance Ok(()) } + #[ensures(state.populations().current().len() == 1, "population after should contain a single individual")] #[ensures(state.populations().len() == old(state.populations().len()) - 1)] fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs new file mode 100644 index 0000000..ef5e1b8 --- /dev/null +++ b/src/components/swarm/fa.rs @@ -0,0 +1,142 @@ +use std::marker::PhantomData; + +use better_any::{Tid, TidAble}; +use derive_more::{Deref, DerefMut}; +use eyre::{ensure, ContextCompat, WrapErr}; +use itertools::multizip; +use rand::Rng; +use serde::Serialize; + +use crate::{ + component::{AnyComponent, ExecResult}, + components::{Block, Component}, + identifier::{Global, Identifier, PhantomId}, + population::{AsSolutions, AsSolutionsMut, BestIndividual}, + problems::{LimitedVectorProblem, SingleObjectiveProblem}, + state::StateReq, + CustomState, Individual, Problem, State, +}; + +/// Updates the and firefly positions. +/// +/// Originally proposed for, and used as operator in [`fa`]. +/// +/// Uses the [`RandomizationParameter`]. +/// +/// [`fa`]: crate::heuristics::fa +#[derive(Clone, Serialize)] +pub struct FireflyPositionsUpdate { + pub alpha: f64, + pub beta: f64, + pub gamma: f64, + id: PhantomId, +} + +impl FireflyPositionsUpdate { + pub fn from_params(alpha: f64, beta: f64, gamma: f64) -> Self { + Self { + alpha, + beta, + gamma, + id: PhantomId::default(), + } + } + + pub fn new_with_id

( + alpha: f64, + beta: f64, + gamma: f64, + ) -> Box> + where + P: LimitedVectorProblem, + { + Box::new(Self::from_params(alpha, beta, gamma)) + } +} + +impl FireflyPositionsUpdate { + pub fn new

(alpha: f64, beta: f64, gamma: f64) -> Box> + where + P: LimitedVectorProblem, + { + Self::new_with_id(alpha, beta, gamma) + } +} + +impl Component

for FireflyPositionsUpdate + where + P: LimitedVectorProblem, + I: Identifier, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(RandomizationParameter(self.alpha)); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + let mut populations = state.populations_mut(); + let mut rng = state.random_mut(); + + // Prepare parameters + let &Self { + beta, gamma, .. + } = self; + let a = state.get_value::(); + + // Get necessary state + let mut jj: Vec> = populations.current().into_iter().cloned().collect(); + let mut ii: Vec> = populations.current().into_iter().cloned().collect(); + let mut x: Vec<&mut Vec> = populations.current_mut().as_solutions_mut(); + + // scale for adapting to problem domain; at the moment only if domain in each dim is the same + let scale = (problem.domain()[0].end - problem.domain()[0].start).abs(); + + // Perform the update step. + // compare all individuals + for (u, i) in ii.iter_mut().enumerate() { + for j in jj.iter_mut() { + // if individual j is "more attractive" (i.e. has lower fitness), move towards j + if i.get_objective() > j.get_objective() { + // draw random values from uniform distribution between 0 and 1 + // according to paper: also possible to use normal distribution, depending on problem + let rand: Vec = (0..problem.dimension()).map(|_| rng.gen_range(0.0..1.0)).collect(); + // calculate distance between i and j; without .sqrt() as it has to be squared again in the next step + let r = i.solution_mut() + .into_iter() + .zip(j.solution_mut()) + .map(|(p, q)| (p.clone() - q.clone()).powf(2.0)) + .sum::(); + // calculate "attractiveness" + let b = beta * (- gamma * r).exp(); + // calculate difference of solutions j and i + let diff = i.solution_mut() + .into_iter() + .zip(j.solution_mut()) + .map(|(p, q)| q.clone() - p.clone()) + .collect::>(); + // calculate values that should be added to current position + let pos = diff + .into_iter() + .zip(rand) + .map(|(p, q)| b * p + a * (q - 0.5) * scale) + .collect::>(); + // Add values to firefly position + for s in 0..i.solution_mut().len() { + x[u][s] += pos[s]; + } + } + } + } + Ok(()) + } +} + +/// The randomization parameter used to update the firefly positions. +#[derive(Deref, DerefMut, Tid)] +pub struct RandomizationParameter( + #[deref] + #[deref_mut] + pub f64, +); + +impl CustomState<'_> for RandomizationParameter {} \ No newline at end of file diff --git a/src/components/swarm/mod.rs b/src/components/swarm/mod.rs new file mode 100644 index 0000000..faccfbf --- /dev/null +++ b/src/components/swarm/mod.rs @@ -0,0 +1,2 @@ +pub mod fa; +pub mod pso; \ No newline at end of file diff --git a/src/components/swarm.rs b/src/components/swarm/pso.rs similarity index 71% rename from src/components/swarm.rs rename to src/components/swarm/pso.rs index d81e49a..78052c1 100644 --- a/src/components/swarm.rs +++ b/src/components/swarm/pso.rs @@ -55,17 +55,17 @@ impl ParticleVelocitiesInit { } pub fn new

(v_max: f64) -> ExecResult>> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Ok(Box::new(Self::from_params(v_max)?)) } } impl Component

for ParticleVelocitiesInit -where - P: LimitedVectorProblem, - I: Identifier, + where + P: LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(ParticleVelocities::::new(Vec::new())); @@ -78,8 +78,8 @@ where .take(problem.dimension()) .collect::>() }) - .take(state.populations().current().len()) - .collect::>(); + .take(state.populations().current().len()) + .collect::>(); state.set_value::>(velocities); @@ -125,9 +125,9 @@ pub struct ParticleVelocitiesUpdate { impl ParticleVelocitiesUpdate { pub fn from_params(weight: f64, c_1: f64, c_2: f64, v_max: f64) -> ExecResult { - ensure!(weight > 0., "`weight` must be > 0, but was {}", weight); - ensure!(c_1 > 0., "`c_1` must be > 0, but was {}", c_1); - ensure!(c_2 > 0., "`c_2` must be > 0, but was {}", c_2); + ensure!(weight >= 0., "`weight` must be >= 0, but was {}", weight); + ensure!(c_1 >= 0., "`c_1` must be >= 0, but was {}", c_1); + ensure!(c_2 >= 0., "`c_2` must be >= 0, but was {}", c_2); ensure!(v_max > 0., "`v_max` must be > 0, but was {}", v_max); Ok(Self { weight, @@ -144,8 +144,8 @@ impl ParticleVelocitiesUpdate { c_2: f64, v_max: f64, ) -> ExecResult>> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Ok(Box::new(Self::from_params(weight, c_1, c_2, v_max)?)) } @@ -153,17 +153,17 @@ impl ParticleVelocitiesUpdate { impl ParticleVelocitiesUpdate { pub fn new

(weight: f64, c_1: f64, c_2: f64, v_max: f64) -> ExecResult>> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Self::new_with_id(weight, c_1, c_2, v_max) } } impl Component

for ParticleVelocitiesUpdate -where - P: LimitedVectorProblem, - I: Identifier, + where + P: LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(InertiaWeight::::new(self.weight)); @@ -249,17 +249,17 @@ impl PersonalBestParticlesInit { } pub fn new

() -> Box> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Box::new(Self::from_params()) } } impl Component

for PersonalBestParticlesInit -where - P: LimitedVectorProblem, - I: Identifier, + where + P: LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(BestParticles::::new(Vec::new())); @@ -282,17 +282,17 @@ impl PersonalBestParticlesUpdate { } pub fn new

() -> Box> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Box::new(Self::from_params()) } } impl Component

for PersonalBestParticlesUpdate -where - P: LimitedVectorProblem, - I: Identifier, + where + P: LimitedVectorProblem, + I: Identifier, { fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { let populations = state.populations(); @@ -335,17 +335,17 @@ impl GlobalBestParticleUpdate { } pub fn new

() -> Box> - where - P: SingleObjectiveProblem + LimitedVectorProblem, + where + P: SingleObjectiveProblem + LimitedVectorProblem, { Box::new(Self::from_params()) } } impl Component

for GlobalBestParticleUpdate -where - P: SingleObjectiveProblem + LimitedVectorProblem, - I: Identifier, + where + P: SingleObjectiveProblem + LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state @@ -422,128 +422,4 @@ impl ParticleSwarmUpdate { ) -> Box> { Self::new_with_id() } -} - -/// Updates the and firefly positions. -/// -/// Originally proposed for, and used as operator in [`fa`]. -/// -/// Uses the [`RandomizationParameter`]. -/// -/// [`fa`]: crate::heuristics::fa -#[derive(Clone, Serialize)] -pub struct FireflyPositionsUpdate { - pub alpha: f64, - pub beta: f64, - pub gamma: f64, - id: PhantomId, -} - -impl FireflyPositionsUpdate { - pub fn from_params(alpha: f64, beta: f64, gamma: f64) -> Self { - Self { - alpha, - beta, - gamma, - id: PhantomId::default(), - } - } - - pub fn new_with_id

( - alpha: f64, - beta: f64, - gamma: f64, - ) -> Box> - where - P: LimitedVectorProblem, - { - Box::new(Self::from_params(alpha, beta, gamma)) - } -} - -impl FireflyPositionsUpdate { - pub fn new

(alpha: f64, beta: f64, gamma: f64) -> Box> - where - P: LimitedVectorProblem, - { - Self::new_with_id(alpha, beta, gamma) - } -} - -impl Component

for FireflyPositionsUpdate - where - P: LimitedVectorProblem, - I: Identifier, -{ - fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { - state.insert(RandomizationParameter(self.alpha)); - Ok(()) - } - - fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { - let mut populations = state.populations_mut(); - let mut rng = state.random_mut(); - - // Prepare parameters - let &Self { - beta, gamma, .. - } = self; - let a = state.get_value::(); - - // Get necessary state - let mut jj: Vec> = populations.current().into_iter().cloned().collect(); - let mut ii: Vec> = populations.current().into_iter().cloned().collect(); - let mut x: Vec<&mut Vec> = populations.current_mut().as_solutions_mut(); - - // scale for adapting to problem domain; at the moment only if domain in each dim is the same - let scale = (problem.domain()[0].end - problem.domain()[0].start).abs(); - - // Perform the update step. - // compare all individuals - for (u, i) in ii.iter_mut().enumerate() { - for j in jj.iter_mut() { - // if individual j is "more attractive" (i.e. has lower fitness), move towards j - if i.get_objective() > j.get_objective() { - // draw random values from uniform distribution between 0 and 1 - // according to paper: also possible to use normal distribution, depending on problem - let rand: Vec = (0..problem.dimension()).map(|_| rng.gen_range(0.0..1.0)).collect(); - // calculate distance between i and j; without .sqrt() as it has to be squared again in the next step - let r = i.solution_mut() - .into_iter() - .zip(j.solution_mut()) - .map(|(p, q)| (p.clone() - q.clone()).powf(2.0)) - .sum::(); - // calculate "attractiveness" - let b = beta * (- gamma * r).exp(); - // calculate difference of solutions j and i - let diff = i.solution_mut() - .into_iter() - .zip(j.solution_mut()) - .map(|(p, q)| q.clone() - p.clone()) - .collect::>(); - // calculate values that should be added to current position - let pos = diff - .into_iter() - .zip(rand) - .map(|(p, q)| b * p + a * (q - 0.5) * scale) - .collect::>(); - // Add values to firefly position - for s in 0..i.solution_mut().len() { - x[u][s] += pos[s]; - } - } - } - } - Ok(()) - } -} - -/// The randomization parameter used to update the firefly positions. -#[derive(Deref, DerefMut, Tid)] -pub struct RandomizationParameter( - #[deref] - #[deref_mut] - pub f64, -); - -impl CustomState<'_> for RandomizationParameter {} \ No newline at end of file +} \ No newline at end of file diff --git a/src/heuristics/bh.rs b/src/heuristics/bh.rs new file mode 100644 index 0000000..e8d248e --- /dev/null +++ b/src/heuristics/bh.rs @@ -0,0 +1,96 @@ +//! Black Hole Algorithm (BH). +//! +//! # References +//! +//! \[1\] Abdolreza Hatamlou. 2013. +//! Black hole: A new heuristic optimization approach for data clustering. +//! In Information Sciences 222, 175–184. +//! DOI: + +use eyre::WrapErr; + +use crate::{ + component::ExecResult, + components::{boundary, initialization, mapping, swarm}, + conditions::Condition, + configuration::Configuration, + identifier::{Global, Identifier}, + lens::ValueOf, + logging::Logger, + problems::{LimitedVectorProblem, SingleObjectiveProblem}, + state::common, + Component, +}; +use crate::components::replacement; +use crate::prelude::selection::selection; + +/// Parameters for [`real_bh`]. +pub struct RealProblemParameters { + pub num_particles: u32, +} + +/// An example single-objective BH algorithm operating on a real search space. +/// +/// Uses the [`bh`] component internally. +pub fn real_bh

( + params: RealProblemParameters, + condition: Box>, +) -> ExecResult> + where + P: SingleObjectiveProblem + LimitedVectorProblem, +{ + let RealProblemParameters { + num_particles, + } = params; + + Ok(Configuration::builder() + .do_(initialization::RandomSpread::new(num_particles)) + .evaluate() + .update_best_individual() + .do_(bh::( + Parameters { + particle_update: swarm::pso::ParticleVelocitiesUpdate::new( + 0.0, + 0.0, + 1.0, + 1.0, + ) + .wrap_err("failed to construct particle velocities update")?, + constraints: boundary::Saturation::new(), + }, + condition, + )) + .build()) +} + +/// Basic building blocks of [`bh`]. +pub struct Parameters

{ + pub particle_update: Box>, + pub constraints: Box>, +} + +/// A generic single-objective Black Hole algorithm (BH) template. +pub fn bh(params: Parameters

, condition: Box>) -> Box> + where + P: SingleObjectiveProblem + LimitedVectorProblem, + I: Identifier, +{ + let Parameters { + particle_update, + constraints, + } = params; + + Configuration::builder() + .while_(condition, |builder| { + builder + .do_(particle_update) + .do_(constraints) + .evaluate_with::() + .update_best_individual() + .do_(replacement::bh::EventHorizon::new()) + .evaluate_with::() + .update_best_individual() + .do_(Logger::new()) + }) + .build_component() +} diff --git a/src/heuristics/fa.rs b/src/heuristics/fa.rs index 17649bf..503d435 100644 --- a/src/heuristics/fa.rs +++ b/src/heuristics/fa.rs @@ -54,7 +54,7 @@ pub fn real_fa

( .update_best_individual() .do_(fa::( Parameters { - firefly_update: swarm::FireflyPositionsUpdate::new( + firefly_update: swarm::fa::FireflyPositionsUpdate::new( alpha, beta, gamma, @@ -62,7 +62,7 @@ pub fn real_fa

( constraints: boundary::Saturation::new(), alpha_update: Box::from(mapping::sa::GeometricCooling::new( delta, - ValueOf::::new(), + ValueOf::::new(), )), }, condition, diff --git a/src/heuristics/mod.rs b/src/heuristics/mod.rs index 51320c3..f4c078a 100644 --- a/src/heuristics/mod.rs +++ b/src/heuristics/mod.rs @@ -8,6 +8,7 @@ //! structure and adapt it to one's needs. pub mod aco; +pub mod bh; pub mod cro; pub mod de; pub mod es; diff --git a/src/heuristics/pso.rs b/src/heuristics/pso.rs index 70c1bd2..81e0fe9 100644 --- a/src/heuristics/pso.rs +++ b/src/heuristics/pso.rs @@ -62,8 +62,8 @@ where .update_best_individual() .do_(pso::( Parameters { - particle_init: swarm::ParticleSwarmInit::new(v_max)?, - particle_update: swarm::ParticleVelocitiesUpdate::new( + particle_init: swarm::pso::ParticleSwarmInit::new(v_max)?, + particle_update: swarm::pso::ParticleVelocitiesUpdate::new( start_weight, c_one, c_two, @@ -75,9 +75,9 @@ where start_weight, end_weight, ValueOf::>>::new(), - ValueOf::>::new(), + ValueOf::>::new(), )), - state_update: swarm::ParticleSwarmUpdate::new(), + state_update: swarm::pso::ParticleSwarmUpdate::new(), }, condition, )) diff --git a/src/utils.rs b/src/utils.rs index b7eb6ed..9ff2891 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,8 @@ use std::marker::PhantomData; use derivative::Derivative; +use crate::problems::VectorProblem; +use crate::{SingleObjective, SingleObjectiveProblem}; /// Allows enumeration for functions which normally don't support enumeration, e.g. [`Vec::retain`]. /// @@ -46,6 +48,6 @@ impl serde::Serialize for SerializablePhantom { } /// Calculates squared Euclidean distance between two vectors. -pub fn squared_euclidean(a: &Vec, b:&Vec) -> f64 { - a.iter().zip(b.iter()).map(|(p, q)| (q - p).powf(2.0)).sum::() -} \ No newline at end of file +pub fn squared_euclidean(a: &[f64], b:&Vec) -> f64 { + a.iter().zip(b).map(|(p, q)| (q - p).powi(2)).sum::() +} From 44a35941855a0136a9a0e7da4430ca93767de7b9 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Tue, 19 Sep 2023 14:54:46 +0200 Subject: [PATCH 09/27] Add suggestions and make mahf compile again --- src/components/replacement/bh.rs | 15 ++++--- src/components/swarm/fa.rs | 71 +++++++++++++++++--------------- src/heuristics/bh.rs | 2 +- 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index 71f8a9c..896b7d6 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -3,6 +3,7 @@ use rand::Rng; use serde::{Deserialize, Serialize}; use crate::{Component, ExecResult, Individual, SingleObjectiveProblem, State}; +use crate::population::BestIndividual; use crate::problems::LimitedVectorProblem; use crate::utils::squared_euclidean; @@ -10,9 +11,11 @@ use crate::utils::squared_euclidean; pub struct EventHorizon; impl EventHorizon { - pub fn new() -> Box> + pub fn new

() -> Box> where - P: LimitedVectorProblem, { + P: LimitedVectorProblem, + P: SingleObjectiveProblem, + { Box::new(Self) } } @@ -20,6 +23,7 @@ impl EventHorizon { impl

Component

for EventHorizon where P: LimitedVectorProblem, + P: SingleObjectiveProblem, { fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { @@ -28,13 +32,14 @@ impl

Component

for EventHorizon let f_bh = state.best_objective_value().unwrap().value(); - let fitness_sum = offspring.iter().map(|x| *x.objective().value()).sum::(); + let fitness_sum = offspring.iter().map(|x| x.objective().value()).sum::(); let radius = f_bh / fitness_sum; let rng = &mut state.random_mut(); - let best = state.best_individual().solution(); - let distances = offspring.iter().map(|o| squared_euclidean(*o.as_solution(), best).sqrt()).collect::>(); + let best_ind = state.populations().current().best_individual().cloned(); + let best = best_ind.unwrap().solution().clone(); + let distances = offspring.iter().map(|o| squared_euclidean(o.solution(), &best).sqrt()).collect::>(); for (u, mut i) in offspring.iter().enumerate() { if distances[u] < radius { diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs index ef5e1b8..c5d3234 100644 --- a/src/components/swarm/fa.rs +++ b/src/components/swarm/fa.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use better_any::{Tid, TidAble}; use derive_more::{Deref, DerefMut}; use eyre::{ensure, ContextCompat, WrapErr}; -use itertools::multizip; +use itertools::{izip, multizip}; use rand::Rng; use serde::Serialize; @@ -84,49 +84,52 @@ impl Component

for FireflyPositionsUpdate let a = state.get_value::(); // Get necessary state - let mut jj: Vec> = populations.current().into_iter().cloned().collect(); - let mut ii: Vec> = populations.current().into_iter().cloned().collect(); - let mut x: Vec<&mut Vec> = populations.current_mut().as_solutions_mut(); + let mut individuals = populations.current_mut().clone(); - // scale for adapting to problem domain; at the moment only if domain in each dim is the same - let scale = (problem.domain()[0].end - problem.domain()[0].start).abs(); + // scale for adapting to problem domain + let scales = problem.domain() + .iter() + .map(|p| (p.end - p.start).abs()) + .collect::>(); + + // shifts in position for each firefly + let mut positions = vec![]; // Perform the update step. // compare all individuals - for (u, i) in ii.iter_mut().enumerate() { - for j in jj.iter_mut() { + for i in &individuals { + // new position for firefly i considering all fireflies j + let mut position: Vec> = vec![]; + for j in &individuals { // if individual j is "more attractive" (i.e. has lower fitness), move towards j - if i.get_objective() > j.get_objective() { + if i.objective() > j.objective() { // draw random values from uniform distribution between 0 and 1 // according to paper: also possible to use normal distribution, depending on problem - let rand: Vec = (0..problem.dimension()).map(|_| rng.gen_range(0.0..1.0)).collect(); - // calculate distance between i and j; without .sqrt() as it has to be squared again in the next step - let r = i.solution_mut() - .into_iter() - .zip(j.solution_mut()) - .map(|(p, q)| (p.clone() - q.clone()).powf(2.0)) - .sum::(); - // calculate "attractiveness" - let b = beta * (- gamma * r).exp(); - // calculate difference of solutions j and i - let diff = i.solution_mut() - .into_iter() - .zip(j.solution_mut()) - .map(|(p, q)| q.clone() - p.clone()) - .collect::>(); - // calculate values that should be added to current position - let pos = diff - .into_iter() - .zip(rand) - .map(|(p, q)| b * p + a * (q - 0.5) * scale) - .collect::>(); - // Add values to firefly position - for s in 0..i.solution_mut().len() { - x[u][s] += pos[s]; - } + let rands: Vec = (0..problem.dimension()).map(|_| rng.gen_range(0.0..1.0)).collect(); + position.push(izip!(i.solution(), j.solution(), &scales, rands) + .map(|(xi, xj, scale, rand)| { + // calculate "attractiveness" + let b = beta * (-gamma * (xi - xj).powf(2.0)).exp(); + // calculate value that should be added to current position + b * (xj - xi) + a * (rand - 0.5) * scale + }) + .collect::>()) + } + } + let mut sums = vec![0.0; individuals.len()]; + for v in position { + for (i, x) in v.into_iter().enumerate() { + sums[i] += x; } } + positions.push(sums); } + // Add values to firefly position + let individuals2 = populations.current_mut(); + + let _ = izip!(individuals2, positions) + .map(|(p, q)| izip!(p.solution_mut(), q).map(|(u, v)| *u + v)); + Ok(()) } } diff --git a/src/heuristics/bh.rs b/src/heuristics/bh.rs index e8d248e..618ebb9 100644 --- a/src/heuristics/bh.rs +++ b/src/heuristics/bh.rs @@ -72,7 +72,7 @@ pub struct Parameters

{ /// A generic single-objective Black Hole algorithm (BH) template. pub fn bh(params: Parameters

, condition: Box>) -> Box> where - P: SingleObjectiveProblem + LimitedVectorProblem, + P: SingleObjectiveProblem + LimitedVectorProblem, I: Identifier, { let Parameters { From 68ffab7b3caf1cb8e12907d546fcb96787dd24ca Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Wed, 27 Sep 2023 15:54:40 +0200 Subject: [PATCH 10/27] Simplify FA --- src/components/swarm/fa.rs | 59 ++++++++++++++------------------------ 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs index c5d3234..30481b4 100644 --- a/src/components/swarm/fa.rs +++ b/src/components/swarm/fa.rs @@ -1,21 +1,15 @@ -use std::marker::PhantomData; +use std::array::from_mut; +use std::ops::Deref; use better_any::{Tid, TidAble}; use derive_more::{Deref, DerefMut}; -use eyre::{ensure, ContextCompat, WrapErr}; -use itertools::{izip, multizip}; +use itertools::{izip}; use rand::Rng; use serde::Serialize; -use crate::{ - component::{AnyComponent, ExecResult}, - components::{Block, Component}, - identifier::{Global, Identifier, PhantomId}, - population::{AsSolutions, AsSolutionsMut, BestIndividual}, - problems::{LimitedVectorProblem, SingleObjectiveProblem}, - state::StateReq, - CustomState, Individual, Problem, State, -}; +use crate::{component::ExecResult, components::Component, identifier::{Global, Identifier, PhantomId}, problems::{LimitedVectorProblem}, CustomState, State}; +use crate::state::common; +use crate::state::common::Evaluator; /// Updates the and firefly positions. /// @@ -84,7 +78,8 @@ impl Component

for FireflyPositionsUpdate let a = state.get_value::(); // Get necessary state - let mut individuals = populations.current_mut().clone(); + let mut individuals = populations.current_mut(); + let mut evaluator = state.borrow::>(); // scale for adapting to problem domain let scales = problem.domain() @@ -92,44 +87,32 @@ impl Component

for FireflyPositionsUpdate .map(|p| (p.end - p.start).abs()) .collect::>(); - // shifts in position for each firefly - let mut positions = vec![]; // Perform the update step. // compare all individuals - for i in &individuals { - // new position for firefly i considering all fireflies j - let mut position: Vec> = vec![]; - for j in &individuals { + for i in 0..individuals.len() { + for j in 0..individuals.len() { // if individual j is "more attractive" (i.e. has lower fitness), move towards j - if i.objective() > j.objective() { + if individuals[i].objective() > individuals[j].objective() { // draw random values from uniform distribution between 0 and 1 // according to paper: also possible to use normal distribution, depending on problem let rands: Vec = (0..problem.dimension()).map(|_| rng.gen_range(0.0..1.0)).collect(); - position.push(izip!(i.solution(), j.solution(), &scales, rands) + let mut current = individuals[i].clone(); + izip!(current.solution_mut(), individuals[j].solution(), &scales, rands) .map(|(xi, xj, scale, rand)| { // calculate "attractiveness" - let b = beta * (-gamma * (xi - xj).powf(2.0)).exp(); + //let b = ; // calculate value that should be added to current position - b * (xj - xi) + a * (rand - 0.5) * scale - }) - .collect::>()) - } - } - let mut sums = vec![0.0; individuals.len()]; - for v in position { - for (i, x) in v.into_iter().enumerate() { - sums[i] += x; + let pos = beta * (-gamma * (*xi - xj).powf(2.0)).exp() * (xj - *xi) + a * (rand - 0.5) * scale; + (xi, pos) }) + .for_each(|(xi, pos)| *xi += pos); + individuals[i] = current; + + evaluator.evaluate(problem, state, from_mut(&mut individuals[i])); + *state.borrow_value_mut::() += 1; } } - positions.push(sums); } - // Add values to firefly position - let individuals2 = populations.current_mut(); - - let _ = izip!(individuals2, positions) - .map(|(p, q)| izip!(p.solution_mut(), q).map(|(u, v)| *u + v)); - Ok(()) } } From 6154697ce40207b20b5a95ca7a825e3dab05d2ac Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 28 Sep 2023 15:06:40 +0200 Subject: [PATCH 11/27] Try to accomplish evaluation --- src/components/swarm/fa.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs index 30481b4..67586ef 100644 --- a/src/components/swarm/fa.rs +++ b/src/components/swarm/fa.rs @@ -68,8 +68,8 @@ impl Component

for FireflyPositionsUpdate } fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { - let mut populations = state.populations_mut(); - let mut rng = state.random_mut(); + + // Prepare parameters let &Self { @@ -78,8 +78,12 @@ impl Component

for FireflyPositionsUpdate let a = state.get_value::(); // Get necessary state - let mut individuals = populations.current_mut(); - let mut evaluator = state.borrow::>(); + + //let binding = state.borrow_mut::>(); + //let mut evaluator = binding.deref(); + //let mut populations = state.populations_mut(); + let mut rng = state.random_mut(); + let mut individuals = state.populations_mut().pop(); // scale for adapting to problem domain let scales = problem.domain() @@ -108,11 +112,17 @@ impl Component

for FireflyPositionsUpdate .for_each(|(xi, pos)| *xi += pos); individuals[i] = current; - evaluator.evaluate(problem, state, from_mut(&mut individuals[i])); + state.holding::>( + |evaluator: &mut Evaluator, state| { + evaluator.evaluate(problem, state, from_mut(&mut individuals[i])); + Ok(()) + }, + )?; *state.borrow_value_mut::() += 1; } } } + state.populations_mut().push(individuals); Ok(()) } } From 5799786ab60923091142540b71ebf5ec76ee5b27 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Fri, 29 Sep 2023 16:43:17 +0200 Subject: [PATCH 12/27] Fix FA and improve black hole algorithm --- src/components/replacement/bh.rs | 11 +++-- src/components/swarm/bh.rs | 77 ++++++++++++++++++++++++++++++++ src/components/swarm/fa.rs | 21 +++------ src/components/swarm/mod.rs | 3 +- src/heuristics/bh.rs | 15 +------ src/utils.rs | 2 - 6 files changed, 93 insertions(+), 36 deletions(-) create mode 100644 src/components/swarm/bh.rs diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index 896b7d6..481c4ad 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -35,15 +35,18 @@ impl

Component

for EventHorizon let fitness_sum = offspring.iter().map(|x| x.objective().value()).sum::(); let radius = f_bh / fitness_sum; - let rng = &mut state.random_mut(); - let best_ind = state.populations().current().best_individual().cloned(); let best = best_ind.unwrap().solution().clone(); - let distances = offspring.iter().map(|o| squared_euclidean(o.solution(), &best).sqrt()).collect::>(); + let distances = offspring + .iter() + .map(|o| squared_euclidean(o.solution(), &best).sqrt()) + .collect::>(); for (u, mut i) in offspring.iter().enumerate() { if distances[u] < radius { - let rand: Vec = (0..problem.dimension()).map(|_| rng.gen_range(problem.domain()[0].clone())).collect(); + let rand: Vec = (0..problem.dimension()) + .map(|_| state.random_mut().gen_range(problem.domain()[0].clone())) + .collect(); let j = Individual::new_unevaluated(rand); i = &j; } diff --git a/src/components/swarm/bh.rs b/src/components/swarm/bh.rs new file mode 100644 index 0000000..9501ef1 --- /dev/null +++ b/src/components/swarm/bh.rs @@ -0,0 +1,77 @@ +use rand::{ + distributions::{Distribution, Uniform}, +}; +use serde::Serialize; + +use crate::{component::ExecResult, components::Component, identifier::{Global, Identifier, PhantomId}, problems::{LimitedVectorProblem}, State}; +use crate::population::{AsSolutionsMut, BestIndividual}; + +/// Updates the positions in the black hole algorithm. +/// +/// Originally proposed for, and used as operator in [`bh`]. +/// The operator is similar to the [`ParticleVelocitiesUpdate`] in [`pso`]. +/// Specifically, they are identical when for [`pso`]: +/// inertia weight = 0, +/// c_1 = 0, +/// c_2 = 1, +/// v_max = 1 +/// +/// [`bh`]: crate::heuristics::bh +#[derive(Clone, Serialize)] +pub struct BlackHoleParticlesUpdate { + id: PhantomId, +} + +impl BlackHoleParticlesUpdate { + pub fn from_params() -> Self { + Self { + id: PhantomId::default(), + } + } + + pub fn new_with_id

() -> Box> + where + P: LimitedVectorProblem, + { + Box::new(Self::from_params()) + } +} + +impl BlackHoleParticlesUpdate { + pub fn new

() -> Box> + where + P: LimitedVectorProblem, + { + Self::new_with_id() + } +} + +impl Component

for BlackHoleParticlesUpdate + where + P: LimitedVectorProblem, + I: Identifier, +{ + fn init(&self, _problem: &P, _state: &mut State

) -> ExecResult<()> { + Ok(()) + } + + fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + let mut distr = Uniform::new(0.0, 1.0); + + // Get necessary state like global best `xg` + let best = state.populations().current().best_individual().cloned(); + let xg = best.unwrap().solution(); + let xs = state.populations_mut().current_mut().as_solutions_mut(); + + // Perform the update step. + for x in xs { + for i in 0..x.len() { + // Calculate change in position + let pos = distr.sample(&mut *state.random_mut()) * (xg[i] - x[i]); + // Add value to particle position + x[i] += pos; + } + } + Ok(()) + } +} \ No newline at end of file diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs index 67586ef..588497f 100644 --- a/src/components/swarm/fa.rs +++ b/src/components/swarm/fa.rs @@ -1,5 +1,4 @@ use std::array::from_mut; -use std::ops::Deref; use better_any::{Tid, TidAble}; use derive_more::{Deref, DerefMut}; @@ -69,20 +68,13 @@ impl Component

for FireflyPositionsUpdate fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { - - // Prepare parameters let &Self { beta, gamma, .. } = self; let a = state.get_value::(); - // Get necessary state - - //let binding = state.borrow_mut::>(); - //let mut evaluator = binding.deref(); - //let mut populations = state.populations_mut(); - let mut rng = state.random_mut(); + // Get population from state let mut individuals = state.populations_mut().pop(); // scale for adapting to problem domain @@ -91,22 +83,17 @@ impl Component

for FireflyPositionsUpdate .map(|p| (p.end - p.start).abs()) .collect::>(); - // Perform the update step. - // compare all individuals for i in 0..individuals.len() { for j in 0..individuals.len() { // if individual j is "more attractive" (i.e. has lower fitness), move towards j if individuals[i].objective() > individuals[j].objective() { // draw random values from uniform distribution between 0 and 1 // according to paper: also possible to use normal distribution, depending on problem - let rands: Vec = (0..problem.dimension()).map(|_| rng.gen_range(0.0..1.0)).collect(); + let rands: Vec = (0..problem.dimension()).map(|_| state.random_mut().gen_range(0.0..1.0)).collect(); let mut current = individuals[i].clone(); izip!(current.solution_mut(), individuals[j].solution(), &scales, rands) .map(|(xi, xj, scale, rand)| { - // calculate "attractiveness" - //let b = ; - // calculate value that should be added to current position let pos = beta * (-gamma * (*xi - xj).powf(2.0)).exp() * (xj - *xi) + a * (rand - 0.5) * scale; (xi, pos) }) .for_each(|(xi, pos)| *xi += pos); @@ -114,7 +101,9 @@ impl Component

for FireflyPositionsUpdate state.holding::>( |evaluator: &mut Evaluator, state| { - evaluator.evaluate(problem, state, from_mut(&mut individuals[i])); + evaluator + .as_inner_mut() + .evaluate(problem, state, from_mut(&mut individuals[i])); Ok(()) }, )?; diff --git a/src/components/swarm/mod.rs b/src/components/swarm/mod.rs index faccfbf..16e6e0a 100644 --- a/src/components/swarm/mod.rs +++ b/src/components/swarm/mod.rs @@ -1,2 +1,3 @@ +pub mod bh; pub mod fa; -pub mod pso; \ No newline at end of file +pub mod pso; diff --git a/src/heuristics/bh.rs b/src/heuristics/bh.rs index 618ebb9..8024502 100644 --- a/src/heuristics/bh.rs +++ b/src/heuristics/bh.rs @@ -7,22 +7,17 @@ //! In Information Sciences 222, 175–184. //! DOI: -use eyre::WrapErr; - use crate::{ component::ExecResult, - components::{boundary, initialization, mapping, swarm}, + components::{boundary, initialization, swarm}, conditions::Condition, configuration::Configuration, identifier::{Global, Identifier}, - lens::ValueOf, logging::Logger, problems::{LimitedVectorProblem, SingleObjectiveProblem}, - state::common, Component, }; use crate::components::replacement; -use crate::prelude::selection::selection; /// Parameters for [`real_bh`]. pub struct RealProblemParameters { @@ -49,13 +44,7 @@ pub fn real_bh

( .update_best_individual() .do_(bh::( Parameters { - particle_update: swarm::pso::ParticleVelocitiesUpdate::new( - 0.0, - 0.0, - 1.0, - 1.0, - ) - .wrap_err("failed to construct particle velocities update")?, + particle_update: swarm::bh::BlackHoleParticlesUpdate::new(), constraints: boundary::Saturation::new(), }, condition, diff --git a/src/utils.rs b/src/utils.rs index 9ff2891..67a50be 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,8 +3,6 @@ use std::marker::PhantomData; use derivative::Derivative; -use crate::problems::VectorProblem; -use crate::{SingleObjective, SingleObjectiveProblem}; /// Allows enumeration for functions which normally don't support enumeration, e.g. [`Vec::retain`]. /// From 46b0b3b95f47a2c0efb0200db318069ae55abb3e Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 12 Oct 2023 16:09:49 +0200 Subject: [PATCH 13/27] Fix black hole algorithm --- src/components/replacement/bh.rs | 25 +++++++++++++--------- src/components/replacement/mod.rs | 2 +- src/components/replacement/sa.rs | 2 -- src/components/swarm/bh.rs | 29 ++++++++++++++----------- src/components/swarm/fa.rs | 35 ++++++++++++++++++++----------- src/components/swarm/pso.rs | 12 +++++------ src/heuristics/bh.rs | 22 +++++++++---------- src/heuristics/fa.rs | 19 +++++++---------- src/heuristics/pso.rs | 3 ++- 9 files changed, 82 insertions(+), 67 deletions(-) diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index 481c4ad..2191886 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -1,11 +1,11 @@ //! Replacement components for the Black Hole algorithm (BH). -use rand::Rng; -use serde::{Deserialize, Serialize}; -use crate::{Component, ExecResult, Individual, SingleObjectiveProblem, State}; use crate::population::BestIndividual; use crate::problems::LimitedVectorProblem; use crate::utils::squared_euclidean; +use crate::{Component, ExecResult, Individual, SingleObjectiveProblem, State}; +use rand::Rng; +use serde::{Deserialize, Serialize}; #[derive(Clone, Serialize, Deserialize)] pub struct EventHorizon; @@ -28,30 +28,35 @@ impl

Component

for EventHorizon fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { let mut populations = state.populations_mut(); - let mut offspring = populations.pop(); + let offspring = populations.pop(); let f_bh = state.best_objective_value().unwrap().value(); let fitness_sum = offspring.iter().map(|x| x.objective().value()).sum::(); let radius = f_bh / fitness_sum; - let best_ind = state.populations().current().best_individual().cloned(); + let best_ind = offspring.best_individual().cloned(); let best = best_ind.unwrap().solution().clone(); let distances = offspring .iter() .map(|o| squared_euclidean(o.solution(), &best).sqrt()) .collect::>(); - for (u, mut i) in offspring.iter().enumerate() { - if distances[u] < radius { + let mut new_offspring: Vec> = vec![]; + for (u, i) in offspring.iter().enumerate() { + // do not replace best individual + if distances[u] < radius && distances[u] != 0.0 { let rand: Vec = (0..problem.dimension()) .map(|_| state.random_mut().gen_range(problem.domain()[0].clone())) .collect(); let j = Individual::new_unevaluated(rand); - i = &j; + //println!("{:?}, {:?}", u, &j); + new_offspring.push(j); + } else { + new_offspring.push(i.clone()); } } - populations.push(offspring); + populations.push(new_offspring); Ok(()) } -} \ No newline at end of file +} diff --git a/src/components/replacement/mod.rs b/src/components/replacement/mod.rs index 14d203f..8bea95c 100644 --- a/src/components/replacement/mod.rs +++ b/src/components/replacement/mod.rs @@ -8,8 +8,8 @@ use crate::{ Individual, Problem, State, }; -pub mod common; pub mod bh; +pub mod common; pub mod sa; pub use common::{ diff --git a/src/components/replacement/sa.rs b/src/components/replacement/sa.rs index abed9ba..5e86de3 100644 --- a/src/components/replacement/sa.rs +++ b/src/components/replacement/sa.rs @@ -49,7 +49,6 @@ impl Component

for ExponentialAnnealingAcceptance Ok(()) } - #[ensures(state.populations().current().len() == 1, "population after should contain a single individual")] #[ensures(state.populations().len() == old(state.populations().len()) - 1)] fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { @@ -76,7 +75,6 @@ impl Component

for ExponentialAnnealingAcceptance } else { populations.pop(); } - Ok(()) } } diff --git a/src/components/swarm/bh.rs b/src/components/swarm/bh.rs index 9501ef1..2cb59fe 100644 --- a/src/components/swarm/bh.rs +++ b/src/components/swarm/bh.rs @@ -1,10 +1,13 @@ -use rand::{ - distributions::{Distribution, Uniform}, -}; +use rand::distributions::{Distribution, Uniform}; use serde::Serialize; - -use crate::{component::ExecResult, components::Component, identifier::{Global, Identifier, PhantomId}, problems::{LimitedVectorProblem}, State}; use crate::population::{AsSolutionsMut, BestIndividual}; +use crate::{ + component::ExecResult, + components::Component, + identifier::{Global, Identifier, PhantomId}, + problems::{LimitedVectorProblem}, + SingleObjectiveProblem, State +}; /// Updates the positions in the black hole algorithm. /// @@ -31,7 +34,7 @@ impl BlackHoleParticlesUpdate { pub fn new_with_id

() -> Box> where - P: LimitedVectorProblem, + P: SingleObjectiveProblem + LimitedVectorProblem, { Box::new(Self::from_params()) } @@ -40,7 +43,7 @@ impl BlackHoleParticlesUpdate { impl BlackHoleParticlesUpdate { pub fn new

() -> Box> where - P: LimitedVectorProblem, + P: SingleObjectiveProblem + LimitedVectorProblem, { Self::new_with_id() } @@ -48,7 +51,7 @@ impl BlackHoleParticlesUpdate { impl Component

for BlackHoleParticlesUpdate where - P: LimitedVectorProblem, + P: SingleObjectiveProblem + LimitedVectorProblem, I: Identifier, { fn init(&self, _problem: &P, _state: &mut State

) -> ExecResult<()> { @@ -56,12 +59,14 @@ impl Component

for BlackHoleParticlesUpdate } fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { - let mut distr = Uniform::new(0.0, 1.0); + let distr = Uniform::new(0.0, 1.0); // Get necessary state like global best `xg` let best = state.populations().current().best_individual().cloned(); - let xg = best.unwrap().solution(); - let xs = state.populations_mut().current_mut().as_solutions_mut(); + let binding = best.unwrap(); + let xg = binding.solution(); + let mut binding2 = state.populations_mut(); + let xs = binding2.current_mut().as_solutions_mut(); // Perform the update step. for x in xs { @@ -74,4 +79,4 @@ impl Component

for BlackHoleParticlesUpdate } Ok(()) } -} \ No newline at end of file +} diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs index 588497f..260ed71 100644 --- a/src/components/swarm/fa.rs +++ b/src/components/swarm/fa.rs @@ -2,13 +2,18 @@ use std::array::from_mut; use better_any::{Tid, TidAble}; use derive_more::{Deref, DerefMut}; -use itertools::{izip}; +use itertools::izip; use rand::Rng; use serde::Serialize; -use crate::{component::ExecResult, components::Component, identifier::{Global, Identifier, PhantomId}, problems::{LimitedVectorProblem}, CustomState, State}; use crate::state::common; use crate::state::common::Evaluator; +use crate::{ + component::ExecResult, + components::Component, + identifier::{Global, Identifier, PhantomId}, + problems::{LimitedVectorProblem}, + CustomState, State}; /// Updates the and firefly positions. /// @@ -67,35 +72,37 @@ impl Component

for FireflyPositionsUpdate } fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { - // Prepare parameters - let &Self { - beta, gamma, .. - } = self; + let &Self { beta, gamma, .. } = self; let a = state.get_value::(); // Get population from state let mut individuals = state.populations_mut().pop(); // scale for adapting to problem domain - let scales = problem.domain() + let scales = problem + .domain() .iter() .map(|p| (p.end - p.start).abs()) .collect::>(); // Perform the update step. - for i in 0..individuals.len() { + for i in 0..individuals.len() { for j in 0..individuals.len() { // if individual j is "more attractive" (i.e. has lower fitness), move towards j if individuals[i].objective() > individuals[j].objective() { // draw random values from uniform distribution between 0 and 1 // according to paper: also possible to use normal distribution, depending on problem - let rands: Vec = (0..problem.dimension()).map(|_| state.random_mut().gen_range(0.0..1.0)).collect(); + let rands: Vec = (0..problem.dimension()) + .map(|_| state.random_mut().gen_range(0.0..1.0)) + .collect(); let mut current = individuals[i].clone(); izip!(current.solution_mut(), individuals[j].solution(), &scales, rands) .map(|(xi, xj, scale, rand)| { - let pos = beta * (-gamma * (*xi - xj).powf(2.0)).exp() * (xj - *xi) + a * (rand - 0.5) * scale; - (xi, pos) }) + let pos = beta * (-gamma * (*xi - xj).powf(2.0)).exp() * (xj - *xi) + + a * (rand - 0.5) * scale; + (xi, pos) + }) .for_each(|(xi, pos)| *xi += pos); individuals[i] = current; @@ -103,7 +110,11 @@ impl Component

for FireflyPositionsUpdate |evaluator: &mut Evaluator, state| { evaluator .as_inner_mut() - .evaluate(problem, state, from_mut(&mut individuals[i])); + .evaluate( + problem, + state, + from_mut(&mut individuals[i]) + ); Ok(()) }, )?; diff --git a/src/components/swarm/pso.rs b/src/components/swarm/pso.rs index 78052c1..14b870d 100644 --- a/src/components/swarm/pso.rs +++ b/src/components/swarm/pso.rs @@ -335,17 +335,17 @@ impl GlobalBestParticleUpdate { } pub fn new

() -> Box> - where - P: SingleObjectiveProblem + LimitedVectorProblem, + where + P: SingleObjectiveProblem + LimitedVectorProblem, { Box::new(Self::from_params()) } } impl Component

for GlobalBestParticleUpdate - where - P: SingleObjectiveProblem + LimitedVectorProblem, - I: Identifier, +where + P: SingleObjectiveProblem + LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state @@ -422,4 +422,4 @@ impl ParticleSwarmUpdate { ) -> Box> { Self::new_with_id() } -} \ No newline at end of file +} diff --git a/src/heuristics/bh.rs b/src/heuristics/bh.rs index 8024502..1061b60 100644 --- a/src/heuristics/bh.rs +++ b/src/heuristics/bh.rs @@ -9,7 +9,7 @@ use crate::{ component::ExecResult, - components::{boundary, initialization, swarm}, + components::{boundary, initialization, replacement, swarm}, conditions::Condition, configuration::Configuration, identifier::{Global, Identifier}, @@ -17,7 +17,6 @@ use crate::{ problems::{LimitedVectorProblem, SingleObjectiveProblem}, Component, }; -use crate::components::replacement; /// Parameters for [`real_bh`]. pub struct RealProblemParameters { @@ -31,12 +30,10 @@ pub fn real_bh

( params: RealProblemParameters, condition: Box>, ) -> ExecResult> - where - P: SingleObjectiveProblem + LimitedVectorProblem, +where + P: SingleObjectiveProblem + LimitedVectorProblem, { - let RealProblemParameters { - num_particles, - } = params; + let RealProblemParameters { num_particles } = params; Ok(Configuration::builder() .do_(initialization::RandomSpread::new(num_particles)) @@ -46,6 +43,7 @@ pub fn real_bh

( Parameters { particle_update: swarm::bh::BlackHoleParticlesUpdate::new(), constraints: boundary::Saturation::new(), + replacement: replacement::bh::EventHorizon::new(), }, condition, )) @@ -56,17 +54,19 @@ pub fn real_bh

( pub struct Parameters

{ pub particle_update: Box>, pub constraints: Box>, + pub replacement: Box>, } /// A generic single-objective Black Hole algorithm (BH) template. pub fn bh(params: Parameters

, condition: Box>) -> Box> - where - P: SingleObjectiveProblem + LimitedVectorProblem, - I: Identifier, +where + P: SingleObjectiveProblem, + I: Identifier, { let Parameters { particle_update, constraints, + replacement, } = params; Configuration::builder() @@ -76,7 +76,7 @@ pub fn bh(params: Parameters

, condition: Box>) -> Box< .do_(constraints) .evaluate_with::() .update_best_individual() - .do_(replacement::bh::EventHorizon::new()) + .do_(replacement) .evaluate_with::() .update_best_individual() .do_(Logger::new()) diff --git a/src/heuristics/fa.rs b/src/heuristics/fa.rs index 503d435..a68371d 100644 --- a/src/heuristics/fa.rs +++ b/src/heuristics/fa.rs @@ -8,7 +8,6 @@ //! SAGA 2009. Lecture Notes in Computer Science, vol 5792. Springer, Berlin, Heidelberg. //! DOI: - use crate::{ component::ExecResult, components::{boundary, initialization, mapping, swarm}, @@ -37,8 +36,8 @@ pub fn real_fa

( params: RealProblemParameters, condition: Box>, ) -> ExecResult> - where - P: SingleObjectiveProblem + LimitedVectorProblem, +where + P: SingleObjectiveProblem + LimitedVectorProblem, { let RealProblemParameters { pop_size, @@ -54,11 +53,7 @@ pub fn real_fa

( .update_best_individual() .do_(fa::( Parameters { - firefly_update: swarm::fa::FireflyPositionsUpdate::new( - alpha, - beta, - gamma, - ), + firefly_update: swarm::fa::FireflyPositionsUpdate::new(alpha, beta, gamma), constraints: boundary::Saturation::new(), alpha_update: Box::from(mapping::sa::GeometricCooling::new( delta, @@ -79,9 +74,9 @@ pub struct Parameters

{ /// A generic single-objective Firefly Algorithm (FA) template. pub fn fa(params: Parameters

, condition: Box>) -> Box> - where - P: SingleObjectiveProblem, - I: Identifier, +where + P: SingleObjectiveProblem, + I: Identifier, { let Parameters { firefly_update, @@ -100,4 +95,4 @@ pub fn fa(params: Parameters

, condition: Box>) -> Box< .do_(Logger::new()) }) .build_component() -} \ No newline at end of file +} diff --git a/src/heuristics/pso.rs b/src/heuristics/pso.rs index 81e0fe9..fdec30c 100644 --- a/src/heuristics/pso.rs +++ b/src/heuristics/pso.rs @@ -75,7 +75,8 @@ where start_weight, end_weight, ValueOf::>>::new(), - ValueOf::>::new(), + ValueOf::>::new( + ), )), state_update: swarm::pso::ParticleSwarmUpdate::new(), }, From c905af711b61a9eee2a01966530fe44271e00246 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 12 Oct 2023 16:45:37 +0200 Subject: [PATCH 14/27] Fix formatting --- src/components/mapping/mod.rs | 4 +-- src/components/replacement/bh.rs | 13 ++++--- src/components/swarm/bh.rs | 26 ++++++++------ src/components/swarm/fa.rs | 58 ++++++++++++++++---------------- src/components/swarm/pso.rs | 48 +++++++++++++------------- 5 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/components/mapping/mod.rs b/src/components/mapping/mod.rs index 550b180..699aa08 100644 --- a/src/components/mapping/mod.rs +++ b/src/components/mapping/mod.rs @@ -22,8 +22,8 @@ //! using [`ValueOf>>`] as input lens and //! [`ValueOf`] as output lens: //! -//! [`InertiaWeight`]: crate::components::swarm::InertiaWeight -//! [`ParticleVelocitiesUpdate`]: crate::components::swarm::ParticleVelocitiesUpdate +//! [`InertiaWeight`]: crate::components::swarm::pso::InertiaWeight +//! [`ParticleVelocitiesUpdate`]: crate::components::swarm::pso::ParticleVelocitiesUpdate //! [`ValueOf>>`]: crate::lens::ValueOf //! [`ValueOf`]: crate::lens::ValueOf //! diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index 2191886..132d5e2 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -12,20 +12,19 @@ pub struct EventHorizon; impl EventHorizon { pub fn new

() -> Box> - where - P: LimitedVectorProblem, - P: SingleObjectiveProblem, + where + P: LimitedVectorProblem, + P: SingleObjectiveProblem, { Box::new(Self) } } impl

Component

for EventHorizon - where - P: LimitedVectorProblem, - P: SingleObjectiveProblem, +where + P: LimitedVectorProblem, + P: SingleObjectiveProblem, { - fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { let mut populations = state.populations_mut(); let offspring = populations.pop(); diff --git a/src/components/swarm/bh.rs b/src/components/swarm/bh.rs index 2cb59fe..606238f 100644 --- a/src/components/swarm/bh.rs +++ b/src/components/swarm/bh.rs @@ -1,13 +1,15 @@ -use rand::distributions::{Distribution, Uniform}; -use serde::Serialize; use crate::population::{AsSolutionsMut, BestIndividual}; use crate::{ component::ExecResult, + components, components::Component, + heuristics, identifier::{Global, Identifier, PhantomId}, - problems::{LimitedVectorProblem}, - SingleObjectiveProblem, State + problems::LimitedVectorProblem, + SingleObjectiveProblem, State, }; +use rand::distributions::{Distribution, Uniform}; +use serde::Serialize; /// Updates the positions in the black hole algorithm. /// @@ -20,6 +22,8 @@ use crate::{ /// v_max = 1 /// /// [`bh`]: crate::heuristics::bh +/// [`ParticleVelocitiesUpdate`]: components::swarm::pso::ParticleVelocitiesUpdate` +/// [`pso`]: heuristics::pso #[derive(Clone, Serialize)] pub struct BlackHoleParticlesUpdate { id: PhantomId, @@ -33,8 +37,8 @@ impl BlackHoleParticlesUpdate { } pub fn new_with_id

() -> Box> - where - P: SingleObjectiveProblem + LimitedVectorProblem, + where + P: SingleObjectiveProblem + LimitedVectorProblem, { Box::new(Self::from_params()) } @@ -42,17 +46,17 @@ impl BlackHoleParticlesUpdate { impl BlackHoleParticlesUpdate { pub fn new

() -> Box> - where - P: SingleObjectiveProblem + LimitedVectorProblem, + where + P: SingleObjectiveProblem + LimitedVectorProblem, { Self::new_with_id() } } impl Component

for BlackHoleParticlesUpdate - where - P: SingleObjectiveProblem + LimitedVectorProblem, - I: Identifier, +where + P: SingleObjectiveProblem + LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, _state: &mut State

) -> ExecResult<()> { Ok(()) diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs index 260ed71..56f80f6 100644 --- a/src/components/swarm/fa.rs +++ b/src/components/swarm/fa.rs @@ -12,8 +12,9 @@ use crate::{ component::ExecResult, components::Component, identifier::{Global, Identifier, PhantomId}, - problems::{LimitedVectorProblem}, - CustomState, State}; + problems::LimitedVectorProblem, + CustomState, State, +}; /// Updates the and firefly positions. /// @@ -40,13 +41,9 @@ impl FireflyPositionsUpdate { } } - pub fn new_with_id

( - alpha: f64, - beta: f64, - gamma: f64, - ) -> Box> - where - P: LimitedVectorProblem, + pub fn new_with_id

(alpha: f64, beta: f64, gamma: f64) -> Box> + where + P: LimitedVectorProblem, { Box::new(Self::from_params(alpha, beta, gamma)) } @@ -54,17 +51,17 @@ impl FireflyPositionsUpdate { impl FireflyPositionsUpdate { pub fn new

(alpha: f64, beta: f64, gamma: f64) -> Box> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Self::new_with_id(alpha, beta, gamma) } } impl Component

for FireflyPositionsUpdate - where - P: LimitedVectorProblem, - I: Identifier, +where + P: LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(RandomizationParameter(self.alpha)); @@ -97,24 +94,27 @@ impl Component

for FireflyPositionsUpdate .map(|_| state.random_mut().gen_range(0.0..1.0)) .collect(); let mut current = individuals[i].clone(); - izip!(current.solution_mut(), individuals[j].solution(), &scales, rands) - .map(|(xi, xj, scale, rand)| { - let pos = beta * (-gamma * (*xi - xj).powf(2.0)).exp() * (xj - *xi) - + a * (rand - 0.5) * scale; - (xi, pos) - }) - .for_each(|(xi, pos)| *xi += pos); + izip!( + current.solution_mut(), + individuals[j].solution(), + &scales, + rands + ) + .map(|(xi, xj, scale, rand)| { + let pos = beta * (-gamma * (*xi - xj).powf(2.0)).exp() * (xj - *xi) + + a * (rand - 0.5) * scale; + (xi, pos) + }) + .for_each(|(xi, pos)| *xi += pos); individuals[i] = current; state.holding::>( |evaluator: &mut Evaluator, state| { - evaluator - .as_inner_mut() - .evaluate( - problem, - state, - from_mut(&mut individuals[i]) - ); + evaluator.as_inner_mut().evaluate( + problem, + state, + from_mut(&mut individuals[i]), + ); Ok(()) }, )?; @@ -135,4 +135,4 @@ pub struct RandomizationParameter( pub f64, ); -impl CustomState<'_> for RandomizationParameter {} \ No newline at end of file +impl CustomState<'_> for RandomizationParameter {} diff --git a/src/components/swarm/pso.rs b/src/components/swarm/pso.rs index 14b870d..6e62af4 100644 --- a/src/components/swarm/pso.rs +++ b/src/components/swarm/pso.rs @@ -55,17 +55,17 @@ impl ParticleVelocitiesInit { } pub fn new

(v_max: f64) -> ExecResult>> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Ok(Box::new(Self::from_params(v_max)?)) } } impl Component

for ParticleVelocitiesInit - where - P: LimitedVectorProblem, - I: Identifier, +where + P: LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(ParticleVelocities::::new(Vec::new())); @@ -78,8 +78,8 @@ impl Component

for ParticleVelocitiesInit .take(problem.dimension()) .collect::>() }) - .take(state.populations().current().len()) - .collect::>(); + .take(state.populations().current().len()) + .collect::>(); state.set_value::>(velocities); @@ -144,8 +144,8 @@ impl ParticleVelocitiesUpdate { c_2: f64, v_max: f64, ) -> ExecResult>> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Ok(Box::new(Self::from_params(weight, c_1, c_2, v_max)?)) } @@ -153,17 +153,17 @@ impl ParticleVelocitiesUpdate { impl ParticleVelocitiesUpdate { pub fn new

(weight: f64, c_1: f64, c_2: f64, v_max: f64) -> ExecResult>> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Self::new_with_id(weight, c_1, c_2, v_max) } } impl Component

for ParticleVelocitiesUpdate - where - P: LimitedVectorProblem, - I: Identifier, +where + P: LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(InertiaWeight::::new(self.weight)); @@ -249,17 +249,17 @@ impl PersonalBestParticlesInit { } pub fn new

() -> Box> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Box::new(Self::from_params()) } } impl Component

for PersonalBestParticlesInit - where - P: LimitedVectorProblem, - I: Identifier, +where + P: LimitedVectorProblem, + I: Identifier, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(BestParticles::::new(Vec::new())); @@ -282,17 +282,17 @@ impl PersonalBestParticlesUpdate { } pub fn new

() -> Box> - where - P: LimitedVectorProblem, + where + P: LimitedVectorProblem, { Box::new(Self::from_params()) } } impl Component

for PersonalBestParticlesUpdate - where - P: LimitedVectorProblem, - I: Identifier, +where + P: LimitedVectorProblem, + I: Identifier, { fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { let populations = state.populations(); From 961d3bf42d0db448a541560c24d8ac9eb7b25410 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 12 Oct 2023 16:55:23 +0200 Subject: [PATCH 15/27] Fix documentation --- src/components/mapping/mod.rs | 2 +- src/components/swarm/bh.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/mapping/mod.rs b/src/components/mapping/mod.rs index 699aa08..4732ff4 100644 --- a/src/components/mapping/mod.rs +++ b/src/components/mapping/mod.rs @@ -40,7 +40,7 @@ //! 0.4, //! 0.9, //! ValueOf::>>::new(), -//! ValueOf::>::new(), +//! ValueOf::>::new(), //! ) //! # } //! ``` diff --git a/src/components/swarm/bh.rs b/src/components/swarm/bh.rs index 606238f..07ec791 100644 --- a/src/components/swarm/bh.rs +++ b/src/components/swarm/bh.rs @@ -1,9 +1,7 @@ use crate::population::{AsSolutionsMut, BestIndividual}; use crate::{ component::ExecResult, - components, components::Component, - heuristics, identifier::{Global, Identifier, PhantomId}, problems::LimitedVectorProblem, SingleObjectiveProblem, State, @@ -22,8 +20,8 @@ use serde::Serialize; /// v_max = 1 /// /// [`bh`]: crate::heuristics::bh -/// [`ParticleVelocitiesUpdate`]: components::swarm::pso::ParticleVelocitiesUpdate` -/// [`pso`]: heuristics::pso +/// [`ParticleVelocitiesUpdate`]: crate::components::swarm::pso::ParticleVelocitiesUpdate` +/// [`pso`]: crate::heuristics::pso #[derive(Clone, Serialize)] pub struct BlackHoleParticlesUpdate { id: PhantomId, From 78bb32e0d937c9009490ff05f84f22ff52cfbe16 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Fri, 13 Oct 2023 09:16:05 +0200 Subject: [PATCH 16/27] Fix formatting --- src/components/replacement/bh.rs | 9 +++++---- src/components/swarm/bh.rs | 7 ++++--- src/components/swarm/fa.rs | 3 +-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index 132d5e2..2eb4490 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -1,12 +1,13 @@ //! Replacement components for the Black Hole algorithm (BH). -use crate::population::BestIndividual; -use crate::problems::LimitedVectorProblem; -use crate::utils::squared_euclidean; -use crate::{Component, ExecResult, Individual, SingleObjectiveProblem, State}; use rand::Rng; use serde::{Deserialize, Serialize}; +use crate::{ + population::BestIndividual, problems::LimitedVectorProblem, utils::squared_euclidean, + Component, ExecResult, Individual, SingleObjectiveProblem, State, +}; + #[derive(Clone, Serialize, Deserialize)] pub struct EventHorizon; diff --git a/src/components/swarm/bh.rs b/src/components/swarm/bh.rs index 07ec791..efdb8ec 100644 --- a/src/components/swarm/bh.rs +++ b/src/components/swarm/bh.rs @@ -1,13 +1,14 @@ -use crate::population::{AsSolutionsMut, BestIndividual}; +use rand::distributions::{Distribution, Uniform}; +use serde::Serialize; + use crate::{ component::ExecResult, components::Component, identifier::{Global, Identifier, PhantomId}, + population::{AsSolutionsMut, BestIndividual}, problems::LimitedVectorProblem, SingleObjectiveProblem, State, }; -use rand::distributions::{Distribution, Uniform}; -use serde::Serialize; /// Updates the positions in the black hole algorithm. /// diff --git a/src/components/swarm/fa.rs b/src/components/swarm/fa.rs index 56f80f6..f64e90d 100644 --- a/src/components/swarm/fa.rs +++ b/src/components/swarm/fa.rs @@ -6,13 +6,12 @@ use itertools::izip; use rand::Rng; use serde::Serialize; -use crate::state::common; -use crate::state::common::Evaluator; use crate::{ component::ExecResult, components::Component, identifier::{Global, Identifier, PhantomId}, problems::LimitedVectorProblem, + state::{common, common::Evaluator}, CustomState, State, }; From a88249a344677fcd53d0f346a7201246ce5a2276 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Mon, 4 Dec 2023 11:02:46 +0100 Subject: [PATCH 17/27] Improve according to review comments --- src/components/replacement/bh.rs | 32 +++++++++++++++----------------- src/components/swarm/bh.rs | 12 ++++++------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index 2eb4490..a716e1a 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -4,8 +4,8 @@ use rand::Rng; use serde::{Deserialize, Serialize}; use crate::{ - population::BestIndividual, problems::LimitedVectorProblem, utils::squared_euclidean, - Component, ExecResult, Individual, SingleObjectiveProblem, State, + problems::LimitedVectorProblem, utils::squared_euclidean, + Component, ExecResult, SingleObjectiveProblem, State, }; #[derive(Clone, Serialize, Deserialize)] @@ -28,35 +28,33 @@ where { fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { let mut populations = state.populations_mut(); - let offspring = populations.pop(); + let mut offspring = populations.pop(); let f_bh = state.best_objective_value().unwrap().value(); let fitness_sum = offspring.iter().map(|x| x.objective().value()).sum::(); let radius = f_bh / fitness_sum; - let best_ind = offspring.best_individual().cloned(); - let best = best_ind.unwrap().solution().clone(); + let (index, best) = offspring + .iter() + .enumerate() + .min_by_key(|(_u, i)| i.objective()) + .unwrap(); let distances = offspring .iter() - .map(|o| squared_euclidean(o.solution(), &best).sqrt()) + .map(|o| squared_euclidean(o.solution(), best.solution()).sqrt()) .collect::>(); - let mut new_offspring: Vec> = vec![]; - for (u, i) in offspring.iter().enumerate() { + for (u, i) in offspring.iter_mut().enumerate() { // do not replace best individual - if distances[u] < radius && distances[u] != 0.0 { - let rand: Vec = (0..problem.dimension()) - .map(|_| state.random_mut().gen_range(problem.domain()[0].clone())) + if distances[u] < radius && u != index { + let rand: Vec = problem.domain().iter() + .map(|d| state.random_mut().gen_range(d.clone())) .collect(); - let j = Individual::new_unevaluated(rand); - //println!("{:?}, {:?}", u, &j); - new_offspring.push(j); - } else { - new_offspring.push(i.clone()); + *i.solution_mut() = rand; } } - populations.push(new_offspring); + populations.push(offspring); Ok(()) } } diff --git a/src/components/swarm/bh.rs b/src/components/swarm/bh.rs index efdb8ec..a4ecd45 100644 --- a/src/components/swarm/bh.rs +++ b/src/components/swarm/bh.rs @@ -62,20 +62,20 @@ where } fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { - let distr = Uniform::new(0.0, 1.0); + let distribution = Uniform::new(0.0, 1.0); // Get necessary state like global best `xg` let best = state.populations().current().best_individual().cloned(); - let binding = best.unwrap(); - let xg = binding.solution(); - let mut binding2 = state.populations_mut(); - let xs = binding2.current_mut().as_solutions_mut(); + let best_ind = best.unwrap(); + let xg = best_ind.solution(); + let mut population = state.populations_mut(); + let xs = population.current_mut().as_solutions_mut(); // Perform the update step. for x in xs { for i in 0..x.len() { // Calculate change in position - let pos = distr.sample(&mut *state.random_mut()) * (xg[i] - x[i]); + let pos = distribution.sample(&mut *state.random_mut()) * (xg[i] - x[i]); // Add value to particle position x[i] += pos; } From 6efd78f237f4702759f39514cf17a539674ce7c5 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Mon, 4 Dec 2023 11:07:44 +0100 Subject: [PATCH 18/27] Fix fmt --- src/components/replacement/bh.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index a716e1a..df435fd 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -4,8 +4,8 @@ use rand::Rng; use serde::{Deserialize, Serialize}; use crate::{ - problems::LimitedVectorProblem, utils::squared_euclidean, - Component, ExecResult, SingleObjectiveProblem, State, + problems::LimitedVectorProblem, utils::squared_euclidean, Component, ExecResult, + SingleObjectiveProblem, State, }; #[derive(Clone, Serialize, Deserialize)] @@ -48,7 +48,9 @@ where for (u, i) in offspring.iter_mut().enumerate() { // do not replace best individual if distances[u] < radius && u != index { - let rand: Vec = problem.domain().iter() + let rand: Vec = problem + .domain() + .iter() .map(|d| state.random_mut().gen_range(d.clone())) .collect(); *i.solution_mut() = rand; From 31de8da8ab1f79a4861e866337ed7627ef6e218e Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Mon, 4 Dec 2023 11:12:22 +0100 Subject: [PATCH 19/27] Fix format and include last comment --- src/components/replacement/bh.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/replacement/bh.rs b/src/components/replacement/bh.rs index df435fd..f4132dd 100644 --- a/src/components/replacement/bh.rs +++ b/src/components/replacement/bh.rs @@ -28,7 +28,7 @@ where { fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { let mut populations = state.populations_mut(); - let mut offspring = populations.pop(); + let offspring = populations.current_mut(); let f_bh = state.best_objective_value().unwrap().value(); @@ -56,7 +56,6 @@ where *i.solution_mut() = rand; } } - populations.push(offspring); Ok(()) } } From 6bb3047ed6fe0a35df686f226bee239e781da17b Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Wed, 8 Nov 2023 11:46:14 +0100 Subject: [PATCH 20/27] Add variance to step size measures --- src/utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils.rs b/src/utils.rs index 67a50be..4e368e6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -49,3 +49,4 @@ impl serde::Serialize for SerializablePhantom { pub fn squared_euclidean(a: &[f64], b:&Vec) -> f64 { a.iter().zip(b).map(|(p, q)| (q - p).powi(2)).sum::() } + From 82e0ccdfaece078a0b0a5d233c8adb7facc51f09 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Wed, 10 Jan 2024 15:28:57 +0100 Subject: [PATCH 21/27] Add note to improvement measure --- src/components/measures/improvement.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/measures/improvement.rs b/src/components/measures/improvement.rs index 473e1e9..baca7b8 100644 --- a/src/components/measures/improvement.rs +++ b/src/components/measures/improvement.rs @@ -40,6 +40,7 @@ where let mut cur = current_pop.clone(); for i in 0..current_pop.len() { //if !cur[i].is_evaluated() { + //TODO Does not work; need to evaluate in config state.holding::>( |evaluator: &mut Evaluator

, state| { evaluator.as_inner_mut().evaluate( From 29c02a8940f0e13fddefaa256d504d20afc41044 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 23 May 2024 15:10:07 +0200 Subject: [PATCH 22/27] Add convergence behaviour measures --- src/components/archive.rs | 64 ++++++ src/components/measures/convergence.rs | 277 +++++++++++++++++++++++++ src/components/measures/mod.rs | 1 + src/conditions/common.rs | 86 +++++++- src/conditions/mod.rs | 2 +- 5 files changed, 424 insertions(+), 6 deletions(-) create mode 100644 src/components/measures/convergence.rs diff --git a/src/components/archive.rs b/src/components/archive.rs index a3f5f5a..0dcf92f 100644 --- a/src/components/archive.rs +++ b/src/components/archive.rs @@ -1,5 +1,6 @@ //! Archive for specified parts of population. +use std::cell::Ref; use better_any::{Tid, TidAble}; use serde::{Deserialize, Serialize}; @@ -176,4 +177,67 @@ impl

Component

for IntermediateArchiveUpdate .update(state.populations().current()); Ok(()) } +} + + +/// An archive for storing all best individual yet, e.g. for subsequent calculation of measures. +#[derive(Default, Tid)] +pub struct BestIndividualsArchive(Vec>); + +impl CustomState<'_> for BestIndividualsArchive

{} + +impl BestIndividualsArchive

{ + /// Creates a new, empty `BestIndividualsArchive`. + fn new() -> Self { + Self(Vec::new()) + } + + /// Updates the archive using the `BestIndividual`, adding it to a vector of previously found best individuals. + fn update(&mut self, best_individual: Option>>) { + self.0.push(best_individual.unwrap().clone()); + } + + /// Returns a reference to the archived individuals. + pub fn archived_best_individuals(&self) -> &[Individual

] { + &self.0 + } + + /// Returns a mutable reference to the archived individuals. + pub fn archived_best_individuals_mut(&mut self) -> &mut [Individual

] { + &mut self.0 + } +} + +/// Updates the [`crate::components::archive::BestIndividualsArchive`] with the current best individual. +#[derive(Clone, Serialize, Deserialize)] +pub struct BestIndividualsArchiveUpdate; + +impl BestIndividualsArchiveUpdate { + pub fn from_params() -> Self { + Self {} + } + + pub fn new

() -> Box> + where + P: Problem + SingleObjectiveProblem, + { + Box::new(Self::from_params()) + } +} + +impl

Component

for BestIndividualsArchiveUpdate + where + P: Problem + SingleObjectiveProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(BestIndividualsArchive::

::new()); + Ok(()) + } + + fn execute(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state + .borrow_mut::>() + .update(state.best_individual()); + Ok(()) + } } \ No newline at end of file diff --git a/src/components/measures/convergence.rs b/src/components/measures/convergence.rs new file mode 100644 index 0000000..b066d4c --- /dev/null +++ b/src/components/measures/convergence.rs @@ -0,0 +1,277 @@ +//! Convergence rate measures for best solutions. +//! +//! # References +//! +//! \[1\] Halim, A.H., Ismail, I. & Das, S. 2020. +//! Performance assessment of the metaheuristic optimization algorithms: an exhaustive review. +//! Artif Intell Rev 54, 2323–2409 (2021). +//! DOI: + + +use better_any::{Tid, TidAble}; +use std::{any::type_name, marker::PhantomData}; +use derivative::Derivative; +use serde::Serialize; + +use crate::component::AnyComponent; +use crate::{Component, CustomState, ExecResult, Problem, SingleObjectiveProblem, State}; +use crate::components::archive; +use crate::lens::{AnyLens, Lens, LensMap}; +use crate::logging::extractor::{EntryExtractor, EntryName}; +use crate::problems::{KnownOptimumProblem, VectorProblem}; +use crate::utils::SerializablePhantom; + +/// Trait for representing a component that measures the convergence rate. +pub trait ConvergenceRateMeasure: AnyComponent { + /// Calculates the convergence rate. + fn measure(&self, problem: &P, previous: f64, current: f64) -> f64; +} + +pub fn convergence_rate_measure(component: &T, problem: &P, state: &mut State

) -> ExecResult<()> +where + P: Problem + SingleObjectiveProblem, + T: ConvergenceRateMeasure

+ 'static, +{ + let mut convergence_rate = state.borrow_mut::>(); + let archive = state.borrow_mut::>(); + let best_individuals = archive.archived_best_individuals(); + + let len = best_individuals.len(); + if len > 1 { + let current_best = best_individuals[len-1].clone().objective().value(); + let previous_best = best_individuals[len-2].clone().objective().value(); + convergence_rate.update(component.measure(problem, previous_best, current_best)); + } else { + convergence_rate.update(0.0); + + } + + Ok(()) +} + +/// The convergence rate as measured by the component `I`. +#[derive(Tid)] +pub struct ConvergenceRate { + pub convergence_rate: f64, + marker: PhantomData +} + +impl ConvergenceRate { + /// Creates a new `ConvergenceRate` with initial values of 0. + pub fn new() -> Self { + Self { + convergence_rate: 0., + marker: PhantomData, + } + } + + /// Updates the convergence rate. + pub fn update(&mut self, convergence_rate: f64) { + self.convergence_rate = convergence_rate; + } +} + +impl Default for ConvergenceRate { + fn default() -> Self { + Self::new() + } +} + +impl CustomState<'_> for ConvergenceRate {} + +/// Lens for accessing the convergence rate of [`ConvergenceRate`]. +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Default(bound = ""), Clone(bound = ""))] +pub struct ConvergenceRateLens(SerializablePhantom); + +impl AnyLens for ConvergenceRateLens { + type Target = f64; +} + +impl EntryName for ConvergenceRateLens { + fn entry_name() -> &'static str { + type_name::() + } +} + +impl ConvergenceRateLens { + /// Constructs the lens. + pub fn new() -> Self { Self(SerializablePhantom::default()) } + + /// Constructs the lens for logging. + pub fn entry

() -> Box> + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, + { + Box::::default() + } +} + +impl LensMap for ConvergenceRateLens { + type Source = ConvergenceRate; + + fn map(&self, source: &Self::Source) -> Self::Target { + source.convergence_rate + } +} + +/// Measures the convergence rate between two iterations if the optimum is known. +/// +/// The value is stored in the [`ConvergenceRate`] state. +#[derive(Clone, Serialize)] +pub struct KnownOptimumIterationWiseConvergence; + +impl KnownOptimumIterationWiseConvergence { + pub fn from_params() -> Self { Self } + + pub fn new

() -> Box> + where + P: VectorProblem + KnownOptimumProblem, { Box::new(Self::from_params()) } +} + +impl

ConvergenceRateMeasure

for KnownOptimumIterationWiseConvergence +where + P: VectorProblem + KnownOptimumProblem, +{ + fn measure(&self, problem: &P, previous: f64, current: f64) -> f64 { + let optimum = problem.known_optimum().value(); + + let convergence_rate = (optimum - current).abs() / (optimum - previous).abs(); + convergence_rate + } +} + +impl

Component

for KnownOptimumIterationWiseConvergence +where + P: VectorProblem + KnownOptimumProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(ConvergenceRate::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + convergence_rate_measure(self, problem, state) + } +} + +/// Measures the convergence progressive rate between two iterations if the optimum is known. +/// +/// The value is stored in the [`ConvergenceRate`] state. +#[derive(Clone, Serialize)] +pub struct KnownOptimumConvergenceProgressiveRate; + +impl KnownOptimumConvergenceProgressiveRate { + pub fn from_params() -> Self { Self } + + pub fn new

() -> Box> + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { Box::new(Self::from_params()) } +} + +impl

ConvergenceRateMeasure

for KnownOptimumConvergenceProgressiveRate + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +{ + fn measure(&self, problem: &P, _previous: f64, current: f64) -> f64 { + let optimum = problem.known_optimum().value(); + + let convergence_rate = (optimum - current).abs(); + convergence_rate + } +} + +impl

Component

for KnownOptimumConvergenceProgressiveRate + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(ConvergenceRate::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + convergence_rate_measure(self, problem, state) + } +} + +/// Measures the convergence progressive rate between two iterations if the optimum is unknown. +/// +/// The value is stored in the [`ConvergenceRate`] state. +#[derive(Clone, Serialize)] +pub struct UnknownOptimumConvergenceProgressiveRate; + +impl UnknownOptimumConvergenceProgressiveRate { + pub fn from_params() -> Self { Self } + + pub fn new

() -> Box> + where + P: VectorProblem + SingleObjectiveProblem, { Box::new(Self::from_params()) } +} + +impl

ConvergenceRateMeasure

for UnknownOptimumConvergenceProgressiveRate + where + P: VectorProblem + SingleObjectiveProblem, +{ + fn measure(&self, _problem: &P, previous: f64, current: f64) -> f64 { + let convergence_rate = (current - previous).abs(); + convergence_rate + } +} + +impl

Component

for UnknownOptimumConvergenceProgressiveRate + where + P: VectorProblem + SingleObjectiveProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(ConvergenceRate::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + convergence_rate_measure(self, problem, state) + } +} + +/// Measures the logarithmic convergence rate between two iterations if the optimum is known. +/// +/// The value is stored in the [`ConvergenceRate`] state. +#[derive(Clone, Serialize)] +pub struct KnownOptimumLogarithmicConvergenceRate; + +impl KnownOptimumLogarithmicConvergenceRate { + pub fn from_params() -> Self { Self } + + pub fn new

() -> Box> + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { Box::new(Self::from_params()) } +} + +impl

ConvergenceRateMeasure

for KnownOptimumLogarithmicConvergenceRate + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +{ + fn measure(&self, problem: &P, _previous: f64, current: f64) -> f64 { + let optimum = problem.known_optimum().value(); + + let convergence_rate = (optimum - current).abs(); + convergence_rate.log10() + } +} + +impl

Component

for KnownOptimumLogarithmicConvergenceRate + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +{ + fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { + state.insert(ConvergenceRate::::new()); + Ok(()) + } + + fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { + convergence_rate_measure(self, problem, state) + } +} diff --git a/src/components/measures/mod.rs b/src/components/measures/mod.rs index f3e9c89..a322596 100644 --- a/src/components/measures/mod.rs +++ b/src/components/measures/mod.rs @@ -1,3 +1,4 @@ +pub mod convergence; pub mod diversity; pub mod improvement; pub mod stepsize; diff --git a/src/conditions/common.rs b/src/conditions/common.rs index 30a9f0b..285f798 100644 --- a/src/conditions/common.rs +++ b/src/conditions/common.rs @@ -191,10 +191,10 @@ where /// # Common lenses /// /// The most common lens used with this condition is [`ValueOf`], -/// for which the [`LessThanN::iterations`] method is provided. +/// for which the [`EveryN::iterations`] method is provided. /// /// [`ValueOf`]: ValueOf -/// [`LessThanN::iterations`]: LessThanN>::iterations +/// [`EveryN::iterations`]: EveryN>::iterations /// /// # Examples /// @@ -223,12 +223,12 @@ pub struct EveryN { } impl EveryN { - /// Constructs a new `LessThanN` with the given `n` and `lens`. + /// Constructs a new `EveryN` with the given `n` and `lens`. pub fn from_params(n: u32, lens: L) -> Self { Self { n, lens } } - /// Constructs a new `LessThanN` with the given `n` and `lens`. + /// Constructs a new `EveryN` with the given `n` and `lens`. pub fn new

(n: u32, lens: L) -> Box> where P: Problem, @@ -239,7 +239,7 @@ impl EveryN { } impl EveryN> { - /// Creates a new `LessThanN` that evaluates to `true` every `n` [`Iterations`]. + /// Creates a new `EveryN` that evaluates to `true` every `n` [`Iterations`]. pub fn iterations

(n: u32) -> Box> where P: Problem, @@ -259,6 +259,82 @@ where } } + +/// Evaluates to `true` if `lens` evaluates to a value `v` such that `v == n`. +/// +/// The condition is most commonly used as a trigger for logging. +/// +/// # Common lenses +/// +/// The most common lens used with this condition is [`ValueOf`], +/// for which the [`crate::conditions::EqualToN::iterations`] method is provided. +/// +/// [`ValueOf`]: ValueOf +/// [`EqualToN::iterations`]: crate::conditions::EqualToN>::iterations +/// +/// # Examples +/// +/// Logging the best objective value at exactly 10 iterations: +/// +/// ``` +/// # use mahf::{ExecResult, SingleObjectiveProblem, State}; +/// use mahf::{conditions::EqualToN, lens::common::BestObjectiveValueLens}; +/// +/// # fn example(state: &mut State

) -> ExecResult<()> { +/// state.configure_log(|config| { +/// config.with(EqualToN::iterations(10), BestObjectiveValueLens::entry()); +/// Ok(()) +/// })?; +/// # Ok(()) +/// # } +/// ``` +#[derive(Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Clone(bound = ""))] +pub struct EqualToN { + /// The value of N. + pub n: u32, + /// The lens to the value to compare with `n` + pub lens: L, +} + +impl crate::conditions::EqualToN { + /// Constructs a new `EqualToN` with the given `n` and `lens`. + pub fn from_params(n: u32, lens: L) -> Self { + Self { n, lens } + } + + /// Constructs a new `EqualToN` with the given `n` and `lens`. + pub fn new

(n: u32, lens: L) -> Box> + where + P: Problem, + L: Lens, + { + Box::new(Self::from_params(n, lens)) + } +} + +impl crate::conditions::EqualToN> { + /// Creates a new `EqualToN` that evaluates to `true` at exactly `n` [`Iterations`]. + pub fn iterations

(n: u32) -> Box> + where + P: Problem, + { + Box::new(Self::from_params(n, ValueOf::::new())) + } +} + +impl Condition

for crate::conditions::EqualToN + where + P: Problem, + L: Lens, +{ + fn evaluate(&self, problem: &P, state: &mut State

) -> ExecResult { + let value = self.lens.get(problem, state)?; + Ok(value == self.n) + } +} + /// Holds the previous value for comparison. #[derive(Deref, DerefMut, Tid)] struct Previous(Option); diff --git a/src/conditions/mod.rs b/src/conditions/mod.rs index dca6d2f..8cba308 100644 --- a/src/conditions/mod.rs +++ b/src/conditions/mod.rs @@ -14,7 +14,7 @@ pub mod common; pub mod cro; pub mod logical; -pub use common::{ChangeOf, EveryN, LessThanN, OptimumReached, RandomChance}; +pub use common::{ChangeOf, EveryN, LessThanN, EqualToN, OptimumReached, RandomChance}; pub use logical::{And, Not, Or}; /// Trait to represent a condition *component* for loops or branches. From d4f45e982301f572bbb5fa92d3aab800d87d3346 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Fri, 24 May 2024 15:22:19 +0200 Subject: [PATCH 23/27] Cleanup and format --- src/components/archive.rs | 31 +++-- src/components/measures/convergence.rs | 104 ++++++++------ src/components/measures/improvement.rs | 131 ++++++++++-------- src/components/measures/stepsize.rs | 183 ++++++++++++++++++------- src/conditions/common.rs | 17 ++- src/conditions/mod.rs | 2 +- src/lens/common.rs | 50 ++++--- src/utils.rs | 2 +- 8 files changed, 323 insertions(+), 197 deletions(-) diff --git a/src/components/archive.rs b/src/components/archive.rs index 0dcf92f..198efa7 100644 --- a/src/components/archive.rs +++ b/src/components/archive.rs @@ -1,10 +1,12 @@ //! Archive for specified parts of population. -use std::cell::Ref; +use crate::{ + component::ExecResult, components::Component, problems::SingleObjectiveProblem, + state::StateReq, CustomState, Individual, Problem, State, +}; use better_any::{Tid, TidAble}; use serde::{Deserialize, Serialize}; - -use crate::{component::ExecResult, components::Component, problems::SingleObjectiveProblem, state::StateReq, CustomState, Individual, State, Problem}; +use std::cell::Ref; /// An archive for storing elitist individuals. #[derive(Default, Tid)] @@ -131,7 +133,7 @@ impl IntermediateArchive

{ /// Updates the archive using the `population`, keeping all individuals at the current step of the algorithm. fn update(&mut self, population: &[Individual

]) { - self.0 = Vec::from(population.clone()); + self.0 = Vec::from(population); } /// Returns a reference to the archived population. @@ -155,16 +157,16 @@ impl IntermediateArchiveUpdate { } pub fn new

() -> Box> - where - P: Problem, + where + P: Problem, { Box::new(Self::from_params()) } } impl

Component

for IntermediateArchiveUpdate - where - P: Problem, +where + P: Problem, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(IntermediateArchive::

::new()); @@ -179,7 +181,6 @@ impl

Component

for IntermediateArchiveUpdate } } - /// An archive for storing all best individual yet, e.g. for subsequent calculation of measures. #[derive(Default, Tid)] pub struct BestIndividualsArchive(Vec>); @@ -208,7 +209,7 @@ impl BestIndividualsArchive

{ } } -/// Updates the [`crate::components::archive::BestIndividualsArchive`] with the current best individual. +/// Updates the [`BestIndividualsArchive`] with the current best individual. #[derive(Clone, Serialize, Deserialize)] pub struct BestIndividualsArchiveUpdate; @@ -218,16 +219,16 @@ impl BestIndividualsArchiveUpdate { } pub fn new

() -> Box> - where - P: Problem + SingleObjectiveProblem, + where + P: Problem + SingleObjectiveProblem, { Box::new(Self::from_params()) } } impl

Component

for BestIndividualsArchiveUpdate - where - P: Problem + SingleObjectiveProblem, +where + P: Problem + SingleObjectiveProblem, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(BestIndividualsArchive::

::new()); @@ -240,4 +241,4 @@ impl

Component

for BestIndividualsArchiveUpdate .update(state.best_individual()); Ok(()) } -} \ No newline at end of file +} diff --git a/src/components/measures/convergence.rs b/src/components/measures/convergence.rs index b066d4c..e8a9e97 100644 --- a/src/components/measures/convergence.rs +++ b/src/components/measures/convergence.rs @@ -7,19 +7,17 @@ //! Artif Intell Rev 54, 2323–2409 (2021). //! DOI: - -use better_any::{Tid, TidAble}; -use std::{any::type_name, marker::PhantomData}; -use derivative::Derivative; -use serde::Serialize; - use crate::component::AnyComponent; -use crate::{Component, CustomState, ExecResult, Problem, SingleObjectiveProblem, State}; use crate::components::archive; use crate::lens::{AnyLens, Lens, LensMap}; use crate::logging::extractor::{EntryExtractor, EntryName}; use crate::problems::{KnownOptimumProblem, VectorProblem}; use crate::utils::SerializablePhantom; +use crate::{Component, CustomState, ExecResult, Problem, SingleObjectiveProblem, State}; +use better_any::{Tid, TidAble}; +use derivative::Derivative; +use serde::Serialize; +use std::{any::type_name, marker::PhantomData}; /// Trait for representing a component that measures the convergence rate. pub trait ConvergenceRateMeasure: AnyComponent { @@ -27,7 +25,11 @@ pub trait ConvergenceRateMeasure: AnyComponent { fn measure(&self, problem: &P, previous: f64, current: f64) -> f64; } -pub fn convergence_rate_measure(component: &T, problem: &P, state: &mut State

) -> ExecResult<()> +pub fn convergence_rate_measure( + component: &T, + problem: &P, + state: &mut State

, +) -> ExecResult<()> where P: Problem + SingleObjectiveProblem, T: ConvergenceRateMeasure

+ 'static, @@ -38,12 +40,11 @@ where let len = best_individuals.len(); if len > 1 { - let current_best = best_individuals[len-1].clone().objective().value(); - let previous_best = best_individuals[len-2].clone().objective().value(); + let current_best = best_individuals[len - 1].clone().objective().value(); + let previous_best = best_individuals[len - 2].clone().objective().value(); convergence_rate.update(component.measure(problem, previous_best, current_best)); } else { convergence_rate.update(0.0); - } Ok(()) @@ -53,7 +54,7 @@ where #[derive(Tid)] pub struct ConvergenceRate { pub convergence_rate: f64, - marker: PhantomData + marker: PhantomData, } impl ConvergenceRate { @@ -97,7 +98,9 @@ impl EntryName for ConvergenceRateLens { impl ConvergenceRateLens { /// Constructs the lens. - pub fn new() -> Self { Self(SerializablePhantom::default()) } + pub fn new() -> Self { + Self(SerializablePhantom::default()) + } /// Constructs the lens for logging. pub fn entry

() -> Box> @@ -125,11 +128,16 @@ impl LensMap for ConvergenceRateLens { pub struct KnownOptimumIterationWiseConvergence; impl KnownOptimumIterationWiseConvergence { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new

() -> Box> where - P: VectorProblem + KnownOptimumProblem, { Box::new(Self::from_params()) } + P: VectorProblem + KnownOptimumProblem, + { + Box::new(Self::from_params()) + } } impl

ConvergenceRateMeasure

for KnownOptimumIterationWiseConvergence @@ -139,8 +147,7 @@ where fn measure(&self, problem: &P, previous: f64, current: f64) -> f64 { let optimum = problem.known_optimum().value(); - let convergence_rate = (optimum - current).abs() / (optimum - previous).abs(); - convergence_rate + (optimum - current).abs() / (optimum - previous).abs() } } @@ -165,28 +172,32 @@ where pub struct KnownOptimumConvergenceProgressiveRate; impl KnownOptimumConvergenceProgressiveRate { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new

() -> Box> - where - P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { Box::new(Self::from_params()) } + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, + { + Box::new(Self::from_params()) + } } impl

ConvergenceRateMeasure

for KnownOptimumConvergenceProgressiveRate - where - P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { fn measure(&self, problem: &P, _previous: f64, current: f64) -> f64 { let optimum = problem.known_optimum().value(); - let convergence_rate = (optimum - current).abs(); - convergence_rate + (optimum - current).abs() } } impl

Component

for KnownOptimumConvergenceProgressiveRate - where - P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(ConvergenceRate::::new()); @@ -205,26 +216,30 @@ impl

Component

for KnownOptimumConvergenceProgressiveRate pub struct UnknownOptimumConvergenceProgressiveRate; impl UnknownOptimumConvergenceProgressiveRate { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new

() -> Box> - where - P: VectorProblem + SingleObjectiveProblem, { Box::new(Self::from_params()) } + where + P: VectorProblem + SingleObjectiveProblem, + { + Box::new(Self::from_params()) + } } impl

ConvergenceRateMeasure

for UnknownOptimumConvergenceProgressiveRate - where - P: VectorProblem + SingleObjectiveProblem, +where + P: VectorProblem + SingleObjectiveProblem, { fn measure(&self, _problem: &P, previous: f64, current: f64) -> f64 { - let convergence_rate = (current - previous).abs(); - convergence_rate + (current - previous).abs() } } impl

Component

for UnknownOptimumConvergenceProgressiveRate - where - P: VectorProblem + SingleObjectiveProblem, +where + P: VectorProblem + SingleObjectiveProblem, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(ConvergenceRate::::new()); @@ -243,16 +258,21 @@ impl

Component

for UnknownOptimumConvergenceProgressiveRate pub struct KnownOptimumLogarithmicConvergenceRate; impl KnownOptimumLogarithmicConvergenceRate { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new

() -> Box> - where - P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { Box::new(Self::from_params()) } + where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, + { + Box::new(Self::from_params()) + } } impl

ConvergenceRateMeasure

for KnownOptimumLogarithmicConvergenceRate - where - P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { fn measure(&self, problem: &P, _previous: f64, current: f64) -> f64 { let optimum = problem.known_optimum().value(); @@ -263,8 +283,8 @@ impl

ConvergenceRateMeasure

for KnownOptimumLogarithmicConvergenceRate } impl

Component

for KnownOptimumLogarithmicConvergenceRate - where - P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, +where + P: VectorProblem + SingleObjectiveProblem + KnownOptimumProblem, { fn init(&self, _problem: &P, state: &mut State

) -> ExecResult<()> { state.insert(ConvergenceRate::::new()); diff --git a/src/components/measures/improvement.rs b/src/components/measures/improvement.rs index baca7b8..b137398 100644 --- a/src/components/measures/improvement.rs +++ b/src/components/measures/improvement.rs @@ -2,34 +2,39 @@ //! //! # References //! -//! \[1\] +//! \[1\] A. Scheibenpflug, S. Wagner, E. Pitzer, B. Burlacu, M. Affenzeller. 2012 +//! On the analysis, classification and prediction of metaheuristic algorithm behavior for combinatorial optimization problems. +//! 24th European Modeling and Simulation Symposium, EMSS 1, (2012), 368-372 - -use std::any::{type_name}; -use std::array::from_mut; -use std::marker::PhantomData; -use better_any::{Tid, TidAble}; -use derivative::Derivative; -use serde::Serialize; use crate::component::AnyComponent; -use crate::{Component, CustomState, ExecResult, Individual, Problem, SingleObjectiveProblem, State}; use crate::components::archive; use crate::lens::{AnyLens, Lens, LensMap}; use crate::logging::extractor::{EntryExtractor, EntryName}; use crate::problems::{LimitedVectorProblem, VectorProblem}; -use crate::state::common; -use crate::state::common::Evaluator; use crate::utils::SerializablePhantom; +use crate::{ + Component, CustomState, ExecResult, Individual, Problem, SingleObjectiveProblem, State, +}; +use better_any::{Tid, TidAble}; +use derivative::Derivative; +use serde::Serialize; +use std::any::type_name; +use std::marker::PhantomData; /// Trait for representing a component that measures the improvement of the solutions an operator caused. pub trait ImprovementMeasure: AnyComponent { /// Calculates the amount of improvement between two `solutions`. - fn measure(&self, problem: &P, previous: &[Individual

], current: &[Individual

]) -> (Vec, Vec); + fn measure( + &self, + problem: &P, + previous: &[Individual

], + current: &[Individual

], + ) -> (Vec, Vec); } /// A default implementation of [`Component::execute`] for types implementing [`ImprovementMeasure`]. /// -/// Note that, if called between or directly after operators, solutions will be evaluated here. +/// Note that, if called between or directly after operators, solutions have to be evaluated beforehand in the main loop. pub fn improvement_measure(component: &T, problem: &P, state: &mut State

) -> ExecResult<()> where P: Problem, @@ -37,34 +42,18 @@ where { let current_pop = state.populations_mut().pop(); - let mut cur = current_pop.clone(); - for i in 0..current_pop.len() { - //if !cur[i].is_evaluated() { - //TODO Does not work; need to evaluate in config - state.holding::>( - |evaluator: &mut Evaluator

, state| { - evaluator.as_inner_mut().evaluate( - problem, - state, - from_mut(&mut cur[i]), - ); - Ok(()) - }, - ).expect("TODO: panic message"); - *state.borrow_value_mut::() += 1; - //} - } + let cur = current_pop.clone(); + state.populations_mut().push(cur); let archive = state.borrow_mut::>(); - let previous_pop= archive.archived_population(); + let previous_pop = archive.archived_population(); let mut improvement = state.borrow_mut::>(); - if previous_pop.is_empty() { improvement.update((vec![0.0], vec![0.0])); } else { - improvement.update(component.measure(problem, &previous_pop, ¤t_pop)); + improvement.update(component.measure(problem, previous_pop, ¤t_pop)); } Ok(()) @@ -99,7 +88,9 @@ impl Improvement { } impl Default for Improvement { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } impl CustomState<'_> for Improvement {} @@ -115,25 +106,34 @@ impl AnyLens for TotalImprovementLens { } impl EntryName for TotalImprovementLens { - fn entry_name() -> &'static str { type_name::() } + fn entry_name() -> &'static str { + type_name::() + } } impl TotalImprovementLens { /// Construct the lens. - pub fn new() -> Self { Self(SerializablePhantom::default()) } + pub fn new() -> Self { + Self(SerializablePhantom::default()) + } /// Constructs the lens for logging. pub fn entry

() -> Box> - where - P: VectorProblem, - Self: Lens

, - ::Target: Serialize + Send + 'static, { Box::::default() } + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, + { + Box::::default() + } } impl LensMap for TotalImprovementLens { type Source = Improvement; - fn map(&self, source: &Self::Source) -> Self::Target { source.total_improvement.clone() } + fn map(&self, source: &Self::Source) -> Self::Target { + source.total_improvement.clone() + } } /// Lens for accessing the improvement percentages of [`Improvement`]. @@ -147,28 +147,37 @@ impl AnyLens for PercentageImprovementLens { } impl EntryName for PercentageImprovementLens { - fn entry_name() -> &'static str { "Improvement in percent" } + fn entry_name() -> &'static str { + "Improvement in percent" + } } impl PercentageImprovementLens { /// Construct the lens. - pub fn new() -> Self { Self(SerializablePhantom::default()) } + pub fn new() -> Self { + Self(SerializablePhantom::default()) + } /// Constructs the lens for logging. pub fn entry

() -> Box> - where - P: VectorProblem, - Self: Lens

, - ::Target: Serialize + Send + 'static, { Box::::default() } + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, + { + Box::::default() + } } impl LensMap for PercentageImprovementLens { type Source = Improvement; - fn map(&self, source: &Self::Source) -> Self::Target { source.percent_improvement.clone() } + fn map(&self, source: &Self::Source) -> Self::Target { + source.percent_improvement.clone() + } } -/// Measures the improvement by calculating the total and percentual difference between a solution +/// Measures the improvement by calculating the total and percental difference between a solution /// before and after the application of an operator. /// /// Note that the results are flawed if the operator shuffles the population. @@ -178,12 +187,14 @@ impl LensMap for PercentageImprovementLens { pub struct FitnessImprovement; impl FitnessImprovement { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new_with_id

() -> Box> - where - P: LimitedVectorProblem, - P: SingleObjectiveProblem, + where + P: LimitedVectorProblem, + P: SingleObjectiveProblem, { Box::new(Self::from_params()) } @@ -194,7 +205,9 @@ impl FitnessImprovement { where P: VectorProblem, P: SingleObjectiveProblem, - { Box::new(Self::from_params()) } + { + Box::new(Self::from_params()) + } } impl

ImprovementMeasure

for FitnessImprovement @@ -202,7 +215,12 @@ where P: VectorProblem, P: SingleObjectiveProblem, { - fn measure(&self, _problem: &P, previous: &[Individual

], current: &[Individual

]) -> (Vec, Vec) { + fn measure( + &self, + _problem: &P, + previous: &[Individual

], + current: &[Individual

], + ) -> (Vec, Vec) { let mut diffs = vec![]; let mut percents = vec![]; @@ -211,7 +229,6 @@ where let frac = (previous[u].objective().value() / current[u].objective().value()) * 100.0; diffs.push(diff); percents.push(frac); - } (percents, diffs) } @@ -230,4 +247,4 @@ where fn execute(&self, problem: &P, state: &mut State

) -> ExecResult<()> { improvement_measure(self, problem, state) } -} \ No newline at end of file +} diff --git a/src/components/measures/stepsize.rs b/src/components/measures/stepsize.rs index 6ad263b..911deb3 100644 --- a/src/components/measures/stepsize.rs +++ b/src/components/measures/stepsize.rs @@ -2,28 +2,34 @@ //! //! # References //! -//! \[1\] +//! \[1\] A. Scheibenpflug, S. Wagner, E. Pitzer, B. Burlacu, M. Affenzeller. 2012 +//! On the analysis, classification and prediction of metaheuristic algorithm behavior for combinatorial optimization problems. +//! 24th European Modeling and Simulation Symposium, EMSS 1, (2012), 368-372 - -use std::any::type_name; -use std::marker::PhantomData; -use better_any::{Tid, TidAble}; -use derivative::Derivative; -use serde::Serialize; -use statrs::statistics::Statistics; use crate::component::AnyComponent; -use crate::{Component, CustomState, ExecResult, Problem, State}; use crate::components::archive; use crate::lens::{AnyLens, Lens, LensMap}; use crate::logging::extractor::{EntryExtractor, EntryName}; use crate::population::AsSolutions; use crate::problems::VectorProblem; -use crate::utils::{SerializablePhantom, squared_euclidean}; +use crate::utils::{squared_euclidean, SerializablePhantom}; +use crate::{Component, CustomState, ExecResult, Problem, State}; +use better_any::{Tid, TidAble}; +use derivative::Derivative; +use serde::Serialize; +use statrs::statistics::Statistics; +use std::any::type_name; +use std::marker::PhantomData; /// Trait for representing a component that measures the step size of the change caused by an operator. pub trait StepSizeMeasure: AnyComponent { /// Calculates the step size between two `solutions`. - fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> (Vec, Vec); + fn measure( + &self, + problem: &P, + previous: &[&P::Encoding], + current: &[&P::Encoding], + ) -> (Vec, Vec); } /// A default implementation of [`Component::execute`] for types implementing [`StepSizeMeasure`]. @@ -35,13 +41,17 @@ where let populations = state.populations(); let current_pop = populations.current(); let archive = state.borrow_mut::>(); - let previous_pop= archive.archived_population(); + let previous_pop = archive.archived_population(); let mut step_size = state.borrow_mut::>(); if current_pop.is_empty() || previous_pop.is_empty() { step_size.update((vec![0.0], vec![0.0])); } else { - step_size.update(component.measure(problem, &previous_pop.as_solutions(), ¤t_pop.as_solutions())); + step_size.update(component.measure( + problem, + &previous_pop.as_solutions(), + ¤t_pop.as_solutions(), + )); } Ok(()) @@ -84,10 +94,12 @@ impl StepSize { } impl Default for StepSize { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } -impl CustomState<'_> for StepSize {} +impl CustomState<'_> for StepSize {} /// Lens for accessing the individual step sizes of [`StepSize`]. #[derive(Serialize, Derivative)] @@ -100,25 +112,34 @@ impl AnyLens for IndividualStepSizeLens { } impl EntryName for IndividualStepSizeLens { - fn entry_name() -> &'static str { type_name::() } + fn entry_name() -> &'static str { + type_name::() + } } impl IndividualStepSizeLens { /// Construct the lens. - pub fn new() -> Self { Self(SerializablePhantom::default()) } + pub fn new() -> Self { + Self(SerializablePhantom::default()) + } /// Constructs the lens for logging. pub fn entry

() -> Box> where P: VectorProblem, Self: Lens

, - ::Target: Serialize + Send + 'static, { Box::::default() } + ::Target: Serialize + Send + 'static, + { + Box::::default() + } } impl LensMap for IndividualStepSizeLens { type Source = StepSize; - fn map(&self, source: &Self::Source) -> Self::Target { source.all_steps.clone() } + fn map(&self, source: &Self::Source) -> Self::Target { + source.all_steps.clone() + } } /// Lens for accessing the individual variances of [`StepSize`]. @@ -132,25 +153,34 @@ impl AnyLens for IndividualVarianceLens { } impl EntryName for IndividualVarianceLens { - fn entry_name() -> &'static str { "Individual Variances" } + fn entry_name() -> &'static str { + "Individual Variances" + } } impl IndividualVarianceLens { /// Construct the lens. - pub fn new() -> Self { Self(SerializablePhantom::default()) } + pub fn new() -> Self { + Self(SerializablePhantom::default()) + } /// Constructs the lens for logging. pub fn entry

() -> Box> - where - P: VectorProblem, - Self: Lens

, - ::Target: Serialize + Send + 'static, { Box::::default() } + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, + { + Box::::default() + } } impl LensMap for IndividualVarianceLens { type Source = StepSize; - fn map(&self, source: &Self::Source) -> Self::Target { source.all_var.clone() } + fn map(&self, source: &Self::Source) -> Self::Target { + source.all_var.clone() + } } /// Lens for accessing the mean step size of [`StepSize`]. @@ -164,25 +194,34 @@ impl AnyLens for MeanStepSizeLens { } impl EntryName for MeanStepSizeLens { - fn entry_name() -> &'static str { "Mean Step Size" } + fn entry_name() -> &'static str { + "Mean Step Size" + } } impl MeanStepSizeLens { /// Construct the lens. - pub fn new() -> Self { Self(SerializablePhantom::default()) } + pub fn new() -> Self { + Self(SerializablePhantom::default()) + } /// Constructs the lens for logging. pub fn entry

() -> Box> - where - P: VectorProblem, - Self: Lens

, - ::Target: Serialize + Send + 'static, { Box::::default() } + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, + { + Box::::default() + } } impl LensMap for MeanStepSizeLens { type Source = StepSize; - fn map(&self, source: &Self::Source) -> Self::Target { source.step_size } + fn map(&self, source: &Self::Source) -> Self::Target { + source.step_size + } } /// Lens for accessing the variance of [`StepSize`]. @@ -196,25 +235,34 @@ impl AnyLens for StepSizeVarianceLens { } impl EntryName for StepSizeVarianceLens { - fn entry_name() -> &'static str { "Step Size Variance" } + fn entry_name() -> &'static str { + "Step Size Variance" + } } impl StepSizeVarianceLens { /// Construct the lens. - pub fn new() -> Self { Self(SerializablePhantom::default()) } + pub fn new() -> Self { + Self(SerializablePhantom::default()) + } /// Constructs the lens for logging. pub fn entry

() -> Box> - where - P: VectorProblem, - Self: Lens

, - ::Target: Serialize + Send + 'static, { Box::::default() } + where + P: VectorProblem, + Self: Lens

, + ::Target: Serialize + Send + 'static, + { + Box::::default() + } } impl LensMap for StepSizeVarianceLens { type Source = StepSize; - fn map(&self, source: &Self::Source) -> Self::Target { source.variance } + fn map(&self, source: &Self::Source) -> Self::Target { + source.variance + } } /// Measures the step size in terms of the Euclidean distance between two solutions. @@ -224,19 +272,28 @@ impl LensMap for StepSizeVarianceLens { pub struct EuclideanStepSize; impl EuclideanStepSize { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new

() -> Box> where - P: VectorProblem, { Box::new(Self::from_params()) } + P: VectorProblem, + { + Box::new(Self::from_params()) + } } impl

StepSizeMeasure

for EuclideanStepSize where P: VectorProblem, { - fn measure(&self, _problem: &P, previous: &[&Vec], current: &[&Vec]) -> (Vec, Vec) { - + fn measure( + &self, + _problem: &P, + previous: &[&Vec], + current: &[&Vec], + ) -> (Vec, Vec) { let steps: Vec = previous .iter() .zip(current.iter()) @@ -268,23 +325,32 @@ where pub struct PositionalStepSize; impl PositionalStepSize { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new

() -> Box> where - P: VectorProblem, { Box::new(Self::from_params()) } + P: VectorProblem, + { + Box::new(Self::from_params()) + } } impl

StepSizeMeasure

for PositionalStepSize where P: VectorProblem, { - fn measure(&self, _problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> (Vec, Vec) { + fn measure( + &self, + _problem: &P, + previous: &[&P::Encoding], + current: &[&P::Encoding], + ) -> (Vec, Vec) { let diffs: Vec> = previous .iter() .zip(current.iter()) - .map(|(p, q)| - p.iter().zip(q.iter()).map(|(v, w)| (v - w).abs()).collect()) + .map(|(p, q)| p.iter().zip(q.iter()).map(|(v, w)| (v - w).abs()).collect()) .collect(); let mut steps: Vec = vec![]; @@ -318,25 +384,36 @@ where pub struct DimensionalStepSize; impl DimensionalStepSize { - pub fn from_params() -> Self { Self } + pub fn from_params() -> Self { + Self + } pub fn new

() -> Box> where - P: VectorProblem, { Box::new(Self::from_params()) } + P: VectorProblem, + { + Box::new(Self::from_params()) + } } impl

StepSizeMeasure

for DimensionalStepSize where P: VectorProblem, { - fn measure(&self, problem: &P, previous: &[&P::Encoding], current: &[&P::Encoding]) -> (Vec ,Vec) { + fn measure( + &self, + problem: &P, + previous: &[&P::Encoding], + current: &[&P::Encoding], + ) -> (Vec, Vec) { let mut diffs: Vec> = vec![]; let dims = problem.dimension(); for d in 0..dims { let summed: Vec = previous .iter() .zip(current.iter()) - .map(|(p, q)| (p[d] - q[d]).abs()).collect(); + .map(|(p, q)| (p[d] - q[d]).abs()) + .collect(); diffs.push(summed); } diff --git a/src/conditions/common.rs b/src/conditions/common.rs index 285f798..1363254 100644 --- a/src/conditions/common.rs +++ b/src/conditions/common.rs @@ -259,7 +259,6 @@ where } } - /// Evaluates to `true` if `lens` evaluates to a value `v` such that `v == n`. /// /// The condition is most commonly used as a trigger for logging. @@ -306,9 +305,9 @@ impl crate::conditions::EqualToN { /// Constructs a new `EqualToN` with the given `n` and `lens`. pub fn new

(n: u32, lens: L) -> Box> - where - P: Problem, - L: Lens, + where + P: Problem, + L: Lens, { Box::new(Self::from_params(n, lens)) } @@ -317,17 +316,17 @@ impl crate::conditions::EqualToN { impl crate::conditions::EqualToN> { /// Creates a new `EqualToN` that evaluates to `true` at exactly `n` [`Iterations`]. pub fn iterations

(n: u32) -> Box> - where - P: Problem, + where + P: Problem, { Box::new(Self::from_params(n, ValueOf::::new())) } } impl Condition

for crate::conditions::EqualToN - where - P: Problem, - L: Lens, +where + P: Problem, + L: Lens, { fn evaluate(&self, problem: &P, state: &mut State

) -> ExecResult { let value = self.lens.get(problem, state)?; diff --git a/src/conditions/mod.rs b/src/conditions/mod.rs index 8cba308..3f6b9e5 100644 --- a/src/conditions/mod.rs +++ b/src/conditions/mod.rs @@ -14,7 +14,7 @@ pub mod common; pub mod cro; pub mod logical; -pub use common::{ChangeOf, EveryN, LessThanN, EqualToN, OptimumReached, RandomChance}; +pub use common::{ChangeOf, EqualToN, EveryN, LessThanN, OptimumReached, RandomChance}; pub use logical::{And, Not, Or}; /// Trait to represent a condition *component* for loops or branches. diff --git a/src/lens/common.rs b/src/lens/common.rs index 310b1cd..0ebc16c 100644 --- a/src/lens/common.rs +++ b/src/lens/common.rs @@ -359,7 +359,9 @@ where Self: Lens, { /// Constructs the lens. - pub fn new() -> Self { Self(PhantomData) } + pub fn new() -> Self { + Self(PhantomData) + } } impl AnyLens for PopulationLens

{ @@ -367,7 +369,9 @@ impl AnyLens for PopulationLens

{ } impl

EntryName for PopulationLens

{ - fn entry_name() -> &'static str { "Population" } + fn entry_name() -> &'static str { + "Population" + } } impl

PopulationLens

@@ -378,15 +382,17 @@ where ::Target: Serialize + Send, { /// Constructs the lens for logging entries. - pub fn entry() -> Box> { Box::::default() } + pub fn entry() -> Box> { + Box::::default() + } } impl LensMap for PopulationLens

{ type Source = Populations

; fn map(&self, source: &Self::Source) -> Self::Target { - let pop = source.current().clone(); - pop.into_iter().map(|i| i.solution().to_owned()).collect() + let pop = source.current(); + pop.iter().map(|i| i.solution().to_owned()).collect() } } @@ -397,12 +403,14 @@ impl LensMap for PopulationLens

{ pub struct ObjectiveValuesLens

(#[serde(skip)] PhantomData P>); impl

ObjectiveValuesLens

- where - P: Problem, - Self: Lens, +where + P: Problem, + Self: Lens, { /// Constructs the lens. - pub fn new() -> Self { Self(PhantomData) } + pub fn new() -> Self { + Self(PhantomData) + } } impl AnyLens for ObjectiveValuesLens

{ @@ -410,25 +418,29 @@ impl AnyLens for ObjectiveValuesLens

{ } impl

EntryName for ObjectiveValuesLens

{ - fn entry_name() -> &'static str { "Population Objective Values" } + fn entry_name() -> &'static str { + "Population Objective Values" + } } impl

ObjectiveValuesLens

- where - P: SingleObjectiveProblem, - P::Encoding: Clone, - Self: Lens

, - ::Target: Serialize + Send, +where + P: SingleObjectiveProblem, + P::Encoding: Clone, + Self: Lens

, + ::Target: Serialize + Send, { /// Constructs the lens for logging entries. - pub fn entry() -> Box> { Box::::default() } + pub fn entry() -> Box> { + Box::::default() + } } impl LensMap for ObjectiveValuesLens

{ type Source = Populations

; fn map(&self, source: &Self::Source) -> Self::Target { - let pop = source.current().clone(); - pop.into_iter().map(|i| i.objective().to_owned()).collect() + let pop = source.current(); + pop.iter().map(|i| i.objective().to_owned()).collect() } -} \ No newline at end of file +} diff --git a/src/utils.rs b/src/utils.rs index 67a50be..ff50726 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -46,6 +46,6 @@ impl serde::Serialize for SerializablePhantom { } /// Calculates squared Euclidean distance between two vectors. -pub fn squared_euclidean(a: &[f64], b:&Vec) -> f64 { +pub fn squared_euclidean(a: &[f64], b: &Vec) -> f64 { a.iter().zip(b).map(|(p, q)| (q - p).powi(2)).sum::() } From 4a042da3fa66f532eb4314141914c743be2ef186 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Tue, 28 May 2024 14:06:43 +0200 Subject: [PATCH 24/27] Update Rust version for CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b227d..6db2a3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: - master env: - RUST_VERSION: 1.70.0 + RUST_VERSION: 1.78.0 CARGO_TERM_COLOR: always jobs: From d000da91f4c08ee3af512502eb84470cb448677b Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Tue, 28 May 2024 14:21:53 +0200 Subject: [PATCH 25/27] Fix fmt and clippy --- src/components/archive.rs | 8 +++++--- src/components/measures/convergence.rs | 20 +++++++++++-------- src/components/measures/improvement.rs | 27 +++++++++++++------------- src/components/measures/stepsize.rs | 27 ++++++++++++++------------ 4 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/components/archive.rs b/src/components/archive.rs index 198efa7..0f91e97 100644 --- a/src/components/archive.rs +++ b/src/components/archive.rs @@ -1,12 +1,14 @@ //! Archive for specified parts of population. +use better_any::{Tid, TidAble}; + +use serde::{Deserialize, Serialize}; +use std::cell::Ref; + use crate::{ component::ExecResult, components::Component, problems::SingleObjectiveProblem, state::StateReq, CustomState, Individual, Problem, State, }; -use better_any::{Tid, TidAble}; -use serde::{Deserialize, Serialize}; -use std::cell::Ref; /// An archive for storing elitist individuals. #[derive(Default, Tid)] diff --git a/src/components/measures/convergence.rs b/src/components/measures/convergence.rs index e8a9e97..df693dc 100644 --- a/src/components/measures/convergence.rs +++ b/src/components/measures/convergence.rs @@ -7,17 +7,21 @@ //! Artif Intell Rev 54, 2323–2409 (2021). //! DOI: -use crate::component::AnyComponent; -use crate::components::archive; -use crate::lens::{AnyLens, Lens, LensMap}; -use crate::logging::extractor::{EntryExtractor, EntryName}; -use crate::problems::{KnownOptimumProblem, VectorProblem}; -use crate::utils::SerializablePhantom; -use crate::{Component, CustomState, ExecResult, Problem, SingleObjectiveProblem, State}; +use std::{any::type_name, marker::PhantomData}; + use better_any::{Tid, TidAble}; use derivative::Derivative; use serde::Serialize; -use std::{any::type_name, marker::PhantomData}; + +use crate::{ + component::AnyComponent, + components::archive, + lens::{AnyLens, Lens, LensMap}, + logging::extractor::{EntryExtractor, EntryName}, + problems::{KnownOptimumProblem, VectorProblem}, + utils::SerializablePhantom, + Component, CustomState, ExecResult, Problem, SingleObjectiveProblem, State, +}; /// Trait for representing a component that measures the convergence rate. pub trait ConvergenceRateMeasure: AnyComponent { diff --git a/src/components/measures/improvement.rs b/src/components/measures/improvement.rs index b137398..bc6fb08 100644 --- a/src/components/measures/improvement.rs +++ b/src/components/measures/improvement.rs @@ -6,20 +6,21 @@ //! On the analysis, classification and prediction of metaheuristic algorithm behavior for combinatorial optimization problems. //! 24th European Modeling and Simulation Symposium, EMSS 1, (2012), 368-372 -use crate::component::AnyComponent; -use crate::components::archive; -use crate::lens::{AnyLens, Lens, LensMap}; -use crate::logging::extractor::{EntryExtractor, EntryName}; -use crate::problems::{LimitedVectorProblem, VectorProblem}; -use crate::utils::SerializablePhantom; -use crate::{ - Component, CustomState, ExecResult, Individual, Problem, SingleObjectiveProblem, State, -}; +use std::{any::type_name, marker::PhantomData}; + use better_any::{Tid, TidAble}; use derivative::Derivative; use serde::Serialize; -use std::any::type_name; -use std::marker::PhantomData; + +use crate::{ + component::AnyComponent, + components::archive, + lens::{AnyLens, Lens, LensMap}, + logging::extractor::{EntryExtractor, EntryName}, + problems::{LimitedVectorProblem, VectorProblem}, + utils::SerializablePhantom, + Component, CustomState, ExecResult, Individual, Problem, SingleObjectiveProblem, State, +}; /// Trait for representing a component that measures the improvement of the solutions an operator caused. pub trait ImprovementMeasure: AnyComponent { @@ -82,8 +83,8 @@ impl Improvement { /// Updates the improvement using the total and the percentage vectors. pub fn update(&mut self, improvement: (Vec, Vec)) { let (a, b) = improvement; - self.percent_improvement = a.clone(); - self.total_improvement = b.clone(); + self.percent_improvement.clone_from(&a); + self.total_improvement.clone_from(&b); } } diff --git a/src/components/measures/stepsize.rs b/src/components/measures/stepsize.rs index 911deb3..5b2342c 100644 --- a/src/components/measures/stepsize.rs +++ b/src/components/measures/stepsize.rs @@ -6,20 +6,23 @@ //! On the analysis, classification and prediction of metaheuristic algorithm behavior for combinatorial optimization problems. //! 24th European Modeling and Simulation Symposium, EMSS 1, (2012), 368-372 -use crate::component::AnyComponent; -use crate::components::archive; -use crate::lens::{AnyLens, Lens, LensMap}; -use crate::logging::extractor::{EntryExtractor, EntryName}; -use crate::population::AsSolutions; -use crate::problems::VectorProblem; -use crate::utils::{squared_euclidean, SerializablePhantom}; -use crate::{Component, CustomState, ExecResult, Problem, State}; +use std::{any::type_name, marker::PhantomData}; + use better_any::{Tid, TidAble}; use derivative::Derivative; use serde::Serialize; use statrs::statistics::Statistics; -use std::any::type_name; -use std::marker::PhantomData; + +use crate::{ + component::AnyComponent, + components::archive, + lens::{AnyLens, Lens, LensMap}, + logging::extractor::{EntryExtractor, EntryName}, + population::AsSolutions, + problems::VectorProblem, + utils::{squared_euclidean, SerializablePhantom}, + Component, CustomState, ExecResult, Problem, State, +}; /// Trait for representing a component that measures the step size of the change caused by an operator. pub trait StepSizeMeasure: AnyComponent { @@ -86,8 +89,8 @@ impl StepSize { /// Updates the step size using the step size vector. pub fn update(&mut self, all_steps: (Vec, Vec)) { let (a, b) = all_steps; - self.all_steps = a.clone(); - self.all_var = b.clone(); + self.all_steps.clone_from(&a); + self.all_var.clone_from(&b); self.variance = a.clone().variance(); self.step_size = a.mean(); } From dbda6b542a922a902c0deef97091c2f1d06e651b Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Tue, 28 May 2024 14:23:48 +0200 Subject: [PATCH 26/27] Fix fmt again --- src/components/archive.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/archive.rs b/src/components/archive.rs index 0f91e97..0fb61da 100644 --- a/src/components/archive.rs +++ b/src/components/archive.rs @@ -1,9 +1,9 @@ //! Archive for specified parts of population. -use better_any::{Tid, TidAble}; +use std::cell::Ref; +use better_any::{Tid, TidAble}; use serde::{Deserialize, Serialize}; -use std::cell::Ref; use crate::{ component::ExecResult, components::Component, problems::SingleObjectiveProblem, From 27061daaf1427dd63220f8ac744203e3da28a1eb Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Tue, 28 May 2024 16:03:08 +0200 Subject: [PATCH 27/27] Allow non_canonical_clone_impl for clippy in utils --- src/utils.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index ff50726..55a77d7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,5 @@ //! A collection of utilities. +#![allow(clippy::non_canonical_clone_impl)] use std::marker::PhantomData; @@ -32,6 +33,7 @@ pub fn all_eq(arr: &[T]) -> bool { /// Wrapper around [`PhantomData`] that serializes the type name of `T`. /// /// It additionally implements `Send` + `Sync` even if `T` doesn't. + #[derive(Derivative)] #[derivative(Default(bound = ""), Copy(bound = ""), Clone(bound = ""))] pub struct SerializablePhantom(PhantomData T>);