diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b227d..6db2a3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: - master env: - RUST_VERSION: 1.70.0 + RUST_VERSION: 1.78.0 CARGO_TERM_COLOR: always jobs: diff --git a/Cargo.toml b/Cargo.toml index fbadcf7..ced17ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,9 @@ test-case = "3.1.0" float_eq = "1.0.1" contracts = "0.6.3" itertools = "0.10.5" -ron = "0.8.0" +ron = "=0.8.0" indicatif = { version = "0.17.4", features = ["rayon"] } +statrs = "0.16" [dev-dependencies] criterion = "0.5.1" diff --git a/src/components/archive.rs b/src/components/archive.rs index 9ef4564..0fb61da 100644 --- a/src/components/archive.rs +++ b/src/components/archive.rs @@ -1,11 +1,13 @@ -//! Elitist archive. +//! Archive for specified parts of population. + +use std::cell::Ref; use better_any::{Tid, TidAble}; use serde::{Deserialize, Serialize}; use crate::{ component::ExecResult, components::Component, problems::SingleObjectiveProblem, - state::StateReq, CustomState, Individual, State, + state::StateReq, CustomState, Individual, Problem, State, }; /// An archive for storing elitist individuals. @@ -118,3 +120,127 @@ where Ok(()) } } + +/// An archive for storing individuals between operators, e.g. for subsequent calculation of measures. +#[derive(Default, Tid)] +pub struct IntermediateArchive(Vec>); + +impl CustomState<'_> for IntermediateArchive

{} + +impl IntermediateArchive

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

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

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

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

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

Component

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

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

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

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

{} + +impl BestIndividualsArchive

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

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

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

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

Component

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

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

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

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

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

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

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

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

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

ConvergenceRateMeasure

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

Component

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

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

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

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

ConvergenceRateMeasure

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

Component

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

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

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

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

ConvergenceRateMeasure

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

Component

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

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

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

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

ConvergenceRateMeasure

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

Component

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

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

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

], + current: &[Individual

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

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

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

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

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

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

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

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

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

ImprovementMeasure

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

], + current: &[Individual

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

Component

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

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

) -> ExecResult<()> { + improvement_measure(self, problem, state) + } +} diff --git a/src/components/measures/mod.rs b/src/components/measures/mod.rs new file mode 100644 index 0000000..a322596 --- /dev/null +++ b/src/components/measures/mod.rs @@ -0,0 +1,4 @@ +pub mod convergence; +pub mod diversity; +pub mod improvement; +pub mod stepsize; diff --git a/src/components/measures/stepsize.rs b/src/components/measures/stepsize.rs new file mode 100644 index 0000000..5b2342c --- /dev/null +++ b/src/components/measures/stepsize.rs @@ -0,0 +1,445 @@ +//! Step size measures for changes caused by operators. +//! +//! # References +//! +//! \[1\] A. Scheibenpflug, S. Wagner, E. Pitzer, B. Burlacu, M. Affenzeller. 2012 +//! On the analysis, classification and prediction of metaheuristic algorithm behavior for combinatorial optimization problems. +//! 24th European Modeling and Simulation Symposium, EMSS 1, (2012), 368-372 + +use std::{any::type_name, marker::PhantomData}; + +use better_any::{Tid, TidAble}; +use derivative::Derivative; +use serde::Serialize; +use statrs::statistics::Statistics; + +use crate::{ + component::AnyComponent, + components::archive, + lens::{AnyLens, Lens, LensMap}, + logging::extractor::{EntryExtractor, EntryName}, + population::AsSolutions, + problems::VectorProblem, + utils::{squared_euclidean, SerializablePhantom}, + Component, CustomState, ExecResult, Problem, State, +}; + +/// Trait for representing a component that measures the step size of the change caused by an operator. +pub trait StepSizeMeasure: AnyComponent { + /// Calculates the step size between two `solutions`. + fn measure( + &self, + problem: &P, + previous: &[&P::Encoding], + current: &[&P::Encoding], + ) -> (Vec, Vec); +} + +/// A default implementation of [`Component::execute`] for types implementing [`StepSizeMeasure`]. +pub fn step_size_measure(component: &T, problem: &P, state: &mut State

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

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

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

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

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

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

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

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

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

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

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

