Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate most of the changes from PR #173 #178

Merged
merged 22 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ serde = { version = "1.0.160", features = ["derive"] }
erased-serde = "0.3.25"
derive_more = { version = "0.99.17", features = ["deref", "deref_mut", "add", "mul", "not"] }
rand = "0.8.5"
rand_chacha = "0.3.1"
rand_distr = "0.4.3"
dyn-clone = "1.0.11"
derivative = "2.2.0"
Expand All @@ -31,4 +32,11 @@ float_eq = "1.0.1"
contracts = "0.6.3"
itertools = "0.10.5"
ron = "0.8.0"
indicatif = { version = "0.17.4", features = ["rayon"] }
indicatif = { version = "0.17.4", features = ["rayon"] }

[dev-dependencies]
criterion = "0.5.1"

[[bench]]
name = "default"
harness = false
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A framework for modular construction and evaluation of metaheuristics.
# Purpose and Features

MAHF aims to make construction and modification of metaheuristics as simple and reliable as possible. In addition to
construction it also provides utilities for tracking, evaluation and comparison of those heuristics.
construction it also provides utilities for logging, evaluation and comparison of those heuristics.

- Simple modular construction of metaheuristics
- State management and state tracking
Expand Down
30 changes: 30 additions & 0 deletions benches/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use mahf::components::mutation::functional::{
circular_swap, circular_swap2, translocate_slice, translocate_slice2,
};

pub fn circular_swap_benchmark(c: &mut Criterion) {
let mut permutation = vec![0usize, 1, 2, 3, 4];
c.bench_function("swap 1", |b| {
b.iter(|| circular_swap(black_box(&mut permutation), black_box(&[0, 1, 2, 4])))
});
let mut permutation = vec![0usize, 1, 2, 3, 4];
c.bench_function("swap 2", |b| {
b.iter(|| circular_swap2(black_box(&mut permutation), black_box(&[0, 1, 2, 4])))
});
}

pub fn translocate_slice_benchmark(c: &mut Criterion) {
let mut permutation = vec![1usize, 2, 3, 4, 5, 6, 7, 8, 9];
c.bench_function("translocate 1", |b| {
b.iter(|| translocate_slice(black_box(&mut permutation), black_box(3..6), black_box(6)))
});
let mut permutation = vec![1usize, 2, 3, 4, 5, 6, 7, 8, 9];
c.bench_function("translocate 2", |b| {
b.iter(|| translocate_slice2(black_box(&mut permutation), black_box(3..6), black_box(6)))
});
}

// criterion_group!(benches, circular_swap_benchmark);
criterion_group!(benches, translocate_slice_benchmark);
criterion_main!(benches);
52 changes: 52 additions & 0 deletions examples/registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use better_any::{Tid, TidAble};
use derive_more::{Deref, DerefMut};
use mahf::{CustomState, StateRegistry};

#[derive(Debug, Deref, DerefMut, Tid)]
pub struct A(usize);
impl CustomState<'_> for A {}

#[derive(Debug, Deref, DerefMut, Tid)]
pub struct B<'a>(&'a mut A);
impl<'a> CustomState<'a> for B<'a> {}

fn main() {
let mut b_source = A(10);

let mut registry: StateRegistry = StateRegistry::new();

registry.insert(A(0));
registry.insert(B(&mut b_source));

let a = registry.borrow::<A>();
let _a2 = registry.borrow::<A>();
let mut b = registry.borrow_mut::<B>();

assert!(registry.try_borrow_mut::<A>().is_err());

println!("{a:?}");
println!("{b:?}");

b.0 .0 += 1;

assert!(registry.try_borrow_mut::<B>().is_err());

println!("{b:?}");

drop(a);
drop(_a2);
drop(b);

let (a, b) = registry.get_multiple_mut::<(A, B)>();
a.0 += 1;
b.0 .0 += 1;

println!("{a:?}");
println!("{b:?}");

let a = registry.entry::<A>().or_insert(A(0));
println!("{a:?}");
drop(a);

assert!(registry.try_get_multiple_mut::<(A, A)>().is_err());
}
93 changes: 66 additions & 27 deletions examples/sphere.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use mahf::prelude::*;
use mahf::problems::KnownOptimumProblem;
use mahf::SingleObjective;
use std::ops::Range;

use mahf::{
conditions::common::{ChangeOf, DeltaEqChecker},
experiments::par_experiment,
lens::common::{BestObjectiveValueLens, BestSolutionLens},
prelude::*,
};

pub struct Sphere {
pub dim: usize,
}
Expand All @@ -13,7 +17,7 @@ impl Sphere {
}
}

