diff --git a/.github/workflows/check-and-lint.yaml b/.github/workflows/check-and-lint.yaml new file mode 100644 index 0000000..4fe0334 --- /dev/null +++ b/.github/workflows/check-and-lint.yaml @@ -0,0 +1,55 @@ +on: + pull_request: + push: + branches: + - main + + +name: Check and Lint + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + override: true + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features + name: Clippy Output \ No newline at end of file diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..c2d11fa --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,36 @@ +name: Build and test +on: + push: + branches: [ main ] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + test-linux: + name: Cargo check and test on Unix systems + runs-on: ubuntu-latest + steps: + # Enable this when testing with nektos/act + # - uses: actions-rs/toolchain@v1 + # with: + # toolchain: stable + # override: true + # - name: Install Clang + # run: sudo apt update && sudo apt install --yes clang + - uses: actions/checkout@v4 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose +# NOTE disabled due to compiler failure in hv-fonseca crate +# test-windows: +# name: Cargo check and test on Windows +# runs-on: windows-latest +# steps: +# - uses: actions/checkout@v4 +# - name: Build +# run: cargo build --verbose +# - name: Run tests +# run: cargo test --verbose \ No newline at end of file diff --git a/hv-fonseca-et-al-2006-sys/src/lib.rs b/hv-fonseca-et-al-2006-sys/src/lib.rs index c1ce461..8809ae8 100644 --- a/hv-fonseca-et-al-2006-sys/src/lib.rs +++ b/hv-fonseca-et-al-2006-sys/src/lib.rs @@ -10,15 +10,15 @@ include!("bindings.rs"); /// /// **IMPLEMENTATION NOTES**: /// 1) The reference point must dominate the values of all objectives. Dominated solutions by the -/// reference point are automatically excluded from the calculation. +/// reference point are automatically excluded from the calculation. /// 2) The program assumes that all objectives are minimised. Maximisation objectives may be -/// multiplied by -1 to convert them to minimisation. +/// multiplied by -1 to convert them to minimisation. /// /// # Arguments /// /// * `data`: The vector with the objective values. The size of this vector must correspond to the -/// number of individuals `n` in the population. Each sub-vector must have size `d` equal to the -/// number of problem objectives. +/// number of individuals `n` in the population. Each sub-vector must have size `d` equal to the +/// number of problem objectives. /// * `ref_point`: The reference or anti-optimal point to use in the calculation of length `d`. /// /// returns: `f64`. The hyper-volume. diff --git a/hv-wfg-sys/src/lib.rs b/hv-wfg-sys/src/lib.rs index 625a9b7..0db9665 100644 --- a/hv-wfg-sys/src/lib.rs +++ b/hv-wfg-sys/src/lib.rs @@ -1,6 +1,7 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#![allow(clippy::all)] use std::ffi::c_int; @@ -12,18 +13,18 @@ include!("bindings.rs"); /// /// **IMPLEMENTATION NOTES**: /// 1) The program assumes that all objectives are minimised (`MAXIMISATION` is set to `false` -/// during conditional compilation ). Maximisation objectives may be multiplied by -1 to convert -/// them to minimisation. +/// during conditional compilation ). Maximisation objectives may be multiplied by -1 to convert +/// them to minimisation. /// 2) The `opt` numeric variable is set to `2` in the conditional compilation of the `wfg.c` file. -/// This means that some optimisations, such as dominated point exclusion when calculating the -/// exclusive hyper-volume or reverting to simple calculation for the 2-dimensional case, are -/// included. +/// This means that some optimisations, such as dominated point exclusion when calculating the +/// exclusive hyper-volume or reverting to simple calculation for the 2-dimensional case, are +/// included. /// /// # Arguments /// /// * `objective_values`: The vector with the objective values. The size of this vector must -/// correspond to the number of individuals `n` in the population. Each sub-vector must have size -/// `d` equal to the number of problem objectives. +/// correspond to the number of individuals `n` in the population. Each sub-vector must have size +/// `d` equal to the number of problem objectives. /// * `ref_point`: The reference or anti-optimal point to use in the calculation of length `d`. /// /// returns: `Result`. The hyper-volume. diff --git a/optirustic/examples/nsga2_sch.rs b/optirustic/examples/nsga2_sch.rs index 053639c..a076c3d 100644 --- a/optirustic/examples/nsga2_sch.rs +++ b/optirustic/examples/nsga2_sch.rs @@ -1,13 +1,16 @@ +use std::env; use std::error::Error; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use log::LevelFilter; -use plotters::chart::ChartBuilder; +use plotters::backend::BitMapBackend; +use plotters::chart::{ChartBuilder, SeriesLabelPosition}; use plotters::drawing::IntoDrawingArea; -use plotters::prelude::*; +use plotters::element::{Circle, PathElement, Rectangle}; use plotters::prelude::full_palette::{GREY_A400, RED_700}; +use plotters::prelude::{Color, LineSeries, Palette, Palette99, ShapeStyle, BLACK, WHITE}; -use optirustic::algorithms::{Algorithm, MaxGeneration, NSGA2, NSGA2Arg, StoppingConditionType}; +use optirustic::algorithms::{Algorithm, MaxGeneration, NSGA2Arg, StoppingConditionType, NSGA2}; use optirustic::core::builtin_problems::SCHProblem; use optirustic::core::Individual; @@ -46,25 +49,23 @@ fn main() -> Result<(), Box> { let mut algo = NSGA2::new(problem, args)?; algo.run()?; + let out_path = PathBuf::from(&env::current_dir().unwrap()) + .join("examples") + .join("results"); + // Plot the results - plot(&algo.get_results().individuals)?; + plot(&algo.get_results().individuals, &out_path)?; // Export serialised results at last generation - algo.save_to_json( - &PathBuf::from("optirustic/examples/results"), - Some("SCH_2obj"), - )?; + algo.save_to_json(&out_path, Some("SCH_2obj"))?; Ok(()) } /// Draw the expected objective functions and the solutions from the algorithm. -fn plot(individuals: &[Individual]) -> Result<(), Box> { - let root = BitMapBackend::new( - "optirustic/examples/results/SCH_2_obj_NSGA2_solutions.png", - (1024, 768), - ) - .into_drawing_area(); +fn plot(individuals: &[Individual], out_path: &Path) -> Result<(), Box> { + let png_file = out_path.join("SCH_2_obj_NSGA2_solutions.png"); + let root = BitMapBackend::new(&png_file, (1024, 768)).into_drawing_area(); static FONT: &str = "sans-serif"; root.fill(&WHITE)?; diff --git a/optirustic/examples/nsga2_zdt1.rs b/optirustic/examples/nsga2_zdt1.rs index 077102f..10ea7f0 100644 --- a/optirustic/examples/nsga2_zdt1.rs +++ b/optirustic/examples/nsga2_zdt1.rs @@ -1,9 +1,10 @@ +use std::env; use std::error::Error; use std::path::PathBuf; use log::LevelFilter; -use optirustic::algorithms::{Algorithm, MaxGeneration, NSGA2, NSGA2Arg, StoppingConditionType}; +use optirustic::algorithms::{Algorithm, MaxGeneration, NSGA2Arg, StoppingConditionType, NSGA2}; use optirustic::core::builtin_problems::ZTD1Problem; /// Solve the ZDT1 problem (SCH) where the following 2 objectives are minimised: @@ -48,10 +49,10 @@ fn main() -> Result<(), Box> { } // Export serialised results at last generation - algo.save_to_json( - &PathBuf::from("optirustic/examples/results"), - Some("ZDT1_2obj"), - )?; + let out_path = PathBuf::from(&env::current_dir().unwrap()) + .join("examples") + .join("results"); + algo.save_to_json(&out_path, Some("ZDT1_2obj"))?; Ok(()) } diff --git a/optirustic/examples/reference_point.rs b/optirustic/examples/reference_point.rs new file mode 100644 index 0000000..6c59368 --- /dev/null +++ b/optirustic/examples/reference_point.rs @@ -0,0 +1,20 @@ +use optirustic::core::OError; +use optirustic::utils::DasDarren1998; + +/// Generate and plot reference points using the Ds & Darren (1998) method. +fn main() -> Result<(), OError> { + // Consider the case of a 3D hyperplane with 3 objectives + let number_of_objectives = 3; + // Each objective axis is split into 5 gaps of equal size. + let number_of_partitions = 5; + + let m = DasDarren1998::new(number_of_objectives, number_of_partitions); + // This returns the coordinates of the reference points between 0 and 1 + println!("Total pints = {:?}", m.number_of_points()); + + let weights = m.get_weights(); + println!("Weights = {:?}", weights); + + // Save the charts of points to inspect them + m.plot("ref_points_3obj_5gaps.png") +} diff --git a/optirustic/src/algorithms/mod.rs b/optirustic/src/algorithms/mod.rs index f9ce60e..7c7818a 100644 --- a/optirustic/src/algorithms/mod.rs +++ b/optirustic/src/algorithms/mod.rs @@ -1,5 +1,5 @@ pub use algorithm::{Algorithm, AlgorithmExport, AlgorithmSerialisedExport, ExportHistory}; -pub use nsga2::{NSGA2, NSGA2Arg}; +pub use nsga2::{NSGA2Arg, NSGA2}; pub use stopping_condition::{ MaxDuration, MaxGeneration, StoppingCondition, StoppingConditionType, }; diff --git a/optirustic/src/algorithms/nsga2.rs b/optirustic/src/algorithms/nsga2.rs index ab98a1f..b63f6c2 100644 --- a/optirustic/src/algorithms/nsga2.rs +++ b/optirustic/src/algorithms/nsga2.rs @@ -2,6 +2,11 @@ use std::fmt::{Display, Formatter}; use std::ops::Rem; use std::path::PathBuf; +use log::{debug, info}; +use rand::RngCore; + +use optirustic_macros::{as_algorithm, as_algorithm_args, impl_algorithm_trait_items}; + use crate::algorithms::Algorithm; use crate::core::utils::{argsort, get_rng, vector_max, vector_min, Sort}; use crate::core::{Individual, Individuals, IndividualsMut, OError, VariableValue}; @@ -10,9 +15,6 @@ use crate::operators::{ SimulatedBinaryCrossover, SimulatedBinaryCrossoverArgs, TournamentSelector, }; use crate::utils::fast_non_dominated_sort; -use log::{debug, info}; -use optirustic_macros::{as_algorithm, as_algorithm_args, impl_algorithm_trait_items}; -use rand::RngCore; /// Input arguments for the NSGA2 algorithm. #[as_algorithm_args] @@ -614,7 +616,7 @@ mod test_problems { const BOUND_TOL: f64 = 1.0 / 1000.0; const LOOSE_BOUND_TOL: f64 = 0.1; - #[test_with_retries(3)] + #[test_with_retries(10)] /// Test problem 1 from Deb et al. (2002). Optional solution x in [0; 2] fn test_sch_problem() { let problem = SCHProblem::create().unwrap(); @@ -640,7 +642,7 @@ mod test_problems { } } - #[test_with_retries(3)] + #[test_with_retries(10)] /// Test the ZTD1 problem from Deb et al. (2002) with 30 variables. Solution x1 in [0; 1] and /// x2 to x30 = 0. The exact solutions are tested using a strict and loose bounds. fn test_ztd1_problem() { @@ -687,7 +689,7 @@ mod test_problems { } } - #[test_with_retries(3)] + #[test_with_retries(10)] /// Test the ZTD2 problem from Deb et al. (2002) with 30 variables. Solution x1 in [0; 1] and /// x2 to x30 = 0. The exact solutions are tested using a strict and loose bounds. fn test_ztd2_problem() { @@ -739,7 +741,7 @@ mod test_problems { } } - #[test_with_retries(3)] + #[test_with_retries(10)] /// Test the ZTD3 problem from Deb et al. (2002) with 30 variables. Solution x1 in [0; 1] and /// x2 to x30 = 0. The exact solutions are tested using a strict and loose bounds. fn test_ztd3_problem() { @@ -791,7 +793,7 @@ mod test_problems { } } - #[test_with_retries(3)] + #[test_with_retries(10)] /// Test the ZTD4 problem from Deb et al. (2002) with 30 variables. Solution x1 in [0; 1] and /// x2 to x10 = 0. The exact solutions are tested using a strict and loose bounds. fn test_ztd4_problem() { @@ -844,7 +846,7 @@ mod test_problems { } } - #[test_with_retries(3)] + #[test_with_retries(10)] /// Test the ZTD6 problem from Deb et al. (2002) with 30 variables. Solution x1 in [0; 1] and /// x2 to x10 = 0. The exact solutions are tested using a strict and loose bounds. fn test_ztd6_problem() { diff --git a/optirustic/src/core/constraint.rs b/optirustic/src/core/constraint.rs index d142a9c..f126ab2 100644 --- a/optirustic/src/core/constraint.rs +++ b/optirustic/src/core/constraint.rs @@ -30,7 +30,7 @@ pub enum RelationalOperator { /// # Example /// /// ``` -/// use optirustic::core::constraint::{Constraint, RelationalOperator}; +/// use optirustic::core::{Constraint, RelationalOperator}; /// let c = Constraint::new("Z>=5.2",RelationalOperator::GreaterOrEqualTo, 5.2); /// assert_eq!(c.is_met(10.1), true); /// assert_eq!(c.is_met(3.11), false); @@ -52,7 +52,7 @@ impl Constraint { /// /// * `name`: The constraint name. /// * `operator`: The relational operator to use to compare a value against the constraint - /// target value. + /// target value. /// * `target`: The constraint target. /// /// returns: `Constraint` @@ -71,7 +71,7 @@ impl Constraint { /// /// * `name`: The constraint name. /// * `operator`: The relational operator to use to compare a value against the constraint - /// target value. + /// target value. /// * `target`: The constraint target. /// * `scale`: Apply a scaling factor to the `target`. /// * `offset`: Apply an offset to the `target`. diff --git a/optirustic/src/core/error.rs b/optirustic/src/core/error.rs index a93e800..a91ec1e 100644 --- a/optirustic/src/core/error.rs +++ b/optirustic/src/core/error.rs @@ -9,8 +9,8 @@ pub enum OError { NoObjective, #[error("You must provide at least one variable to properly define a problem")] NoVariables, - #[error("The {0} type named '{1}' already exist")] - DuplicatedName(String, String), + #[error("The {0} vector contains duplicated names")] + DuplicatedNames(String), #[error("The {0} index {1} does not exist")] NonExistingIndex(String, usize), #[error("The {0} named '{1}' does not exist")] diff --git a/optirustic/src/core/individual.rs b/optirustic/src/core/individual.rs index 2b1408d..4403642 100644 --- a/optirustic/src/core/individual.rs +++ b/optirustic/src/core/individual.rs @@ -352,7 +352,7 @@ impl Individual { /// # Arguments /// /// * `transform`: The function to apply to transform each objective value. This function - /// receives the objective value and its name. + /// receives the objective value and its name. /// /// returns: `Result, OError>` pub fn transform_objective_values Result>( @@ -637,11 +637,11 @@ impl_individuals!(Vec); mod test { use std::sync::Arc; + use crate::core::utils::dummy_evaluator; use crate::core::{ BoundedNumber, Constraint, Individual, Objective, ObjectiveDirection, Problem, RelationalOperator, VariableType, }; - use crate::core::utils::dummy_evaluator; #[test] /// Test when an objective does not exist diff --git a/optirustic/src/core/objective.rs b/optirustic/src/core/objective.rs index 17cc6a7..ce2e3ed 100644 --- a/optirustic/src/core/objective.rs +++ b/optirustic/src/core/objective.rs @@ -65,7 +65,7 @@ impl Objective { /// /// return: `ObjectiveDirection` pub fn direction(&self) -> ObjectiveDirection { - self.direction.clone() + self.direction } } diff --git a/optirustic/src/core/problem.rs b/optirustic/src/core/problem.rs index 0d68a9a..bad23b7 100644 --- a/optirustic/src/core/problem.rs +++ b/optirustic/src/core/problem.rs @@ -4,7 +4,8 @@ use std::fmt::{Debug, Display, Formatter}; use serde::{Deserialize, Serialize}; -use crate::core::{Constraint, Individual, Objective, ObjectiveDirection, OError, VariableType}; +use crate::core::utils::has_duplicated; +use crate::core::{Constraint, Individual, OError, Objective, ObjectiveDirection, VariableType}; /// The struct containing the results of the evaluation function. This is the output of /// [`Evaluator::evaluate`], the user-defined function should produce. When the algorithm generates @@ -92,7 +93,7 @@ pub struct ProblemExport { /// where /// - where the integer $M \geq 1$ is the number of objectives; /// - $x$ the $N$-variable solution vector bounded to $$$ x_i^{(L)} \leq x_i \leq x_i^{(U)}$ with -/// $i=1,2,...,N$. +/// $i=1,2,...,N$. /// /// The problem is also subjected to the following constraints: /// - $$$ g_j(x) \geq 0 $ with $j=1,2,...,J$ and $J$ the number of inequality constraints. @@ -160,9 +161,9 @@ impl Problem { /// * `variable_types`: The vector of variable types to set on the problem. /// * `constraints`: The optional vector of constraints. /// * `evaluator`: The trait with the function to use to evaluate the objective and constraint - /// values when new offsprings are generated by an algorithm. The [`Evaluator::evaluate`] - /// receives the [`Individual`] with the new variables/solutions and should return the - /// [`EvaluationResult`]. + /// values when new offsprings are generated by an algorithm. The [`Evaluator::evaluate`] + /// receives the [`Individual`] with the new variables/solutions and should return the + /// [`EvaluationResult`]. /// /// returns: `Result` pub fn new( @@ -180,6 +181,22 @@ impl Problem { } let constraints = constraints.unwrap_or_default(); + // check unique names + let obj_names = objectives.iter().map(|o| o.name()); + if has_duplicated(obj_names) { + return Err(OError::DuplicatedNames("objective".to_string())); + } + + let var_names = variable_types.iter().map(|o| o.name()); + if has_duplicated(var_names) { + return Err(OError::DuplicatedNames("variables".to_string())); + } + + let const_names = constraints.iter().map(|o| o.name()); + if has_duplicated(const_names) { + return Err(OError::DuplicatedNames("constraints".to_string())); + } + Ok(Self { variables: variable_types, objectives, @@ -371,8 +388,8 @@ pub mod builtin_problems { use std::f64::consts::PI; use crate::core::{ - BoundedNumber, EvaluationResult, Evaluator, Individual, Objective, ObjectiveDirection, - OError, Problem, VariableType, + BoundedNumber, EvaluationResult, Evaluator, Individual, OError, Objective, + ObjectiveDirection, Problem, VariableType, }; /// The Schaffer’s study (SCH) problem. @@ -781,11 +798,11 @@ pub mod builtin_problems { #[cfg(test)] mod test { + use crate::core::utils::dummy_evaluator; use crate::core::{ BoundedNumber, Constraint, Objective, ObjectiveDirection, Problem, RelationalOperator, VariableType, }; - use crate::core::utils::dummy_evaluator; #[test] /// Test when objectives and constraints already exist diff --git a/optirustic/src/core/utils.rs b/optirustic/src/core/utils.rs index 91be70d..a453e22 100644 --- a/optirustic/src/core/utils.rs +++ b/optirustic/src/core/utils.rs @@ -1,15 +1,17 @@ +use std::collections::HashSet; use std::error::Error; +use std::hash::Hash; #[cfg(test)] use std::sync::Arc; use rand::{RngCore, SeedableRng}; use rand_chacha::ChaCha8Rng; +#[cfg(test)] +use crate::core::builtin_problems::ZTD1Problem; #[cfg(test)] use crate::core::{BoundedNumber, Objective, ObjectiveDirection, Problem, VariableType}; use crate::core::{EvaluationResult, Evaluator, Individual, OError}; -#[cfg(test)] -use crate::core::builtin_problems::ZTD1Problem; /// Get the random number generator. If no seed is provided, this randomly generated. /// @@ -107,6 +109,22 @@ pub fn argsort(data: &[f64], sort_type: Sort) -> Vec { indices } +/// Check whether a vector contains duplicated elements. +/// +/// # Arguments +/// +/// * `iter`: The iterator. +/// +/// returns: `bool` +pub fn has_duplicated(iter: T) -> bool +where + T: IntoIterator, + T::Item: Eq + Hash, +{ + let mut uniq = HashSet::new(); + !iter.into_iter().all(move |x| uniq.insert(x)) +} + /// Create the individuals for a `N`-objective dummy problem, where `N` is the number of items in /// the arrays of `objective_values`. /// diff --git a/optirustic/src/core/variable.rs b/optirustic/src/core/variable.rs index 355c307..28dfdf1 100644 --- a/optirustic/src/core/variable.rs +++ b/optirustic/src/core/variable.rs @@ -7,7 +7,7 @@ use rand::prelude::{IteratorRandom, SliceRandom}; use rand::Rng; use serde::{Deserialize, Serialize}; -use crate::core::{Individual, OError, Problem}; +use crate::core::{OError, Problem}; /// A trait to define a decision variable. pub trait Variable: Display { @@ -251,11 +251,6 @@ impl Display for VariableType { } } -/// A trait to allow generating a value for an individual. -pub trait VariableValueGenerator { - fn generate(individual: &Individual); -} - /// The value of a variable to set on an individual. #[derive(Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] @@ -315,13 +310,6 @@ impl VariableValue { } } -impl VariableValueGenerator for VariableValue { - fn generate(_individual: &Individual) { - // TODO loop variables - todo!() - } -} - impl Debug for VariableValue { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { diff --git a/optirustic/src/metrics/hypervolume.rs b/optirustic/src/metrics/hypervolume.rs index 98627ee..1f57bd0 100644 --- a/optirustic/src/metrics/hypervolume.rs +++ b/optirustic/src/metrics/hypervolume.rs @@ -1,7 +1,7 @@ -use crate::core::{Individual, Individuals, Objective, ObjectiveDirection, OError}; -use crate::core::utils::{vector_max, vector_min}; -use crate::metrics::{HyperVolumeFonseca2006, HyperVolumeWhile2012}; +use crate::core::utils::vector_max; +use crate::core::{Individual, Individuals, OError, Objective, ObjectiveDirection}; use crate::metrics::hypervolume_2d::HyperVolume2D; +use crate::metrics::{HyperVolumeFonseca2006, HyperVolumeWhile2012}; /// Calculates a reference point by taking the maximum of each objective (or minimum if the /// objective is maximised) from the calculated individual's objective values, so that the point will @@ -13,7 +13,7 @@ use crate::metrics::hypervolume_2d::HyperVolume2D; /// /// * `individuals`: The individuals to use in the calculation. /// * `offset`: The offset for each objective to add to the calculated reference point. This must -/// have a size equal to the number of objectives in the problem ([`crate::core::Problem::number_of_objectives`]). +/// have a size equal to the number of objectives in the problem ([`crate::core::Problem::number_of_objectives`]). /// /// returns: `Result, OError>` The reference point. This returns an error if there are /// no individuals or the size of the offset does not match [`crate::core::Problem::number_of_objectives`]. @@ -47,12 +47,14 @@ pub fn estimate_reference_point( let mut ref_point: Vec = Vec::new(); for obj_name in obj_names.iter() { let obj_values = individuals.objective_values(obj_name)?; - // get minimum if objective is minimised - let coordinate = if problem.is_objective_minimised(obj_name)? { - vector_max(&obj_values) + // invert coordinate when maximised to return original value + let factor = if problem.is_objective_minimised(obj_name)? { + 1.0 } else { - vector_min(&obj_values) - }?; + -1.0 + }; + let coordinate = factor * vector_max(&obj_values)?; + ref_point.push(coordinate); } @@ -177,19 +179,19 @@ pub(crate) fn check_ref_point_coordinate( /// objectives `n`, a different method is used to ensure a correct and fast calculation: /// /// - with `2` objectives: by calculating the rectangle area of each point contributing to the -/// hyper-volume. +/// hyper-volume. /// - with `3` objectives: by using the algorithm proposed by [Fonseca et al. (2006)](http://dx.doi.org/10.1109/CEC.2006.1688440) -/// in [`HyperVolumeFonseca2006`]. +/// in [`HyperVolumeFonseca2006`]. /// - with `4` or more objectives: by using the algorithm proposed by [While et al. (2012)](http://dx.doi.org/10.1109/TEVC.2010.2077298) -/// in [`HyperVolumeWhile2012`]. +/// in [`HyperVolumeWhile2012`]. /// /// # Arguments /// /// * `individuals`: The individuals to use in the calculation. The algorithm will use the objective -/// vales stored in each individual. +/// vales stored in each individual. /// * `reference_point`: The reference or anti-optimal point to use in the calculation. If you are -/// not sure about the point to use you could pick the worst value of each objective from the -/// individual's values using [`estimate_reference_point`]. +/// not sure about the point to use you could pick the worst value of each objective from the +/// individual's values using [`estimate_reference_point`]. /// /// returns: `Result` pub fn hyper_volume( @@ -233,10 +235,10 @@ pub fn hyper_volume( mod test { use std::sync::Arc; + use crate::core::utils::{dummy_evaluator, individuals_from_obj_values_ztd1}; use crate::core::{ BoundedNumber, Individual, Objective, ObjectiveDirection, Problem, VariableType, }; - use crate::core::utils::{dummy_evaluator, individuals_from_obj_values_ztd1}; use crate::metrics::hypervolume::estimate_reference_point; #[test] diff --git a/optirustic/src/metrics/hypervolume_2d.rs b/optirustic/src/metrics/hypervolume_2d.rs index df270df..7cf018b 100644 --- a/optirustic/src/metrics/hypervolume_2d.rs +++ b/optirustic/src/metrics/hypervolume_2d.rs @@ -1,6 +1,6 @@ use std::mem; -use crate::core::{Individual, Individuals, ObjectiveDirection, OError}; +use crate::core::{Individual, Individuals, OError, ObjectiveDirection}; use crate::metrics::hypervolume::{check_args, check_ref_point_coordinate}; use crate::utils::fast_non_dominated_sort; @@ -24,22 +24,22 @@ impl HyperVolume2D { /// **IMPLEMENTATION NOTES**: /// 1) The reference point must dominate the values of all objectives. /// 2) For problems with at least one maximised objective, this implementation ensures that the - /// Pareto front shape is strictly convex and has the same orientation of minimisation problems - /// by inverting the sign of the objective values to maximise, and the reference point - /// coordinates. + /// Pareto front shape is strictly convex and has the same orientation of minimisation problems + /// by inverting the sign of the objective values to maximise, and the reference point + /// coordinates. /// 3) Dominated and unfeasible solutions are excluded using the NSGA2 [`crate::utils::fast_non_dominated_sort()`] - /// algorithm in order to get the Pareto front (i.e. with non-dominated solutions) to use in - /// the calculation. + /// algorithm in order to get the Pareto front (i.e. with non-dominated solutions) to use in + /// the calculation. /// 4) If `individuals` or the resulting Pareto front does not contain more than 2 points, a - /// zero hyper-volume is returned. + /// zero hyper-volume is returned. /// /// # Arguments /// /// * `individuals`: The individuals to use in the calculation. The algorithm will use the - /// objective vales stored in each individual. + /// objective vales stored in each individual. /// * `reference_point`: The non-dominated reference or anti-optimal point to use in the - /// calculation. If you are not sure about the point to use, you could pick the worst value of - /// each objective from the individual's variable using [`crate::metrics::estimate_reference_point`]. + /// calculation. If you are not sure about the point to use, you could pick the worst value of + /// each objective from the individual's variable using [`crate::metrics::estimate_reference_point`]. /// /// returns: `Result` pub fn new(individuals: &mut [Individual], reference_point: &[f64]) -> Result { @@ -135,13 +135,13 @@ mod test { use float_cmp::{approx_eq, assert_approx_eq}; + use crate::core::utils::{ + dummy_evaluator, individuals_from_obj_values_dummy, individuals_from_obj_values_ztd1, + }; use crate::core::{ BoundedNumber, Constraint, Individual, Objective, ObjectiveDirection, Problem, RelationalOperator, VariableType, VariableValue, }; - use crate::core::utils::{ - dummy_evaluator, individuals_from_obj_values_dummy, individuals_from_obj_values_ztd1, - }; use crate::metrics::hypervolume_2d::HyperVolume2D; use crate::metrics::test_utils::parse_pagmo_test_data_file; @@ -160,12 +160,12 @@ mod test { let ref_point = [0.2, 20.0]; let hv = HyperVolume2D::new(&mut individuals, &ref_point); let err = hv.unwrap_err().to_string(); - assert!(err.contains("The coordinate #1 of the reference point (0.2) must be strictly larger than the maximum value of objective 'obj0'"), "{}", err); + assert!(err.contains("The coordinate (0.2) of the reference point #1 must be strictly larger than the maximum value of objective 'obj0'"), "{}", err); // y too small let ref_point = [20.0, 1.0]; let hv = HyperVolume2D::new(&mut individuals, &ref_point); let err = hv.unwrap_err().to_string(); - assert!(err.contains("The coordinate #2 of the reference point (1) must be strictly larger than the maximum value of objective 'obj1'"), "{}", err); + assert!(err.contains("The coordinate (1) of the reference point #2 must be strictly larger than the maximum value of objective 'obj1'"), "{}", err); // Maximise obj 1 - x too large let mut individuals = individuals_from_obj_values_dummy( @@ -175,7 +175,7 @@ mod test { let ref_point = [6.0, 20.0]; let hv = HyperVolume2D::new(&mut individuals, &ref_point); let err = hv.unwrap_err().to_string(); - assert!(err.contains("The coordinate #1 of the reference point (6) must be strictly smaller than the minimum value of objective 'obj0'"), "{}", err); + assert!(err.contains("The coordinate (6) of the reference point #1 must be strictly smaller than the minimum value of objective 'obj0'"), "{}", err); // Maximise obj 2 - y too large let mut individuals = individuals_from_obj_values_dummy( @@ -185,7 +185,7 @@ mod test { let ref_point = [20.0, 19.0]; let hv = HyperVolume2D::new(&mut individuals, &ref_point); let err = hv.unwrap_err().to_string(); - assert!(err.contains("The coordinate #2 of the reference point (19) must be strictly smaller than the minimum value of objective 'obj1'"), "{}", err); + assert!(err.contains("The coordinate (19) of the reference point #2 must be strictly smaller than the minimum value of objective 'obj1'"), "{}", err); } #[test] @@ -216,7 +216,7 @@ mod test { } #[test] - /// Two solution is dominated - this return the area of rectangle between ref point and min + /// Two solutions are dominated - this return the area of rectangle between ref point and min fn test_two_dominated_solutions() { let ref_point = [10.0, 10.0]; let obj_values = [vec![-1.0, 2.0], vec![0.5, 4.0], vec![0.0, 6.0]]; @@ -227,14 +227,15 @@ mod test { } #[test] - /// Two solution is dominated - cannot use fast sorting - fn test_one_solutions() { + #[should_panic] + /// Two individuals are needed - cannot use fast sorting + fn test_one_solution() { let ref_point = [10.0, 10.0]; let obj_values = [vec![-1.0, -2.0]]; let mut ind = individuals_from_obj_values_ztd1(&obj_values); let hv = HyperVolume2D::new(&mut ind, &ref_point); - assert_eq!(hv.unwrap().compute(), 0.0); + hv.unwrap(); } #[test] diff --git a/optirustic/src/metrics/hypervolume_fonseca_2006.rs b/optirustic/src/metrics/hypervolume_fonseca_2006.rs index d1dd007..9647ba7 100644 --- a/optirustic/src/metrics/hypervolume_fonseca_2006.rs +++ b/optirustic/src/metrics/hypervolume_fonseca_2006.rs @@ -15,10 +15,10 @@ use crate::utils::fast_non_dominated_sort; /// **IMPLEMENTATION NOTES**: /// 1) Points dominated by the reference point are removed from the calculation. /// 2) Dominated and unfeasible solutions are excluded using the NSGA2 [`crate::utils::fast_non_dominated_sort()`] -/// algorithm in order to get the Pareto front. As assumed in the paper, non-dominated points do -/// not contribute do the metric. +/// algorithm in order to get the Pareto front. As assumed in the paper, non-dominated points do +/// not contribute do the metric. /// 3) The coordinates of maximised objectives of the reference point are multiplied by -1 as the -/// algorithm assumes all objectives are maximised. +/// algorithm assumes all objectives are maximised. #[derive(Debug)] pub struct HyperVolumeFonseca2006 { /// The individuals to use. The size of this vector corresponds to the individual size and the @@ -108,10 +108,10 @@ impl HyperVolumeFonseca2006 { mod test { use float_cmp::approx_eq; - use crate::core::ObjectiveDirection; use crate::core::utils::individuals_from_obj_values_dummy; - use crate::metrics::HyperVolumeFonseca2006; + use crate::core::ObjectiveDirection; use crate::metrics::test_utils::parse_pagmo_test_data_file; + use crate::metrics::HyperVolumeFonseca2006; #[test] /// Reference point must be strictly larger than any objective diff --git a/optirustic/src/metrics/hypervolume_while_2012.rs b/optirustic/src/metrics/hypervolume_while_2012.rs index 36be986..93c03e3 100644 --- a/optirustic/src/metrics/hypervolume_while_2012.rs +++ b/optirustic/src/metrics/hypervolume_while_2012.rs @@ -13,10 +13,10 @@ use crate::utils::fast_non_dominated_sort; /// /// **IMPLEMENTATION NOTES**: /// 1) Dominated and unfeasible solutions are excluded using the NSGA2 [`crate::utils::fast_non_dominated_sort()`] -/// algorithm in order to get the Pareto front. As assumed in the paper, non-dominated points do -/// not contribute do the metric. +/// algorithm in order to get the Pareto front. As assumed in the paper, non-dominated points do +/// not contribute do the metric. /// 2) The coordinates of maximised objectives of the reference point are multiplied by -1 as the -/// algorithm assumes all objectives are maximised. +/// algorithm assumes all objectives are maximised. #[derive(Debug)] pub struct HyperVolumeWhile2012 { /// The individuals to use. The size of this vector corresponds to the individual size and the @@ -102,8 +102,8 @@ impl HyperVolumeWhile2012 { mod test { use float_cmp::approx_eq; - use crate::core::ObjectiveDirection; use crate::core::utils::individuals_from_obj_values_dummy; + use crate::core::ObjectiveDirection; use crate::metrics::hypervolume_while_2012::HyperVolumeWhile2012; use crate::metrics::test_utils::parse_pagmo_test_data_file; diff --git a/optirustic/src/operators/comparison.rs b/optirustic/src/operators/comparison.rs index b3d9e24..8c547b8 100644 --- a/optirustic/src/operators/comparison.rs +++ b/optirustic/src/operators/comparison.rs @@ -36,7 +36,7 @@ pub trait BinaryComparisonOperator { /// constraint-dominated if: /// 1) $S_1$ is feasible but $S_2$ is not. /// 2) Both $S_1$ and $S_2$ are infeasible and $CV(S_1) < CV(S_2)$ (where $CV$ is the constraint -/// violation function); or +/// violation function); or /// 3) both are feasible and $S_1$ Pareto-dominate $S_2$ ($ S_1 \prec S_2 $). /// /// @@ -201,11 +201,11 @@ impl BinaryComparisonOperator for CrowdedComparison { mod test_pareto_constrained_dominance { use std::sync::Arc; + use crate::core::utils::dummy_evaluator; use crate::core::{ BoundedNumber, Constraint, Individual, Objective, ObjectiveDirection, Problem, RelationalOperator, VariableType, }; - use crate::core::utils::dummy_evaluator; use crate::operators::{ BinaryComparisonOperator, ParetoConstrainedDominance, PreferredSolution, }; @@ -478,13 +478,13 @@ mod test_pareto_constrained_dominance { mod test_crowded_comparison { use std::sync::Arc; + use crate::core::utils::dummy_evaluator; use crate::core::{ BoundedNumber, Individual, Objective, ObjectiveDirection, Problem, VariableType, VariableValue, }; - use crate::core::utils::dummy_evaluator; - use crate::operators::{BinaryComparisonOperator, PreferredSolution}; use crate::operators::comparison::CrowdedComparison; + use crate::operators::{BinaryComparisonOperator, PreferredSolution}; #[test] fn test_different_rank() { diff --git a/optirustic/src/operators/crossover.rs b/optirustic/src/operators/crossover.rs index 066bc1f..c240eb9 100644 --- a/optirustic/src/operators/crossover.rs +++ b/optirustic/src/operators/crossover.rs @@ -1,5 +1,5 @@ -use rand::{Rng, RngCore}; use rand::prelude::SliceRandom; +use rand::{Rng, RngCore}; use serde::{Deserialize, Serialize}; use crate::core::{Individual, OError, VariableType, VariableValue}; @@ -73,7 +73,7 @@ impl Default for SimulatedBinaryCrossoverArgs { /// /// and /// > the C implementation available at -/// to account for bounded variables. +/// > to account for bounded variables. /// /// See: , /// full text available at . An alternative @@ -155,7 +155,7 @@ impl SimulatedBinaryCrossover { /// # Arguments /// /// * `args.`: The operator input parameters. See [`SimulatedBinaryCrossoverArgs`] for a detail - /// explanation of the parameters. + /// explanation of the parameters. /// /// returns: `Result` pub fn new(args: SimulatedBinaryCrossoverArgs) -> Result { @@ -356,11 +356,11 @@ impl Crossover for SimulatedBinaryCrossover { mod test { use std::sync::Arc; + use crate::core::utils::{dummy_evaluator, get_rng}; use crate::core::{ BoundedNumber, Individual, Objective, ObjectiveDirection, Problem, VariableType, VariableValue, }; - use crate::core::utils::{dummy_evaluator, get_rng}; use crate::operators::{Crossover, SimulatedBinaryCrossover, SimulatedBinaryCrossoverArgs}; #[test] diff --git a/optirustic/src/operators/mutation.rs b/optirustic/src/operators/mutation.rs index e87e780..d4ac36e 100644 --- a/optirustic/src/operators/mutation.rs +++ b/optirustic/src/operators/mutation.rs @@ -84,7 +84,10 @@ impl PolynomialMutationArgs { /// fn main() -> Result<(), Box> { /// // create a new one-variable /// let objectives = vec![Objective::new("obj1", ObjectiveDirection::Minimise)]; -/// let variables = vec![VariableType::Real(BoundedNumber::new("var1", 0.0, 1000.0)?)]; +/// let variables = vec![ +/// VariableType::Real(BoundedNumber::new("var1", 0.0, 1000.0)?), +/// VariableType::Integer(BoundedNumber::new("var2", -1, 1)?) +/// ]; /// /// // dummy evaluator function /// #[derive(Debug)] @@ -235,11 +238,11 @@ impl Mutation for PolynomialMutation { mod test { use std::sync::Arc; + use crate::core::utils::{dummy_evaluator, get_rng}; use crate::core::{ BoundedNumber, Individual, Objective, ObjectiveDirection, Problem, VariableType, VariableValue, }; - use crate::core::utils::{dummy_evaluator, get_rng}; use crate::operators::{Mutation, PolynomialMutation, PolynomialMutationArgs}; #[test] diff --git a/optirustic/src/operators/selector.rs b/optirustic/src/operators/selector.rs index aa22a9c..4f5ffc2 100644 --- a/optirustic/src/operators/selector.rs +++ b/optirustic/src/operators/selector.rs @@ -62,7 +62,7 @@ impl TournamentSelector { /// # Arguments /// /// * `number_of_competitors`: The number of competitors in the tournament. Default to 2 - /// individuals. + /// individuals. /// /// returns: `TournamentSelector` pub fn new(number_of_competitors: usize) -> Self { diff --git a/optirustic/src/utils/fast_non_dominated_sort.rs b/optirustic/src/utils/fast_non_dominated_sort.rs index 17731a1..d2312d8 100644 --- a/optirustic/src/utils/fast_non_dominated_sort.rs +++ b/optirustic/src/utils/fast_non_dominated_sort.rs @@ -34,8 +34,8 @@ pub struct NonDominatedSortResults { /// /// * `individuals`: The individuals to sort by dominance. /// * `first_front_only`: Return the first front only with the rank 1 (i.e. containing only -/// non-dominated individuals). If you need only the first front set this to true to avoid -/// ranking the remaining individuals. +/// non-dominated individuals). If you need only the first front set this to true to avoid +/// ranking the remaining individuals. /// /// returns: `Result`. pub fn fast_non_dominated_sort( @@ -156,8 +156,8 @@ pub fn fast_non_dominated_sort( #[cfg(test)] mod test { - use crate::core::{ObjectiveDirection, VariableValue}; use crate::core::utils::individuals_from_obj_values_dummy; + use crate::core::{ObjectiveDirection, VariableValue}; use crate::utils::fast_non_dominated_sort; #[test] diff --git a/optirustic/src/utils/reference_points.rs b/optirustic/src/utils/reference_points.rs index fb6b4c1..cb3f1a7 100644 --- a/optirustic/src/utils/reference_points.rs +++ b/optirustic/src/utils/reference_points.rs @@ -40,7 +40,7 @@ fn binomial_coefficient(mut n: u64, k: u64) -> u64 { /// /// # Example /// ```rust -#[doc = include_str!("reference_points.rs")] +#[doc = include_str!("../../examples/reference_point.rs")] /// ``` pub struct DasDarren1998 { /// The number of problem objectives. @@ -57,7 +57,7 @@ impl DasDarren1998 { /// /// * `number_of_objectives`: The number of problem objectives. /// * `number_of_partitions`: The number of uniform gaps between two consecutive points along - /// all objective axis on the hyperplane. + /// all objective axis on the hyperplane. /// /// returns: `DasDarren1998` pub fn new(number_of_objectives: usize, number_of_partitions: usize) -> Self { @@ -107,7 +107,7 @@ impl DasDarren1998 { /// /// * `final_weights`: The vector with the final weights. /// * `weight`: The vector for a weight or reference point. This must have a size equal to the - /// number of objectives. + /// number of objectives. /// * `left_partitions`: The number of partition left to process for the objective. /// * `obj_index`: The objective index being process. /// @@ -303,7 +303,7 @@ mod test { assert_eq!(weights.len() as u64, m.number_of_points()); for (wi, exp_weight_coordinates) in expected_weights.iter().enumerate() { - assert_approx_array_eq(&weights[wi], exp_weight_coordinates, None); + assert_approx_array_eq(&weights[wi], exp_weight_coordinates, Some(0.001)); } }