StepSizeMeasure

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

Component

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

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

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

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

StepSizeMeasure

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

Component

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

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

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

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

StepSizeMeasure

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

Component

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

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

) -> ExecResult<()> { + step_size_measure(self, problem, state) + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 94a6770..8c8b7a9 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -13,11 +13,11 @@ use crate::{ pub mod archive; pub mod boundary; pub mod control_flow; -pub mod diversity; pub mod evaluation; pub mod generative; pub mod initialization; pub mod mapping; +pub mod measures; pub mod misc; pub mod mutation; pub mod recombination; diff --git a/src/conditions/common.rs b/src/conditions/common.rs index 30a9f0b..1363254 100644 --- a/src/conditions/common.rs +++ b/src/conditions/common.rs @@ -191,10 +191,10 @@ where /// # Common lenses /// /// The most common lens used with this condition is [`ValueOf`], -/// for which the [`LessThanN::iterations`] method is provided. +/// for which the [`EveryN::iterations`] method is provided. /// /// [`ValueOf`]: ValueOf -/// [`LessThanN::iterations`]: LessThanN>::iterations +/// [`EveryN::iterations`]: EveryN>::iterations /// /// # Examples /// @@ -223,12 +223,12 @@ pub struct EveryN { } impl EveryN { - /// Constructs a new `LessThanN` with the given `n` and `lens`. + /// Constructs a new `EveryN` with the given `n` and `lens`. pub fn from_params(n: u32, lens: L) -> Self { Self { n, lens } } - /// Constructs a new `LessThanN` with the given `n` and `lens`. + /// Constructs a new `EveryN` with the given `n` and `lens`. pub fn new

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

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

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

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

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

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

) -> ExecResult { + let value = self.lens.get(problem, state)?; + Ok(value == self.n) + } +} + /// Holds the previous value for comparison. #[derive(Deref, DerefMut, Tid)] struct Previous(Option); diff --git a/src/conditions/mod.rs b/src/conditions/mod.rs index dca6d2f..3f6b9e5 100644 --- a/src/conditions/mod.rs +++ b/src/conditions/mod.rs @@ -14,7 +14,7 @@ pub mod common; pub mod cro; pub mod logical; -pub use common::{ChangeOf, EveryN, LessThanN, OptimumReached, RandomChance}; +pub use common::{ChangeOf, EqualToN, EveryN, LessThanN, OptimumReached, RandomChance}; pub use logical::{And, Not, Or}; /// Trait to represent a condition *component* for loops or branches. diff --git a/src/lens/common.rs b/src/lens/common.rs index c9bc874..0ebc16c 100644 --- a/src/lens/common.rs +++ b/src/lens/common.rs @@ -346,3 +346,101 @@ impl LensRef

for BestObjectiveValueLens

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

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

PopulationLens

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

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

EntryName for PopulationLens

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

PopulationLens

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

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

{ + type Source = Populations

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

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

ObjectiveValuesLens

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

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

EntryName for ObjectiveValuesLens

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

ObjectiveValuesLens

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

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

{ + type Source = Populations

; + + fn map(&self, source: &Self::Source) -> Self::Target { + let pop = source.current(); + pop.iter().map(|i| i.objective().to_owned()).collect() + } +} diff --git a/src/utils.rs b/src/utils.rs index d5a2c7f..55a77d7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,5 @@ //! A collection of utilities. +#![allow(clippy::non_canonical_clone_impl)] use std::marker::PhantomData; @@ -32,6 +33,7 @@ pub fn all_eq(arr: &[T]) -> bool { /// Wrapper around [`PhantomData`] that serializes the type name of `T`. /// /// It additionally implements `Send` + `Sync` even if `T` doesn't. + #[derive(Derivative)] #[derivative(Default(bound = ""), Copy(bound = ""), Clone(bound = ""))] pub struct SerializablePhantom(PhantomData T>); @@ -47,5 +49,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).powi(2)).sum::() + a.iter().zip(b).map(|(p, q)| (q - p).powi(2)).sum::() }