diff --git a/examples/bmf.rs b/examples/bmf.rs index e8f5f8fc..0e43a10d 100644 --- a/examples/bmf.rs +++ b/examples/bmf.rs @@ -7,9 +7,9 @@ fn main() -> anyhow::Result<()> { let config = pso::real_pso( pso::RealProblemParameters { num_particles: 100, - a: 1.0, - b: 1.0, - c: 1.0, + weight: 1.0, + c_one: 1.0, + c_two: 1.0, v_max: 1.0, }, termination::FixedIterations::new(500), diff --git a/param-study/.gitignore b/param-study/.gitignore deleted file mode 100644 index d68ab2fc..00000000 --- a/param-study/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -output/ -paramils/ \ No newline at end of file diff --git a/param-study/Cargo.toml b/param-study/Cargo.toml deleted file mode 100644 index 3637ef61..00000000 --- a/param-study/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -edition = "2021" -name = "param-study" -version = "0.1.0" - -[dependencies] -mahf = {path = ".."} diff --git a/param-study/paramils.sh b/param-study/paramils.sh deleted file mode 100755 index ee591be4..00000000 --- a/param-study/paramils.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -ruby paramils/param_ils_2_3_run.rb -pruning 0 -numRun 0 -validN 300 -scenariofile $@ \ No newline at end of file diff --git a/param-study/paramils/README.md b/param-study/paramils/README.md deleted file mode 100644 index fc1ea275..00000000 --- a/param-study/paramils/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ParamILS - -ParamILS can be donloaded from [www.cs.ubc.ca/labs/beta/Projects/ParamILS](http://www.cs.ubc.ca/labs/beta/Projects/ParamILS/). diff --git a/param-study/params/iwo.txt b/param-study/params/iwo.txt deleted file mode 100644 index b11dc382..00000000 --- a/param-study/params/iwo.txt +++ /dev/null @@ -1,7 +0,0 @@ -initial_population_size {5, 10, 20, 30, 40, 50}[10] -max_population_size {10, 20, 30, 40, 50}[20] -min_number_of_seeds {0, 1, 2, 3, 4}[0] -max_number_of_seeds {2, 3, 4, 5, 6}[4] -initial_deviation {1.0, 0.8, 0.5, 0.1, 0.08, 0.05, 0.01}[0.1] -final_deviation {0.08, 0.05, 0.01, 0.001, 0.0005, 0.0001, 0.00005, 0.00001}[0.01] -modulation_index {1, 2, 3, 4, 5, 6}[3] \ No newline at end of file diff --git a/param-study/params/pso.txt b/param-study/params/pso.txt deleted file mode 100644 index ee4ad75e..00000000 --- a/param-study/params/pso.txt +++ /dev/null @@ -1,5 +0,0 @@ -population_size {5, 10, 20, 30, 40, 50}[10] -a {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}[1.0] -b {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}[0.5] -c {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}[0.5] -v_max {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}[0.5] diff --git a/param-study/problems/bmf-test.txt b/param-study/problems/bmf-test.txt deleted file mode 100644 index 071c6375..00000000 --- a/param-study/problems/bmf-test.txt +++ /dev/null @@ -1,3 +0,0 @@ -"ackley<30>" -"sphere<30>" -"rastrigin<30>" \ No newline at end of file diff --git a/param-study/problems/bmf-train.txt b/param-study/problems/bmf-train.txt deleted file mode 100644 index 714bd08f..00000000 --- a/param-study/problems/bmf-train.txt +++ /dev/null @@ -1,6 +0,0 @@ -"sphere<10>" -"sphere<20>" -"ackley<10>" -"ackley<20>" -"rastrigin<10>" -"rastrigin<20>" \ No newline at end of file diff --git a/param-study/scenarios/iwo.txt b/param-study/scenarios/iwo.txt deleted file mode 100644 index e46cc840..00000000 --- a/param-study/scenarios/iwo.txt +++ /dev/null @@ -1,12 +0,0 @@ -algo = cargo run --release -- iwo -execdir = . -deterministic = 0 -run_obj = qual -overall_obj = mean -cutoff_time = 5 -cutoff_length = 700 -tunerTimeout = 240 -paramfile = params/iwo.txt -outdir = output/iwo -instance_file = problems/bmf-train.txt -test_instance_file = problems/bmf-test.txt diff --git a/param-study/scenarios/pso.txt b/param-study/scenarios/pso.txt deleted file mode 100644 index 5c7a45de..00000000 --- a/param-study/scenarios/pso.txt +++ /dev/null @@ -1,12 +0,0 @@ -algo = cargo run --release -- pso -execdir = . -deterministic = 0 -run_obj = qual -overall_obj = mean -cutoff_time = 5 -cutoff_length = 700 -tunerTimeout = 240 -paramfile = params/pso.txt -outdir = output/pso -instance_file = problems/bmf-train.txt -test_instance_file = problems/bmf-test.txt diff --git a/param-study/src/instances/iwo.rs b/param-study/src/instances/iwo.rs deleted file mode 100644 index 081d027c..00000000 --- a/param-study/src/instances/iwo.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::{ - declare_parameters, - util::{print_result, ArgsIter, Setup}, -}; -use mahf::{ - float_eq::float_eq, framework, framework::Random, heuristics::iwo, operators::termination, - problems::bmf::BenchmarkFunction, tracking, -}; -use std::time::Instant; - -declare_parameters! { - initial_population_size: u32, - max_population_size: u32, - min_number_of_seeds: u32, - max_number_of_seeds: u32, - initial_deviation: f64, - final_deviation: f64, - modulation_index: u32, -} - -pub fn run(setup: &Setup, args: &mut ArgsIter) { - let params = parameters(args); - - if !(params.initial_population_size <= params.max_population_size - && params.min_number_of_seeds <= params.max_number_of_seeds - && params.final_deviation <= params.initial_deviation) - { - // TODO: Is there a better way to indicate "illigal configuration"? - return; - } - - let problem = BenchmarkFunction::try_from(setup.instance.as_str()).unwrap(); - - let config = iwo::real_iwo( - iwo::RealProblemParameters { - initial_population_size: params.initial_population_size, - max_population_size: params.max_population_size, - min_number_of_seeds: params.min_number_of_seeds, - max_number_of_seeds: params.max_number_of_seeds, - initial_deviation: params.initial_deviation, - final_deviation: params.final_deviation, - modulation_index: params.modulation_index, - }, - termination::FixedIterations::new(setup.cutoff_length), - tracking::Logger::default(), - ); - - let rng = Random::seeded(setup.seed); - - let start = Instant::now(); - let state = framework::run(&problem, &config, Some(rng)); - let end = Instant::now(); - let runtime = end - start; - - let allowed_error = match problem.name() { - "rastrigin" => 5.0, - "ackley" => 0.3, - "sphere" => 0.01, - _ => 1.0, - }; - - print_result( - float_eq!( - problem.known_optimum(), - state - .best_objective_value::() - .unwrap() - .value(), - abs <= allowed_error - ), - runtime.as_secs_f64(), - state.iterations(), - state - .best_objective_value::() - .unwrap() - .value(), - setup.seed, - ); -} diff --git a/param-study/src/instances/mod.rs b/param-study/src/instances/mod.rs deleted file mode 100644 index f945cee0..00000000 --- a/param-study/src/instances/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod iwo; -pub mod pso; diff --git a/param-study/src/instances/pso.rs b/param-study/src/instances/pso.rs deleted file mode 100644 index 623f9652..00000000 --- a/param-study/src/instances/pso.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::time::Instant; - -use mahf::{ - float_eq::float_eq, - framework::{self, Random}, - heuristics::pso, - operators::termination, - problems::bmf::BenchmarkFunction, - tracking, -}; - -use crate::{ - declare_parameters, - util::{print_result, ArgsIter, Setup}, -}; - -declare_parameters! { - population_size: u32, - a: f64, - b: f64, - c: f64, - v_max: f64, -} - -pub fn run(setup: &Setup, args: &mut ArgsIter) { - let params = parameters(args); - - let problem = BenchmarkFunction::try_from(setup.instance.as_str()).unwrap(); - - let config = pso::real_pso( - pso::RealProblemParameters { - num_particles: params.population_size, - a: params.a, - b: params.b, - c: params.c, - v_max: params.v_max, - }, - termination::FixedIterations::new(setup.cutoff_length), - tracking::Logger::default(), - ); - - let rng = Random::seeded(setup.seed); - - let start = Instant::now(); - let state = framework::run(&problem, &config, Some(rng)); - let end = Instant::now(); - let runtime = end - start; - - let allowed_error = match problem.name() { - "rastrigin" => 5.0, - "ackley" => 0.3, - "sphere" => 0.01, - _ => 1.0, - }; - - print_result( - float_eq!( - problem.known_optimum(), - state - .best_objective_value::() - .unwrap() - .value(), - abs <= allowed_error - ), - runtime.as_secs_f64(), - state.iterations(), - state - .best_objective_value::() - .unwrap() - .value(), - setup.seed, - ); -} diff --git a/param-study/src/main.rs b/param-study/src/main.rs deleted file mode 100644 index f352da62..00000000 --- a/param-study/src/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod instances; -mod util; - -fn main() { - let (heuristic, ref setup, ref mut args) = util::get_parameters(); - - match heuristic.as_str() { - "iwo" => instances::iwo::run(setup, args), - "pso" => instances::pso::run(setup, args), - _ => panic!("Unknown heuristic {}", heuristic), - } -} diff --git a/param-study/src/util.rs b/param-study/src/util.rs deleted file mode 100644 index 1301eb53..00000000 --- a/param-study/src/util.rs +++ /dev/null @@ -1,67 +0,0 @@ -pub type ArgsIter = std::iter::Skip; - -#[derive(Debug, Default)] -pub struct Setup { - pub instance: String, - pub instance_information: String, - pub cutoff_time: f64, - pub cutoff_length: u32, - pub seed: u64, -} - -/// Returns the base params and an iterator for the remaining params. -pub fn get_parameters() -> (String, Setup, ArgsIter) { - let mut args = std::env::args().skip(1); - - let heuristic = args.next().unwrap(); - - let base = Setup { - instance: args.next().unwrap(), - instance_information: args.next().unwrap(), - cutoff_time: args.next().unwrap().parse().unwrap(), - cutoff_length: args.next().unwrap().parse().unwrap(), - seed: args.next().unwrap().parse().unwrap(), - }; - - (heuristic, base, args) -} - -#[macro_export] -macro_rules! declare_parameters { - { $($p_name:ident : $p_type:ty,)* } => { - #[derive(Debug, Default)] - struct Parameters { - $($p_name: $p_type),* - } - - fn parameters(args: &mut ArgsIter) -> Parameters { - let mut params = Parameters::default(); - - while let Some(param) = args.next() { - let value = args.next().unwrap(); - - match param.as_str() { - $(concat!("-", stringify!($p_name)) => params.$p_name = value.parse().unwrap(),)* - unknown => panic!("unknown param {}", unknown), - } - } - - params - } - }; -} - -/// Prints output for ParamILS. -/// -/// This can be called multiple times and the last call will define -/// the final result of this evaluation. -pub fn print_result(sat: bool, runtime: f64, runlength: u32, best: f64, seed: u64) { - println!( - "Result for ParamILS: {}, {}, {}, {}, {}", - if sat { "SAT" } else { "TIMEOUT" }, - runtime, - runlength, - best, - seed - ); -} diff --git a/src/framework/components.rs b/src/framework/components.rs index 4e24186a..f5224df7 100644 --- a/src/framework/components.rs +++ b/src/framework/components.rs @@ -1,11 +1,9 @@ //! The Component trait and structural components. use crate::{ - framework::{ - conditions::Condition, - state::{common, State}, - }, + framework::conditions::Condition, problems::Problem, + state::{common, State}, }; use serde::Serialize; use std::any::Any; diff --git a/src/framework/conditions.rs b/src/framework/conditions.rs index bcefcacf..5ff65452 100644 --- a/src/framework/conditions.rs +++ b/src/framework/conditions.rs @@ -1,8 +1,5 @@ /// The Condition trait and combinators. -use crate::{ - framework::{components::AnyComponent, state::State}, - problems::Problem, -}; +use crate::{framework::components::AnyComponent, problems::Problem, state::State}; use serde::Serialize; /// A condition for loops or branches. diff --git a/src/framework/configuration.rs b/src/framework/configuration.rs index 101b785b..8d42fb44 100644 --- a/src/framework/configuration.rs +++ b/src/framework/configuration.rs @@ -2,10 +2,10 @@ use crate::{ framework::{ components::{Block, Branch, Component, Loop, Scope}, conditions::Condition, - state, }, operators, problems::{MultiObjectiveProblem, Problem, SingleObjectiveProblem}, + state, }; /// A heuristic, constructed from a set of components. diff --git a/src/framework/mod.rs b/src/framework/mod.rs index caefb5f5..9c6d13e7 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -2,7 +2,6 @@ pub mod components; pub mod conditions; -pub mod state; mod configuration; pub use configuration::{Configuration, ConfigurationBuilder}; @@ -17,6 +16,7 @@ mod random; pub use random::{Random, RandomConfig}; use crate::problems::Problem; +use crate::state; use crate::tracking::Log; /// Runs the heuristic on the given problem. diff --git a/src/framework/random.rs b/src/framework/random.rs index 6dba8599..8f7eec5e 100644 --- a/src/framework/random.rs +++ b/src/framework/random.rs @@ -4,7 +4,7 @@ use rand::{RngCore, SeedableRng}; use serde::Serialize; use std::any::type_name; -use crate::framework::state::CustomState; +use crate::state::CustomState; /// A random number generator. /// diff --git a/src/heuristics/aco.rs b/src/heuristics/aco.rs index 82dd1c4b..0f804a47 100644 --- a/src/heuristics/aco.rs +++ b/src/heuristics/aco.rs @@ -136,13 +136,13 @@ pub fn aco( } mod ant_ops { - use rand::distributions::{Distribution, WeightedIndex}; - + use crate::state::PheromoneMatrix; use crate::{ - framework::{components::*, state::State, Individual, Random, SingleObjective}, - operators::custom_state::PheromoneMatrix, + framework::{components::*, Individual, Random, SingleObjective}, problems::tsp::SymmetricTsp, + state::State, }; + use rand::distributions::{Distribution, WeightedIndex}; #[derive(serde::Serialize)] pub struct AcoGeneration { diff --git a/src/heuristics/pso.rs b/src/heuristics/pso.rs index dbf2ee15..e70f077e 100644 --- a/src/heuristics/pso.rs +++ b/src/heuristics/pso.rs @@ -4,14 +4,15 @@ use crate::{ framework::{components::Component, conditions::Condition, Configuration}, operators::*, problems::{LimitedVectorProblem, SingleObjectiveProblem}, + state, }; /// Parameters for [real_pso]. pub struct RealProblemParameters { pub num_particles: u32, - pub a: f64, - pub b: f64, - pub c: f64, + pub weight: f64, + pub c_one: f64, + pub c_two: f64, pub v_max: f64, } @@ -27,9 +28,9 @@ where { let RealProblemParameters { num_particles, - a, - b, - c, + weight, + c_one, + c_two, v_max, } = params; @@ -39,9 +40,9 @@ where .update_best_individual() .do_(pso( Parameters { - particle_init: pso_ops::PsoStateInitialization::new(v_max), - particle_update: generation::swarm::PsoGeneration::new(a, b, c, v_max), - state_update: pso_ops::PsoStateUpdate::new(), + particle_init: state::PsoState::intializer(v_max), + particle_update: generation::swarm::PsoGeneration::new(weight, c_one, c_two, v_max), + state_update: state::PsoState::updater(), }, termination, logger, @@ -83,110 +84,3 @@ where }) .build_component() } - -#[allow(clippy::new_ret_no_self)] -pub mod pso_ops { - use crate::problems::SingleObjectiveProblem; - use crate::{ - framework::{components::*, state::State, Individual}, - operators::custom_state::PsoState, - problems::{LimitedVectorProblem, Problem}, - }; - use rand::Rng; - - #[derive(Debug, serde::Serialize)] - pub struct PsoStateInitialization { - v_max: f64, - } - impl PsoStateInitialization { - pub fn new(v_max: f64) -> Box> - where - P: SingleObjectiveProblem> + LimitedVectorProblem, - { - Box::new(Self { v_max }) - } - } - impl

Component

for PsoStateInitialization - where - P: SingleObjectiveProblem> + LimitedVectorProblem, - { - fn initialize(&self, _problem: &P, state: &mut State) { - // Initialize with empty state to satisfy `state.require()` statements - state.insert(PsoState { - velocities: vec![], - bests: vec![], - global_best: Individual::

::new_unevaluated(Vec::new()), - }) - } - - fn execute(&self, problem: &P, state: &mut State) { - let population = state.population_stack_mut::

().pop(); - let rng = state.random_mut(); - - let velocities = population - .iter() - .map(|_| { - (0..problem.dimension()) - .into_iter() - .map(|_| rng.gen_range(-self.v_max..=self.v_max)) - .collect::>() - }) - .collect::>>(); - - let bests = population.to_vec(); - - let global_best = bests - .iter() - .min_by_key(|i| Individual::objective(i)) - .cloned() - .unwrap(); - - state.population_stack_mut().push(population); - - state.insert(PsoState { - velocities, - bests, - global_best, - }); - } - } - - /// State update for PSO. - /// - /// Updates best found solutions of particles and global best in [PsoState]. - #[derive(Debug, serde::Serialize)] - pub struct PsoStateUpdate; - impl PsoStateUpdate { - pub fn new() -> Box> - where - P: Problem> + LimitedVectorProblem, - { - Box::new(Self) - } - } - impl

Component

for PsoStateUpdate - where - P: Problem> + LimitedVectorProblem, - { - fn initialize(&self, _problem: &P, state: &mut State) { - state.require::>(); - } - - fn execute(&self, _problem: &P, state: &mut State) { - let population = state.population_stack_mut().pop(); - let mut pso_state = state.get_mut::>(); - - for (i, individual) in population.iter().enumerate() { - if pso_state.bests[i].objective() > individual.objective() { - pso_state.bests[i] = individual.clone(); - - if pso_state.global_best.objective() > individual.objective() { - pso_state.global_best = individual.clone(); - } - } - } - - state.population_stack_mut().push(population); - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 3142ab5f..5918bb45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod heuristics; pub mod operators; pub mod prelude; pub mod problems; +pub mod state; pub mod tracking; pub mod utils; diff --git a/src/operators/archive.rs b/src/operators/archive.rs deleted file mode 100644 index dcd30c7d..00000000 --- a/src/operators/archive.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Archiving methods - -use crate::{ - framework::{components::*, state::State}, - operators::custom_state::ElitistArchiveState, - problems::SingleObjectiveProblem, -}; -use serde::{Deserialize, Serialize}; - -/// Updates the [ElitistArchiveState] with the current population. -#[derive(Serialize, Deserialize)] -pub struct ElitistArchive { - pub n_elitists: usize, -} -impl ElitistArchive { - pub fn new(n_elitists: usize) -> Box> { - Box::new(Self { n_elitists }) - } -} -impl

Component

for ElitistArchive -where - P: SingleObjectiveProblem, -{ - fn initialize(&self, _problem: &P, state: &mut State) { - state.insert::>(ElitistArchiveState::new(self.n_elitists)); - } - - fn execute(&self, _problem: &P, state: &mut State) { - let population = state.population_stack_mut().pop(); - state - .get_mut::>() - .update(&population); - state.population_stack_mut().push(population); - } -} - -/// Adds elitists from [ElitistArchiveState] to the population. -#[derive(Serialize, Deserialize)] -pub struct AddElitists; -impl AddElitists { - pub fn new() -> Box> { - Box::new(Self) - } -} -impl

Component

for AddElitists -where - P: SingleObjectiveProblem, -{ - fn initialize(&self, _problem: &P, state: &mut State) { - state.require::>(); - } - - fn execute(&self, _problem: &P, state: &mut State) { - let mut population = state.population_stack_mut().pop(); - let elitism_state = state.get::>(); - - for elitist in elitism_state.elitists() { - if !population.contains(elitist) { - population.push(elitist.clone()); - } - } - - state.population_stack_mut().push(population); - } -} diff --git a/src/operators/custom_state.rs b/src/operators/custom_state.rs deleted file mode 100644 index 422c1ec5..00000000 --- a/src/operators/custom_state.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Custom state types -//! -//! A collection of custom state types required by specific metaheuristics and evaluation procedures. - -use crate::{ - framework::{state::CustomState, Individual, SingleObjective}, - problems::{Problem, SingleObjectiveProblem}, -}; -use serde::Serialize; - -// Custom States for Specific Metaheuristics // - -/// State required for PSO. -/// -/// For preserving velocities of particles, own best values and global best particle. -pub struct PsoState { - pub velocities: Vec>, - pub bests: Vec>, - pub global_best: Individual

, -} -impl CustomState for PsoState

{} - -#[derive(Clone, Serialize)] -pub struct PheromoneMatrix { - dimension: usize, - inner: Vec, -} -impl PheromoneMatrix { - pub fn new(dimension: usize, initial_value: f64) -> Self { - PheromoneMatrix { - dimension, - inner: vec![initial_value; dimension * dimension], - } - } -} -impl std::ops::Index for PheromoneMatrix { - type Output = [f64]; - - fn index(&self, index: usize) -> &Self::Output { - assert!(index < self.dimension); - let start = index * self.dimension; - let end = start + self.dimension; - &self.inner[start..end] - } -} -impl std::ops::IndexMut for PheromoneMatrix { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - assert!(index < self.dimension); - let start = index * self.dimension; - let end = start + self.dimension; - &mut self.inner[start..end] - } -} -impl std::ops::MulAssign for PheromoneMatrix { - fn mul_assign(&mut self, rhs: f64) { - for x in &mut self.inner { - *x *= rhs; - } - } -} -impl CustomState for PheromoneMatrix {} - -// Custom States for Operators // - -/// State required for Elitism. -/// -/// For preserving n elitist individuals. -pub struct ElitistArchiveState { - elitists: Vec>, - n_elitists: usize, -} -impl CustomState for ElitistArchiveState

{} - -impl ElitistArchiveState

{ - pub fn new(n_elitists: usize) -> Self { - Self { - elitists: Vec::new(), - n_elitists, - } - } - - pub fn update(&mut self, population: &[Individual

]) { - let mut pop = population.iter().collect::>(); - pop.sort_unstable_by_key(|i| i.objective()); - pop.truncate(self.n_elitists); - self.elitists = pop.into_iter().cloned().collect(); - } - - pub fn elitists(&self) -> &[Individual

] { - &self.elitists - } - - pub fn elitists_mut(&mut self) -> &mut [Individual

] { - &mut self.elitists - } -} - -/// State required for Termination by Steps without Improvement. -/// -/// For preserving current number of steps without improvement and corresponding fitness value. -pub struct FitnessImprovementState { - pub current_steps: usize, - pub current_objective: SingleObjective, -} -impl FitnessImprovementState { - pub fn update(&mut self, objective: &SingleObjective) -> bool { - if objective >= &self.current_objective { - self.current_steps += 1; - false - } else { - self.current_objective = *objective; - self.current_steps = 0; - true - } - } -} -impl CustomState for FitnessImprovementState {} - -// Custom States for Metrics and Logging // - -/// State for logging/tracking population diversity -pub struct DiversityState { - pub diversity: f64, - pub max_div: f64, -} -impl CustomState for DiversityState {} - -/// State for logging current population -pub struct PopulationState { - pub current_pop: Vec>, -} -impl CustomState for PopulationState {} diff --git a/src/operators/diversity.rs b/src/operators/diversity.rs deleted file mode 100644 index a00b678a..00000000 --- a/src/operators/diversity.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Postprocess variants - -use crate::{ - framework::{ - components::*, - state::{common::Population, State}, - }, - operators::custom_state::DiversityState, - problems::{Problem, VectorProblem}, -}; - -/// Postprocess procedure for tracking population diversity -/// -/// Currently only for VectorProblem -/// -/// Measures can be chosen between dimension-wise (DW), mean of pairwise distance between solutions (PW), -/// average standard deviation of each position (also "true diversity", TD), and distance to average point (DTAP). -/// All measures are normalized with the maximum diversity found so far. -#[derive(Copy, Clone, Debug, serde::Serialize)] -pub enum DiversityMeasure { - DW, - PW, - TD, - DTAP, -} - -#[derive(Debug, serde::Serialize)] -pub struct FloatVectorDiversity { - pub measure: DiversityMeasure, -} - -impl

Component

for FloatVectorDiversity -where - P: Problem> + VectorProblem, -{ - fn initialize(&self, _problem: &P, state: &mut State) { - state.insert(DiversityState { - diversity: 0.0, - max_div: 0.0, - }); - } - - fn execute(&self, problem: &P, state: &mut State) { - let (population_stack, diversity_state) = - state.get_multiple_mut::<(Population

, DiversityState)>(); - - let population = population_stack.current(); - - if population.is_empty() { - diversity_state.diversity = 0.0; - return; - } - - let n = population.len() as f64; - let d = problem.dimension(); - let iter_solutions = || population.iter().map(|i| i.solution()); - - let selected_measure = self.measure; - match selected_measure { - DiversityMeasure::DW => { - diversity_state.diversity = (0..d) - .into_iter() - .map(|k| { - let xk = iter_solutions().map(|s| s[k]).sum::() / n; - iter_solutions().map(|s| (s[k] - xk).abs()).sum::() / n - }) - .sum::() - / (d as f64) - } - DiversityMeasure::PW => { - let mut sum = 0.0; - let solutions: Vec> = iter_solutions().cloned().collect(); - for i in 1..n as usize { - for j in 0..=i - 1 { - sum += (0..d) - .into_iter() - .map(|k| (solutions[i][k] - solutions[j][k]).powi(2)) - .sum::(); - diversity_state.diversity += sum.sqrt(); - } - } - diversity_state.diversity = diversity_state.diversity * 2.0 / (n * (n - 1.0)); - } - DiversityMeasure::TD => { - diversity_state.diversity = (0..d) - .into_iter() - .map(|k| { - let xk = iter_solutions().map(|s| s[k]).sum::() / n; - let sum = iter_solutions().map(|i| i[k].powi(2)).sum::() / n; - sum - xk.powi(2) - }) - .sum::() - .sqrt() - / (d as f64) - } - DiversityMeasure::DTAP => { - let mut sum = 0.0; - for i in iter_solutions() { - sum += (0..d) - .into_iter() - .map(|k| { - let xk = iter_solutions().map(|s| s[k]).sum::() / n; - (i[k] - xk).powi(2) - }) - .sum::() - .sqrt(); - } - diversity_state.diversity = sum / n; - } - } - - // set new maximum diversity found so far - if diversity_state.diversity > diversity_state.max_div { - diversity_state.max_div = diversity_state.diversity - } - - // normalize by division with maximum diversity - diversity_state.diversity /= diversity_state.max_div; - } -} diff --git a/src/operators/evaluation.rs b/src/operators/evaluation.rs index 37567ed4..eb1eb25e 100644 --- a/src/operators/evaluation.rs +++ b/src/operators/evaluation.rs @@ -3,11 +3,9 @@ use serde::Serialize; use crate::{ - framework::{ - components::Component, - state::{common, State}, - }, + framework::components::Component, problems::{MultiObjectiveProblem, Problem, SingleObjectiveProblem}, + state::{common, State}, }; #[derive(Serialize)] diff --git a/src/operators/generation/mod.rs b/src/operators/generation/mod.rs index 5963d609..9e57489a 100644 --- a/src/operators/generation/mod.rs +++ b/src/operators/generation/mod.rs @@ -3,16 +3,17 @@ use crate::{ framework::{ components::{AnyComponent, Component}, - state::State, Individual, }, problems::{LimitedVectorProblem, Problem, VectorProblem}, + state::State, }; use rand::distributions::uniform::SampleUniform; use serde::Serialize; pub mod mutation; pub mod recombination; +pub mod swarm; /// Specialized component trait to generate a new population from the current one. /// @@ -154,80 +155,3 @@ where *population = self.random_spread(problem, state.random_mut(), population_size); } } - -pub mod swarm { - use rand::distributions::Uniform; - use rand::Rng; - - use crate::{ - framework::{components::*, state::State, Individual, Random}, - operators::custom_state::PsoState, - problems::SingleObjectiveProblem, - }; - - /// Applies the PSO specific generation operator. - /// - /// Requires [PsoStateUpdate][crate::heuristics::pso::pso_ops::PsoStateUpdate]. - #[derive(serde::Serialize)] - pub struct PsoGeneration { - pub a: f64, - pub b: f64, - pub c: f64, - pub v_max: f64, - } - impl PsoGeneration { - pub fn new

(a: f64, b: f64, c: f64, v_max: f64) -> Box> - where - P: SingleObjectiveProblem>, - { - Box::new(Self { a, b, c, v_max }) - } - } - impl

Component

for PsoGeneration - where - P: SingleObjectiveProblem>, - { - fn initialize(&self, _problem: &P, state: &mut State) { - state.require::>(); - } - - fn execute(&self, _problem: &P, state: &mut State) { - let &Self { a, b, c, v_max } = self; - - let mut offspring = Vec::new(); - let mut parents = state.population_stack_mut::

().pop(); - - let rng = state.random_mut(); - let rng_iter = |rng: &mut Random| { - rng.sample_iter(Uniform::new(0., 1.)) - .take(parents.len()) - .collect::>() - }; - - let rs = rng_iter(rng); - let rt = rng_iter(rng); - - let pso_state = state.get_mut::>(); - - for (i, x) in parents.drain(..).enumerate() { - let mut x = x.into_solution(); - let v = &mut pso_state.velocities[i]; - let xl = pso_state.bests[i].solution(); - let xg = pso_state.global_best.solution(); - - for i in 0..v.len() { - v[i] = a * v[i] + b * rs[i] * (xg[i] - x[i]) + c * rt[i] * (xl[i] - x[i]); - v[i] = v[i].clamp(-v_max, v_max); - } - - for i in 0..x.len() { - x[i] = (x[i] + v[i]).clamp(-1.0, 1.0); - } - - offspring.push(Individual::

::new_unevaluated(x)); - } - - state.population_stack_mut().push(offspring); - } - } -} diff --git a/src/operators/generation/mutation.rs b/src/operators/generation/mutation.rs index 59ae5e74..38120237 100644 --- a/src/operators/generation/mutation.rs +++ b/src/operators/generation/mutation.rs @@ -5,11 +5,9 @@ use rand_distr::Distribution; use serde::{Deserialize, Serialize}; use crate::{ - framework::{ - components::*, - state::{common::Progress, State}, - }, + framework::components::*, problems::{LimitedVectorProblem, Problem}, + state::{common::Progress, State}, }; use super::{Generation, Generator}; diff --git a/src/operators/generation/recombination.rs b/src/operators/generation/recombination.rs index fb41d270..2c6f60a9 100644 --- a/src/operators/generation/recombination.rs +++ b/src/operators/generation/recombination.rs @@ -5,10 +5,7 @@ use std::cmp::max; use rand::{seq::IteratorRandom, Rng}; use serde::{Deserialize, Serialize}; -use crate::{ - framework::{components::*, state::State}, - problems::Problem, -}; +use crate::{framework::components::*, problems::Problem, state::State}; use super::{Recombination, Recombinator}; diff --git a/src/operators/generation/swarm.rs b/src/operators/generation/swarm.rs new file mode 100644 index 00000000..14ba28a8 --- /dev/null +++ b/src/operators/generation/swarm.rs @@ -0,0 +1,96 @@ +//! Swarm Operators + +use crate::state::PsoState; +use rand::distributions::Uniform; +use rand::Rng; + +use crate::{ + framework::{components::*, Individual, Random}, + problems::SingleObjectiveProblem, + state::State, +}; + +/// Applies the PSO specific generation operator. +/// +/// Requires [PsoStateUpdate][crate::heuristics::pso::pso_ops::PsoStateUpdate]. +#[derive(serde::Serialize)] +pub struct PsoGeneration { + /// Inertia weight for influence of old velocity + pub weight: f64, + /// First constant factor for influence of previous best (also called Acceleration coefficient 1) + pub c_one: f64, + /// Second constant factor for influence of global best (also called Acceleration coefficient 2) + pub c_two: f64, + /// Maximum velocity + pub v_max: f64, +} +impl PsoGeneration { + pub fn new

(weight: f64, c_one: f64, c_two: f64, v_max: f64) -> Box> + where + P: SingleObjectiveProblem>, + { + Box::new(Self { + weight, + c_one, + c_two, + v_max, + }) + } +} +impl

Component

for PsoGeneration +where + P: SingleObjectiveProblem>, +{ + fn initialize(&self, _problem: &P, state: &mut State) { + state.require::>(); + } + + fn execute(&self, _problem: &P, state: &mut State) { + let &Self { + weight, + c_one, + c_two, + v_max, + } = self; + + let mut offspring = Vec::new(); + let mut parents = state.population_stack_mut::

().pop(); + + let rng = state.random_mut(); + let rng_iter = |rng: &mut Random| { + rng.sample_iter(Uniform::new(0., 1.)) + .take(parents.len()) + .collect::>() + }; + + // it might be debatable if one should use a vector of different random numbers or of the same + // both versions exist in the literature + let r_one = rng_iter(rng); + let r_two = rng_iter(rng); + + let pso_state = state.get_mut::>(); + + for (i, x) in parents.drain(..).enumerate() { + let mut x = x.into_solution(); + let v = &mut pso_state.velocities[i]; + let xp = pso_state.bests[i].solution(); + let xg = pso_state.global_best.solution(); + + for i in 0..v.len() { + v[i] = weight * v[i] + + c_one * r_one[i] * (xp[i] - x[i]) + + c_two * r_two[i] * (xg[i] - x[i]); + v[i] = v[i].clamp(-v_max, v_max); + } + + for i in 0..x.len() { + //TODO we will need constraint handling here + x[i] = (x[i] + v[i]).clamp(-1.0, 1.0); + } + + offspring.push(Individual::

::new_unevaluated(x)); + } + + state.population_stack_mut().push(offspring); + } +} diff --git a/src/operators/initialization.rs b/src/operators/initialization.rs index 1d03e38f..d0ce5206 100644 --- a/src/operators/initialization.rs +++ b/src/operators/initialization.rs @@ -4,8 +4,9 @@ use rand::{distributions::uniform::SampleUniform, seq::SliceRandom, Rng}; use serde::{Deserialize, Serialize}; use crate::{ - framework::{components::*, state::State, Individual, Random}, + framework::{components::*, Individual, Random}, problems::{LimitedVectorProblem, Problem, VectorProblem}, + state::State, }; /// Specialized component trait to initialize a new population on the stack. diff --git a/src/operators/misc.rs b/src/operators/misc.rs index 521b6325..84195220 100644 --- a/src/operators/misc.rs +++ b/src/operators/misc.rs @@ -1,8 +1,9 @@ use serde::{Serialize, Serializer}; use crate::{ - framework::{components::Component, state::State}, + framework::components::Component, problems::{Problem, SingleObjectiveProblem}, + state::State, }; /// Doesn't do anything. diff --git a/src/operators/mod.rs b/src/operators/mod.rs index f6b4e45f..0e2d22b9 100644 --- a/src/operators/mod.rs +++ b/src/operators/mod.rs @@ -4,9 +4,6 @@ #![allow(clippy::new_ret_no_self)] -pub mod archive; -pub mod custom_state; -pub mod diversity; pub mod evaluation; pub mod generation; pub mod initialization; diff --git a/src/operators/replacement.rs b/src/operators/replacement.rs index 0f14ec35..032d6553 100644 --- a/src/operators/replacement.rs +++ b/src/operators/replacement.rs @@ -1,8 +1,9 @@ //! Replacement methods use crate::{ - framework::{components::*, state::State, Individual}, + framework::{components::*, Individual}, problems::{Problem, SingleObjectiveProblem}, + state::State, }; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; @@ -86,7 +87,7 @@ impl Replacement

