From 3767a2b0e81f46e78510280569d5bbed3a167abc Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 31 Aug 2023 15:14:15 +0200 Subject: [PATCH 01/13] 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 6e51f1ef0af42fcddeb0d365b22c713d47278dbd Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 14 Sep 2023 15:08:42 +0200 Subject: [PATCH 02/13] Add black hole algorithm draft and split swarm module --- 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 | 10 ++ 11 files changed, 341 insertions(+), 163 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 5a6d91d..1c4f12d 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`]. /// @@ -44,3 +46,11 @@ 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 +where + P: VectorProblem, +{ + a.iter().zip(b).map(|p, q| (p - q).powf(2.0)).sum::() +} \ No newline at end of file From fc74112930d802e64f138a77340ef27078dfc8d3 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Tue, 19 Sep 2023 14:54:46 +0200 Subject: [PATCH 03/13] Add suggestions and make mahf compile again --- src/components/replacement/bh.rs | 15 ++++--- src/components/swarm/fa.rs | 71 +++++++++++++++++--------------- src/heuristics/bh.rs | 2 +- src/utils.rs | 6 +-- 4 files changed, 50 insertions(+), 44 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 { diff --git a/src/utils.rs b/src/utils.rs index 1c4f12d..ebed378 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -48,9 +48,7 @@ impl serde::Serialize for SerializablePhantom { } /// Calculates squared Euclidean distance between two vectors. -pub fn squared_euclidean

(a: Vec

, b: Vec

) -> f64 -where - P: VectorProblem, +pub fn squared_euclidean (a: &Vec, b: &Vec) -> f64 { - a.iter().zip(b).map(|p, q| (p - q).powf(2.0)).sum::() + a.iter().zip(b).map(|(p, q)| (p - q).powf(2.0)).sum::() } \ No newline at end of file From 47c8e6461de0981ea8b223ac670fdd8af339a20b Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Wed, 27 Sep 2023 15:54:40 +0200 Subject: [PATCH 04/13] 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 06fb885368ab43f1b4bbefa295f972c75c65d08f Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 28 Sep 2023 15:06:40 +0200 Subject: [PATCH 05/13] 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 5a2cd02a4f0a639bd05cff0f47d21d551e82f226 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Fri, 29 Sep 2023 16:43:17 +0200 Subject: [PATCH 06/13] 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 ebed378..a1d1956 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 5a03c13510392f6f62f74e8b757a26a69acacaea Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 12 Oct 2023 16:09:49 +0200 Subject: [PATCH 07/13] 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 ++- src/utils.rs | 5 ++--- 10 files changed, 84 insertions(+), 70 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(), }, diff --git a/src/utils.rs b/src/utils.rs index a1d1956..e322537 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -46,7 +46,6 @@ impl serde::Serialize for SerializablePhantom { } /// Calculates squared Euclidean distance between two vectors. -pub fn squared_euclidean (a: &Vec, b: &Vec) -> f64 -{ +pub fn squared_euclidean (a: &[f64], b: &Vec) -> f64 { a.iter().zip(b).map(|(p, q)| (p - q).powf(2.0)).sum::() -} \ No newline at end of file +} From b363d88082f100454076d0cd9f7275c53b04e61f Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 12 Oct 2023 16:45:37 +0200 Subject: [PATCH 08/13] 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 +++++++++++++------------- src/utils.rs | 2 +- 6 files changed, 77 insertions(+), 74 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(); diff --git a/src/utils.rs b/src/utils.rs index e322537..ebf09a7 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)| (p - q).powf(2.0)).sum::() } From 6af5e29c822cd7bb12723e1cb75eea91ed728b66 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Thu, 12 Oct 2023 16:55:23 +0200 Subject: [PATCH 09/13] 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 ca81a37f416158f9cb1d388cf63f8bfc5dcd8355 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Fri, 13 Oct 2023 09:16:05 +0200 Subject: [PATCH 10/13] 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 d4a1467689b3e87041c6b9fa57191c5a2aaf1506 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Mon, 4 Dec 2023 11:02:46 +0100 Subject: [PATCH 11/13] Improve according to review comments --- src/components/replacement/bh.rs | 32 +++++++++++++++----------------- src/components/swarm/bh.rs | 12 ++++++------ src/utils.rs | 2 +- 3 files changed, 22 insertions(+), 24 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; } diff --git a/src/utils.rs b/src/utils.rs index ebf09a7..d5a2c7f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -47,5 +47,5 @@ impl serde::Serialize for SerializablePhantom { /// Calculates squared Euclidean distance between two vectors. pub fn squared_euclidean(a: &[f64], b: &Vec) -> f64 { - a.iter().zip(b).map(|(p, q)| (p - q).powf(2.0)).sum::() + a.iter().zip(b).map(|(p, q)| (p - q).powi(2)).sum::() } From 161021af94c546716e1a184ed11457164e702901 Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Mon, 4 Dec 2023 11:07:44 +0100 Subject: [PATCH 12/13] 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 9af3603d999cabb6514580d1e5b479f1b977f76a Mon Sep 17 00:00:00 2001 From: Helena Stegherr Date: Mon, 4 Dec 2023 11:12:22 +0100 Subject: [PATCH 13/13] 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(()) } }