impl problems::Problem for Sphere {
impl Problem for Sphere {
type Encoding = Vec<f64>;
type Objective = SingleObjective;

Expand All @@ -36,8 +40,9 @@ impl problems::LimitedVectorProblem for Sphere {
}
}

impl problems::ObjectiveFunction for Sphere {
fn objective(solution: &Self::Encoding) -> Self::Objective {
impl ObjectiveFunction for Sphere {
fn objective(&self, solution: &Self::Encoding) -> Self::Objective {
debug_assert_eq!(solution.len(), self.dim);
solution
.iter()
.map(|x| x.powi(2))
Expand All @@ -47,35 +52,69 @@ impl problems::ObjectiveFunction for Sphere {
}
}

impl KnownOptimumProblem for Sphere {
impl problems::KnownOptimumProblem for Sphere {
fn known_optimum(&self) -> SingleObjective {
0.0.try_into().unwrap()
}
}

fn main() {
fn main() -> ExecResult<()> {
// Required for pretty error messages and stacktrace.
color_eyre::install()?;

// Specify the problem: Sphere function with 10 dimensions.
let problem: Sphere = Sphere::new(/*dim: */ 10);
// Specify the metaheuristic: Particle Swarm Optimization (pre-implemented in MAHF).
let config: Configuration<Sphere> = pso::real_pso(
let problem = Sphere::new(/* dim: */ 10);

// Specify the metaheuristic: e.g. Particle Swarm Optimization ...
let _: Configuration<Sphere> = pso::real_pso(
/*params: */
pso::RealProblemParameters {
num_particles: 20,
weight: 1.0,
c_one: 1.0,
c_two: 1.0,
num_particles: 120,
start_weight: 0.9,
end_weight: 0.4,
c_one: 1.7,
c_two: 1.7,
v_max: 1.0,
},
/*termination: */
termination::FixedIterations::new(/*max_iterations: */ 500)
& termination::DistanceToOpt::new(0.01),
);

// Execute the metaheuristic on the problem with a random seed.
let state: State<Sphere> = config.optimize(&problem);

// Print the results.
println!("Found Individual: {:?}", state.best_individual().unwrap());
println!("This took {} iterations.", state.iterations());
println!("Global Optimum: {:?}", problem.known_optimum());
/*condition: */
conditions::LessThanN::iterations(10_000)
& conditions::DistanceToOptimumGreaterThan::new(0.01)?,
)?;

// ... or a Genetic Algorithm.
let config = ga::real_ga(
/*params: */
ga::RealProblemParameters {
population_size: 120,
tournament_size: 5,
pm: 1.0,
deviation: 0.1,
pc: 0.8,
},
/*condition: */
conditions::LessThanN::iterations(10_000)
& conditions::DistanceToOptimumGreaterThan::new(0.01)?,
)?;

let setup = |state: &mut State<_>| -> ExecResult<()> {
state.insert_evaluator(evaluate::Sequential::new());
state.configure_log(|config| {
config
.with_common(conditions::EveryN::iterations(50))
.with(
ChangeOf::new(
DeltaEqChecker::new(0.001.try_into().unwrap()),
BestObjectiveValueLens::new(),
) & !conditions::DistanceToOptimumGreaterThan::new(0.05)?,
BestObjectiveValueLens::entry(),
)
.with(
conditions::EveryN::iterations(1_000),
BestSolutionLens::entry(),
);
Ok(())
})
};

par_experiment(&config, setup, &[problem], 4096, "data/bmf/GA", false)
}
25 changes: 25 additions & 0 deletions src/component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Base utilities for `components`, `conditions`, and `lens`es.

use trait_set::trait_set;

trait_set! {
/// Collection of traits required by every component.
///
/// # Requirements
///
/// The [`dyn_clone::DynClone`] and [`erased_serde::Serialize`] traits are automatically
/// implemented for [`Component`]s and [`Condition`]s that implement [`Clone`] and
/// [`serde::Serialize`].
///
/// [`Component`]: crate::components::Component
/// [`Condition`]: crate::conditions::Condition
pub trait AnyComponent = dyn_clone::DynClone + erased_serde::Serialize + Send + Sync;
}

/// The result type for fallible execution.
///
/// Note that methods returning this type are considered 'application code', and the caller
/// is expected to only care about if it is an error or not, and not about handling different
/// errors in a different way.
/// This is also the reason an [`eyre::Result`] is used instead of a custom error type.
pub type ExecResult<T> = eyre::Result<T>;
85 changes: 85 additions & 0 deletions src/components/archive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Elitist archive.

use better_any::{Tid, TidAble};
use serde::{Deserialize, Serialize};

use crate::{
component::ExecResult, components::Component, problems::SingleObjectiveProblem,
state::StateReq, CustomState, Individual, State,
};

#[derive(Default, Tid)]
pub struct ElitistArchive<P: SingleObjectiveProblem + 'static>(Vec<Individual<P>>);

impl<P: SingleObjectiveProblem> CustomState<'_> for ElitistArchive<P> {}

impl<P: SingleObjectiveProblem> ElitistArchive<P> {
fn new() -> Self {
Self(Vec::new())
}

fn update(&mut self, population: &[Individual<P>], num_elitists: usize) {
self.0.extend_from_slice(population);
self.0.sort_unstable_by_key(|i| *i.objective());
self.0.truncate(num_elitists);
}

pub fn elitists(&self) -> &[Individual<P>] {
&self.0
}

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

#[derive(Clone, Serialize, Deserialize)]
pub struct ElitistArchiveUpdate {
pub num_elitists: usize,
}

impl<P> Component<P> for ElitistArchiveUpdate
where
P: SingleObjectiveProblem,
{
fn init(&self, _problem: &P, state: &mut State<P>) -> ExecResult<()> {
state.insert(ElitistArchive::<P>::new());
Ok(())
}

fn execute(&self, _problem: &P, state: &mut State<P>) -> ExecResult<()> {
state
.borrow_mut::<ElitistArchive<P>>()
.update(state.populations().current(), self.num_elitists);
Ok(())
}
}

#[derive(Clone, Serialize, Deserialize)]
pub struct ElitistArchiveIntoPopulation {
pub num_elitists: usize,
}

impl<P> Component<P> for ElitistArchiveIntoPopulation
where
P: SingleObjectiveProblem,
{
fn require(&self, _problem: &P, state_req: &StateReq<P>) -> ExecResult<()> {
state_req.require::<Self, ElitistArchive<P>>()?;
Ok(())
}

fn execute(&self, _problem: &P, state: &mut State<P>) -> ExecResult<()> {
let archive = state.borrow::<ElitistArchive<P>>();
let mut populations = state.populations_mut();
let population = populations.current_mut();

for elitist in archive.elitists() {
if !population.contains(elitist) {
population.push(elitist.clone());
}
}

Ok(())
}
}
Loading