for MuPlusLambda { #[cfg(test)] mod mupluslambda { - use crate::framework::state::State; + use crate::state::State; use crate::testing::*; use super::*; diff --git a/src/operators/selection.rs b/src/operators/selection.rs index da70727d..f8e4d742 100644 --- a/src/operators/selection.rs +++ b/src/operators/selection.rs @@ -8,8 +8,9 @@ use rand::{ use serde::{Deserialize, Serialize}; use crate::{ - framework::{components::*, state::State, Individual, SingleObjective}, + framework::{components::*, Individual, SingleObjective}, problems::{Problem, SingleObjectiveProblem}, + state::State, }; /// Specialized component trait to select a subset of the current population and push it on the stack. diff --git a/src/operators/termination.rs b/src/operators/termination.rs index e9df13c9..b00b55f2 100644 --- a/src/operators/termination.rs +++ b/src/operators/termination.rs @@ -1,15 +1,12 @@ //! Termination methods use crate::{ - framework::{ - conditions::Condition, - state::{ - common::{Evaluations, Iterations, Progress}, - State, - }, - }, - operators::custom_state::FitnessImprovementState, + framework::{conditions::Condition, SingleObjective}, problems::{HasKnownOptimum, HasKnownTarget, Problem, SingleObjectiveProblem}, + state::{ + common::{Evaluations, Iterations, Progress}, + CustomState, State, + }, }; use serde::{Deserialize, Serialize}; @@ -229,7 +226,7 @@ where #[cfg(test)] mod distance_to_opt { use super::*; - use crate::framework::state::common; + use crate::state::common; use crate::testing::*; #[test] @@ -246,6 +243,27 @@ mod distance_to_opt { } } +/// State required for Termination by Steps without Improvement. +/// +/// For preserving current number of steps without improvement and corresponding fitness value. +struct FitnessImprovementState { + pub current_steps: usize, + pub current_objective: SingleObjective, +} +impl FitnessImprovementState { + pub fn update(&mut self, objective: &SingleObjective) -> bool { + if objective >= &self.current_objective { + self.current_steps += 1; + false + } else { + self.current_objective = *objective; + self.current_steps = 0; + true + } + } +} +impl CustomState for FitnessImprovementState {} + /// Terminates after a specified number of steps (iterations) did not yield any improvement. /// /// Progress is unknown, as steps depend on current performance of optimizer. @@ -284,7 +302,7 @@ where #[cfg(test)] mod steps_without_improvement { use super::*; - use crate::framework::state::common; + use crate::state::common; use crate::testing::*; #[test] diff --git a/src/state/archive.rs b/src/state/archive.rs new file mode 100644 index 00000000..38f00303 --- /dev/null +++ b/src/state/archive.rs @@ -0,0 +1,101 @@ +//! Archiving methods + +use crate::{ + framework::{components::*, Individual}, + problems::SingleObjectiveProblem, + state::{CustomState, State}, +}; +use serde::{Deserialize, Serialize}; + +/// State required for Elitism. +/// +/// For preserving n elitist individuals. +pub struct ElitistArchive { + elitists: Vec>, +} + +impl CustomState for ElitistArchive

{} + +impl ElitistArchive

{ + fn new() -> Self { + Self { + elitists: Vec::new(), + } + } + + fn state_update(&mut self, population: &[Individual

], n_elitists: usize) { + let mut pop = population.iter().collect::>(); + pop.sort_unstable_by_key(|i| i.objective()); + pop.truncate(n_elitists); + self.elitists = pop.into_iter().cloned().collect(); + } +} + +impl ElitistArchive

{ + pub fn elitists(&self) -> &[Individual

] { + &self.elitists + } + + pub fn elitists_mut(&mut self) -> &mut [Individual

] { + &mut self.elitists + } +} + +impl ElitistArchive

{ + /// Updates the [ElitistArchiveState] with the current population. + pub fn update(n_elitists: usize) -> Box> { + #[derive(Serialize, Deserialize)] + pub struct ElitistArchiveUpdate { + pub n_elitists: usize, + } + + impl

Component

for ElitistArchiveUpdate + where + P: SingleObjectiveProblem, + { + fn initialize(&self, _problem: &P, state: &mut State) { + state.insert::>(ElitistArchive::new()); + } + + fn execute(&self, _problem: &P, state: &mut State) { + let population = state.population_stack_mut().pop(); + state + .get_mut::>() + .state_update(&population, self.n_elitists); + state.population_stack_mut().push(population); + } + } + + Box::new(ElitistArchiveUpdate { n_elitists }) + } + + /// Adds elitists from [ElitistArchiveState] to the population. + pub fn add_elitists() -> Box> { + #[derive(Serialize, Deserialize)] + pub struct AddElitists; + + impl

Component

for AddElitists + where + P: SingleObjectiveProblem, + { + fn initialize(&self, _problem: &P, state: &mut State) { + state.require::>(); + } + + fn execute(&self, _problem: &P, state: &mut State) { + let mut population = state.population_stack_mut().pop(); + let elitism_state = state.get::>(); + + for elitist in elitism_state.elitists() { + if !population.contains(elitist) { + population.push(elitist.clone()); + } + } + + state.population_stack_mut().push(population); + } + } + + Box::new(AddElitists) + } +} diff --git a/src/framework/state/common.rs b/src/state/common.rs similarity index 100% rename from src/framework/state/common.rs rename to src/state/common.rs diff --git a/src/framework/state/many.rs b/src/state/container/many.rs similarity index 98% rename from src/framework/state/many.rs rename to src/state/container/many.rs index c98ad122..e04fd938 100644 --- a/src/framework/state/many.rs +++ b/src/state/container/many.rs @@ -1,7 +1,7 @@ use std::ops::{Deref, DerefMut}; use std::{any::TypeId, collections::HashSet}; -use crate::framework::state::{CustomState, State}; +use crate::state::{CustomState, State}; /// Allows borrowing multiple [CustomState]'s mutable from [State] at the same time. /// It is meant to significantly simplify the definition of [Component][crate::framework::components::Component]'s diff --git a/src/framework/state/map.rs b/src/state/container/map.rs similarity index 97% rename from src/framework/state/map.rs rename to src/state/container/map.rs index c3413d9d..f593dd23 100644 --- a/src/framework/state/map.rs +++ b/src/state/container/map.rs @@ -3,7 +3,7 @@ use std::{ collections::BTreeMap, }; -use crate::framework::state::CustomState; +use crate::state::CustomState; /// Utility trait to upcast [CustomState](CustomState) to [Any]. pub trait AsAny: Any { diff --git a/src/framework/state/mod.rs b/src/state/container/mod.rs similarity index 97% rename from src/framework/state/mod.rs rename to src/state/container/mod.rs index e82d6d5e..1d6eff9b 100644 --- a/src/framework/state/mod.rs +++ b/src/state/container/mod.rs @@ -3,8 +3,9 @@ use std::ops::{Deref, DerefMut}; use crate::{ - framework::{random, Individual, SingleObjective}, + framework::{Individual, Random, SingleObjective}, problems::{MultiObjectiveProblem, Problem, SingleObjectiveProblem}, + state::common, tracking::Log, }; @@ -15,8 +16,6 @@ mod map; use map::AsAny; pub(crate) use map::StateMap; -pub mod common; - /// A marker trait for custom state. pub trait CustomState: AsAny {} @@ -271,8 +270,8 @@ macro_rules! impl_convenience_functions { } /// Returns mutable [Random](random::Random) state. - pub fn random_mut(&mut self) -> &$l mut random::Random { - self.get_mut::() + pub fn random_mut(&mut self) -> &$l mut Random { + self.get_mut::() } /// Returns the [Log]. diff --git a/src/state/diversity.rs b/src/state/diversity.rs new file mode 100644 index 00000000..19d03ad2 --- /dev/null +++ b/src/state/diversity.rs @@ -0,0 +1,134 @@ +//! Postprocess variants + +use crate::{ + framework::components::*, + problems::{Problem, VectorProblem}, + state::{common::Population, CustomState, State}, +}; + +/// Postprocess procedure for tracking population diversity +/// +/// Currently only for VectorProblem +/// +/// Measures can be chosen between dimension-wise (DW), mean of pairwise distance between solutions (PW), +/// average standard deviation of each position (also "true diversity", TD), and distance to average point (DTAP). +/// All measures are normalized with the maximum diversity found so far. +#[derive(Copy, Clone, Debug, serde::Serialize)] +pub enum DiversityMeasure { + DW, + PW, + TD, + DTAP, +} + +/// State for logging/tracking population diversity +pub struct DiversityState { + pub diversity: f64, + pub max_div: f64, +} +impl CustomState for DiversityState {} + +impl DiversityState { + pub fn for_float_vector

(measure: DiversityMeasure) -> Box> + where + P: Problem> + VectorProblem, + { + #[derive(Debug, serde::Serialize)] + pub struct FloatVectorDiversity { + pub measure: DiversityMeasure, + } + + impl

Component

for FloatVectorDiversity + where + P: Problem> + VectorProblem, + { + fn initialize(&self, _problem: &P, state: &mut State) { + state.insert(DiversityState { + diversity: 0.0, + max_div: 0.0, + }); + } + + fn execute(&self, problem: &P, state: &mut State) { + let (population_stack, diversity_state) = + state.get_multiple_mut::<(Population

, DiversityState)>(); + + let population = population_stack.current(); + + if population.is_empty() { + diversity_state.diversity = 0.0; + return; + } + + let n = population.len() as f64; + let d = problem.dimension(); + let iter_solutions = || population.iter().map(|i| i.solution()); + + let selected_measure = self.measure; + match selected_measure { + DiversityMeasure::DW => { + diversity_state.diversity = (0..d) + .into_iter() + .map(|k| { + let xk = iter_solutions().map(|s| s[k]).sum::() / n; + iter_solutions().map(|s| (s[k] - xk).abs()).sum::() / n + }) + .sum::() + / (d as f64) + } + DiversityMeasure::PW => { + let mut sum = 0.0; + let solutions: Vec> = iter_solutions().cloned().collect(); + for i in 1..n as usize { + for j in 0..=i - 1 { + sum += (0..d) + .into_iter() + .map(|k| (solutions[i][k] - solutions[j][k]).powi(2)) + .sum::(); + diversity_state.diversity += sum.sqrt(); + } + } + diversity_state.diversity = + diversity_state.diversity * 2.0 / (n * (n - 1.0)); + } + DiversityMeasure::TD => { + diversity_state.diversity = (0..d) + .into_iter() + .map(|k| { + let xk = iter_solutions().map(|s| s[k]).sum::() / n; + let sum = iter_solutions().map(|i| i[k].powi(2)).sum::() / n; + sum - xk.powi(2) + }) + .sum::() + .sqrt() + / (d as f64) + } + DiversityMeasure::DTAP => { + let mut sum = 0.0; + for i in iter_solutions() { + sum += (0..d) + .into_iter() + .map(|k| { + let xk = iter_solutions().map(|s| s[k]).sum::() / n; + (i[k] - xk).powi(2) + }) + .sum::() + .sqrt(); + } + diversity_state.diversity = sum / n; + } + } + + // set new maximum diversity found so far + if diversity_state.diversity > diversity_state.max_div { + diversity_state.max_div = diversity_state.diversity + } + + // normalize by division with maximum diversity + diversity_state.diversity /= diversity_state.max_div; + } + } + + Box::new(FloatVectorDiversity { measure }) + } +} diff --git a/src/state/mod.rs b/src/state/mod.rs new file mode 100644 index 00000000..20141d89 --- /dev/null +++ b/src/state/mod.rs @@ -0,0 +1,14 @@ +//! Custom States and corresponding Operators + +mod container; +pub use container::{CustomState, MultiStateTuple, MutState, State}; + +pub mod archive; +pub mod common; +pub mod diversity; + +mod pso; +pub use pso::PsoState; + +mod pheromones; +pub use pheromones::PheromoneMatrix; diff --git a/src/state/pheromones.rs b/src/state/pheromones.rs new file mode 100644 index 00000000..d7a55760 --- /dev/null +++ b/src/state/pheromones.rs @@ -0,0 +1,42 @@ +use crate::state::CustomState; +use serde::Serialize; + +#[derive(Clone, Serialize)] +pub struct PheromoneMatrix { + dimension: usize, + inner: Vec, +} +impl PheromoneMatrix { + pub fn new(dimension: usize, initial_value: f64) -> Self { + PheromoneMatrix { + dimension, + inner: vec![initial_value; dimension * dimension], + } + } +} +impl std::ops::Index for PheromoneMatrix { + type Output = [f64]; + + fn index(&self, index: usize) -> &Self::Output { + assert!(index < self.dimension); + let start = index * self.dimension; + let end = start + self.dimension; + &self.inner[start..end] + } +} +impl std::ops::IndexMut for PheromoneMatrix { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + assert!(index < self.dimension); + let start = index * self.dimension; + let end = start + self.dimension; + &mut self.inner[start..end] + } +} +impl std::ops::MulAssign for PheromoneMatrix { + fn mul_assign(&mut self, rhs: f64) { + for x in &mut self.inner { + *x *= rhs; + } + } +} +impl CustomState for PheromoneMatrix {} diff --git a/src/state/pso.rs b/src/state/pso.rs new file mode 100644 index 00000000..1efb54b1 --- /dev/null +++ b/src/state/pso.rs @@ -0,0 +1,115 @@ +use crate::{ + framework::{components::*, Individual}, + problems::{LimitedVectorProblem, Problem, SingleObjectiveProblem}, + state::{CustomState, State}, +}; +use rand::Rng; + +/// State required for PSO. +/// +/// For preserving velocities of particles, own best values and global best particle. +pub struct PsoState { + pub velocities: Vec>, + pub bests: Vec>, + pub global_best: Individual

, +} + +impl CustomState for PsoState

{} + +impl PsoState

+where + P: SingleObjectiveProblem> + LimitedVectorProblem, +{ + /// State initialization for PSO. + /// + /// Initializes velocities, best found solutions of particles and global best in [PsoState]. + pub fn intializer(v_max: f64) -> Box> { + #[derive(Debug, serde::Serialize)] + pub struct PsoStateInitialization { + v_max: f64, + } + + impl

Component

for PsoStateInitialization + where + P: SingleObjectiveProblem> + LimitedVectorProblem, + { + fn initialize(&self, _problem: &P, state: &mut State) { + // Initialize with empty state to satisfy `state.require()` statements + state.insert(PsoState { + velocities: vec![], + bests: vec![], + global_best: Individual::

::new_unevaluated(Vec::new()), + }) + } + + fn execute(&self, problem: &P, state: &mut State) { + let population = state.population_stack_mut::

().pop(); + let rng = state.random_mut(); + + let velocities = population + .iter() + .map(|_| { + (0..problem.dimension()) + .into_iter() + .map(|_| rng.gen_range(-self.v_max..=self.v_max)) + .collect::>() + }) + .collect::>>(); + + let bests = population.to_vec(); + + let global_best = bests + .iter() + .min_by_key(|i| Individual::objective(i)) + .cloned() + .unwrap(); + + state.population_stack_mut().push(population); + + state.insert(PsoState { + velocities, + bests, + global_best, + }); + } + } + + Box::new(PsoStateInitialization { v_max }) + } + + /// State update for PSO. + /// + /// Updates best found solutions of particles and global best in [PsoState]. + pub fn updater() -> Box> { + #[derive(Debug, serde::Serialize)] + pub struct PsoStateUpdate; + + impl

Component

for PsoStateUpdate + where + P: Problem> + LimitedVectorProblem, + { + fn initialize(&self, _problem: &P, state: &mut State) { + state.require::>(); + } + + fn execute(&self, _problem: &P, state: &mut State) { + let population = state.population_stack_mut().pop(); + let mut pso_state = state.get_mut::>(); + + for (i, individual) in population.iter().enumerate() { + if pso_state.bests[i].objective() > individual.objective() { + pso_state.bests[i] = individual.clone(); + + if pso_state.global_best.objective() > individual.objective() { + pso_state.global_best = individual.clone(); + } + } + } + + state.population_stack_mut().push(population); + } + } + + Box::new(PsoStateUpdate) + } +} diff --git a/src/tracking/functions.rs b/src/tracking/functions.rs index 21a1c637..2c1dfe59 100644 --- a/src/tracking/functions.rs +++ b/src/tracking/functions.rs @@ -1,6 +1,6 @@ use crate::{ - framework::state::{common, CustomState, State}, problems::SingleObjectiveProblem, + state::{common, CustomState, State}, tracking::log::Entry, }; use serde::Serialize; diff --git a/src/tracking/log.rs b/src/tracking/log.rs index 75840388..a20ef8f9 100644 --- a/src/tracking/log.rs +++ b/src/tracking/log.rs @@ -1,4 +1,4 @@ -use crate::framework::state::{common, CustomState, State}; +use crate::state::{common, CustomState, State}; use erased_serde::Serialize as DynSerialize; use serde::Serialize; use std::any::type_name; diff --git a/src/tracking/logger.rs b/src/tracking/logger.rs index a9097d93..fbd57874 100644 --- a/src/tracking/logger.rs +++ b/src/tracking/logger.rs @@ -1,11 +1,9 @@ use std::ops::{Deref, Sub}; use crate::{ - framework::{ - components::Component, - state::{CustomState, State}, - }, + framework::components::Component, problems::{Problem, SingleObjectiveProblem}, + state::{CustomState, State}, tracking::{self, log::Step, set::LogSet, trigger, Log}, }; use serde::Serialize; diff --git a/src/tracking/set.rs b/src/tracking/set.rs index 9836102f..85202f57 100644 --- a/src/tracking/set.rs +++ b/src/tracking/set.rs @@ -1,6 +1,6 @@ use crate::{ - framework::state::{common, CustomState, State}, problems::Problem, + state::{common, CustomState, State}, tracking::{ functions::{self, LogFn}, log::Step, diff --git a/src/tracking/trigger.rs b/src/tracking/trigger.rs index 687a0323..a0034891 100644 --- a/src/tracking/trigger.rs +++ b/src/tracking/trigger.rs @@ -1,4 +1,4 @@ -use crate::framework::state::{common::Iterations, CustomState, State}; +use crate::state::{common::Iterations, CustomState, State}; use derive_deref::Deref; use std::{ any::Any,