From de8cbe54625a9cc13a0355ffa0c49dfe6c1a225f Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Fri, 31 May 2024 22:20:58 +0530 Subject: [PATCH 01/31] Initialized agemoea and igd --- include/ensmallen.hpp | 2 + include/ensmallen_bits/age_moea/agemoea.hpp | 456 ++++++++++++++++++ .../ensmallen_bits/utility/indicators/igd.hpp | 92 ++++ 3 files changed, 550 insertions(+) create mode 100644 include/ensmallen_bits/age_moea/agemoea.hpp create mode 100644 include/ensmallen_bits/utility/indicators/igd.hpp diff --git a/include/ensmallen.hpp b/include/ensmallen.hpp index bacc21026..dacbe44d4 100644 --- a/include/ensmallen.hpp +++ b/include/ensmallen.hpp @@ -67,6 +67,7 @@ #include "ensmallen_bits/utility/arma_traits.hpp" #include "ensmallen_bits/utility/indicators/epsilon.hpp" #include "ensmallen_bits/utility/indicators/igd_plus.hpp" +#include "ensmallen_bits/utility/indicators/igd.hpp" // Contains traits, must be placed before report callback. #include "ensmallen_bits/function.hpp" // TODO: should move to function/ @@ -111,6 +112,7 @@ #include "ensmallen_bits/katyusha/katyusha.hpp" #include "ensmallen_bits/lbfgs/lbfgs.hpp" #include "ensmallen_bits/lookahead/lookahead.hpp" +#include "ensmallen_bits/age_moea/agemoea.hpp" #include "ensmallen_bits/moead/moead.hpp" #include "ensmallen_bits/nsga2/nsga2.hpp" #include "ensmallen_bits/padam/padam.hpp" diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/age_moea/agemoea.hpp new file mode 100644 index 000000000..bdb308a59 --- /dev/null +++ b/include/ensmallen_bits/age_moea/agemoea.hpp @@ -0,0 +1,456 @@ +/** + * @file agemoea.hpp + * @author Satyam Shukla + * + * AGE-MOEA is a multi-objective optimization algorithm, widely used in + * many real-world applications. AGE-MOEA generates offsprings using + * crossover and mutation and then selects the next generation according + * to non-dominated-sorting and survival score comparison. + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#ifndef ENSMALLEN_AGEMOEA_AGEMOEA_HPP +#define ENSMALLEN_AGEMOEA_AGEMOEA_HPP + +namespace ens { + +/** + * This class implements the AGEMOEA algorithm. + * + * The algorithm works by generating a candidate population from a fixed + * starting point. At each stage of optimization, a new population of children + * is generated. This new population along with its predecessor is sorted using + * non-domination as the metric. Following this, the population is further + * segregated in fronts. A new population is generated from these fronts having + * size equal to that of the starting population. + * + * During evolution, two parents are randomly chosen using binary tournament + * selection. A pair of children are generated by crossing over these two + * candidates followed by mutation. + * + * The best front (Pareto optimal) is returned by the Optimize() method. + * + * For more information, see the following: + * + * @code + * @inproceedings{panichella2019adaptive, + * title={An adaptive evolutionary algorithm based on non-euclidean geometry for many-objective optimization}, + * author={Panichella, Annibale}, + * booktitle={Proceedings of the genetic and evolutionary computation conference}, + * pages={595--603}, + * year={2019} + * } + * @endcode + * + */ +class AGEMOEA +{ + public: + /** + * Constructor for the AGE-MOEA optimizer. + * + * The default values provided over here are not necessarily suitable for a + * given function. Therefore it is highly recommended to adjust the + * parameters according to the problem. + * + * @param populationSize The number of candidates in the population. + * This should be atleast 4 in size and a multiple of 4. + * @param maxGenerations The maximum number of generations allowed for NSGA-II. + * @param crossoverProb The probability that a crossover will occur. + * @param mutationProb The probability that a mutation will occur. + * @param mutationStrength The strength of the mutation. + * @param epsilon The minimum difference required to distinguish between + * candidate solutions. + * @param lowerBound Lower bound of the coordinates of the initial population. + * @param upperBound Upper bound of the coordinates of the initial population. + */ + AGEMOEA(const size_t populationSize = 100, + const size_t maxGenerations = 2000, + const double crossoverProb = 0.6, + const double mutationProb = 0.3, + const double mutationStrength = 1e-3, + const double distributionIndex = 20, + const double epsilon = 1e-6, + const arma::vec& lowerBound = arma::zeros(1, 1), + const arma::vec& upperBound = arma::ones(1, 1)); + + /** + * Constructor for the AGE-MOEA optimizer. This constructor provides an overload + * to use `lowerBound` and `upperBound` of type double. + * + * The default values provided over here are not necessarily suitable for a + * given function. Therefore it is highly recommended to adjust the + * parameters according to the problem. + * + * @param populationSize The number of candidates in the population. + * This should be atleast 4 in size and a multiple of 4. + * @param maxGenerations The maximum number of generations allowed for NSGA-II. + * @param crossoverProb The probability that a crossover will occur. + * @param mutationProb The probability that a mutation will occur. + * @param mutationStrength The strength of the mutation. + * @param epsilon The minimum difference required to distinguish between + * candidate solutions. + * @param lowerBound Lower bound of the coordinates of the initial population. + * @param upperBound Upper bound of the coordinates of the initial population. + */ + AGEMOEA(const size_t populationSize = 100, + const size_t maxGenerations = 2000, + const double crossoverProb = 0.6, + const double mutationProb = 0.3, + const double mutationStrength = 1e-3, + const double distributionIndex = 20, + const double epsilon = 1e-6, + const double lowerBound = 0, + const double upperBound = 1); + + /** + * Optimize a set of objectives. The initial population is generated using the + * starting point. The output is the best generated front. + * + * @tparam ArbitraryFunctionType std::tuple of multiple objectives. + * @tparam MatType Type of matrix to optimize. + * @tparam CallbackTypes Types of callback functions. + * @param objectives Vector of objective functions to optimize for. + * @param iterate Starting point. + * @param callbacks Callback functions. + * @return MatType::elem_type The minimum of the accumulated sum over the + * objective values in the best front. + */ + template + typename MatType::elem_type Optimize( + std::tuple& objectives, + MatType& iterate, + CallbackTypes&&... callbacks); + + //! Get the population size. + size_t PopulationSize() const { return populationSize; } + //! Modify the population size. + size_t& PopulationSize() { return populationSize; } + + //! Get the maximum number of generations. + size_t MaxGenerations() const { return maxGenerations; } + //! Modify the maximum number of generations. + size_t& MaxGenerations() { return maxGenerations; } + + //! Get the crossover rate. + double CrossoverRate() const { return crossoverProb; } + //! Modify the crossover rate. + double& CrossoverRate() { return crossoverProb; } + + //! Get the mutation probability. + double MutationProbability() const { return mutationProb; } + //! Modify the mutation probability. + double& MutationProbability() { return mutationProb; } + + //! Get the mutation strength. + double MutationStrength() const { return mutationStrength; } + //! Modify the mutation strength. + double& MutationStrength() { return mutationStrength; } + + //! Get the tolerance. + double Epsilon() const { return epsilon; } + //! Modify the tolerance. + double& Epsilon() { return epsilon; } + + //! Retrieve value of lowerBound. + const arma::vec& LowerBound() const { return lowerBound; } + //! Modify value of lowerBound. + arma::vec& LowerBound() { return lowerBound; } + + //! Retrieve value of upperBound. + const arma::vec& UpperBound() const { return upperBound; } + //! Modify value of upperBound. + arma::vec& UpperBound() { return upperBound; } + + //! Retrieve the Pareto optimal points in variable space. This returns an empty cube + //! until `Optimize()` has been called. + const arma::cube& ParetoSet() const { return paretoSet; } + + //! Retrieve the best front (the Pareto frontier). This returns an empty cube until + //! `Optimize()` has been called. + const arma::cube& ParetoFront() const { return paretoFront; } + + /** + * Retrieve the best front (the Pareto frontier). This returns an empty + * vector until `Optimize()` has been called. Note that this function is + * deprecated and will be removed in ensmallen 3.x! Use `ParetoFront()` + * instead. + */ + ens_deprecated const std::vector& Front() + { + if (rcFront.size() == 0) + { + // Match the old return format. + for (size_t i = 0; i < paretoFront.n_slices; ++i) + { + rcFront.push_back(arma::mat(paretoFront.slice(i))); + } + } + + return rcFront; + } + + private: + /** + * Evaluate objectives for the elite population. + * + * @tparam ArbitraryFunctionType std::tuple of multiple function types. + * @tparam MatType Type of matrix to optimize. + * @param population The elite population. + * @param objectives The set of objectives. + * @param calculatedObjectives Vector to store calculated objectives. + */ + template + typename std::enable_if::type + EvaluateObjectives(std::vector&, + std::tuple&, + std::vector >&); + + template + typename std::enable_if::type + EvaluateObjectives(std::vector& population, + std::tuple& objectives, + std::vector >& + calculatedObjectives); + + /** + * Reproduce candidates from the elite population to generate a new + * population. + * + * @tparam MatType Type of matrix to optimize. + * @param population The elite population. + * @param objectives The set of objectives. + * @param lowerBound Lower bound of the coordinates of the initial population. + * @param upperBound Upper bound of the coordinates of the initial population. + */ + template + void BinaryTournamentSelection(std::vector& population, + const MatType& lowerBound, + const MatType& upperBound); + + /** + * Crossover two parents to create a pair of new children. + * + * @tparam MatType Type of matrix to optimize. + * @param childA A newly generated candidate. + * @param childB Another newly generated candidate. + * @param parentA First parent from elite population. + * @param parentB Second parent from elite population. + */ + template + void Crossover(MatType& childA, + MatType& childB, + const MatType& parentA, + const MatType& parentB); + + /** + * Mutate the coordinates for a candidate. + * + * @tparam MatType Type of matrix to optimize. + * @param child The candidate whose coordinates are being modified. + * @param mutationRate The probablity of a mutation to occur. + * @param lowerBound Lower bound of the coordinates of the initial population. + * @param upperBound Upper bound of the coordinates of the initial population. + */ + template + void Mutate(MatType& candidate, + double mutationRate, + const MatType& lowerBound, + const MatType& upperBound); + + /** + * Sort the candidate population using their domination count and the set of + * dominated nodes. + * + * @tparam MatType Type of matrix to optimize. + * @param fronts The population is sorted into these Pareto fronts. The first + * front is the best, the second worse and so on. + * @param ranks The assigned ranks, used for crowding distance based sorting. + * @param calculatedObjectives The previously calculated objectives. + */ + template + void FastNonDominatedSort(std::vector>& fronts, + std::vector& ranks, + std::vector >& calculatedObjectives); + + /** + * Operator to check if one candidate Pareto-dominates the other. + * + * A candidate is said to dominate the other if it is at least as good as the + * other candidate for all the objectives and there exists at least one + * objective for which it is strictly better than the other candidate. + * + * @tparam MatType Type of matrix to optimize. + * @param calculatedObjectives The previously calculated objectives. + * @param candidateP The candidate being compared from the elite population. + * @param candidateQ The candidate being compared against. + * @return true if candidateP Pareto dominates candidateQ, otherwise, false. + */ + template + bool Dominates(std::vector>& calculatedObjectives, + size_t candidateP, + size_t candidateQ); + + /** + * Assigns Survival Score metric for sorting. + * + * @param front The previously generated Pareto fronts. + * @param index Index d of the non-dominated front. + * @param dimension The calculated + * @param calculatedObjectives The previously calculated objectives. + * @param survivalScore The Survival Score vector to be updated for each individual in the population. + */ + template + void SurvivalScoreAssignment(const std::vector& front, + size_t index, + size_t dimension, + std::vector>& calculatedObjectives, + std::vector& survivalScore); + + /** + * The operator used in the AGE-MOEA survival score based sorting. + * + * If a candidate has a lower rank then it is preferred. + * Otherwise, if the ranks are equal then the candidate with the larger + * Survival Score is preferred. + * + * @param idxP The index of the first cadidate from the elite population being + * sorted. + * @param idxQ The index of the second cadidate from the elite population + * being sorted. + * @param ranks The previously calculated ranks. + * @param survivalScore The Survival score for each individual in + * the population. + * @return true if the first candidate is preferred, otherwise, false. + */ + template + bool SurvivalScoreOperator(size_t idxP, + size_t idxQ, + const std::vector& ranks, + const std::vector& crowdingDistance); + + /** + * Normalizes the front given the extreme points in the current front. + * + * @tparam The type of population datapoints. + * @param population The current population. + * @param front The previously generated Pareto fronts. + * @param extreme The indexes of the extreme points in the front. + */ + template + void NormalizeFront(std::vector& population, + arma::rowvec& normalization, + const std::vector>& fronts, + const std::vector& extreme); + + /** + * Get the geometry information p of Lp norm (p > 0). + * + * @param population The current population. + * @param front The previously generated Pareto fronts. + * @param extreme The indexes of the extreme points in the front. + * @param m The no. of objectives. + * @return The variable p in the Lp norm that best fits the geometry of the current front. + */ + template + size_t GetGeometry(const std::vector& population, + const std::vector& front, + const std::vector& extreme, + size_t m); + + /** + * Finds the pairwise Lp distance between all the points in the front. + * + * @param population The current population. + * @param front The front of the current generation. + * @param dimension The calculated dimension of the front. + * @return A matrix containing the pairwise distance between all points in the front. + */ + arma::mat PairwiseDistance(std::vector& population, + const std::vector& front, + size_t dimension); + + /** + * Finding the indexes of the extreme points in the front. + * + * @tparam The type of population datapoints. + * @param population The current population. + * @param front The front of the current generation. + * @return A set of indexes for the extreme points. + */ + std::vector FindExtremePoints(std::vector& population, + const std::vector& front); + + /** + * + * Finding the distance of each point in the front from the line formed + * by pointA and pointB. + * + * @param population Reference to the current population. + * @param front The front of the current generation(indices of population). + * @param pointA The first point on the line. + * @param pointb The second point on the line. + */ + template + std::vector PointToLineDistance(const vector& population, + std::vector& front, + const MatType& pointA, + const MatType& pointB); + + //! The number of objectives being optimised for. + size_t numObjectives; + + //! The numbeer of variables used per objectives. + size_t numVariables; + + //! The number of candidates in the population. + size_t populationSize; + + //! Maximum number of generations before termination criteria is met. + size_t maxGenerations; + + //! Probability that crossover will occur. + double crossoverProb; + + //! Probability that mutation will occur. + double mutationProb; + + //! Strength of the mutation. + double mutationStrength; + + //! The tolerance for termination. + double epsilon; + + //! Lower bound of the initial swarm. + arma::vec lowerBound; + + //! Upper bound of the initial swarm. + arma::vec upperBound; + + //! The set of all the Pareto optimal points. + //! Stored after Optimize() is called. + arma::cube paretoSet; + + //! The set of all the Pareto optimal objective vectors. + //! Stored after Optimize() is called. + arma::cube paretoFront; + + //! A different representation of the Pareto front, for reverse compatibility + //! purposes. This can be removed when ensmallen 3.x is released! (Along + //! with `Front()`.) This is only populated when `Front()` is called. + std::vector rcFront; +}; + +} // namespace ens + +#endif diff --git a/include/ensmallen_bits/utility/indicators/igd.hpp b/include/ensmallen_bits/utility/indicators/igd.hpp new file mode 100644 index 000000000..139a1a209 --- /dev/null +++ b/include/ensmallen_bits/utility/indicators/igd.hpp @@ -0,0 +1,92 @@ +/** + * @file igd_plus.hpp + * @author Satyam Shukla + * + * Inverse Generational Distance Plus (IGD) indicator. + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#ifndef ENSMALLEN_INDICATORS_IGD_HPP +#define ENSMALLEN_INDICATORS_IGD_HPP + +namespace ens { + +/** + * The IGD indicator returns the average distance from each point in the reference + * front to the nearest point to it's solution. + * + * \f[ d(z,a) = \sqrt{\sum_{i = 1}^{n}(a_i - z_i)^2 \ } \ + * \f] + * + * For more information see: + * + * @code + * @inproceedings{coello2004study, + * title={A study of the parallelization of a coevolutionary multi-objective evolutionary algorithm}, + * author={Coello Coello, Carlos A and Reyes Sierra, Margarita}, + * booktitle={MICAI 2004: Advances in Artificial Intelligence: Third Mexican International Conference on Artificial Intelligence, Mexico City, Mexico, April 26-30, 2004. Proceedings 3}, + * pages={688--697}, + * year={2004}, + * organization={Springer} + * } + * @endcode + */ + class IGD + { + public: + /** + * Default constructor does nothing, but is required to satisfy the Indicator + * policy. + */ + IGD() { } + + /** + * Find the IGD value of the front with respect to the given reference + * front. + * + * @tparam CubeType The cube data type of front. + * @param front The given approximation front. + * @param referenceFront The given reference front. + * @param p The power constant in the distance formula. + * @return The IGD value of the front. + */ + template + static typename CubeType::elem_type Evaluate(const CubeType& front, + const CubeType& referenceFront, + double p) + { + // Convenience typedefs. + typedef typename CubeType::elem_type ElemType; + ElemType igd = 0; + for (size_t i = 0; i < referenceFront.n_slices; i++) + { + ElemType min = std::numeric_limits::max(); + for (size_t j = 0; j < front.n_slices; j++) + { + ElemType dist = 0; + for (size_t k = 0; k < front.slice(j).n_rows; k++) + { + ElemType z = referenceFront(k, 0, i); + ElemType a = front(k, 0, j); + // Assuming minimization of all objectives. + dist += std::pow(a - z, 2); + } + dist = std::sqrt(dist); + if (dist < min) + min = dist; + } + igd += std::pow(min,p); + } + igd /= referenceFront.n_slices; + igd = std::pow(igd, 1.0 / p); + return igd; + } + }; + +} // namespace ens + +#endif \ No newline at end of file From 745f741d8b766a65f690da71285d309de276b982 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sun, 2 Jun 2024 00:38:32 +0530 Subject: [PATCH 02/31] added SBX and normalization --- include/ensmallen_bits/age_moea/agemoea.hpp | 46 +- .../ensmallen_bits/age_moea/agemoea_impl.hpp | 680 ++++++++++++++++++ 2 files changed, 714 insertions(+), 12 deletions(-) create mode 100644 include/ensmallen_bits/age_moea/agemoea_impl.hpp diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/age_moea/agemoea.hpp index bdb308a59..b91d62f1d 100644 --- a/include/ensmallen_bits/age_moea/agemoea.hpp +++ b/include/ensmallen_bits/age_moea/agemoea.hpp @@ -75,6 +75,7 @@ class AGEMOEA const double mutationStrength = 1e-3, const double distributionIndex = 20, const double epsilon = 1e-6, + const double eta = 20, const arma::vec& lowerBound = arma::zeros(1, 1), const arma::vec& upperBound = arma::ones(1, 1)); @@ -104,6 +105,7 @@ class AGEMOEA const double mutationStrength = 1e-3, const double distributionIndex = 20, const double epsilon = 1e-6, + const double eta = 20, const double lowerBound = 0, const double upperBound = 1); @@ -148,6 +150,16 @@ class AGEMOEA //! Modify the mutation probability. double& MutationProbability() { return mutationProb; } + //! Retrieve value of the distribution index. + double DistributionIndex() const { return distributionIndex; } + //! Modify the value of the distribution index. + double& DistributionIndex() { return distributionIndex; } + + //! Retrieve value of eta. + double Eta() const { return eta; } + //! Modify the value of eta. + double& Eta() { return eta; } + //! Get the mutation strength. double MutationStrength() const { return mutationStrength; } //! Modify the mutation strength. @@ -306,13 +318,12 @@ class AGEMOEA * * @param front The previously generated Pareto fronts. * @param index Index d of the non-dominated front. - * @param dimension The calculated + * @param dimension The calculated dimension from grt geometry. * @param calculatedObjectives The previously calculated objectives. * @param survivalScore The Survival Score vector to be updated for each individual in the population. */ template void SurvivalScoreAssignment(const std::vector& front, - size_t index, size_t dimension, std::vector>& calculatedObjectives, std::vector& survivalScore); @@ -337,7 +348,7 @@ class AGEMOEA bool SurvivalScoreOperator(size_t idxP, size_t idxQ, const std::vector& ranks, - const std::vector& crowdingDistance); + const std::vector& survivalScore); /** * Normalizes the front given the extreme points in the current front. @@ -349,9 +360,9 @@ class AGEMOEA */ template void NormalizeFront(std::vector& population, - arma::rowvec& normalization, - const std::vector>& fronts, - const std::vector& extreme); + arma::colvec& normalization, + const std::vector& front, + const arma::Row& extreme); /** * Get the geometry information p of Lp norm (p > 0). @@ -376,7 +387,8 @@ class AGEMOEA * @param dimension The calculated dimension of the front. * @return A matrix containing the pairwise distance between all points in the front. */ - arma::mat PairwiseDistance(std::vector& population, + template + arma::mat& PairwiseDistance(std::vector& population, const std::vector& front, size_t dimension); @@ -388,7 +400,8 @@ class AGEMOEA * @param front The front of the current generation. * @return A set of indexes for the extreme points. */ - std::vector FindExtremePoints(std::vector& population, + template + arma::Row FindExtremePoints(std::vector& population, const std::vector& front); /** @@ -399,13 +412,13 @@ class AGEMOEA * @param population Reference to the current population. * @param front The front of the current generation(indices of population). * @param pointA The first point on the line. - * @param pointb The second point on the line. + * @param pointB The second point on the line. */ template - std::vector PointToLineDistance(const vector& population, + arma::rowvec PointToLineDistance(const std::vector& population, std::vector& front, - const MatType& pointA, - const MatType& pointB); + const arma::rowvec& pointA, + const arma::rowvec& pointB); //! The number of objectives being optimised for. size_t numObjectives; @@ -428,9 +441,15 @@ class AGEMOEA //! Strength of the mutation. double mutationStrength; + //! The crowding degree of the mutation. Higher value produces a mutant + //! resembling its parent. + double distributionIndex; + //! The tolerance for termination. double epsilon; + double eta; + //! Lower bound of the initial swarm. arma::vec lowerBound; @@ -453,4 +472,7 @@ class AGEMOEA } // namespace ens +// Include implementation. +#include "agemoea_impl.hpp" + #endif diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp new file mode 100644 index 000000000..4db765d6f --- /dev/null +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -0,0 +1,680 @@ +/** + * @file nsga2_impl.hpp + * @author Sayan Goswami + * @author Nanubala Gnana Sai + * + * Implementation of the NSGA-II algorithm. Used for multi-objective + * optimization problems on arbitrary functions. + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more Information. + */ + +#ifndef ENSMALLEN_AGEMOEA_AGEMOEA_IMPL_HPP +#define ENSMALLEN_AGEMoEA_AGEMOEA_IMPL_HPP + +#include "agemoea.hpp" +#include + +namespace ens { + +inline AGEMOEA::AGEMOEA(const size_t populationSize, + const size_t maxGenerations, + const double crossoverProb, + const double mutationProb, + const double mutationStrength, + const double distributionIndex, + const double epsilon, + const double eta, + const arma::vec& lowerBound, + const arma::vec& upperBound) : + numObjectives(0), + numVariables(0), + populationSize(populationSize), + maxGenerations(maxGenerations), + crossoverProb(crossoverProb), + mutationProb(mutationProb), + mutationStrength(mutationStrength), + distributionIndex(distributionIndex), + epsilon(epsilon), + eta(eta), + lowerBound(lowerBound), + upperBound(upperBound) +{ /* Nothing to do here. */ } + +inline AGEMOEA::AGEMOEA(const size_t populationSize, + const size_t maxGenerations, + const double crossoverProb, + const double mutationProb, + const double mutationStrength, + const double distributionIndex, + const double epsilon, + const double eta, + const double lowerBound, + const double upperBound) : + numObjectives(0), + numVariables(0), + populationSize(populationSize), + maxGenerations(maxGenerations), + crossoverProb(crossoverProb), + mutationProb(mutationProb), + mutationStrength(mutationStrength), + distributionIndex(distributionIndex), + epsilon(epsilon), + eta(eta), + lowerBound(lowerBound * arma::ones(1, 1)), + upperBound(upperBound * arma::ones(1, 1)) +{ /* Nothing to do here. */ } + +//! Optimize the function. +template +typename MatType::elem_type AGEMOEA::Optimize( + std::tuple& objectives, + MatType& iterateIn, + CallbackTypes&&... callbacks) +{ + // Make sure for evolution to work at least four candidates are present. + if (populationSize < 4 && populationSize % 4 != 0) + { + throw std::logic_error("AGEMOEA::Optimize(): population size should be at" + " least 4, and, a multiple of 4!"); + } + + // Convenience typedefs. + typedef typename MatType::elem_type ElemType; + typedef typename MatTypeTraits::BaseMatType BaseMatType; + + BaseMatType& iterate = (BaseMatType&) iterateIn; + + // Make sure that we have the methods that we need. Long name... + traits::CheckArbitraryFunctionTypeAPI(); + RequireDenseFloatingPointType(); + + // Check if lower bound is a vector of a single dimension. + if (lowerBound.n_rows == 1) + lowerBound = lowerBound(0, 0) * arma::ones(iterate.n_rows, iterate.n_cols); + + // Check if upper bound is a vector of a single dimension. + if (upperBound.n_rows == 1) + upperBound = upperBound(0, 0) * arma::ones(iterate.n_rows, iterate.n_cols); + + // Check the dimensions of lowerBound and upperBound. + assert(lowerBound.n_rows == iterate.n_rows && "The dimensions of " + "lowerBound are not the same as the dimensions of iterate."); + assert(upperBound.n_rows == iterate.n_rows && "The dimensions of " + "upperBound are not the same as the dimensions of iterate."); + + numObjectives = sizeof...(ArbitraryFunctionType); + numVariables = iterate.n_rows; + + // Cache calculated objectives. + std::vector > calculatedObjectives(populationSize); + + // Population size reserved to 2 * populationSize + 1 to accommodate + // for the size of intermediate candidate population. + std::vector population; + population.reserve(2 * populationSize + 1); + + // Pareto fronts, initialized during non-dominated sorting. + // Stores indices of population belonging to a certain front. + std::vector > fronts; + // Initialised in SurvivalScoreAssignment. + std::vector survivalScore; + // Initialised during non-dominated sorting. + std::vector ranks; + + //! Useful temporaries for float-like comparisons. + const BaseMatType castedLowerBound = arma::conv_to::from(lowerBound); + const BaseMatType castedUpperBound = arma::conv_to::from(upperBound); + + // Controls early termination of the optimization process. + bool terminate = false; + + // Generate the population based on a uniform distribution around the given + // starting point. + for (size_t i = 0; i < populationSize; i++) + { + population.push_back(arma::randu(iterate.n_rows, + iterate.n_cols) - 0.5 + iterate); + + // Constrain all genes to be within bounds. + population[i] = arma::min(arma::max(population[i], castedLowerBound), castedUpperBound); + } + + Info << "AGEMOEA initialized successfully. Optimization started." << std::endl; + + // Iterate until maximum number of generations is obtained. + Callback::BeginOptimization(*this, objectives, iterate, callbacks...); + + for (size_t generation = 1; generation <= maxGenerations && !terminate; generation++) + { + Info << "AGEMOEA: iteration " << generation << "." << std::endl; + + // Create new population of candidate from the present elite population. + // Have P_t, generate G_t using P_t. + BinaryTournamentSelection(population, castedLowerBound, castedUpperBound); + + // Evaluate the objectives for the new population. + calculatedObjectives.resize(population.size()); + std::fill(calculatedObjectives.begin(), calculatedObjectives.end(), + arma::Col(numObjectives, arma::fill::zeros)); + EvaluateObjectives(population, objectives, calculatedObjectives); + + // Perform fast non dominated sort on P_t ∪ G_t. + ranks.resize(population.size()); + FastNonDominatedSort(fronts, ranks, calculatedObjectives); + + // Perform survival score assignment. + survivalScore.resize(population.size()); + std::fill(survivalScore.begin(), survivalScore.end(), 0.); + for (size_t fNum = 0; fNum < fronts.size(); fNum++) + { + SurvivalScoreAssignment( + fronts[fNum], calculatedObjectives, survivalScore); + } + + // Sort based on survival score. + std::sort(population.begin(), population.end(), + [this, ranks, survivalScore, population] + (BaseMatType candidateP, BaseMatType candidateQ) + { + size_t idxP{}, idxQ{}; + for (size_t i = 0; i < population.size(); i++) + { + if (arma::approx_equal(population[i], candidateP, "absdiff", epsilon)) + idxP = i; + + if (arma::approx_equal(population[i], candidateQ, "absdiff", epsilon)) + idxQ = i; + } + + return SurvivalScoreOperator(idxP, idxQ, ranks, survivalScore); + } + ); + + // Yield a new population P_{t+1} of size populationSize. + // Discards unfit population from the R_{t} to yield P_{t+1}. + population.resize(populationSize); + + terminate |= Callback::GenerationalStepTaken(*this, objectives, iterate, + calculatedObjectives, fronts, callbacks...); + } + + // Set the candidates from the Pareto Set as the output. + paretoSet.set_size(population[0].n_rows, population[0].n_cols, fronts[0].size()); + // The Pareto Set is stored, can be obtained via ParetoSet() getter. + for (size_t solutionIdx = 0; solutionIdx < fronts[0].size(); ++solutionIdx) + { + paretoSet.slice(solutionIdx) = + arma::conv_to::from(population[fronts[0][solutionIdx]]); + } + + // Set the candidates from the Pareto Front as the output. + paretoFront.set_size(calculatedObjectives[0].n_rows, calculatedObjectives[0].n_cols, + fronts[0].size()); + // The Pareto Front is stored, can be obtained via ParetoFront() getter. + for (size_t solutionIdx = 0; solutionIdx < fronts[0].size(); ++solutionIdx) + { + paretoFront.slice(solutionIdx) = + arma::conv_to::from(calculatedObjectives[fronts[0][solutionIdx]]); + } + + // Clear rcFront, in case it is later requested by the user for reverse + // compatibility reasons. + rcFront.clear(); + + // Assign iterate to first element of the Pareto Set. + iterate = population[fronts[0][0]]; + + Callback::EndOptimization(*this, objectives, iterate, callbacks...); + + ElemType performance = std::numeric_limits::max(); + + for (const arma::Col& objective: calculatedObjectives) + if (arma::accu(objective) < performance) + performance = arma::accu(objective); + + return performance; +} + +//! No objectives to evaluate. +template +typename std::enable_if::type +AGEMOEA::EvaluateObjectives( + std::vector&, + std::tuple&, + std::vector >&) +{ + // Nothing to do here. +} + +//! Evaluate the objectives for the entire population. +template +typename std::enable_if::type +AGEMOEA::EvaluateObjectives( + std::vector& population, + std::tuple& objectives, + std::vector >& calculatedObjectives) +{ + for (size_t i = 0; i < populationSize; i++) + { + calculatedObjectives[i](I) = std::get(objectives).Evaluate(population[i]); + EvaluateObjectives(population, objectives, + calculatedObjectives); + } +} + +//! Reproduce and generate new candidates. +template +inline void AGEMOEA::BinaryTournamentSelection(std::vector& population, + const MatType& lowerBound, + const MatType& upperBound) +{ + std::vector children; + + while (children.size() < population.size()) + { + // Choose two random parents for reproduction from the elite population. + size_t indexA = arma::randi(arma::distr_param(0, populationSize - 1)); + size_t indexB = arma::randi(arma::distr_param(0, populationSize - 1)); + + // Make sure that the parents differ. + if (indexA == indexB) + { + if (indexB < populationSize - 1) + indexB++; + else + indexB--; + } + + // Initialize the children to the respective parents. + MatType childA = population[indexA], childB = population[indexB]; + + if(arma::randu() <= crossoverProb) + Crossover(childA, childB, population[indexA], population[indexB]); + + Mutate(childA, 1.0 / static_cast(numVariables), + lowerBound, upperBound); + Mutate(childB, 1.0 / static_cast(numVariables), + lowerBound, upperBound); + + // Add the children to the candidate population. + children.push_back(childA); + children.push_back(childB); + } + + // Add the candidates to the elite population. + population.insert(std::end(population), std::begin(children), std::end(children)); +} + +//! Perform simulated binary crossover (SBX) of genes for the children. +template +inline void AGEMOEA::Crossover(MatType& childA, + MatType& childB, + const MatType& parentA, + const MatType& parentB) +{ + // Ensure that parentA and parentB are of the same size. + arma::Cube parents(parentA.n_rows, parentA.n_cols, 2); + parents.slice(0) = parentA; + parents.slice(1) = parentB; + MatType& current_min = arma::min(parents, 2); + MatType& current_max = arma::max(parents, 2); + + + MatType& current_diff = current_max - current_min; + current_diff.clamp(1e-10, arma::datum::inf); + + MatType beta1 = 1 + 2.0 * (current_min - lowerBound) / current_diff; + MatType beta2 = 1 + 2.0 * (upperBound - current_max) / current_diff; + MatType alpha1 = 2 - arma::pow(beta1, -(eta + 1)); + MatType alpha2 = 2 - arma::pow(beta2, -(eta + 1)); + + arma::vec us(size(upperBound), arma::fill::randu); + arma::vec mask1 = us > (1.0 / alpha1); + arma::vec betaq1 = arma::pow(us % alpha1, 1 / (eta + 1)); + betaq1 = betaq1 % (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha1)), 1.0 / (eta + 1)) % mask1; + + arma::vec mask2 = us > (1.0 / alpha2); + arma::vec betaq2 = arma::pow(us % alpha2, 1 / (eta + 1)); + betaq2 = betaq2 * (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; + MatType c1 = 0.5 % ((current_min + current_max) - betaq1 % current_diff); + MatType c2 = 0.5 % ((current_min + current_max) + betaq2 % current_diff); + us = new arma::vec(size(upperBound), arma::fill::randu); + childA = parentA % (us <= 0.5); + childB = parentB % (us <= 0.5); + us = new arma::vec(size(upperBound), arma::fill::randu); + childA = childA + c1 % ((us <= 0.5) % (childA == 0)); + childA = childA + c2 % ((us > 0.5) % (childA == 0)); + childB = childB + c2 % ((us <= 0.5) % (childB == 0)); + childB = childB + c1 % ((us > 0.5) % (childB == 0)); +} + +//! Perform Polynomial mutation of the candidate. +template +inline void AGEMOEA::Mutate(MatType& candidate, + double mutationRate, + const MatType& lowerBound, + const MatType& upperBound) +{ + const size_t numVariables = candidate.n_rows; + for (size_t geneIdx = 0; geneIdx < numVariables; ++geneIdx) + { + // Should this gene be mutated? + if (arma::randu() > mutationRate) + continue; + + const double geneRange = upperBound(geneIdx) - lowerBound(geneIdx); + // Normalised distance from the bounds. + const double lowerDelta = (candidate(geneIdx) - lowerBound(geneIdx)) / geneRange; + const double upperDelta = (upperBound(geneIdx) - candidate(geneIdx)) / geneRange; + const double mutationPower = 1. / (distributionIndex + 1.0); + const double rand = arma::randu(); + double value, perturbationFactor; + if (rand < 0.5) + { + value = 2.0 * rand + (1.0 - 2.0 * rand) * + std::pow(upperDelta, distributionIndex + 1.0); + perturbationFactor = std::pow(value, mutationPower) - 1.0; + } + else + { + value = 2.0 * (1.0 - rand) + 2.0 *(rand - 0.5) * + std::pow(lowerDelta, distributionIndex + 1.0); + perturbationFactor = 1.0 - std::pow(value, mutationPower); + } + + candidate(geneIdx) += perturbationFactor * geneRange; + } + //! Enforce bounds. + candidate = arma::min(arma::max(candidate, lowerBound), upperBound); +} + +template +void AGEMOEA::NormalizeFront(std::vector& population, + arma::colvec& normalization, + const std::vector& front, + const arma::Row& extreme) +{ + arma::Mat vectorizedFront(numVariables, front.size()); + arma::Col temp; + for (size_t i = 0; i< front.size(); i++) + { + vectorizedFront.col(i) = arma::vectorise(population[front[i]], 0); + } + arma::uvec unique = arma::find_unique(extreme); + if (extreme.n_elem != unique.n_elem) + { + normalization = arma::max(vectorizedFront, 1); + } + arma::colvec hyperplane = arma::solve(vectorizedFront, new arma::colvec(numVariables, arma::fill::ones)); + if (hyperplane.has_inf() || hyperplane.has_nan() || arma::accu(hyperplane < 0.0) > 0) + { + normalization = arma::max(vectorizedFront, 1); + } + normalization = 1. / hyperplane; + if (hyperplane.has_inf() || hyperplane.has_nan()) + { + normalization = arma::max(vectorizedFront, 1); + } + normalization = normalization + (normalization == 0); + vectorizedFront.each_col( [normalization](arma::vec& a){ a = a / normalization; } ); + for (size_t i = 0; i< front.size(); i++) + { + temp = vectorizedFront.col(i); + population[front[i]] = temp.reshape(arma::size(population[0])); + } +} + +template +inline size_t AGEMOEA::GetGeometry(const std::vector& population, + const std::vector& front, + const std::vector& extreme, + size_t m) +{ + arma::rowvec d = PointToLineDistance (population, front, + new arma::rowvec(numVariables, arma::fill::zeros), + new arma::rowvec(numVariables, arma::fill::ones)); + for (size_t i = 0; i < extreme.size(); i++) + { + d[extreme[i]] = arma::datum::inf; + } + size_t index = arma::index_min(d); + double avg = arma::accu(population[front[index]]) / static_cast (numVariables); + double p = std::log(m) / std::log(1.0 / avg); + if (p <= 0.1 || std::isnan(p)) {p = 1.0;} + return p; +} + +//! Pairwise distance for each point in the given front. +template +inline arma::mat& AGEMOEA::PairwiseDistance(std::vector& population, + const std::vector& front, + size_t dimension) +{ + arma::mat& final = new arma::mat(front.size(),front.size(),arma::fill::zeros); + + + for (size_t i = 0; i < front.size(); i++) + { + for (size_t j = i + 1; i < front.size(); j++) + { + final(i, j) = std::pow(arma::accu(arma::pow(arma::abs(population[i] - population[j]), dimension)), 1.0 / dimension); + final(j, i) = final(i, j); + } + } + return final; +} + +//! Find the index of the of the extreme points in the given front. +template +inline arma::Row AGEMOEA::FindExtremePoints(std::vector& population, + const std::vector& front) +{ + if(numVariables >= front.size()) + { + return front; + } + + arma::mat W(numVariables, numVariables, arma::fill::eye); + W = W + 1e-6; + arma::Row indexes(numVariables); + std::vector selected(front.size()); + for (size_t i = 0; i < numVariables; i++) + { + arma::rowvec dists = PointToLineDistance(population, + front, new arma::rowvec(numVariables, arma::fill::zeros), W.row(i)); + for (size_t j = 0; j < front.size(); i++) + if (selected[j]){dists[i] = arma::datum::inf;} + indexes[i] = front[dists.index_min()]; + selected[dists.index_min()] = true; + } + return indexes; +} + +//! Find the distance of a front from a line formed by two points. +template +arma::rowvec AGEMOEA::PointToLineDistance(const std::vector& population, + std::vector& front, + const arma::rowvec& pointA, + const arma::rowvec& pointB) +{ + arma::rowvec distances(front.size()); + MatType ba = pointB - pointA; + MatType pa; + + for (size_t i = 0; i < front.size(); i++) + { + size_t ind = front[i]; + + //! Points can be matrices as well for ease of objective calculation. + //! Hence they may need to be vectorized row wise before + pa = (arma::vectorise(population[ind], 1) - pointA); + double t = arma::dot(pa, ba) / arma::dot(ba, ba); + distances[i] = arma::accu(arma::pow((pa - t % ba), 2)); + } + return distances; +} + +//! Sort population into Pareto fronts. +template +inline void AGEMOEA::FastNonDominatedSort( + std::vector >& fronts, + std::vector& ranks, + std::vector >& calculatedObjectives) +{ + std::map dominationCount; + std::map > dominated; + + // Reset and initialize fronts. + fronts.clear(); + fronts.push_back(std::vector()); + + for (size_t p = 0; p < populationSize; p++) + { + dominated[p] = std::set(); + dominationCount[p] = 0; + + for (size_t q = 0; q < populationSize; q++) + { + if (Dominates(calculatedObjectives, p, q)) + dominated[p].insert(q); + else if (Dominates(calculatedObjectives, q, p)) + dominationCount[p] += 1; + } + + if (dominationCount[p] == 0) + { + ranks[p] = 0; + fronts[0].push_back(p); + } + } + + size_t i = 0; + + while (!fronts[i].empty()) + { + std::vector nextFront; + + for (size_t p: fronts[i]) + { + for (size_t q: dominated[p]) + { + dominationCount[q]--; + + if (dominationCount[q] == 0) + { + ranks[q] = i + 1; + nextFront.push_back(q); + } + } + } + + i++; + fronts.push_back(nextFront); + } + // Remove the empty final set. + fronts.pop_back(); +} + +//! Check if a candidate Pareto dominates another candidate. +template +inline bool AGEMOEA::Dominates( + std::vector >& calculatedObjectives, + size_t candidateP, + size_t candidateQ) +{ + bool allBetterOrEqual = true; + bool atleastOneBetter = false; + size_t n_objectives = calculatedObjectives[0].n_elem; + + for (size_t i = 0; i < n_objectives; i++) + { + // P is worse than Q for the i-th objective function. + if (calculatedObjectives[candidateP](i) > calculatedObjectives[candidateQ](i)) + allBetterOrEqual = false; + + // P is better than Q for the i-th objective function. + else if (calculatedObjectives[candidateP](i) < calculatedObjectives[candidateQ](i)) + atleastOneBetter = true; + } + + return allBetterOrEqual && atleastOneBetter; +} + +//! Assign survival score to the population. +template +inline void AGEMOEA::SurvivalScoreAssignment( + const std::vector& front, + size_t dimension, + std::vector>& calculatedObjectives, + std::vector& survivalScore) +{ + // Convenience typedefs. + typedef typename MatType::elem_type ElemType; + + size_t fSize = front.size(); + // Stores the sorted indices of the fronts. + arma::uvec sortedIdx = arma::regspace(0, 1, fSize - 1); + + for (size_t m = 0; m < numObjectives; m++) + { + // Cache fValues of individuals for current objective. + arma::Col fValues(fSize); + std::transform(front.begin(), front.end(), fValues.begin(), + [&](const size_t& individual) + { + return calculatedObjectives[individual](m); + }); + + // Sort front indices by ascending fValues for current objective. + std::sort(sortedIdx.begin(), sortedIdx.end(), + [&](const size_t& frontIdxA, const size_t& frontIdxB) + { + return (fValues(frontIdxA) < fValues(frontIdxB)); + }); + + survivalScore[front[sortedIdx(0)]] = + std::numeric_limits::max(); + survivalScore[front[sortedIdx(fSize - 1)]] = + std::numeric_limits::max(); + ElemType minFval = fValues(sortedIdx(0)); + ElemType maxFval = fValues(sortedIdx(fSize - 1)); + ElemType scale = + std::abs(maxFval - minFval) == 0. ? 1. : std::abs(maxFval - minFval); + + for (size_t i = 1; i < fSize - 1; i++) + { + survivalScore[front[sortedIdx(i)]] += + (fValues(sortedIdx(i + 1)) - fValues(sortedIdx(i - 1))) / scale; + } + } +} + +//! Comparator for survival score based sorting. +template +inline bool AGEMOEA::SurvivalScoreOperator(size_t idxP, + size_t idxQ, + const std::vector& ranks, + const std::vector& survivalScore) +{ + if (ranks[idxP] < ranks[idxQ]) + return true; + else if (ranks[idxP] == ranks[idxQ] && survivalScore[idxP] > survivalScore[idxQ]) + return true; + + return false; +} + +} // namespace ens + +#endif \ No newline at end of file From 5622db0c5f8f3cb6413634b66b3ce3eb949c65fb Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sun, 2 Jun 2024 11:26:23 +0530 Subject: [PATCH 03/31] polynomial mutation and error fixes --- include/ensmallen_bits/age_moea/agemoea_impl.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index 4db765d6f..5ea27b70f 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -349,10 +349,10 @@ inline void AGEMOEA::Crossover(MatType& childA, betaq2 = betaq2 * (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; MatType c1 = 0.5 % ((current_min + current_max) - betaq1 % current_diff); MatType c2 = 0.5 % ((current_min + current_max) + betaq2 % current_diff); - us = new arma::vec(size(upperBound), arma::fill::randu); + us.randu(); childA = parentA % (us <= 0.5); childB = parentB % (us <= 0.5); - us = new arma::vec(size(upperBound), arma::fill::randu); + us.randu(); childA = childA + c1 % ((us <= 0.5) % (childA == 0)); childA = childA + c2 % ((us > 0.5) % (childA == 0)); childB = childB + c2 % ((us <= 0.5) % (childB == 0)); @@ -461,7 +461,7 @@ inline arma::mat& AGEMOEA::PairwiseDistance(std::vector& population, const std::vector& front, size_t dimension) { - arma::mat& final = new arma::mat(front.size(),front.size(),arma::fill::zeros); + arma::mat final(front.size(),front.size(),arma::fill::zeros); for (size_t i = 0; i < front.size(); i++) From 21b084c0faa0bb3298512383b252a4a135338440 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Tue, 11 Jun 2024 22:08:34 +0530 Subject: [PATCH 04/31] Added Survival Score and optimization loop --- include/ensmallen_bits/age_moea/agemoea.hpp | 113 ++++--- .../ensmallen_bits/age_moea/agemoea_impl.hpp | 317 +++++++++++------- 2 files changed, 262 insertions(+), 168 deletions(-) diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/age_moea/agemoea.hpp index b91d62f1d..286e826f4 100644 --- a/include/ensmallen_bits/age_moea/agemoea.hpp +++ b/include/ensmallen_bits/age_moea/agemoea.hpp @@ -65,19 +65,20 @@ class AGEMOEA * @param mutationStrength The strength of the mutation. * @param epsilon The minimum difference required to distinguish between * candidate solutions. + * @param eta The distance parameters of the crossover distribution. * @param lowerBound Lower bound of the coordinates of the initial population. * @param upperBound Upper bound of the coordinates of the initial population. */ AGEMOEA(const size_t populationSize = 100, - const size_t maxGenerations = 2000, - const double crossoverProb = 0.6, - const double mutationProb = 0.3, - const double mutationStrength = 1e-3, - const double distributionIndex = 20, - const double epsilon = 1e-6, - const double eta = 20, - const arma::vec& lowerBound = arma::zeros(1, 1), - const arma::vec& upperBound = arma::ones(1, 1)); + const size_t maxGenerations = 2000, + const double crossoverProb = 0.6, + const double mutationProb = 0.3, + const double mutationStrength = 1e-3, + const double distributionIndex = 20, + const double epsilon = 1e-6, + const double eta = 20, + const arma::vec& lowerBound = arma::zeros(1, 1), + const arma::vec& upperBound = arma::ones(1, 1)); /** * Constructor for the AGE-MOEA optimizer. This constructor provides an overload @@ -95,19 +96,20 @@ class AGEMOEA * @param mutationStrength The strength of the mutation. * @param epsilon The minimum difference required to distinguish between * candidate solutions. + * @param eta The distance parameters of the crossover distribution * @param lowerBound Lower bound of the coordinates of the initial population. * @param upperBound Upper bound of the coordinates of the initial population. */ AGEMOEA(const size_t populationSize = 100, - const size_t maxGenerations = 2000, - const double crossoverProb = 0.6, - const double mutationProb = 0.3, - const double mutationStrength = 1e-3, - const double distributionIndex = 20, - const double epsilon = 1e-6, - const double eta = 20, - const double lowerBound = 0, - const double upperBound = 1); + const size_t maxGenerations = 2000, + const double crossoverProb = 0.6, + const double mutationProb = 0.3, + const double mutationStrength = 1e-3, + const double distributionIndex = 20, + const double epsilon = 1e-6, + const double eta = 20, + const double lowerBound = 0, + const double upperBound = 1); /** * Optimize a set of objectives. The initial population is generated using the @@ -324,9 +326,11 @@ class AGEMOEA */ template void SurvivalScoreAssignment(const std::vector& front, - size_t dimension, - std::vector>& calculatedObjectives, - std::vector& survivalScore); + std::vector>& calculatedObjectives, + std::vector& survivalScore, + arma::Row extreme, + size_t dimension, + size_t t); /** * The operator used in the AGE-MOEA survival score based sorting. @@ -354,71 +358,89 @@ class AGEMOEA * Normalizes the front given the extreme points in the current front. * * @tparam The type of population datapoints. - * @param population The current population. - * @param front The previously generated Pareto fronts. + * @param calculatedObjectives The current population evaluated objectives. + * @param normalization The normalizing vector. + * @param front The previously generated Pareto front. * @param extreme The indexes of the extreme points in the front. */ template - void NormalizeFront(std::vector& population, - arma::colvec& normalization, + void NormalizeFront( + std::vector >& calculatedObjectives, + arma::Col& normalization, const std::vector& front, const arma::Row& extreme); /** * Get the geometry information p of Lp norm (p > 0). * - * @param population The current population. + * @param calculatedObjectives The current population evaluated objectives. * @param front The previously generated Pareto fronts. * @param extreme The indexes of the extreme points in the front. - * @param m The no. of objectives. * @return The variable p in the Lp norm that best fits the geometry of the current front. */ template - size_t GetGeometry(const std::vector& population, + size_t GetGeometry( + std::vector >& calculatedObjectives, const std::vector& front, - const std::vector& extreme, - size_t m); + const arma::Row& extreme); /** * Finds the pairwise Lp distance between all the points in the front. * - * @param population The current population. + * @param final The current population evaluated objectives. + * @param calculatedObjectives The current population evaluated objectives. * @param front The front of the current generation. * @param dimension The calculated dimension of the front. * @return A matrix containing the pairwise distance between all points in the front. */ template - arma::mat& PairwiseDistance(std::vector& population, - const std::vector& front, - size_t dimension); + void PairwiseDistance(MatType& final, + std::vector >& calculatedObjectives, + const std::vector& front, + size_t dimension); /** * Finding the indexes of the extreme points in the front. * - * @tparam The type of population datapoints. - * @param population The current population. + * @param indexes vector containing the slected indexes. + * @param calculatedObjectives The current population objectives. * @param front The front of the current generation. * @return A set of indexes for the extreme points. */ template - arma::Row FindExtremePoints(std::vector& population, - const std::vector& front); + void FindExtremePoints(arma::Row& indexes, + std::vector >& calculatedObjectives, + const std::vector& front); /** - * * Finding the distance of each point in the front from the line formed * by pointA and pointB. * - * @param population Reference to the current population. + * @param distance The vector containing the distances of the points in the fron from the line. + * @param calculatedObjectives Reference to the current population evaluated Objectives. * @param front The front of the current generation(indices of population). * @param pointA The first point on the line. * @param pointB The second point on the line. */ - template - arma::rowvec PointToLineDistance(const std::vector& population, - std::vector& front, - const arma::rowvec& pointA, - const arma::rowvec& pointB); + template + void PointToLineDistance(arma::rowvec& distances, + std::vector >& calculatedObjectives, + std::vector& front, + const arma::colvec& pointA, + const arma::colvec& pointB); + + /** + * Find the Diversity score corresponding the solution S using the selected set. + * + * @param selected The current selected set. + * @param pairwiseDistance The current pairwise distance for the whole front. + * @param S The relative index of S being considered within the front. + * @return The diversity score for S which the sum of the two smallest elements. + */ + template + typename MatType::elem_type DiversityScore(std::set& selected, + const MatType& pairwiseDistance, + size_t S); //! The number of objectives being optimised for. size_t numObjectives; @@ -448,6 +470,7 @@ class AGEMOEA //! The tolerance for termination. double epsilon; + //! The distance parameters of the crossover distribution. double eta; //! Lower bound of the initial swarm. diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index 5ea27b70f..ab4afb64b 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -1,9 +1,8 @@ /** - * @file nsga2_impl.hpp - * @author Sayan Goswami - * @author Nanubala Gnana Sai + * @file agemoea_impl.hpp + * @author Satyam Shukla * - * Implementation of the NSGA-II algorithm. Used for multi-objective + * Implementation of the AGEMOEA algorithm. Used for multi-objective * optimization problems on arbitrary functions. * * ensmallen is free software; you may redistribute it and/or modify it under @@ -13,7 +12,7 @@ */ #ifndef ENSMALLEN_AGEMOEA_AGEMOEA_IMPL_HPP -#define ENSMALLEN_AGEMoEA_AGEMOEA_IMPL_HPP +#define ENSMALLEN_AGEMOEA_AGEMOEA_IMPL_HPP #include "agemoea.hpp" #include @@ -21,15 +20,15 @@ namespace ens { inline AGEMOEA::AGEMOEA(const size_t populationSize, - const size_t maxGenerations, - const double crossoverProb, - const double mutationProb, - const double mutationStrength, - const double distributionIndex, - const double epsilon, - const double eta, - const arma::vec& lowerBound, - const arma::vec& upperBound) : + const size_t maxGenerations, + const double crossoverProb, + const double mutationProb, + const double mutationStrength, + const double distributionIndex, + const double epsilon, + const double eta, + const arma::vec& lowerBound, + const arma::vec& upperBound) : numObjectives(0), numVariables(0), populationSize(populationSize), @@ -45,15 +44,15 @@ inline AGEMOEA::AGEMOEA(const size_t populationSize, { /* Nothing to do here. */ } inline AGEMOEA::AGEMOEA(const size_t populationSize, - const size_t maxGenerations, - const double crossoverProb, - const double mutationProb, - const double mutationStrength, - const double distributionIndex, - const double epsilon, - const double eta, - const double lowerBound, - const double upperBound) : + const size_t maxGenerations, + const double crossoverProb, + const double mutationProb, + const double mutationStrength, + const double distributionIndex, + const double epsilon, + const double eta, + const double lowerBound, + const double upperBound) : numObjectives(0), numVariables(0), populationSize(populationSize), @@ -169,13 +168,37 @@ typename MatType::elem_type AGEMOEA::Optimize( ranks.resize(population.size()); FastNonDominatedSort(fronts, ranks, calculatedObjectives); + arma::Col idealPoint(calculatedObjectives[0]); + for (arma::Col c: calculatedObjectives) + { + idealPoint = arma::min(idealPoint, c); + } + + for (arma::Col& c: calculatedObjectives) + { + c = c - idealPoint; + } + + arma::Row extreme(numObjectives, arma::fill::zeros); + FindExtremePoints(extreme, calculatedObjectives, fronts[0]); + arma::Col& normalize; + NormalizeFront(calculatedObjectives, normalize, fronts[0], extreme); + + for (arma::Col& c: calculatedObjectives) + { + c = c / normalize; + } + + size_t dimension = GetGeometry(calculatedObjectives, fronts[0], + extreme); + // Perform survival score assignment. survivalScore.resize(population.size()); std::fill(survivalScore.begin(), survivalScore.end(), 0.); for (size_t fNum = 0; fNum < fronts.size(); fNum++) { SurvivalScoreAssignment( - fronts[fNum], calculatedObjectives, survivalScore); + fronts[fNum], calculatedObjectives, survivalScore, extreme, dimension); } // Sort based on survival score. @@ -300,7 +323,7 @@ inline void AGEMOEA::BinaryTournamentSelection(std::vector& population, MatType childA = population[indexA], childB = population[indexB]; if(arma::randu() <= crossoverProb) - Crossover(childA, childB, population[indexA], population[indexB]); + Crossover(childA, childB, population[indexA], population[indexB]); Mutate(childA, 1.0 / static_cast(numVariables), lowerBound, upperBound); @@ -323,32 +346,38 @@ inline void AGEMOEA::Crossover(MatType& childA, const MatType& parentA, const MatType& parentB) { - // Ensure that parentA and parentB are of the same size. + //! Generates a child from two parent individuals + // according to the polynomial probability distribution. arma::Cube parents(parentA.n_rows, parentA.n_cols, 2); parents.slice(0) = parentA; parents.slice(1) = parentB; - MatType& current_min = arma::min(parents, 2); - MatType& current_max = arma::max(parents, 2); + MatType current_min = arma::min(parents, 2); + MatType current_max = arma::max(parents, 2); - - MatType& current_diff = current_max - current_min; + MatType current_diff = current_max - current_min; current_diff.clamp(1e-10, arma::datum::inf); + //! Calculating beta used for the final crossover. MatType beta1 = 1 + 2.0 * (current_min - lowerBound) / current_diff; MatType beta2 = 1 + 2.0 * (upperBound - current_max) / current_diff; MatType alpha1 = 2 - arma::pow(beta1, -(eta + 1)); MatType alpha2 = 2 - arma::pow(beta2, -(eta + 1)); - arma::vec us(size(upperBound), arma::fill::randu); - arma::vec mask1 = us > (1.0 / alpha1); - arma::vec betaq1 = arma::pow(us % alpha1, 1 / (eta + 1)); + MatType us(arma::size(alpha1), arma::fill::randu); + MatType mask1 = us > (1.0 / alpha1); + MatType betaq1 = arma::pow(us % alpha1, 1. / (eta + 1)); betaq1 = betaq1 % (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha1)), 1.0 / (eta + 1)) % mask1; - arma::vec mask2 = us > (1.0 / alpha2); - arma::vec betaq2 = arma::pow(us % alpha2, 1 / (eta + 1)); + MatType mask2 = us > (1.0 / alpha2); + MatType betaq2 = arma::pow(us % alpha2, 1 / (eta + 1)); betaq2 = betaq2 * (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; + + //! Variables after the cross over for all of them. MatType c1 = 0.5 % ((current_min + current_max) - betaq1 % current_diff); MatType c2 = 0.5 % ((current_min + current_max) + betaq2 % current_diff); + + + //! Decision for the crossover between the two parents for each variable. us.randu(); childA = parentA % (us <= 0.5); childB = parentB % (us <= 0.5); @@ -400,129 +429,125 @@ inline void AGEMOEA::Mutate(MatType& candidate, } template -void AGEMOEA::NormalizeFront(std::vector& population, - arma::colvec& normalization, +inline void AGEMOEA::NormalizeFront( + std::vector >& calculatedObjectives, + arma::Col& normalization, const std::vector& front, const arma::Row& extreme) { - arma::Mat vectorizedFront(numVariables, front.size()); - arma::Col temp; - for (size_t i = 0; i< front.size(); i++) + arma::Mat vectorizedObjectives(numObjectives, front.size()); + for (size_t i = 0; i < front.size(); i++) { - vectorizedFront.col(i) = arma::vectorise(population[front[i]], 0); + vectorizedObjectives.col(i) = calculatedObjectives[front[i]]; } + arma::Col temp; arma::uvec unique = arma::find_unique(extreme); if (extreme.n_elem != unique.n_elem) { - normalization = arma::max(vectorizedFront, 1); + normalization = arma::max(vectorizedObjectives, 1); } - arma::colvec hyperplane = arma::solve(vectorizedFront, new arma::colvec(numVariables, arma::fill::ones)); + arma::Col hyperplane = arma::solve( + vectorizedObjectives.t(), new arma::colvec(front.size(), arma::fill::ones)); if (hyperplane.has_inf() || hyperplane.has_nan() || arma::accu(hyperplane < 0.0) > 0) { - normalization = arma::max(vectorizedFront, 1); + normalization = arma::max(vectorizedObjectives, 1); } normalization = 1. / hyperplane; if (hyperplane.has_inf() || hyperplane.has_nan()) { - normalization = arma::max(vectorizedFront, 1); + normalization = arma::max(vectorizedObjectives, 1); } normalization = normalization + (normalization == 0); - vectorizedFront.each_col( [normalization](arma::vec& a){ a = a / normalization; } ); - for (size_t i = 0; i< front.size(); i++) - { - temp = vectorizedFront.col(i); - population[front[i]] = temp.reshape(arma::size(population[0])); - } } template -inline size_t AGEMOEA::GetGeometry(const std::vector& population, +inline size_t AGEMOEA::GetGeometry( + std::vector >& calculatedObjectives, const std::vector& front, - const std::vector& extreme, - size_t m) + const arma::Row& extreme) { - arma::rowvec d = PointToLineDistance (population, front, - new arma::rowvec(numVariables, arma::fill::zeros), - new arma::rowvec(numVariables, arma::fill::ones)); + arma::Row d; + PointToLineDistance (d, calculatedObjectives, front, + new arma::colvec(numObjectives, arma::fill::zeros), + new arma::colvec(numObjectives, arma::fill::ones)); for (size_t i = 0; i < extreme.size(); i++) { d[extreme[i]] = arma::datum::inf; } size_t index = arma::index_min(d); - double avg = arma::accu(population[front[index]]) / static_cast (numVariables); - double p = std::log(m) / std::log(1.0 / avg); + double avg = arma::accu(calculatedObjectives[front[index]]) / static_cast (numObjectives); + double p = std::log(numObjectives) / std::log(1.0 / avg); if (p <= 0.1 || std::isnan(p)) {p = 1.0;} return p; } //! Pairwise distance for each point in the given front. template -inline arma::mat& AGEMOEA::PairwiseDistance(std::vector& population, +inline void AGEMOEA::PairwiseDistance(MatType& final, + std::vector >& calculatedObjectives, const std::vector& front, size_t dimension) { - arma::mat final(front.size(),front.size(),arma::fill::zeros); - + final.zeros(front.size(),front.size()); for (size_t i = 0; i < front.size(); i++) { for (size_t j = i + 1; i < front.size(); j++) { - final(i, j) = std::pow(arma::accu(arma::pow(arma::abs(population[i] - population[j]), dimension)), 1.0 / dimension); + final(i, j) = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]] - calculatedObjectives[front[j]]), dimension)), 1.0 / dimension); final(j, i) = final(i, j); } } - return final; } //! Find the index of the of the extreme points in the given front. template -inline arma::Row AGEMOEA::FindExtremePoints(std::vector& population, - const std::vector& front) +void AGEMOEA::FindExtremePoints(arma::Row& indexes, + std::vector >& calculatedObjectives, + const std::vector& front) { - if(numVariables >= front.size()) + if(numObjectives >= front.size()) { - return front; + indexes = arma::linspace(front.size() - 1, 0, front.size()); + return; } - arma::mat W(numVariables, numVariables, arma::fill::eye); + arma::mat W(numObjectives, numObjectives, arma::fill::eye); W = W + 1e-6; - arma::Row indexes(numVariables); std::vector selected(front.size()); - for (size_t i = 0; i < numVariables; i++) + arma::colvec z(numObjectives, arma::fill::zeros); + for (size_t i = 0; i < numObjectives; i++) { - arma::rowvec dists = PointToLineDistance(population, - front, new arma::rowvec(numVariables, arma::fill::zeros), W.row(i)); + arma::rowvec dists; + PointToLineDistance(dists, calculatedObjectives, front, z, W.col(i)); for (size_t j = 0; j < front.size(); i++) if (selected[j]){dists[i] = arma::datum::inf;} - indexes[i] = front[dists.index_min()]; + indexes[i] = dists.index_min(); selected[dists.index_min()] = true; } - return indexes; } //! Find the distance of a front from a line formed by two points. -template -arma::rowvec AGEMOEA::PointToLineDistance(const std::vector& population, +template +void AGEMOEA::PointToLineDistance(arma::rowvec& distances, + std::vector >& calculatedObjectives, std::vector& front, - const arma::rowvec& pointA, - const arma::rowvec& pointB) + const arma::colvec& pointA, + const arma::colvec& pointB) { - arma::rowvec distances(front.size()); - MatType ba = pointB - pointA; - MatType pa; + arma::rowvec distancesTemp(front.size()); + arma::colvec ba = pointB - pointA; + arma::colvec pa; for (size_t i = 0; i < front.size(); i++) { size_t ind = front[i]; - - //! Points can be matrices as well for ease of objective calculation. - //! Hence they may need to be vectorized row wise before - pa = (arma::vectorise(population[ind], 1) - pointA); + + pa = (calculatedObjectives[ind] - pointA); double t = arma::dot(pa, ba) / arma::dot(ba, ba); - distances[i] = arma::accu(arma::pow((pa - t % ba), 2)); + distancesTemp[i] = arma::accu(arma::pow((pa - t * ba), 2)); } - return distances; + distances = distancesTemp; } //! Sort population into Pareto fronts. @@ -611,51 +636,97 @@ inline bool AGEMOEA::Dominates( return allBetterOrEqual && atleastOneBetter; } +template +inline typename MatType::elem_type AGEMOEA::DiversityScore(std::set& selected, + const MatType& pairwiseDistance, + size_t S) +{ + typedef typename MatType::elem_type ElemType; + ElemType m = arma::datum::inf; + ElemType m1 = arma::datum::inf; + for (size_t i = 0; i < pairwiseDistance.n_cols; i++) + { + if(i == S){ continue;} + if (pairwiseDistance(S, i) < m) + { + m1 = m; + m = pairwiseDistance(S, i); + } + else if (pairwiseDistance(S, i) < m1) + { + m1 = pairwiseDistance(S, i); + } + } + return m + m1; +} + //! Assign survival score to the population. template inline void AGEMOEA::SurvivalScoreAssignment( const std::vector& front, - size_t dimension, std::vector>& calculatedObjectives, - std::vector& survivalScore) + std::vector& survivalScore, + arma::Row extreme, + size_t dimension, + size_t t) { - // Convenience typedefs. typedef typename MatType::elem_type ElemType; - - size_t fSize = front.size(); - // Stores the sorted indices of the fronts. - arma::uvec sortedIdx = arma::regspace(0, 1, fSize - 1); - - for (size_t m = 0; m < numObjectives; m++) + + if (t > 1) { - // Cache fValues of individuals for current objective. - arma::Col fValues(fSize); - std::transform(front.begin(), front.end(), fValues.begin(), - [&](const size_t& individual) - { - return calculatedObjectives[individual](m); - }); + for (size_t i = 0; i < front.size(); i++) + { + survivalScore[front[i]] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]]), dimension)), 1.0 / dimension); + } + } + + else + { + if (front.size() < numObjectives) + { + return; + } + std::set selected; + std::set remaining; + + + //!create the slecetd and remaining sets. + for (size_t index: extreme) + { + selected.insert(index); + survivalScore[front[index]] = arma::datum::inf; + } + for (size_t i = 0; i < front.size(); i++) + { + if (selected.count(i) == 0) + { + remaining.insert(i); + } + } - // Sort front indices by ascending fValues for current objective. - std::sort(sortedIdx.begin(), sortedIdx.end(), - [&](const size_t& frontIdxA, const size_t& frontIdxB) - { - return (fValues(frontIdxA) < fValues(frontIdxB)); - }); - - survivalScore[front[sortedIdx(0)]] = - std::numeric_limits::max(); - survivalScore[front[sortedIdx(fSize - 1)]] = - std::numeric_limits::max(); - ElemType minFval = fValues(sortedIdx(0)); - ElemType maxFval = fValues(sortedIdx(fSize - 1)); - ElemType scale = - std::abs(maxFval - minFval) == 0. ? 1. : std::abs(maxFval - minFval); - - for (size_t i = 1; i < fSize - 1; i++) + arma::Mat pairwise; + PairwiseDistance(pairwise,calculatedObjectives,front,dimension); + arma::Row proximity(front.size(), arma::fill::zeros); + arma::Row diversity(front.size(), arma::fill::zeros); + arma::Row value(front.size(), arma::fill::zeros); + for (size_t i = 0; i < front.size(); i++) { - survivalScore[front[sortedIdx(i)]] += - (fValues(sortedIdx(i + 1)) - fValues(sortedIdx(i - 1))) / scale; + proximity[i] = arma::norm(calculatedObjectives[front[i]], dimension); + } + + while (remaining.size() > 0) + { + std::set::iterator it; + for (it = remaining.begin(); it != remaining.end(); it++) + { + diversity[*it] = DiversityScore(selected, pairwise, *it); + value[*it] = diversity[*it] / proximity[*it]; + } + size_t index = arma::index_max(value); + survivalScore[front[index]] = value[index]; + value[index] = 0; + selected.insert(index); + remaining.erase(index); } } } From 5e44e557aa7a9fb3dfa0e9421dc6725d4fac19aa Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Tue, 11 Jun 2024 22:12:25 +0530 Subject: [PATCH 05/31] fixed typos --- include/ensmallen_bits/age_moea/agemoea.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/age_moea/agemoea.hpp index 286e826f4..2ceabc58d 100644 --- a/include/ensmallen_bits/age_moea/agemoea.hpp +++ b/include/ensmallen_bits/age_moea/agemoea.hpp @@ -196,7 +196,7 @@ class AGEMOEA * deprecated and will be removed in ensmallen 3.x! Use `ParetoFront()` * instead. */ - ens_deprecated const std::vector& Front() + const std::vector& Front() { if (rcFront.size() == 0) { From ce6a64c233254a8be60f5585e4bfbd360f0b65df Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sat, 15 Jun 2024 23:07:47 +0530 Subject: [PATCH 06/31] error fixes --- include/ensmallen_bits/age_moea/agemoea.hpp | 28 ++----- .../ensmallen_bits/age_moea/agemoea_impl.hpp | 78 ++++++++----------- 2 files changed, 39 insertions(+), 67 deletions(-) diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/age_moea/agemoea.hpp index 2ceabc58d..b3525b178 100644 --- a/include/ensmallen_bits/age_moea/agemoea.hpp +++ b/include/ensmallen_bits/age_moea/agemoea.hpp @@ -61,8 +61,6 @@ class AGEMOEA * This should be atleast 4 in size and a multiple of 4. * @param maxGenerations The maximum number of generations allowed for NSGA-II. * @param crossoverProb The probability that a crossover will occur. - * @param mutationProb The probability that a mutation will occur. - * @param mutationStrength The strength of the mutation. * @param epsilon The minimum difference required to distinguish between * candidate solutions. * @param eta The distance parameters of the crossover distribution. @@ -72,8 +70,6 @@ class AGEMOEA AGEMOEA(const size_t populationSize = 100, const size_t maxGenerations = 2000, const double crossoverProb = 0.6, - const double mutationProb = 0.3, - const double mutationStrength = 1e-3, const double distributionIndex = 20, const double epsilon = 1e-6, const double eta = 20, @@ -92,8 +88,6 @@ class AGEMOEA * This should be atleast 4 in size and a multiple of 4. * @param maxGenerations The maximum number of generations allowed for NSGA-II. * @param crossoverProb The probability that a crossover will occur. - * @param mutationProb The probability that a mutation will occur. - * @param mutationStrength The strength of the mutation. * @param epsilon The minimum difference required to distinguish between * candidate solutions. * @param eta The distance parameters of the crossover distribution @@ -103,8 +97,6 @@ class AGEMOEA AGEMOEA(const size_t populationSize = 100, const size_t maxGenerations = 2000, const double crossoverProb = 0.6, - const double mutationProb = 0.3, - const double mutationStrength = 1e-3, const double distributionIndex = 20, const double epsilon = 1e-6, const double eta = 20, @@ -147,11 +139,6 @@ class AGEMOEA //! Modify the crossover rate. double& CrossoverRate() { return crossoverProb; } - //! Get the mutation probability. - double MutationProbability() const { return mutationProb; } - //! Modify the mutation probability. - double& MutationProbability() { return mutationProb; } - //! Retrieve value of the distribution index. double DistributionIndex() const { return distributionIndex; } //! Modify the value of the distribution index. @@ -162,11 +149,6 @@ class AGEMOEA //! Modify the value of eta. double& Eta() { return eta; } - //! Get the mutation strength. - double MutationStrength() const { return mutationStrength; } - //! Modify the mutation strength. - double& MutationStrength() { return mutationStrength; } - //! Get the tolerance. double Epsilon() const { return epsilon; } //! Modify the tolerance. @@ -329,7 +311,7 @@ class AGEMOEA std::vector>& calculatedObjectives, std::vector& survivalScore, arma::Row extreme, - size_t dimension, + double dimension, size_t t); /** @@ -379,7 +361,7 @@ class AGEMOEA * @return The variable p in the Lp norm that best fits the geometry of the current front. */ template - size_t GetGeometry( + double GetGeometry( std::vector >& calculatedObjectives, const std::vector& front, const arma::Row& extreme); @@ -397,7 +379,7 @@ class AGEMOEA void PairwiseDistance(MatType& final, std::vector >& calculatedObjectives, const std::vector& front, - size_t dimension); + double dimension); /** * Finding the indexes of the extreme points in the front. @@ -408,7 +390,7 @@ class AGEMOEA * @return A set of indexes for the extreme points. */ template - void FindExtremePoints(arma::Row& indexes, + void FindExtremePoints(arma::Row& indexes, std::vector >& calculatedObjectives, const std::vector& front); @@ -425,7 +407,7 @@ class AGEMOEA template void PointToLineDistance(arma::rowvec& distances, std::vector >& calculatedObjectives, - std::vector& front, + const std::vector& front, const arma::colvec& pointA, const arma::colvec& pointB); diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index ab4afb64b..534d4104c 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -22,8 +22,6 @@ namespace ens { inline AGEMOEA::AGEMOEA(const size_t populationSize, const size_t maxGenerations, const double crossoverProb, - const double mutationProb, - const double mutationStrength, const double distributionIndex, const double epsilon, const double eta, @@ -34,8 +32,6 @@ inline AGEMOEA::AGEMOEA(const size_t populationSize, populationSize(populationSize), maxGenerations(maxGenerations), crossoverProb(crossoverProb), - mutationProb(mutationProb), - mutationStrength(mutationStrength), distributionIndex(distributionIndex), epsilon(epsilon), eta(eta), @@ -46,8 +42,6 @@ inline AGEMOEA::AGEMOEA(const size_t populationSize, inline AGEMOEA::AGEMOEA(const size_t populationSize, const size_t maxGenerations, const double crossoverProb, - const double mutationProb, - const double mutationStrength, const double distributionIndex, const double epsilon, const double eta, @@ -58,8 +52,6 @@ inline AGEMOEA::AGEMOEA(const size_t populationSize, populationSize(populationSize), maxGenerations(maxGenerations), crossoverProb(crossoverProb), - mutationProb(mutationProb), - mutationStrength(mutationStrength), distributionIndex(distributionIndex), epsilon(epsilon), eta(eta), @@ -152,7 +144,6 @@ typename MatType::elem_type AGEMOEA::Optimize( for (size_t generation = 1; generation <= maxGenerations && !terminate; generation++) { - Info << "AGEMOEA: iteration " << generation << "." << std::endl; // Create new population of candidate from the present elite population. // Have P_t, generate G_t using P_t. @@ -167,7 +158,6 @@ typename MatType::elem_type AGEMOEA::Optimize( // Perform fast non dominated sort on P_t ∪ G_t. ranks.resize(population.size()); FastNonDominatedSort(fronts, ranks, calculatedObjectives); - arma::Col idealPoint(calculatedObjectives[0]); for (arma::Col c: calculatedObjectives) { @@ -181,7 +171,7 @@ typename MatType::elem_type AGEMOEA::Optimize( arma::Row extreme(numObjectives, arma::fill::zeros); FindExtremePoints(extreme, calculatedObjectives, fronts[0]); - arma::Col& normalize; + arma::Col normalize; NormalizeFront(calculatedObjectives, normalize, fronts[0], extreme); for (arma::Col& c: calculatedObjectives) @@ -189,7 +179,7 @@ typename MatType::elem_type AGEMOEA::Optimize( c = c / normalize; } - size_t dimension = GetGeometry(calculatedObjectives, fronts[0], + double dimension = GetGeometry(calculatedObjectives, fronts[0], extreme); // Perform survival score assignment. @@ -198,7 +188,7 @@ typename MatType::elem_type AGEMOEA::Optimize( for (size_t fNum = 0; fNum < fronts.size(); fNum++) { SurvivalScoreAssignment( - fronts[fNum], calculatedObjectives, survivalScore, extreme, dimension); + fronts[fNum], calculatedObjectives, survivalScore, extreme, dimension, fNum); } // Sort based on survival score. @@ -364,23 +354,23 @@ inline void AGEMOEA::Crossover(MatType& childA, MatType alpha2 = 2 - arma::pow(beta2, -(eta + 1)); MatType us(arma::size(alpha1), arma::fill::randu); - MatType mask1 = us > (1.0 / alpha1); + arma::umat mask1 = us > (1.0 / alpha1); MatType betaq1 = arma::pow(us % alpha1, 1. / (eta + 1)); betaq1 = betaq1 % (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha1)), 1.0 / (eta + 1)) % mask1; - MatType mask2 = us > (1.0 / alpha2); + arma::umat mask2 = us > (1.0 / alpha2); MatType betaq2 = arma::pow(us % alpha2, 1 / (eta + 1)); - betaq2 = betaq2 * (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; + betaq2 = betaq2 % (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; //! Variables after the cross over for all of them. - MatType c1 = 0.5 % ((current_min + current_max) - betaq1 % current_diff); - MatType c2 = 0.5 % ((current_min + current_max) + betaq2 % current_diff); + MatType c1 = 0.5 * ((current_min + current_max) - betaq1 % current_diff); + MatType c2 = 0.5 * ((current_min + current_max) + betaq2 % current_diff); //! Decision for the crossover between the two parents for each variable. us.randu(); - childA = parentA % (us <= 0.5); - childB = parentB % (us <= 0.5); + childA = parentA % (us <= crossoverProb); + childB = parentB % (us <= crossoverProb); us.randu(); childA = childA + c1 % ((us <= 0.5) % (childA == 0)); childA = childA + c2 % ((us > 0.5) % (childA == 0)); @@ -446,8 +436,9 @@ inline void AGEMOEA::NormalizeFront( { normalization = arma::max(vectorizedObjectives, 1); } + arma::colvec one(front.size(), arma::fill::ones); arma::Col hyperplane = arma::solve( - vectorizedObjectives.t(), new arma::colvec(front.size(), arma::fill::ones)); + vectorizedObjectives.t(), one); if (hyperplane.has_inf() || hyperplane.has_nan() || arma::accu(hyperplane < 0.0) > 0) { normalization = arma::max(vectorizedObjectives, 1); @@ -461,15 +452,17 @@ inline void AGEMOEA::NormalizeFront( } template -inline size_t AGEMOEA::GetGeometry( +inline double AGEMOEA::GetGeometry( std::vector >& calculatedObjectives, const std::vector& front, const arma::Row& extreme) { arma::Row d; - PointToLineDistance (d, calculatedObjectives, front, - new arma::colvec(numObjectives, arma::fill::zeros), - new arma::colvec(numObjectives, arma::fill::ones)); + arma::colvec zero(numObjectives, arma::fill::zeros); + arma::colvec one(numObjectives, arma::fill::ones); + + PointToLineDistance (d, calculatedObjectives, front, zero, one); + for (size_t i = 0; i < extreme.size(); i++) { d[extreme[i]] = arma::datum::inf; @@ -478,6 +471,7 @@ inline size_t AGEMOEA::GetGeometry( double avg = arma::accu(calculatedObjectives[front[index]]) / static_cast (numObjectives); double p = std::log(numObjectives) / std::log(1.0 / avg); if (p <= 0.1 || std::isnan(p)) {p = 1.0;} + return p; } @@ -486,13 +480,11 @@ template inline void AGEMOEA::PairwiseDistance(MatType& final, std::vector >& calculatedObjectives, const std::vector& front, - size_t dimension) -{ - final.zeros(front.size(),front.size()); - + double dimension) +{ for (size_t i = 0; i < front.size(); i++) { - for (size_t j = i + 1; i < front.size(); j++) + for (size_t j = i + 1; j < front.size(); j++) { final(i, j) = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]] - calculatedObjectives[front[j]]), dimension)), 1.0 / dimension); final(j, i) = final(i, j); @@ -508,7 +500,7 @@ void AGEMOEA::FindExtremePoints(arma::Row& indexes, { if(numObjectives >= front.size()) { - indexes = arma::linspace(front.size() - 1, 0, front.size()); + indexes = arma::linspace>(front.size() - 1, 0, front.size()); return; } @@ -516,11 +508,11 @@ void AGEMOEA::FindExtremePoints(arma::Row& indexes, W = W + 1e-6; std::vector selected(front.size()); arma::colvec z(numObjectives, arma::fill::zeros); + arma::rowvec dists; for (size_t i = 0; i < numObjectives; i++) { - arma::rowvec dists; PointToLineDistance(dists, calculatedObjectives, front, z, W.col(i)); - for (size_t j = 0; j < front.size(); i++) + for (size_t j = 0; j < front.size(); j++) if (selected[j]){dists[i] = arma::datum::inf;} indexes[i] = dists.index_min(); selected[dists.index_min()] = true; @@ -531,7 +523,7 @@ void AGEMOEA::FindExtremePoints(arma::Row& indexes, template void AGEMOEA::PointToLineDistance(arma::rowvec& distances, std::vector >& calculatedObjectives, - std::vector& front, + const std::vector& front, const arma::colvec& pointA, const arma::colvec& pointB) { @@ -640,7 +632,7 @@ template inline typename MatType::elem_type AGEMOEA::DiversityScore(std::set& selected, const MatType& pairwiseDistance, size_t S) -{ +{ typedef typename MatType::elem_type ElemType; ElemType m = arma::datum::inf; ElemType m1 = arma::datum::inf; @@ -667,12 +659,12 @@ inline void AGEMOEA::SurvivalScoreAssignment( std::vector>& calculatedObjectives, std::vector& survivalScore, arma::Row extreme, - size_t dimension, + double dimension, size_t t) { typedef typename MatType::elem_type ElemType; - if (t > 1) + if (t > 0) { for (size_t i = 0; i < front.size(); i++) { @@ -689,8 +681,7 @@ inline void AGEMOEA::SurvivalScoreAssignment( std::set selected; std::set remaining; - - //!create the slecetd and remaining sets. + //!create the selected and remaining sets. for (size_t index: extreme) { selected.insert(index); @@ -704,16 +695,15 @@ inline void AGEMOEA::SurvivalScoreAssignment( } } - arma::Mat pairwise; + arma::Mat pairwise(front.size(), front.size(), arma::fill::zeros); PairwiseDistance(pairwise,calculatedObjectives,front,dimension); arma::Row proximity(front.size(), arma::fill::zeros); arma::Row diversity(front.size(), arma::fill::zeros); arma::Row value(front.size(), arma::fill::zeros); for (size_t i = 0; i < front.size(); i++) { - proximity[i] = arma::norm(calculatedObjectives[front[i]], dimension); - } - + proximity[i] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]]), dimension)), 1.0 / dimension);; + } while (remaining.size() > 0) { std::set::iterator it; @@ -724,7 +714,7 @@ inline void AGEMOEA::SurvivalScoreAssignment( } size_t index = arma::index_max(value); survivalScore[front[index]] = value[index]; - value[index] = 0; + value[index] = - 0.1; selected.insert(index); remaining.erase(index); } From 8969f734742d28dfc5443e8578d7421fd46b3228 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sun, 16 Jun 2024 16:22:38 +0530 Subject: [PATCH 07/31] bounds on c1 and c2 added --- include/ensmallen_bits/age_moea/agemoea_impl.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index 534d4104c..e92e06416 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -365,7 +365,8 @@ inline void AGEMOEA::Crossover(MatType& childA, //! Variables after the cross over for all of them. MatType c1 = 0.5 * ((current_min + current_max) - betaq1 % current_diff); MatType c2 = 0.5 * ((current_min + current_max) + betaq2 % current_diff); - + c1 = arma::min(arma::max(c1, lowerBound), upperBound); + c2 = arma::min(arma::max(c2, lowerBound), upperBound); //! Decision for the crossover between the two parents for each variable. us.randu(); From 9f25ae217c8890ab196d612ab4c7ca1784e44ebc Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 26 Jun 2024 00:30:14 +0530 Subject: [PATCH 08/31] Added agemoea_test --- include/ensmallen_bits/age_moea/agemoea.hpp | 30 +- .../ensmallen_bits/age_moea/agemoea_impl.hpp | 182 ++++++---- tests/CMakeLists.txt | 1 + tests/agemoea_test.cpp | 326 ++++++++++++++++++ 4 files changed, 453 insertions(+), 86 deletions(-) create mode 100644 tests/agemoea_test.cpp diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/age_moea/agemoea.hpp index b3525b178..bb15e7f73 100644 --- a/include/ensmallen_bits/age_moea/agemoea.hpp +++ b/include/ensmallen_bits/age_moea/agemoea.hpp @@ -242,12 +242,16 @@ class AGEMOEA * @param childB Another newly generated candidate. * @param parentA First parent from elite population. * @param parentB Second parent from elite population. + * @param lowerBound The lower bound of the objectives. + * @param upperBound The upper bound of the objectives. */ template void Crossover(MatType& childA, MatType& childB, const MatType& parentA, - const MatType& parentB); + const MatType& parentB, + const MatType& lowerBound, + const MatType& upperBound); /** * Mutate the coordinates for a candidate. @@ -301,18 +305,22 @@ class AGEMOEA * Assigns Survival Score metric for sorting. * * @param front The previously generated Pareto fronts. - * @param index Index d of the non-dominated front. + * @param idealPoint The ideal point of teh first front. * @param dimension The calculated dimension from grt geometry. * @param calculatedObjectives The previously calculated objectives. * @param survivalScore The Survival Score vector to be updated for each individual in the population. + * @param normalize The normlization vector of the fronts. + * @param dimension The dimension of the first front. + * @param fNum teh current front index. */ template void SurvivalScoreAssignment(const std::vector& front, - std::vector>& calculatedObjectives, - std::vector& survivalScore, - arma::Row extreme, - double dimension, - size_t t); + const arma::Col& idealPoint, + std::vector>& calculatedObjectives, + std::vector& survivalScore, + arma::Col& normalize, + double& dimension, + size_t fNum); /** * The operator used in the AGE-MOEA survival score based sorting. @@ -405,11 +413,11 @@ class AGEMOEA * @param pointB The second point on the line. */ template - void PointToLineDistance(arma::rowvec& distances, + void PointToLineDistance(arma::Row& distances, std::vector >& calculatedObjectives, - const std::vector& front, - const arma::colvec& pointA, - const arma::colvec& pointB); + const std::vector& front, + const arma::Col& pointA, + const arma::Col& pointB); /** * Find the Diversity score corresponding the solution S using the selected set. diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index e92e06416..10b272a53 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -158,37 +158,22 @@ typename MatType::elem_type AGEMOEA::Optimize( // Perform fast non dominated sort on P_t ∪ G_t. ranks.resize(population.size()); FastNonDominatedSort(fronts, ranks, calculatedObjectives); - arma::Col idealPoint(calculatedObjectives[0]); - for (arma::Col c: calculatedObjectives) - { - idealPoint = arma::min(idealPoint, c); - } - - for (arma::Col& c: calculatedObjectives) - { - c = c - idealPoint; - } - - arma::Row extreme(numObjectives, arma::fill::zeros); - FindExtremePoints(extreme, calculatedObjectives, fronts[0]); - arma::Col normalize; - NormalizeFront(calculatedObjectives, normalize, fronts[0], extreme); - - for (arma::Col& c: calculatedObjectives) + + arma::Col idealPoint(calculatedObjectives[fronts[0][0]]); + for (size_t index = 1; index < fronts[0].size(); index++) { - c = c / normalize; + idealPoint = arma::min(idealPoint, calculatedObjectives[fronts[0][index]]); } - double dimension = GetGeometry(calculatedObjectives, fronts[0], - extreme); - // Perform survival score assignment. survivalScore.resize(population.size()); std::fill(survivalScore.begin(), survivalScore.end(), 0.); + double dimension; + arma::Col normalize(numObjectives, arma::fill::zeros); for (size_t fNum = 0; fNum < fronts.size(); fNum++) { - SurvivalScoreAssignment( - fronts[fNum], calculatedObjectives, survivalScore, extreme, dimension, fNum); + SurvivalScoreAssignment(fronts[fNum], idealPoint, + calculatedObjectives, survivalScore, normalize, dimension, fNum); } // Sort based on survival score. @@ -313,7 +298,8 @@ inline void AGEMOEA::BinaryTournamentSelection(std::vector& population, MatType childA = population[indexA], childB = population[indexB]; if(arma::randu() <= crossoverProb) - Crossover(childA, childB, population[indexA], population[indexB]); + Crossover(childA, childB, population[indexA], population[indexB], + lowerBound, upperBound); Mutate(childA, 1.0 / static_cast(numVariables), lowerBound, upperBound); @@ -334,7 +320,9 @@ template inline void AGEMOEA::Crossover(MatType& childA, MatType& childB, const MatType& parentA, - const MatType& parentB) + const MatType& parentB, + const MatType& lowerBound, + const MatType& upperBound) { //! Generates a child from two parent individuals // according to the polynomial probability distribution. @@ -344,6 +332,12 @@ inline void AGEMOEA::Crossover(MatType& childA, MatType current_min = arma::min(parents, 2); MatType current_max = arma::max(parents, 2); + if (arma::accu(parentA - parentB < 1e-14)) + { + childA = parentA; + childB = parentB; + return; + } MatType current_diff = current_max - current_min; current_diff.clamp(1e-10, arma::datum::inf); @@ -354,14 +348,13 @@ inline void AGEMOEA::Crossover(MatType& childA, MatType alpha2 = 2 - arma::pow(beta2, -(eta + 1)); MatType us(arma::size(alpha1), arma::fill::randu); - arma::umat mask1 = us > (1.0 / alpha1); + arma::umat mask1 = us > (1.0 / alpha1); MatType betaq1 = arma::pow(us % alpha1, 1. / (eta + 1)); - betaq1 = betaq1 % (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha1)), 1.0 / (eta + 1)) % mask1; - + betaq1 = betaq1 % (mask1 != 1.0) + arma::pow((1.0 / (2.0 - us % alpha1)), 1.0 / (eta + 1)) % mask1; arma::umat mask2 = us > (1.0 / alpha2); MatType betaq2 = arma::pow(us % alpha2, 1 / (eta + 1)); - betaq2 = betaq2 % (mask1 - 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; - + betaq2 = betaq2 % (mask1 != 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; + //! Variables after the cross over for all of them. MatType c1 = 0.5 * ((current_min + current_max) - betaq1 % current_diff); MatType c2 = 0.5 * ((current_min + current_max) + betaq2 % current_diff); @@ -370,8 +363,8 @@ inline void AGEMOEA::Crossover(MatType& childA, //! Decision for the crossover between the two parents for each variable. us.randu(); - childA = parentA % (us <= crossoverProb); - childB = parentB % (us <= crossoverProb); + childA = parentA % (us <= 0.5); + childB = parentB % (us <= 0.5); us.randu(); childA = childA + c1 % ((us <= 0.5) % (childA == 0)); childA = childA + c2 % ((us > 0.5) % (childA == 0)); @@ -431,23 +424,33 @@ inline void AGEMOEA::NormalizeFront( { vectorizedObjectives.col(i) = calculatedObjectives[front[i]]; } + + if(front.size() < numObjectives) + { + normalization = arma::max(vectorizedObjectives, 1); + return; + } arma::Col temp; arma::uvec unique = arma::find_unique(extreme); if (extreme.n_elem != unique.n_elem) { normalization = arma::max(vectorizedObjectives, 1); + return; } - arma::colvec one(front.size(), arma::fill::ones); + arma::Col one(front.size(), arma::fill::ones); arma::Col hyperplane = arma::solve( vectorizedObjectives.t(), one); - if (hyperplane.has_inf() || hyperplane.has_nan() || arma::accu(hyperplane < 0.0) > 0) + if (hyperplane.has_inf() || hyperplane.has_nan() || (arma::accu(hyperplane < 0.0) > 0)) { normalization = arma::max(vectorizedObjectives, 1); } - normalization = 1. / hyperplane; - if (hyperplane.has_inf() || hyperplane.has_nan()) - { - normalization = arma::max(vectorizedObjectives, 1); + else + { + normalization = 1. / hyperplane; + if (hyperplane.has_inf() || hyperplane.has_nan()) + { + normalization = arma::max(vectorizedObjectives, 1); + } } normalization = normalization + (normalization == 0); } @@ -459,8 +462,8 @@ inline double AGEMOEA::GetGeometry( const arma::Row& extreme) { arma::Row d; - arma::colvec zero(numObjectives, arma::fill::zeros); - arma::colvec one(numObjectives, arma::fill::ones); + arma::Col zero(numObjectives, arma::fill::zeros); + arma::Col one(numObjectives, arma::fill::ones); PointToLineDistance (d, calculatedObjectives, front, zero, one); @@ -499,22 +502,24 @@ void AGEMOEA::FindExtremePoints(arma::Row& indexes, std::vector >& calculatedObjectives, const std::vector& front) { + typedef typename MatType::elem_type ElemType; + if(numObjectives >= front.size()) { - indexes = arma::linspace>(front.size() - 1, 0, front.size()); + indexes = arma::linspace>(0, front.size() - 1, front.size()); return; } - arma::mat W(numObjectives, numObjectives, arma::fill::eye); + arma::Mat W(numObjectives, numObjectives, arma::fill::eye); W = W + 1e-6; std::vector selected(front.size()); - arma::colvec z(numObjectives, arma::fill::zeros); - arma::rowvec dists; + arma::Col z(numObjectives, arma::fill::zeros); + arma::Row dists; for (size_t i = 0; i < numObjectives; i++) { PointToLineDistance(dists, calculatedObjectives, front, z, W.col(i)); for (size_t j = 0; j < front.size(); j++) - if (selected[j]){dists[i] = arma::datum::inf;} + if (selected[j]){dists[j] = arma::datum::inf;} indexes[i] = dists.index_min(); selected[dists.index_min()] = true; } @@ -522,15 +527,16 @@ void AGEMOEA::FindExtremePoints(arma::Row& indexes, //! Find the distance of a front from a line formed by two points. template -void AGEMOEA::PointToLineDistance(arma::rowvec& distances, +void AGEMOEA::PointToLineDistance(arma::Row& distances, std::vector >& calculatedObjectives, - const std::vector& front, - const arma::colvec& pointA, - const arma::colvec& pointB) + const std::vector& front, + const arma::Col& pointA, + const arma::Col& pointB) { - arma::rowvec distancesTemp(front.size()); - arma::colvec ba = pointB - pointA; - arma::colvec pa; + typedef typename MatType::elem_type ElemType; + arma::Row distancesTemp(front.size()); + arma::Col ba = pointB - pointA; + arma::Col pa; for (size_t i = 0; i < front.size(); i++) { @@ -637,48 +643,63 @@ inline typename MatType::elem_type AGEMOEA::DiversityScore(std::set& sel typedef typename MatType::elem_type ElemType; ElemType m = arma::datum::inf; ElemType m1 = arma::datum::inf; - for (size_t i = 0; i < pairwiseDistance.n_cols; i++) + std::set::iterator it; + for (it = selected.begin(); it != selected.end(); it++) { - if(i == S){ continue;} - if (pairwiseDistance(S, i) < m) + if(*it == S){ continue; } + if (pairwiseDistance(S, *it) < m) { m1 = m; - m = pairwiseDistance(S, i); + m = pairwiseDistance(S, *it); } - else if (pairwiseDistance(S, i) < m1) + else if (pairwiseDistance(S, *it) < m1) { - m1 = pairwiseDistance(S, i); + m1 = pairwiseDistance(S, *it); } } return m + m1; } -//! Assign survival score to the population. +//! Assign survival score for a front of the population. template inline void AGEMOEA::SurvivalScoreAssignment( const std::vector& front, + const arma::Col& idealPoint, std::vector>& calculatedObjectives, std::vector& survivalScore, - arma::Row extreme, - double dimension, - size_t t) + arma::Col& normalize, + double& dimension, + size_t fNum) { typedef typename MatType::elem_type ElemType; - - if (t > 0) - { - for (size_t i = 0; i < front.size(); i++) + + if(fNum == 0){ + + if(front.size() < numObjectives) { - survivalScore[front[i]] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]]), dimension)), 1.0 / dimension); + dimension = 1; + arma::Row extreme(numObjectives, arma::fill::zeros); + NormalizeFront(calculatedObjectives, normalize, front, extreme); + return; } - } - - else - { - if (front.size() < numObjectives) + + for (size_t index = 1; index < front.size(); index++) { - return; + calculatedObjectives[front[index]] = calculatedObjectives[front[index]] - idealPoint; + } + + arma::Row extreme(numObjectives, arma::fill::zeros); + FindExtremePoints(extreme, calculatedObjectives, front); + NormalizeFront(calculatedObjectives, normalize, front, extreme); + + for (size_t index = 0; index < front.size(); index++) + { + calculatedObjectives[front[index]] = calculatedObjectives[front[index]] / normalize; } + + dimension = GetGeometry(calculatedObjectives, front, + extreme); + std::set selected; std::set remaining; @@ -703,7 +724,7 @@ inline void AGEMOEA::SurvivalScoreAssignment( arma::Row value(front.size(), arma::fill::zeros); for (size_t i = 0; i < front.size(); i++) { - proximity[i] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]]), dimension)), 1.0 / dimension);; + proximity[i] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]]), dimension)), 1.0 / dimension); } while (remaining.size() > 0) { @@ -715,11 +736,22 @@ inline void AGEMOEA::SurvivalScoreAssignment( } size_t index = arma::index_max(value); survivalScore[front[index]] = value[index]; - value[index] = - 0.1; + value[index] = - 1; selected.insert(index); remaining.erase(index); } } + + else + { + + for(size_t i = 0; i < front.size(); i++) + { + calculatedObjectives[front[i]] = calculatedObjectives[front[i]] / normalize; + survivalScore[front[i]] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]] - idealPoint), dimension)), 1.0 / dimension); + } + + } } //! Comparator for survival score based sorting. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index df5938844..8857ba048 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ set(ENSMALLEN_TESTS_SOURCES lookahead_test.cpp lrsdp_test.cpp moead_test.cpp + agemoea_test.cpp momentum_sgd_test.cpp nesterov_momentum_sgd_test.cpp nsga2_test.cpp diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp new file mode 100644 index 000000000..db294d88e --- /dev/null +++ b/tests/agemoea_test.cpp @@ -0,0 +1,326 @@ +/** + * @file agemoea_test.cpp + * @author Satyam Shukla + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#include +#include "catch.hpp" +#include "test_function_tools.hpp" + +using namespace ens; +using namespace ens::test; +using namespace std; + +/** + * Checks if low <= value <= high. Used by MOEADFonsecaFlemingTest. + * + * @param value The value being checked. + * @param low The lower bound. + * @param high The upper bound. + * @param roundoff To round off precision. + * @tparam The type of elements in the population set. + * @return true if value lies in the range [low, high]. + * @return false if value does not lie in the range [low, high]. + */ +template +bool IsInBounds(const ElemType& value, + const ElemType& low, + const ElemType& high, + const ElemType& roundoff) +{ + return !(value < (low - roundoff)) && !((high + roundoff) < value); +} + + +/** + * Optimize for the Schaffer N.1 function using AGE-MOEA optimizer. + * Tests for data of type double. + */ +TEST_CASE("AGEMOEASchafferN1DoubleTest", "[AGEMOEATest]") +{ + SchafferFunctionN1 SCH; + const double lowerBound = -1000; + const double upperBound = 1000; + const double expectedLowerBound = 0.0; + const double expectedUpperBound = 2.0; + + AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + + typedef decltype(SCH.objectiveA) ObjectiveTypeA; + typedef decltype(SCH.objectiveB) ObjectiveTypeB; + + // We allow a few trials in case of poor convergence. + bool success = false; + for (size_t trial = 0; trial < 3; ++trial) + { + arma::mat coords = SCH.GetInitialPoint(); + std::tuple objectives = SCH.GetObjectives(); + + opt.Optimize(objectives, coords); + arma::cube paretoSet = opt.ParetoSet(); + + bool allInRange = true; + + for (size_t solutionIdx = 0; solutionIdx < paretoSet.n_slices; ++solutionIdx) + { + double val = arma::as_scalar(paretoSet.slice(solutionIdx)); + if (!IsInBounds(val, expectedLowerBound, expectedUpperBound, 0.1)) + { + allInRange = false; + break; + } + } + + if (allInRange) + { + success = true; + break; + } + } + + REQUIRE(success == true); +} + +/** + * Optimize for the Schaffer N.1 function using AGE-MOEA optimizer. + * Tests for data of type double. + */ +TEST_CASE("AGEMOEASchafferN1TestVectorDoubleBounds", "[AGEMOEATest]") +{ + // This test can be a little flaky, so we try it a few times. + SchafferFunctionN1 SCH; + const arma::vec lowerBound = {-1000}; + const arma::vec upperBound = {1000}; + const double expectedLowerBound = 0.0; + const double expectedUpperBound = 2.0; + + AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + + typedef decltype(SCH.objectiveA) ObjectiveTypeA; + typedef decltype(SCH.objectiveB) ObjectiveTypeB; + + bool success = false; + for (size_t trial = 0; trial < 3; ++trial) + { + arma::mat coords = SCH.GetInitialPoint(); + std::tuple objectives = SCH.GetObjectives(); + + opt.Optimize(objectives, coords); + arma::cube paretoSet = opt.ParetoSet(); + + bool allInRange = true; + + for (size_t solutionIdx = 0; solutionIdx < paretoSet.n_slices; ++solutionIdx) + { + double val = arma::as_scalar(paretoSet.slice(solutionIdx)); + if (!IsInBounds(val, expectedLowerBound, expectedUpperBound, 0.1)) + { + allInRange = false; + break; + } + } + + if (allInRange) + { + success = true; + break; + } + } + + REQUIRE(success == true); +} + +/** + * Optimize for the Fonseca Fleming function using AGE-MOEA optimizer. + * Tests for data of type double. + */ +TEST_CASE("AGEMOEAFonsecaFlemingDoubleTest", "[AGEMOEATest]") +{ + FonsecaFlemingFunction FON; + const double lowerBound = -4; + const double upperBound = 4; + const double expectedLowerBound = -1.0 / sqrt(3); + const double expectedUpperBound = 1.0 / sqrt(3); + + AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + typedef decltype(FON.objectiveA) ObjectiveTypeA; + typedef decltype(FON.objectiveB) ObjectiveTypeB; + + bool success = false; + for (size_t trial = 0; trial < 3; ++trial) + { + arma::mat coords = FON.GetInitialPoint(); + std::tuple objectives = FON.GetObjectives(); + + opt.Optimize(objectives, coords); + arma::cube paretoSet = opt.ParetoSet(); + + bool allInRange = true; + + for (size_t solutionIdx = 0; solutionIdx < paretoSet.n_slices; ++solutionIdx) + { + const arma::mat solution = paretoSet.slice(solutionIdx); + double valX = arma::as_scalar(solution(0)); + double valY = arma::as_scalar(solution(1)); + double valZ = arma::as_scalar(solution(2)); + + if (!IsInBounds(valX, expectedLowerBound, expectedUpperBound, 0.3) || + !IsInBounds(valY, expectedLowerBound, expectedUpperBound, 0.3) || + !IsInBounds(valZ, expectedLowerBound, expectedUpperBound, 0.3)) + { + allInRange = false; + break; + } + } + if(allInRange == true) + { + success = true; + break; + } + } + + REQUIRE(success == true); +} + +/** + * Optimize for the Fonseca Fleming function using AGE-MOEA optimizer. + * Tests for data of type float. + */ +/*TEST_CASE("AGEMOEAFonsecaFlemingTestVectorFloatBounds", "[AGEMOEATest]") +{ + FonsecaFlemingFunction FON; + const arma::vec lowerBound = {-4}; + const arma::vec upperBound = {4}; + const float expectedLowerBound = -1.0 / sqrt(3); + const float expectedUpperBound = 1.0 / sqrt(3); + + AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + typedef decltype(FON.objectiveA) ObjectiveTypeA; + typedef decltype(FON.objectiveB) ObjectiveTypeB; + + arma::fmat coords = FON.GetInitialPoint(); + std::tuple objectives = FON.GetObjectives(); + + opt.Optimize(objectives, coords); + arma::fcube paretoSet = arma::conv_to::from(opt.ParetoSet()); + + bool allInRange = true; + + for (size_t solutionIdx = 0; solutionIdx < paretoSet.n_slices; ++solutionIdx) + { + const arma::fmat solution = paretoSet.slice(solutionIdx); + float valX = arma::as_scalar(solution(0)); + float valY = arma::as_scalar(solution(1)); + float valZ = arma::as_scalar(solution(2)); + + if (!IsInBounds(valX, expectedLowerBound, expectedUpperBound, 0.1) || + !IsInBounds(valY, expectedLowerBound, expectedUpperBound, 0.1) || + !IsInBounds(valZ, expectedLowerBound, expectedUpperBound, 0.1)) + { + allInRange = false; + break; + } + } + + REQUIRE(allInRange); +}*/ + +TEST_CASE("AGEMOEAZDTONETest", "[AGEMOEATest]") +{ + //! Parameters taken from original ZDT Paper. + ZDT1<> ZDT_ONE(100); + const double lowerBound = 0; + const double upperBound = 1; + + AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + + typedef decltype(ZDT_ONE.objectiveF1) ObjectiveTypeA; + typedef decltype(ZDT_ONE.objectiveF2) ObjectiveTypeB; + + const size_t trials = 8; + for (size_t trial = 0; trial < trials; ++trial) + { + arma::mat coords = ZDT_ONE.GetInitialPoint(); + std::tuple objectives = + ZDT_ONE.GetObjectives(); + + opt.Optimize(objectives, coords); + + //! Refer the ZDT_ONE implementation for g objective implementation. + //! The optimal g value is taken from the docs of ZDT_ONE. + size_t numVariables = coords.size(); + double sum = arma::accu(coords(arma::span(1, numVariables - 1), 0)); + const double g = 1.0 + 9.0 * sum / (static_cast(numVariables - 1)); + if (trial < trials - 1 && g != Approx(1.0).margin(0.99)) + continue; + + REQUIRE(g == Approx(1.0).margin(0.99)); + break; + } +} + +/** + * Check if the final population lies in the optimal region in variable space. + * + * @param paretoSet The final population in variable space. + */ +bool AVariableBoundsCheck(const arma::cube& paretoSet) +{ + bool inBounds = true; + const arma::mat regions{ + {0.0, 0.182228780, 0.4093136748, + 0.6183967944, 0.8233317983}, + {0.0830015349, 0.2577623634, 0.4538821041, + 0.6525117038, 0.8518328654} + }; + + for (size_t pointIdx = 0; pointIdx < paretoSet.n_slices; ++pointIdx) + { + const arma::mat& point = paretoSet.slice(pointIdx); + const double firstVariable = point(0, 0); + + const bool notInRegion0 = !IsInBounds(firstVariable, regions(0, 0), regions(1, 0), 1e-2); + const bool notInRegion1 = !IsInBounds(firstVariable, regions(0, 1), regions(1, 1), 1e-2); + const bool notInRegion2 = !IsInBounds(firstVariable, regions(0, 2), regions(1, 2), 1e-2); + const bool notInRegion3 = !IsInBounds(firstVariable, regions(0, 3), regions(1, 3), 1e-2); + const bool notInRegion4 = !IsInBounds(firstVariable, regions(0, 4), regions(1, 4), 1e-2); + + if (notInRegion0 && notInRegion1 && notInRegion2 && notInRegion3 && notInRegion4) + { + inBounds = false; + break; + } + } + + return inBounds; +} + +/** + * Test DirichletMOEAD against the third problem of ZDT Test Suite. ZDT-3 is a 30 + * variable-2 objective problem with disconnected Pareto Fronts. + */ +TEST_CASE("AGEMOEADIRICHLETZDT3Test", "[AGEMOEADTest]") +{ + //! Parameters taken from original ZDT Paper. + ZDT3<> ZDT_THREE(300); + const double lowerBound = 0; + const double upperBound = 1; + + AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + + typedef decltype(ZDT_THREE.objectiveF1) ObjectiveTypeA; + typedef decltype(ZDT_THREE.objectiveF2) ObjectiveTypeB; + + arma::mat coords = ZDT_THREE.GetInitialPoint(); + std::tuple objectives = ZDT_THREE.GetObjectives(); + + opt.Optimize(objectives, coords); + + const arma::cube& finalPopulation = opt.ParetoSet(); + REQUIRE(AVariableBoundsCheck(finalPopulation)); +} \ No newline at end of file From 7ec24a3735b59179d176d0461cfeaa7889e2490b Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 26 Jun 2024 10:17:25 +0530 Subject: [PATCH 09/31] removed comments --- tests/agemoea_test.cpp | 43 ------------------------------------------ 1 file changed, 43 deletions(-) diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp index db294d88e..8e0fdc5f6 100644 --- a/tests/agemoea_test.cpp +++ b/tests/agemoea_test.cpp @@ -187,49 +187,6 @@ TEST_CASE("AGEMOEAFonsecaFlemingDoubleTest", "[AGEMOEATest]") REQUIRE(success == true); } -/** - * Optimize for the Fonseca Fleming function using AGE-MOEA optimizer. - * Tests for data of type float. - */ -/*TEST_CASE("AGEMOEAFonsecaFlemingTestVectorFloatBounds", "[AGEMOEATest]") -{ - FonsecaFlemingFunction FON; - const arma::vec lowerBound = {-4}; - const arma::vec upperBound = {4}; - const float expectedLowerBound = -1.0 / sqrt(3); - const float expectedUpperBound = 1.0 / sqrt(3); - - AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); - typedef decltype(FON.objectiveA) ObjectiveTypeA; - typedef decltype(FON.objectiveB) ObjectiveTypeB; - - arma::fmat coords = FON.GetInitialPoint(); - std::tuple objectives = FON.GetObjectives(); - - opt.Optimize(objectives, coords); - arma::fcube paretoSet = arma::conv_to::from(opt.ParetoSet()); - - bool allInRange = true; - - for (size_t solutionIdx = 0; solutionIdx < paretoSet.n_slices; ++solutionIdx) - { - const arma::fmat solution = paretoSet.slice(solutionIdx); - float valX = arma::as_scalar(solution(0)); - float valY = arma::as_scalar(solution(1)); - float valZ = arma::as_scalar(solution(2)); - - if (!IsInBounds(valX, expectedLowerBound, expectedUpperBound, 0.1) || - !IsInBounds(valY, expectedLowerBound, expectedUpperBound, 0.1) || - !IsInBounds(valZ, expectedLowerBound, expectedUpperBound, 0.1)) - { - allInRange = false; - break; - } - } - - REQUIRE(allInRange); -}*/ - TEST_CASE("AGEMOEAZDTONETest", "[AGEMOEATest]") { //! Parameters taken from original ZDT Paper. From 1466ee58a83696cb5f99644bee3bf2d50e395a5c Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sat, 29 Jun 2024 17:12:17 +0530 Subject: [PATCH 10/31] Returns final paretoSet --- include/ensmallen_bits/age_moea/agemoea_impl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index 10b272a53..85e585cf4 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -202,7 +202,7 @@ typename MatType::elem_type AGEMOEA::Optimize( terminate |= Callback::GenerationalStepTaken(*this, objectives, iterate, calculatedObjectives, fronts, callbacks...); } - + EvaluateObjectives(population, objectives, calculatedObjectives); // Set the candidates from the Pareto Set as the output. paretoSet.set_size(population[0].n_rows, population[0].n_cols, fronts[0].size()); // The Pareto Set is stored, can be obtained via ParetoSet() getter. From c0bae8c5fd05d58e979790a0d943f6f1ef834adb Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 3 Jul 2024 01:16:29 +0530 Subject: [PATCH 11/31] style changes --- include/ensmallen_bits/age_moea/agemoea.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/age_moea/agemoea.hpp index bb15e7f73..1f001eac7 100644 --- a/include/ensmallen_bits/age_moea/agemoea.hpp +++ b/include/ensmallen_bits/age_moea/agemoea.hpp @@ -61,6 +61,7 @@ class AGEMOEA * This should be atleast 4 in size and a multiple of 4. * @param maxGenerations The maximum number of generations allowed for NSGA-II. * @param crossoverProb The probability that a crossover will occur. + * @param distributionIndex The crowding degree of the mutation. * @param epsilon The minimum difference required to distinguish between * candidate solutions. * @param eta The distance parameters of the crossover distribution. @@ -88,6 +89,7 @@ class AGEMOEA * This should be atleast 4 in size and a multiple of 4. * @param maxGenerations The maximum number of generations allowed for NSGA-II. * @param crossoverProb The probability that a crossover will occur. + * @param distributionIndex The crowding degree of the mutation. * @param epsilon The minimum difference required to distinguish between * candidate solutions. * @param eta The distance parameters of the crossover distribution @@ -224,7 +226,6 @@ class AGEMOEA * population. * * @tparam MatType Type of matrix to optimize. - * @param population The elite population. * @param objectives The set of objectives. * @param lowerBound Lower bound of the coordinates of the initial population. * @param upperBound Upper bound of the coordinates of the initial population. @@ -257,7 +258,7 @@ class AGEMOEA * Mutate the coordinates for a candidate. * * @tparam MatType Type of matrix to optimize. - * @param child The candidate whose coordinates are being modified. + * @param candidate The candidate whose coordinates are being modified. * @param mutationRate The probablity of a mutation to occur. * @param lowerBound Lower bound of the coordinates of the initial population. * @param upperBound Upper bound of the coordinates of the initial population. @@ -306,7 +307,6 @@ class AGEMOEA * * @param front The previously generated Pareto fronts. * @param idealPoint The ideal point of teh first front. - * @param dimension The calculated dimension from grt geometry. * @param calculatedObjectives The previously calculated objectives. * @param survivalScore The Survival Score vector to be updated for each individual in the population. * @param normalize The normlization vector of the fronts. @@ -381,7 +381,6 @@ class AGEMOEA * @param calculatedObjectives The current population evaluated objectives. * @param front The front of the current generation. * @param dimension The calculated dimension of the front. - * @return A matrix containing the pairwise distance between all points in the front. */ template void PairwiseDistance(MatType& final, @@ -395,7 +394,6 @@ class AGEMOEA * @param indexes vector containing the slected indexes. * @param calculatedObjectives The current population objectives. * @param front The front of the current generation. - * @return A set of indexes for the extreme points. */ template void FindExtremePoints(arma::Row& indexes, From 1bee1cc98dd28693d4692fc28ce4f3c7baad3128 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 3 Jul 2024 01:24:48 +0530 Subject: [PATCH 12/31] style fixes --- .../ensmallen_bits/age_moea/agemoea_impl.hpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index 85e585cf4..fd0abeeec 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -341,7 +341,7 @@ inline void AGEMOEA::Crossover(MatType& childA, MatType current_diff = current_max - current_min; current_diff.clamp(1e-10, arma::datum::inf); - //! Calculating beta used for the final crossover. + // Calculating beta used for the final crossover. MatType beta1 = 1 + 2.0 * (current_min - lowerBound) / current_diff; MatType beta2 = 1 + 2.0 * (upperBound - current_max) / current_diff; MatType alpha1 = 2 - arma::pow(beta1, -(eta + 1)); @@ -355,13 +355,13 @@ inline void AGEMOEA::Crossover(MatType& childA, MatType betaq2 = arma::pow(us % alpha2, 1 / (eta + 1)); betaq2 = betaq2 % (mask1 != 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; - //! Variables after the cross over for all of them. + // Variables after the cross over for all of them. MatType c1 = 0.5 * ((current_min + current_max) - betaq1 % current_diff); MatType c2 = 0.5 * ((current_min + current_max) + betaq2 % current_diff); c1 = arma::min(arma::max(c1, lowerBound), upperBound); c2 = arma::min(arma::max(c2, lowerBound), upperBound); - //! Decision for the crossover between the two parents for each variable. + // Decision for the crossover between the two parents for each variable. us.randu(); childA = parentA % (us <= 0.5); childB = parentB % (us <= 0.5); @@ -635,6 +635,7 @@ inline bool AGEMOEA::Dominates( return allBetterOrEqual && atleastOneBetter; } +//! Assign diversity score for a given point and teh selected set. template inline typename MatType::elem_type AGEMOEA::DiversityScore(std::set& selected, const MatType& pairwiseDistance, @@ -673,6 +674,7 @@ inline void AGEMOEA::SurvivalScoreAssignment( { typedef typename MatType::elem_type ElemType; + // Calculations for the first front. if(fNum == 0){ if(front.size() < numObjectives) @@ -703,7 +705,7 @@ inline void AGEMOEA::SurvivalScoreAssignment( std::set selected; std::set remaining; - //!create the selected and remaining sets. + // Create the selected and remaining sets. for (size_t index: extreme) { selected.insert(index); @@ -722,10 +724,14 @@ inline void AGEMOEA::SurvivalScoreAssignment( arma::Row proximity(front.size(), arma::fill::zeros); arma::Row diversity(front.size(), arma::fill::zeros); arma::Row value(front.size(), arma::fill::zeros); + + // Calculate the diversity and proximity score. for (size_t i = 0; i < front.size(); i++) { - proximity[i] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]]), dimension)), 1.0 / dimension); + proximity[i] = std::pow(arma::accu(arma::pow( + arma::abs(calculatedObjectives[front[i]]), dimension)), 1.0 / dimension); } + while (remaining.size() > 0) { std::set::iterator it; @@ -742,13 +748,15 @@ inline void AGEMOEA::SurvivalScoreAssignment( } } + // Calculations for the other fronts. else { for(size_t i = 0; i < front.size(); i++) { calculatedObjectives[front[i]] = calculatedObjectives[front[i]] / normalize; - survivalScore[front[i]] = std::pow(arma::accu(arma::pow(arma::abs(calculatedObjectives[front[i]] - idealPoint), dimension)), 1.0 / dimension); + survivalScore[front[i]] = std::pow(arma::accu(arma::pow(arma::abs( + calculatedObjectives[front[i]] - idealPoint), dimension)), 1.0 / dimension); } } From 2c909bb5846ff6f18ff591a1196ac6e5675420d5 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Thu, 4 Jul 2024 00:03:54 +0530 Subject: [PATCH 13/31] resolved clamp error --- .../ensmallen_bits/age_moea/agemoea_impl.hpp | 3 +- tests/agemoea_test.cpp | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index fd0abeeec..e337ddb35 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -339,7 +339,8 @@ inline void AGEMOEA::Crossover(MatType& childA, return; } MatType current_diff = current_max - current_min; - current_diff.clamp(1e-10, arma::datum::inf); + current_diff.transform( [](typename MatType::elem_type val) + { return (val < 1e-10 ? 1e-10:val); } ); // Calculating beta used for the final crossover. MatType beta1 = 1 + 2.0 * (current_min - lowerBound) / current_diff; diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp index 8e0fdc5f6..294ae119f 100644 --- a/tests/agemoea_test.cpp +++ b/tests/agemoea_test.cpp @@ -187,6 +187,55 @@ TEST_CASE("AGEMOEAFonsecaFlemingDoubleTest", "[AGEMOEATest]") REQUIRE(success == true); } +/** + * Optimize for the Fonseca Fleming function using AGE-MOEA optimizer. + * Tests for data of type float. + */ +TEST_CASE("AGEMOEAFonsecaFlemingTestVectorFloatBounds", "[AGEMOEATest]") +{ + FonsecaFlemingFunction FON; + const arma::vec lowerBound = {-4, -4, -4}; + const arma::vec upperBound = {4, 4, 4}; + const float expectedLowerBound = -1.0 / sqrt(3); + const float expectedUpperBound = 1.0 / sqrt(3); + + AGEMOEA opt(20, 300, 0.8, 20, 1e-6, 20, lowerBound, upperBound); + typedef decltype(FON.objectiveA) ObjectiveTypeA; + typedef decltype(FON.objectiveB) ObjectiveTypeB; + + bool success = false; + for (size_t trial = 0; trial < 3; ++trial) + { + arma::fmat coords = FON.GetInitialPoint(); + std::tuple objectives = FON.GetObjectives(); + + opt.Optimize(objectives, coords); + arma::fcube paretoSet = arma::conv_to::from(opt.ParetoSet()); + + bool allInRange = true; + for (size_t solutionIdx = 0; solutionIdx < paretoSet.n_slices; ++solutionIdx) + { + const arma::fmat solution = paretoSet.slice(solutionIdx); + float valX = arma::as_scalar(solution(0)); + float valY = arma::as_scalar(solution(1)); + float valZ = arma::as_scalar(solution(2)); + + if (!IsInBounds(valX, expectedLowerBound, expectedUpperBound, 0.3) || + !IsInBounds(valY, expectedLowerBound, expectedUpperBound, 0.3) || + !IsInBounds(valZ, expectedLowerBound, expectedUpperBound, 0.3)) + { + allInRange = false; + } + } + if (allInRange) + { + success = true; + break; + } + } + REQUIRE(success == true); +} + TEST_CASE("AGEMOEAZDTONETest", "[AGEMOEATest]") { //! Parameters taken from original ZDT Paper. From 3bdfc75bdb753b566f43c7d2fcd5771786f5e6c9 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Fri, 5 Jul 2024 22:27:55 +0530 Subject: [PATCH 14/31] Reviewed changes --- include/ensmallen_bits/age_moea/agemoea_impl.hpp | 2 -- include/ensmallen_bits/utility/indicators/igd.hpp | 2 +- tests/agemoea_test.cpp | 15 +++++++-------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/age_moea/agemoea_impl.hpp index e337ddb35..8a5305a65 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/age_moea/agemoea_impl.hpp @@ -144,7 +144,6 @@ typename MatType::elem_type AGEMOEA::Optimize( for (size_t generation = 1; generation <= maxGenerations && !terminate; generation++) { - // Create new population of candidate from the present elite population. // Have P_t, generate G_t using P_t. BinaryTournamentSelection(population, castedLowerBound, castedUpperBound); @@ -752,7 +751,6 @@ inline void AGEMOEA::SurvivalScoreAssignment( // Calculations for the other fronts. else { - for(size_t i = 0; i < front.size(); i++) { calculatedObjectives[front[i]] = calculatedObjectives[front[i]] / normalize; diff --git a/include/ensmallen_bits/utility/indicators/igd.hpp b/include/ensmallen_bits/utility/indicators/igd.hpp index 139a1a209..ca8919045 100644 --- a/include/ensmallen_bits/utility/indicators/igd.hpp +++ b/include/ensmallen_bits/utility/indicators/igd.hpp @@ -1,5 +1,5 @@ /** - * @file igd_plus.hpp + * @file igd.hpp * @author Satyam Shukla * * Inverse Generational Distance Plus (IGD) indicator. diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp index 294ae119f..e4ea7b34f 100644 --- a/tests/agemoea_test.cpp +++ b/tests/agemoea_test.cpp @@ -36,7 +36,6 @@ bool IsInBounds(const ElemType& value, return !(value < (low - roundoff)) && !((high + roundoff) < value); } - /** * Optimize for the Schaffer N.1 function using AGE-MOEA optimizer. * Tests for data of type double. @@ -171,7 +170,7 @@ TEST_CASE("AGEMOEAFonsecaFlemingDoubleTest", "[AGEMOEATest]") if (!IsInBounds(valX, expectedLowerBound, expectedUpperBound, 0.3) || !IsInBounds(valY, expectedLowerBound, expectedUpperBound, 0.3) || - !IsInBounds(valZ, expectedLowerBound, expectedUpperBound, 0.3)) + !IsInBounds(valZ, expectedLowerBound, expectedUpperBound, 0.3)) { allInRange = false; break; @@ -290,11 +289,11 @@ bool AVariableBoundsCheck(const arma::cube& paretoSet) const arma::mat& point = paretoSet.slice(pointIdx); const double firstVariable = point(0, 0); - const bool notInRegion0 = !IsInBounds(firstVariable, regions(0, 0), regions(1, 0), 1e-2); - const bool notInRegion1 = !IsInBounds(firstVariable, regions(0, 1), regions(1, 1), 1e-2); - const bool notInRegion2 = !IsInBounds(firstVariable, regions(0, 2), regions(1, 2), 1e-2); - const bool notInRegion3 = !IsInBounds(firstVariable, regions(0, 3), regions(1, 3), 1e-2); - const bool notInRegion4 = !IsInBounds(firstVariable, regions(0, 4), regions(1, 4), 1e-2); + const bool notInRegion0 = !IsInBounds(firstVariable, regions(0, 0), regions(1, 0), 2e-2); + const bool notInRegion1 = !IsInBounds(firstVariable, regions(0, 1), regions(1, 1), 2e-2); + const bool notInRegion2 = !IsInBounds(firstVariable, regions(0, 2), regions(1, 2), 2e-2); + const bool notInRegion3 = !IsInBounds(firstVariable, regions(0, 3), regions(1, 3), 2e-2); + const bool notInRegion4 = !IsInBounds(firstVariable, regions(0, 4), regions(1, 4), 2e-2); if (notInRegion0 && notInRegion1 && notInRegion2 && notInRegion3 && notInRegion4) { @@ -317,7 +316,7 @@ TEST_CASE("AGEMOEADIRICHLETZDT3Test", "[AGEMOEADTest]") const double lowerBound = 0; const double upperBound = 1; - AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + AGEMOEA opt(20, 500, 0.8, 20, 1e-6, 20, lowerBound, upperBound); typedef decltype(ZDT_THREE.objectiveF1) ObjectiveTypeA; typedef decltype(ZDT_THREE.objectiveF2) ObjectiveTypeB; From ff72db847df9bd38161c9449660f5f0448eb1086 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Mon, 8 Jul 2024 23:19:58 +0530 Subject: [PATCH 15/31] update test params --- tests/agemoea_test.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp index e4ea7b34f..b75f500bc 100644 --- a/tests/agemoea_test.cpp +++ b/tests/agemoea_test.cpp @@ -48,7 +48,7 @@ TEST_CASE("AGEMOEASchafferN1DoubleTest", "[AGEMOEATest]") const double expectedLowerBound = 0.0; const double expectedUpperBound = 2.0; - AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + AGEMOEA opt(20, 500, 0.6, 20, 1e-6, 20, lowerBound, upperBound); typedef decltype(SCH.objectiveA) ObjectiveTypeA; typedef decltype(SCH.objectiveB) ObjectiveTypeB; @@ -98,7 +98,7 @@ TEST_CASE("AGEMOEASchafferN1TestVectorDoubleBounds", "[AGEMOEATest]") const double expectedLowerBound = 0.0; const double expectedUpperBound = 2.0; - AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + AGEMOEA opt(20, 500, 0.6, 20, 1e-6, 20, lowerBound, upperBound); typedef decltype(SCH.objectiveA) ObjectiveTypeA; typedef decltype(SCH.objectiveB) ObjectiveTypeB; @@ -146,12 +146,12 @@ TEST_CASE("AGEMOEAFonsecaFlemingDoubleTest", "[AGEMOEATest]") const double expectedLowerBound = -1.0 / sqrt(3); const double expectedUpperBound = 1.0 / sqrt(3); - AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + AGEMOEA opt(20, 500, 0.6, 20, 1e-6, 20, lowerBound, upperBound); typedef decltype(FON.objectiveA) ObjectiveTypeA; typedef decltype(FON.objectiveB) ObjectiveTypeB; bool success = false; - for (size_t trial = 0; trial < 3; ++trial) + for (size_t trial = 0; trial < 6; ++trial) { arma::mat coords = FON.GetInitialPoint(); std::tuple objectives = FON.GetObjectives(); @@ -198,12 +198,12 @@ TEST_CASE("AGEMOEAFonsecaFlemingTestVectorFloatBounds", "[AGEMOEATest]") const float expectedLowerBound = -1.0 / sqrt(3); const float expectedUpperBound = 1.0 / sqrt(3); - AGEMOEA opt(20, 300, 0.8, 20, 1e-6, 20, lowerBound, upperBound); + AGEMOEA opt(20, 300, 0.6, 20, 1e-6, 20, lowerBound, upperBound); typedef decltype(FON.objectiveA) ObjectiveTypeA; typedef decltype(FON.objectiveB) ObjectiveTypeB; bool success = false; - for (size_t trial = 0; trial < 3; ++trial) + for (size_t trial = 0; trial < 6; ++trial) { arma::fmat coords = FON.GetInitialPoint(); std::tuple objectives = FON.GetObjectives(); @@ -289,11 +289,11 @@ bool AVariableBoundsCheck(const arma::cube& paretoSet) const arma::mat& point = paretoSet.slice(pointIdx); const double firstVariable = point(0, 0); - const bool notInRegion0 = !IsInBounds(firstVariable, regions(0, 0), regions(1, 0), 2e-2); - const bool notInRegion1 = !IsInBounds(firstVariable, regions(0, 1), regions(1, 1), 2e-2); - const bool notInRegion2 = !IsInBounds(firstVariable, regions(0, 2), regions(1, 2), 2e-2); - const bool notInRegion3 = !IsInBounds(firstVariable, regions(0, 3), regions(1, 3), 2e-2); - const bool notInRegion4 = !IsInBounds(firstVariable, regions(0, 4), regions(1, 4), 2e-2); + const bool notInRegion0 = !IsInBounds(firstVariable, regions(0, 0), regions(1, 0), 3e-2); + const bool notInRegion1 = !IsInBounds(firstVariable, regions(0, 1), regions(1, 1), 3e-2); + const bool notInRegion2 = !IsInBounds(firstVariable, regions(0, 2), regions(1, 2), 3e-2); + const bool notInRegion3 = !IsInBounds(firstVariable, regions(0, 3), regions(1, 3), 3e-2); + const bool notInRegion4 = !IsInBounds(firstVariable, regions(0, 4), regions(1, 4), 3e-2); if (notInRegion0 && notInRegion1 && notInRegion2 && notInRegion3 && notInRegion4) { @@ -306,7 +306,7 @@ bool AVariableBoundsCheck(const arma::cube& paretoSet) } /** - * Test DirichletMOEAD against the third problem of ZDT Test Suite. ZDT-3 is a 30 + * Test AGEMOEA against the third problem of ZDT Test Suite. ZDT-3 is a 30 * variable-2 objective problem with disconnected Pareto Fronts. */ TEST_CASE("AGEMOEADIRICHLETZDT3Test", "[AGEMOEADTest]") From 4e90dcbfc1e827af3d2ac144277e5377d63eee28 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Tue, 9 Jul 2024 23:32:09 +0530 Subject: [PATCH 16/31] Added documentation --- doc/optimizers.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/doc/optimizers.md b/doc/optimizers.md index bb8a25073..f7ddda9a9 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -501,6 +501,69 @@ optimizer.Optimize(f, coordinates); * [Adam: A Method for Stochastic Optimization](http://arxiv.org/abs/1412.6980) (see section 7) * [Differentiable separable functions](#differentiable-separable-functions) +## AGEMOEA + +*An optimizer for arbitrary multi-objective functions.* + +Adaptive Geometry Estimation based Multi-Objective Evolutionary Algorithm (AGE-MOEA) is an optimization framework based on NSGA-II yet differs from it in replacing the crowding distance of NSGA-II by a survival score, for which calculations need the diversity and proximity of non-dominated sets. To simplify the computation of the survival score, in each generation, the geometry of the initial non-dominated subset is estimated by AGE-MOEA afterwards, this estimation which gets more accurate as the algorithm matures, is used as the geometry of the Pareto set + +#### Constructors + + * `AGEMOEA()` + * `AGEMOEA(`_`populationSize, maxGenerations, crossoverProb, distributionIndex, epsilon, eta, lowerBound, upperBound`_`)` + +#### Attributes + +| **type** | **name** | **description** | **default** | +|----------|----------|-----------------|-------------| +| `size_t` | **`populationSize`** | The number of candidates in the population. This should be at least 4 in size and a multiple of 4. | `100` | +| `size_t` | **`maxGenerations`** | The maximum number of generations allowed for AGEMOEA. | `2000` | +| `double` | **`crossoverProb`** | Probability that a crossover will occur. | `0.6` | +| `double` | **`distributionIndex`** | The crowding degree of the mutation. | `20` | +| `double` | **`epsilon`** | The value used internally to evaluate approximate equality in crowding distance based sorting. | `1e-6` | +| `double` | **`eta`** | The distance parameters of the crossover distribution. | `20` | +| `double`, `arma::vec` | **`lowerBound`** | Lower bound of the coordinates on the coordinates of the whole population during the search process. | `0` | +| `double`, `arma::vec` | **`upperBound`** | Lower bound of the coordinates on the coordinates of the whole population during the search process. | `1` | + +Note that the parameters `lowerBound` and `upperBound` are overloaded. Data types of `double` or `arma::mat` may be used. If they are initialized as single values of `double`, then the same value of the bound applies to all the axes, resulting in an initialization following a uniform distribution in a hypercube. If they are initialized as matrices of `arma::mat`, then the value of `lowerBound[i]` applies to axis `[i]`; similarly, for values in `upperBound`. This results in an initialization following a uniform distribution in a hyperrectangle within the specified bounds. + +Attributes of the optimizer may also be changed via the member methods +`PopulationSize()`, `MaxGenerations()`, `CrossoverRate()`, `DistributionIndex()`, `Eta()`, `Epsilon()`, `LowerBound()` and `UpperBound()`. + +#### Examples + +
+Click to collapse/expand example code. + + +```c++ +SchafferFunctionN1 SCH; +arma::vec lowerBound("-1000"); +arma::vec upperBound("1000"); +AGEMOEA opt(50, 1000, 0.6, 20, 1e-6, 20, lowerBound, upperBound); + +typedef decltype(SCH.objectiveA) ObjectiveTypeA; +typedef decltype(SCH.objectiveB) ObjectiveTypeB; + +arma::mat coords = SCH.GetInitialPoint(); +std::tuple objectives = SCH.GetObjectives(); + +// obj will contain the minimum sum of objectiveA and objectiveB found on the best front. +double obj = opt.Optimize(objectives, coords); +// Now obtain the best front. +arma::cube bestFront = opt.ParetoFront(); +``` + +
+ +#### See also: + + * [SGD in Wikipedia](https://en.wikipedia.org/wiki/Stochastic_gradient_descent) + * [SGD](#standard-sgd) + * [Adaptive Gradient Methods with Dynamic Bound of Learning Rate](https://arxiv.org/abs/1902.09843) + * [Adam: A Method for Stochastic Optimization](http://arxiv.org/abs/1412.6980) + * [Differentiable separable functions](#differentiable-separable-functions) + ## AMSBound *An optimizer for [differentiable separable functions](#differentiable-separable-functions).* @@ -2107,7 +2170,7 @@ size equal to that of the starting population. #### Constructors * `NSGA2()` - * `NSGA2(`_`populationSize, maxGenerations, crossoverProb, mutationProb, mutationStrength, epsilon, lowerBound, upperBound`_`)` + * `NSGA2(`_`populationSize, maxGenerations, crossoverProb, distributionIndex, epsilon, eta, lowerBound, upperBound`_`)` #### Attributes From be5bc43febb251890e825063e73a940c87ac2648 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 10 Jul 2024 22:31:50 +0530 Subject: [PATCH 17/31] added more tries to ZDT3 --- tests/agemoea_test.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp index b75f500bc..4dbdefa82 100644 --- a/tests/agemoea_test.cpp +++ b/tests/agemoea_test.cpp @@ -320,12 +320,17 @@ TEST_CASE("AGEMOEADIRICHLETZDT3Test", "[AGEMOEADTest]") typedef decltype(ZDT_THREE.objectiveF1) ObjectiveTypeA; typedef decltype(ZDT_THREE.objectiveF2) ObjectiveTypeB; + bool success = true; + for (size_t tries = 0; tries < 2; tries++) + { + arma::mat coords = ZDT_THREE.GetInitialPoint(); + std::tuple objectives = ZDT_THREE.GetObjectives(); - arma::mat coords = ZDT_THREE.GetInitialPoint(); - std::tuple objectives = ZDT_THREE.GetObjectives(); - - opt.Optimize(objectives, coords); + opt.Optimize(objectives, coords); - const arma::cube& finalPopulation = opt.ParetoSet(); - REQUIRE(AVariableBoundsCheck(finalPopulation)); + const arma::cube& finalPopulation = opt.ParetoSet(); + success = AVariableBoundsCheck(finalPopulation); + if (success){ break;} + } + REQUIRE(success); } \ No newline at end of file From fb4149e308de3557bf7f30c7a27cccc7c741c6f9 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Thu, 11 Jul 2024 00:43:57 +0530 Subject: [PATCH 18/31] Documentation changes --- doc/optimizers.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/optimizers.md b/doc/optimizers.md index f7ddda9a9..c9f0881df 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -558,11 +558,8 @@ arma::cube bestFront = opt.ParetoFront(); #### See also: - * [SGD in Wikipedia](https://en.wikipedia.org/wiki/Stochastic_gradient_descent) - * [SGD](#standard-sgd) - * [Adaptive Gradient Methods with Dynamic Bound of Learning Rate](https://arxiv.org/abs/1902.09843) - * [Adam: A Method for Stochastic Optimization](http://arxiv.org/abs/1412.6980) - * [Differentiable separable functions](#differentiable-separable-functions) + * [An adaptive evolutionary algorithm based on non-euclidean geometry for many-objective optimization](https://doi.org/10.1145/3321707.3321839) + * [Wikipedia Multi-Objective optimization](https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://en.wikipedia.org/wiki/Multi-objective_optimization&ved=2ahUKEwjmnsj0lp2HAxVnzjgGHVWkCyUQFnoECB8QAQ&usg=AOvVaw0G76gxf9FiNUirlc4O_BCJ) ## AMSBound From ad9e80f87bd61e01d204d384479b38878c02a718 Mon Sep 17 00:00:00 2001 From: Satyam Shukla <118750341+IWNMWE@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:11:27 +0530 Subject: [PATCH 19/31] Updated ensmallen.hpp --- include/ensmallen.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ensmallen.hpp b/include/ensmallen.hpp index dacbe44d4..99aceea4c 100644 --- a/include/ensmallen.hpp +++ b/include/ensmallen.hpp @@ -66,8 +66,8 @@ #include "ensmallen_bits/utility/any.hpp" #include "ensmallen_bits/utility/arma_traits.hpp" #include "ensmallen_bits/utility/indicators/epsilon.hpp" -#include "ensmallen_bits/utility/indicators/igd_plus.hpp" #include "ensmallen_bits/utility/indicators/igd.hpp" +#include "ensmallen_bits/utility/indicators/igd_plus.hpp" // Contains traits, must be placed before report callback. #include "ensmallen_bits/function.hpp" // TODO: should move to function/ From e32ef1bda370fef2f1067e82eeb2129c88757456 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 24 Jul 2024 22:06:02 +0530 Subject: [PATCH 20/31] style fixes --- doc/function_types.md | 1 + doc/optimizers.md | 4 +- .../{age_moea => agemoea}/agemoea.hpp | 75 +++++---- .../{age_moea => agemoea}/agemoea_impl.hpp | 151 ++++++++++-------- .../ensmallen_bits/utility/indicators/igd.hpp | 6 +- 5 files changed, 136 insertions(+), 101 deletions(-) rename include/ensmallen_bits/{age_moea => agemoea}/agemoea.hpp (89%) rename include/ensmallen_bits/{age_moea => agemoea}/agemoea_impl.hpp (88%) diff --git a/doc/function_types.md b/doc/function_types.md index 418ac3501..e8f8e469b 100644 --- a/doc/function_types.md +++ b/doc/function_types.md @@ -891,6 +891,7 @@ front. The following optimizers can be used with multi-objective functions: - [NSGA2](#nsga2) - [MOEA/D-DE](#moead) +- [AGEMOEA](#agemoea) ## Constrained functions diff --git a/doc/optimizers.md b/doc/optimizers.md index c9f0881df..9f0acf50a 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -505,7 +505,7 @@ optimizer.Optimize(f, coordinates); *An optimizer for arbitrary multi-objective functions.* -Adaptive Geometry Estimation based Multi-Objective Evolutionary Algorithm (AGE-MOEA) is an optimization framework based on NSGA-II yet differs from it in replacing the crowding distance of NSGA-II by a survival score, for which calculations need the diversity and proximity of non-dominated sets. To simplify the computation of the survival score, in each generation, the geometry of the initial non-dominated subset is estimated by AGE-MOEA afterwards, this estimation which gets more accurate as the algorithm matures, is used as the geometry of the Pareto set +Adaptive Geometry Estimation based Multi-Objective Evolutionary Algorithm (AGE-MOEA) is an optimization framework based on NSGA-II yet differs from it in replacing the crowding distance of NSGA-II by a survival score, for which calculations need the diversity and proximity of non-dominated sets. To simplify the computation of the survival score, in each generation, the geometry of the initial non-dominated subset is estimated by AGE-MOEA afterwards, this estimation which gets more accurate as the algorithm matures, is used as the geometry of the Pareto set. #### Constructors @@ -559,7 +559,7 @@ arma::cube bestFront = opt.ParetoFront(); #### See also: * [An adaptive evolutionary algorithm based on non-euclidean geometry for many-objective optimization](https://doi.org/10.1145/3321707.3321839) - * [Wikipedia Multi-Objective optimization](https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://en.wikipedia.org/wiki/Multi-objective_optimization&ved=2ahUKEwjmnsj0lp2HAxVnzjgGHVWkCyUQFnoECB8QAQ&usg=AOvVaw0G76gxf9FiNUirlc4O_BCJ) + * [Multi-Objective Optimization in Wikipedia](https://en.wikipedia.org/wiki/Multi-objective_optimization) ## AMSBound diff --git a/include/ensmallen_bits/age_moea/agemoea.hpp b/include/ensmallen_bits/agemoea/agemoea.hpp similarity index 89% rename from include/ensmallen_bits/age_moea/agemoea.hpp rename to include/ensmallen_bits/agemoea/agemoea.hpp index 1f001eac7..23c49511c 100644 --- a/include/ensmallen_bits/age_moea/agemoea.hpp +++ b/include/ensmallen_bits/agemoea/agemoea.hpp @@ -280,9 +280,10 @@ class AGEMOEA * @param calculatedObjectives The previously calculated objectives. */ template - void FastNonDominatedSort(std::vector>& fronts, - std::vector& ranks, - std::vector >& calculatedObjectives); + void FastNonDominatedSort( + std::vector >& fronts, + std::vector& ranks, + std::vector >& calculatedObjectives); /** * Operator to check if one candidate Pareto-dominates the other. @@ -298,9 +299,10 @@ class AGEMOEA * @return true if candidateP Pareto dominates candidateQ, otherwise, false. */ template - bool Dominates(std::vector>& calculatedObjectives, - size_t candidateP, - size_t candidateQ); + bool Dominates( + std::vector>& calculatedObjectives, + size_t candidateP, + size_t candidateQ); /** * Assigns Survival Score metric for sorting. @@ -314,13 +316,14 @@ class AGEMOEA * @param fNum teh current front index. */ template - void SurvivalScoreAssignment(const std::vector& front, - const arma::Col& idealPoint, - std::vector>& calculatedObjectives, - std::vector& survivalScore, - arma::Col& normalize, - double& dimension, - size_t fNum); + void SurvivalScoreAssignment( + const std::vector& front, + const arma::Col& idealPoint, + std::vector>& calculatedObjectives, + std::vector& survivalScore, + arma::Col& normalize, + double& dimension, + size_t fNum); /** * The operator used in the AGE-MOEA survival score based sorting. @@ -339,10 +342,11 @@ class AGEMOEA * @return true if the first candidate is preferred, otherwise, false. */ template - bool SurvivalScoreOperator(size_t idxP, - size_t idxQ, - const std::vector& ranks, - const std::vector& survivalScore); + bool SurvivalScoreOperator( + size_t idxP, + size_t idxQ, + const std::vector& ranks, + const std::vector& survivalScore); /** * Normalizes the front given the extreme points in the current front. @@ -355,10 +359,10 @@ class AGEMOEA */ template void NormalizeFront( - std::vector >& calculatedObjectives, - arma::Col& normalization, - const std::vector& front, - const arma::Row& extreme); + std::vector>& calculatedObjectives, + arma::Col& normalization, + const std::vector& front, + const arma::Row& extreme); /** * Get the geometry information p of Lp norm (p > 0). @@ -371,8 +375,8 @@ class AGEMOEA template double GetGeometry( std::vector >& calculatedObjectives, - const std::vector& front, - const arma::Row& extreme); + const std::vector& front, + const arma::Row& extreme); /** * Finds the pairwise Lp distance between all the points in the front. @@ -383,10 +387,11 @@ class AGEMOEA * @param dimension The calculated dimension of the front. */ template - void PairwiseDistance(MatType& final, + void PairwiseDistance( + MatType& final, std::vector >& calculatedObjectives, - const std::vector& front, - double dimension); + const std::vector& front, + double dimension); /** * Finding the indexes of the extreme points in the front. @@ -396,9 +401,10 @@ class AGEMOEA * @param front The front of the current generation. */ template - void FindExtremePoints(arma::Row& indexes, + void FindExtremePoints( + arma::Row& indexes, std::vector >& calculatedObjectives, - const std::vector& front); + const std::vector& front); /** * Finding the distance of each point in the front from the line formed @@ -411,11 +417,12 @@ class AGEMOEA * @param pointB The second point on the line. */ template - void PointToLineDistance(arma::Row& distances, + void PointToLineDistance( + arma::Row& distances, std::vector >& calculatedObjectives, - const std::vector& front, - const arma::Col& pointA, - const arma::Col& pointB); + const std::vector& front, + const arma::Col& pointA, + const arma::Col& pointB); /** * Find the Diversity score corresponding the solution S using the selected set. @@ -427,8 +434,8 @@ class AGEMOEA */ template typename MatType::elem_type DiversityScore(std::set& selected, - const MatType& pairwiseDistance, - size_t S); + const MatType& pairwiseDistance, + size_t S); //! The number of objectives being optimised for. size_t numObjectives; diff --git a/include/ensmallen_bits/age_moea/agemoea_impl.hpp b/include/ensmallen_bits/agemoea/agemoea_impl.hpp similarity index 88% rename from include/ensmallen_bits/age_moea/agemoea_impl.hpp rename to include/ensmallen_bits/agemoea/agemoea_impl.hpp index 8a5305a65..f5a49998b 100644 --- a/include/ensmallen_bits/age_moea/agemoea_impl.hpp +++ b/include/ensmallen_bits/agemoea/agemoea_impl.hpp @@ -134,7 +134,8 @@ typename MatType::elem_type AGEMOEA::Optimize( iterate.n_cols) - 0.5 + iterate); // Constrain all genes to be within bounds. - population[i] = arma::min(arma::max(population[i], castedLowerBound), castedUpperBound); + population[i] = arma::min(arma::max(population[i], castedLowerBound), + castedUpperBound); } Info << "AGEMOEA initialized successfully. Optimization started." << std::endl; @@ -161,14 +162,16 @@ typename MatType::elem_type AGEMOEA::Optimize( arma::Col idealPoint(calculatedObjectives[fronts[0][0]]); for (size_t index = 1; index < fronts[0].size(); index++) { - idealPoint = arma::min(idealPoint, calculatedObjectives[fronts[0][index]]); + idealPoint = arma::min(idealPoint, + calculatedObjectives[fronts[0][index]]); } // Perform survival score assignment. survivalScore.resize(population.size()); std::fill(survivalScore.begin(), survivalScore.end(), 0.); double dimension; - arma::Col normalize(numObjectives, arma::fill::zeros); + arma::Col normalize(numObjectives, + arma::fill::zeros); for (size_t fNum = 0; fNum < fronts.size(); fNum++) { SurvivalScoreAssignment(fronts[fNum], idealPoint, @@ -183,14 +186,17 @@ typename MatType::elem_type AGEMOEA::Optimize( size_t idxP{}, idxQ{}; for (size_t i = 0; i < population.size(); i++) { - if (arma::approx_equal(population[i], candidateP, "absdiff", epsilon)) + if (arma::approx_equal(population[i], candidateP, + "absdiff", epsilon)) idxP = i; - if (arma::approx_equal(population[i], candidateQ, "absdiff", epsilon)) + if (arma::approx_equal(population[i], candidateQ, + "absdiff", epsilon)) idxQ = i; } - return SurvivalScoreOperator(idxP, idxQ, ranks, survivalScore); + return SurvivalScoreOperator(idxP, idxQ, ranks, + survivalScore); } ); @@ -203,7 +209,8 @@ typename MatType::elem_type AGEMOEA::Optimize( } EvaluateObjectives(population, objectives, calculatedObjectives); // Set the candidates from the Pareto Set as the output. - paretoSet.set_size(population[0].n_rows, population[0].n_cols, fronts[0].size()); + paretoSet.set_size(population[0].n_rows, population[0].n_cols, + fronts[0].size()); // The Pareto Set is stored, can be obtained via ParetoSet() getter. for (size_t solutionIdx = 0; solutionIdx < fronts[0].size(); ++solutionIdx) { @@ -212,8 +219,8 @@ typename MatType::elem_type AGEMOEA::Optimize( } // Set the candidates from the Pareto Front as the output. - paretoFront.set_size(calculatedObjectives[0].n_rows, calculatedObjectives[0].n_cols, - fronts[0].size()); + paretoFront.set_size(calculatedObjectives[0].n_rows, + calculatedObjectives[0].n_cols, fronts[0].size()); // The Pareto Front is stored, can be obtained via ParetoFront() getter. for (size_t solutionIdx = 0; solutionIdx < fronts[0].size(); ++solutionIdx) { @@ -301,9 +308,9 @@ inline void AGEMOEA::BinaryTournamentSelection(std::vector& population, lowerBound, upperBound); Mutate(childA, 1.0 / static_cast(numVariables), - lowerBound, upperBound); + lowerBound, upperBound); Mutate(childB, 1.0 / static_cast(numVariables), - lowerBound, upperBound); + lowerBound, upperBound); // Add the children to the candidate population. children.push_back(childA); @@ -317,15 +324,16 @@ inline void AGEMOEA::BinaryTournamentSelection(std::vector& population, //! Perform simulated binary crossover (SBX) of genes for the children. template inline void AGEMOEA::Crossover(MatType& childA, - MatType& childB, - const MatType& parentA, - const MatType& parentB, - const MatType& lowerBound, - const MatType& upperBound) + MatType& childB, + const MatType& parentA, + const MatType& parentB, + const MatType& lowerBound, + const MatType& upperBound) { //! Generates a child from two parent individuals // according to the polynomial probability distribution. - arma::Cube parents(parentA.n_rows, parentA.n_cols, 2); + arma::Cube parents(parentA.n_rows, + parentA.n_cols, 2); parents.slice(0) = parentA; parents.slice(1) = parentB; MatType current_min = arma::min(parents, 2); @@ -338,7 +346,7 @@ inline void AGEMOEA::Crossover(MatType& childA, return; } MatType current_diff = current_max - current_min; - current_diff.transform( [](typename MatType::elem_type val) + current_diff.transform( [](typename MatType::elem_type val) { return (val < 1e-10 ? 1e-10:val); } ); // Calculating beta used for the final crossover. @@ -350,10 +358,12 @@ inline void AGEMOEA::Crossover(MatType& childA, MatType us(arma::size(alpha1), arma::fill::randu); arma::umat mask1 = us > (1.0 / alpha1); MatType betaq1 = arma::pow(us % alpha1, 1. / (eta + 1)); - betaq1 = betaq1 % (mask1 != 1.0) + arma::pow((1.0 / (2.0 - us % alpha1)), 1.0 / (eta + 1)) % mask1; + betaq1 = betaq1 % (mask1 != 1.0) + arma::pow((1.0 / (2.0 - us % alpha1)), + 1.0 / (eta + 1)) % mask1; arma::umat mask2 = us > (1.0 / alpha2); MatType betaq2 = arma::pow(us % alpha2, 1 / (eta + 1)); - betaq2 = betaq2 % (mask1 != 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), 1.0 / (eta + 1)) % mask2; + betaq2 = betaq2 % (mask1 != 1.0) + arma::pow((1.0 / (2.0 - us % alpha2)), + 1.0 / (eta + 1)) % mask2; // Variables after the cross over for all of them. MatType c1 = 0.5 * ((current_min + current_max) - betaq1 % current_diff); @@ -375,9 +385,9 @@ inline void AGEMOEA::Crossover(MatType& childA, //! Perform Polynomial mutation of the candidate. template inline void AGEMOEA::Mutate(MatType& candidate, - double mutationRate, - const MatType& lowerBound, - const MatType& upperBound) + double mutationRate, + const MatType& lowerBound, + const MatType& upperBound) { const size_t numVariables = candidate.n_rows; for (size_t geneIdx = 0; geneIdx < numVariables; ++geneIdx) @@ -388,8 +398,10 @@ inline void AGEMOEA::Mutate(MatType& candidate, const double geneRange = upperBound(geneIdx) - lowerBound(geneIdx); // Normalised distance from the bounds. - const double lowerDelta = (candidate(geneIdx) - lowerBound(geneIdx)) / geneRange; - const double upperDelta = (upperBound(geneIdx) - candidate(geneIdx)) / geneRange; + const double lowerDelta = (candidate(geneIdx) + - lowerBound(geneIdx)) / geneRange; + const double upperDelta = (upperBound(geneIdx) + - candidate(geneIdx)) / geneRange; const double mutationPower = 1. / (distributionIndex + 1.0); const double rand = arma::randu(); double value, perturbationFactor; @@ -414,12 +426,13 @@ inline void AGEMOEA::Mutate(MatType& candidate, template inline void AGEMOEA::NormalizeFront( - std::vector >& calculatedObjectives, - arma::Col& normalization, - const std::vector& front, - const arma::Row& extreme) + std::vector >& calculatedObjectives, + arma::Col& normalization, + const std::vector& front, + const arma::Row& extreme) { - arma::Mat vectorizedObjectives(numObjectives, front.size()); + arma::Mat vectorizedObjectives(numObjectives, + front.size()); for (size_t i = 0; i < front.size(); i++) { vectorizedObjectives.col(i) = calculatedObjectives[front[i]]; @@ -458,8 +471,8 @@ inline void AGEMOEA::NormalizeFront( template inline double AGEMOEA::GetGeometry( std::vector >& calculatedObjectives, - const std::vector& front, - const arma::Row& extreme) + const std::vector& front, + const arma::Row& extreme) { arma::Row d; arma::Col zero(numObjectives, arma::fill::zeros); @@ -481,10 +494,11 @@ inline double AGEMOEA::GetGeometry( //! Pairwise distance for each point in the given front. template -inline void AGEMOEA::PairwiseDistance(MatType& final, - std::vector >& calculatedObjectives, - const std::vector& front, - double dimension) +inline void AGEMOEA::PairwiseDistance( + MatType& final, + std::vector >& calculatedObjectives, + const std::vector& front, + double dimension) { for (size_t i = 0; i < front.size(); i++) { @@ -498,9 +512,10 @@ inline void AGEMOEA::PairwiseDistance(MatType& final, //! Find the index of the of the extreme points in the given front. template -void AGEMOEA::FindExtremePoints(arma::Row& indexes, - std::vector >& calculatedObjectives, - const std::vector& front) +void AGEMOEA::FindExtremePoints( + arma::Row& indexes, + std::vector >& calculatedObjectives, + const std::vector& front) { typedef typename MatType::elem_type ElemType; @@ -527,11 +542,12 @@ void AGEMOEA::FindExtremePoints(arma::Row& indexes, //! Find the distance of a front from a line formed by two points. template -void AGEMOEA::PointToLineDistance(arma::Row& distances, - std::vector >& calculatedObjectives, - const std::vector& front, - const arma::Col& pointA, - const arma::Col& pointB) +void AGEMOEA::PointToLineDistance( + arma::Row& distances, + std::vector >& calculatedObjectives, + const std::vector& front, + const arma::Col& pointA, + const arma::Col& pointB) { typedef typename MatType::elem_type ElemType; arma::Row distancesTemp(front.size()); @@ -540,11 +556,11 @@ void AGEMOEA::PointToLineDistance(arma::Row& distan for (size_t i = 0; i < front.size(); i++) { - size_t ind = front[i]; + size_t ind = front[i]; - pa = (calculatedObjectives[ind] - pointA); - double t = arma::dot(pa, ba) / arma::dot(ba, ba); - distancesTemp[i] = arma::accu(arma::pow((pa - t * ba), 2)); + pa = (calculatedObjectives[ind] - pointA); + double t = arma::dot(pa, ba) / arma::dot(ba, ba); + distancesTemp[i] = arma::accu(arma::pow((pa - t * ba), 2)); } distances = distancesTemp; } @@ -628,7 +644,8 @@ inline bool AGEMOEA::Dominates( allBetterOrEqual = false; // P is better than Q for the i-th objective function. - else if (calculatedObjectives[candidateP](i) < calculatedObjectives[candidateQ](i)) + else if (calculatedObjectives[candidateP](i) < + calculatedObjectives[candidateQ](i)) atleastOneBetter = true; } @@ -637,9 +654,10 @@ inline bool AGEMOEA::Dominates( //! Assign diversity score for a given point and teh selected set. template -inline typename MatType::elem_type AGEMOEA::DiversityScore(std::set& selected, - const MatType& pairwiseDistance, - size_t S) +inline typename MatType::elem_type AGEMOEA::DiversityScore( + std::set& selected, + const MatType& pairwiseDistance, + size_t S) { typedef typename MatType::elem_type ElemType; ElemType m = arma::datum::inf; @@ -687,7 +705,8 @@ inline void AGEMOEA::SurvivalScoreAssignment( for (size_t index = 1; index < front.size(); index++) { - calculatedObjectives[front[index]] = calculatedObjectives[front[index]] - idealPoint; + calculatedObjectives[front[index]] = calculatedObjectives[front[index]] + - idealPoint; } arma::Row extreme(numObjectives, arma::fill::zeros); @@ -696,7 +715,8 @@ inline void AGEMOEA::SurvivalScoreAssignment( for (size_t index = 0; index < front.size(); index++) { - calculatedObjectives[front[index]] = calculatedObjectives[front[index]] / normalize; + calculatedObjectives[front[index]] = calculatedObjectives[front[index]] + / normalize; } dimension = GetGeometry(calculatedObjectives, front, @@ -721,9 +741,12 @@ inline void AGEMOEA::SurvivalScoreAssignment( arma::Mat pairwise(front.size(), front.size(), arma::fill::zeros); PairwiseDistance(pairwise,calculatedObjectives,front,dimension); - arma::Row proximity(front.size(), arma::fill::zeros); - arma::Row diversity(front.size(), arma::fill::zeros); - arma::Row value(front.size(), arma::fill::zeros); + arma::Row proximity(front.size(), + arma::fill::zeros); + arma::Row diversity(front.size(), + arma::fill::zeros); + arma::Row value(front.size(), + arma::fill::zeros); // Calculate the diversity and proximity score. for (size_t i = 0; i < front.size(); i++) @@ -755,7 +778,8 @@ inline void AGEMOEA::SurvivalScoreAssignment( { calculatedObjectives[front[i]] = calculatedObjectives[front[i]] / normalize; survivalScore[front[i]] = std::pow(arma::accu(arma::pow(arma::abs( - calculatedObjectives[front[i]] - idealPoint), dimension)), 1.0 / dimension); + calculatedObjectives[front[i]] - idealPoint), dimension)), + 1.0 / dimension); } } @@ -763,10 +787,11 @@ inline void AGEMOEA::SurvivalScoreAssignment( //! Comparator for survival score based sorting. template -inline bool AGEMOEA::SurvivalScoreOperator(size_t idxP, - size_t idxQ, - const std::vector& ranks, - const std::vector& survivalScore) +inline bool AGEMOEA::SurvivalScoreOperator( + size_t idxP, + size_t idxQ, + const std::vector& ranks, + const std::vector& survivalScore) { if (ranks[idxP] < ranks[idxQ]) return true; @@ -778,4 +803,4 @@ inline bool AGEMOEA::SurvivalScoreOperator(size_t idxP, } // namespace ens -#endif \ No newline at end of file +#endif diff --git a/include/ensmallen_bits/utility/indicators/igd.hpp b/include/ensmallen_bits/utility/indicators/igd.hpp index ca8919045..c6bbcc68a 100644 --- a/include/ensmallen_bits/utility/indicators/igd.hpp +++ b/include/ensmallen_bits/utility/indicators/igd.hpp @@ -16,8 +16,10 @@ namespace ens { /** - * The IGD indicator returns the average distance from each point in the reference - * front to the nearest point to it's solution. + * The inverted generational distance( IGD) is a metric for assessing the quality + * of approximations to the Pareto front obtained by multi-objective optimization + * algorithms.The IGD indicator returns the average distance from each point in + * the reference front to the nearest point to it's solution. * * \f[ d(z,a) = \sqrt{\sum_{i = 1}^{n}(a_i - z_i)^2 \ } \ * \f] From 4095b1f1def9287af01543dc2a6a6b68786101e7 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 24 Jul 2024 22:24:14 +0530 Subject: [PATCH 21/31] change in nsga2 documentation --- doc/optimizers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/optimizers.md b/doc/optimizers.md index 9f0acf50a..de2e533e1 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -2167,7 +2167,7 @@ size equal to that of the starting population. #### Constructors * `NSGA2()` - * `NSGA2(`_`populationSize, maxGenerations, crossoverProb, distributionIndex, epsilon, eta, lowerBound, upperBound`_`)` + * `NSGA2(`_`populationSize, maxGenerations, crossoverProb, mutationStrength, epsilon, lowerBound, upperBound`_`)` #### Attributes From b4fb3d7f9e3fe3bb7ab1bd026e48c518d13f9278 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Wed, 24 Jul 2024 22:29:10 +0530 Subject: [PATCH 22/31] change file path --- include/ensmallen.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ensmallen.hpp b/include/ensmallen.hpp index 99aceea4c..c1e7f3984 100644 --- a/include/ensmallen.hpp +++ b/include/ensmallen.hpp @@ -112,7 +112,7 @@ #include "ensmallen_bits/katyusha/katyusha.hpp" #include "ensmallen_bits/lbfgs/lbfgs.hpp" #include "ensmallen_bits/lookahead/lookahead.hpp" -#include "ensmallen_bits/age_moea/agemoea.hpp" +#include "ensmallen_bits/agemoea/agemoea.hpp" #include "ensmallen_bits/moead/moead.hpp" #include "ensmallen_bits/nsga2/nsga2.hpp" #include "ensmallen_bits/padam/padam.hpp" From 6a7f048ac94b6ada92a2fde2ae3b227ed841d1a6 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Fri, 26 Jul 2024 23:45:02 +0530 Subject: [PATCH 23/31] Added documentation and style fixes --- doc/function_types.md | 82 +++++++++++++++++++ doc/optimizers.md | 2 +- include/ensmallen_bits/agemoea/agemoea.hpp | 8 +- .../ensmallen_bits/agemoea/agemoea_impl.hpp | 17 ++-- .../ensmallen_bits/utility/indicators/igd.hpp | 3 +- tests/CMakeLists.txt | 1 + tests/indicators_test.cpp | 49 +++++++++-- 7 files changed, 143 insertions(+), 19 deletions(-) diff --git a/doc/function_types.md b/doc/function_types.md index e8f8e469b..5c1b3a2e6 100644 --- a/doc/function_types.md +++ b/doc/function_types.md @@ -1195,3 +1195,85 @@ including ensmallen: This can be useful for situations where you know that the checks should be ignored. However, be aware that the code may fail to compile and give more confusing and difficult error messages! + +# Performance Indicators + +## Epsilon + +Epsilon metric is a performance metric used in multi-objective optimization which measures +the smallest factor by which a set of solution objectives must be scaled to dominate a +reference set of solutions. Specifically, given a set of Pareto-optimal solutions, the +epsilon indicator finds the minimum value ϵ such that each solution in the set is at +least as good as every solution in the reference set when the objectives are scaled by ϵ. + +#### Constructors + +* `Epsilon()` + +#### Examples + +```c++ + arma::cube referenceFront(2, 1, 3); + double tol = 1e-10; + referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; + referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; + referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; + arma::cube front = referenceFront * 1.1; + double eps = Epsilon::Evaluate(front, referenceFront); +``` +#### See also: + +* [Performance Assessment of Multiobjective Optimizers: An Analysis and Review](https://sop.tik.ee.ethz.ch/publicationListFiles/ztlf2003a.pdf) + +## IGD + +Inverse Generational Distance (IGD) is a performance metric used in multi-objective optimization +to evaluate the quality of a set of solutions relative to a reference set, typically representing +the true Pareto front. IGD measures the average distance from each point in the reference set to +the closest point in the obtained solution set. + +#### Constructors + +* `IGD()` + +#### Examples + +```c++ + arma::cube referenceFront(2, 1, 3); + double tol = 1e-10; + referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; + referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; + referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; + arma::cube front = referenceFront * 1.1; + // The third parameter is the power constant in the distance formula. + double igd = IGD::Evaluate(front, referenceFront, 1); +``` +#### See also: + +* [Performance Assessment of Multiobjective Optimizers: An Analysis and Review](https://sop.tik.ee.ethz.ch/publicationListFiles/ztlf2003a.pdf) + +## IGD Plus + +IGD Plus (IGD+) is a variant of the Inverse Generational Distance (IGD) metric used in multi-objective +optimization. It refines the traditional IGD metric by incorporating a preference for Pareto-dominance +in the distance calculation. This modification helps IGD+ better reflect both the convergence to the +Pareto front and the diversity of the solution set. + +#### Constructors + +* `IGDPlus()` + +#### Examples + +```c++ + arma::cube referenceFront(2, 1, 3); + double tol = 1e-10; + referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; + referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; + referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; + arma::cube front = referenceFront * 1.1; + double igdPlus = IGDPlus::Evaluate(front, referenceFront); +``` +#### See also: + +* [Modified Distance Calculation in Generational Distance and Inverted Generational Distance](https://link.springer.com/chapter/10.1007/978-3-319-15892-1_8) \ No newline at end of file diff --git a/doc/optimizers.md b/doc/optimizers.md index de2e533e1..12b447216 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -2167,7 +2167,7 @@ size equal to that of the starting population. #### Constructors * `NSGA2()` - * `NSGA2(`_`populationSize, maxGenerations, crossoverProb, mutationStrength, epsilon, lowerBound, upperBound`_`)` + * `NSGA2(`_`populationSize, maxGenerations, mutationProb, crossoverProb, mutationStrength, epsilon, lowerBound, upperBound`_`)` #### Attributes diff --git a/include/ensmallen_bits/agemoea/agemoea.hpp b/include/ensmallen_bits/agemoea/agemoea.hpp index 23c49511c..f7c5c2f79 100644 --- a/include/ensmallen_bits/agemoea/agemoea.hpp +++ b/include/ensmallen_bits/agemoea/agemoea.hpp @@ -121,10 +121,10 @@ class AGEMOEA template - typename MatType::elem_type Optimize( - std::tuple& objectives, - MatType& iterate, - CallbackTypes&&... callbacks); + typename MatType::elem_type Optimize( + std::tuple& objectives, + MatType& iterate, + CallbackTypes&&... callbacks); //! Get the population size. size_t PopulationSize() const { return populationSize; } diff --git a/include/ensmallen_bits/agemoea/agemoea_impl.hpp b/include/ensmallen_bits/agemoea/agemoea_impl.hpp index f5a49998b..4cf21e2b1 100644 --- a/include/ensmallen_bits/agemoea/agemoea_impl.hpp +++ b/include/ensmallen_bits/agemoea/agemoea_impl.hpp @@ -303,7 +303,7 @@ inline void AGEMOEA::BinaryTournamentSelection(std::vector& population, // Initialize the children to the respective parents. MatType childA = population[indexA], childB = population[indexB]; - if(arma::randu() <= crossoverProb) + if (arma::randu() <= crossoverProb) Crossover(childA, childB, population[indexA], population[indexB], lowerBound, upperBound); @@ -438,7 +438,7 @@ inline void AGEMOEA::NormalizeFront( vectorizedObjectives.col(i) = calculatedObjectives[front[i]]; } - if(front.size() < numObjectives) + if (front.size() < numObjectives) { normalization = arma::max(vectorizedObjectives, 1); return; @@ -487,7 +487,8 @@ inline double AGEMOEA::GetGeometry( size_t index = arma::index_min(d); double avg = arma::accu(calculatedObjectives[front[index]]) / static_cast (numObjectives); double p = std::log(numObjectives) / std::log(1.0 / avg); - if (p <= 0.1 || std::isnan(p)) {p = 1.0;} + if (p <= 0.1 || std::isnan(p)) + p = 1.0; return p; } @@ -519,7 +520,7 @@ void AGEMOEA::FindExtremePoints( { typedef typename MatType::elem_type ElemType; - if(numObjectives >= front.size()) + if (numObjectives >= front.size()) { indexes = arma::linspace>(0, front.size() - 1, front.size()); return; @@ -665,7 +666,7 @@ inline typename MatType::elem_type AGEMOEA::DiversityScore( std::set::iterator it; for (it = selected.begin(); it != selected.end(); it++) { - if(*it == S){ continue; } + if (*it == S){ continue; } if (pairwiseDistance(S, *it) < m) { m1 = m; @@ -693,9 +694,9 @@ inline void AGEMOEA::SurvivalScoreAssignment( typedef typename MatType::elem_type ElemType; // Calculations for the first front. - if(fNum == 0){ + if (fNum == 0){ - if(front.size() < numObjectives) + if (front.size() < numObjectives) { dimension = 1; arma::Row extreme(numObjectives, arma::fill::zeros); @@ -774,7 +775,7 @@ inline void AGEMOEA::SurvivalScoreAssignment( // Calculations for the other fronts. else { - for(size_t i = 0; i < front.size(); i++) + for (size_t i = 0; i < front.size(); i++) { calculatedObjectives[front[i]] = calculatedObjectives[front[i]] / normalize; survivalScore[front[i]] = std::pow(arma::accu(arma::pow(arma::abs( diff --git a/include/ensmallen_bits/utility/indicators/igd.hpp b/include/ensmallen_bits/utility/indicators/igd.hpp index c6bbcc68a..3adf1d772 100644 --- a/include/ensmallen_bits/utility/indicators/igd.hpp +++ b/include/ensmallen_bits/utility/indicators/igd.hpp @@ -75,7 +75,8 @@ namespace ens { ElemType z = referenceFront(k, 0, i); ElemType a = front(k, 0, j); // Assuming minimization of all objectives. - dist += std::pow(a - z, 2); + //! IGD does not clip negative differences to 0 + dist += std::pow(a - z, 2); } dist = std::sqrt(dist); if (dist < min) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8857ba048..515247ee0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,7 @@ set(ENSMALLEN_TESTS_SOURCES gradient_descent_test.cpp grid_search_test.cpp iqn_test.cpp + indicators_test.cpp katyusha_test.cpp lbfgs_test.cpp line_search_test.cpp diff --git a/tests/indicators_test.cpp b/tests/indicators_test.cpp index 3f641fcbe..34d31677b 100644 --- a/tests/indicators_test.cpp +++ b/tests/indicators_test.cpp @@ -2,7 +2,7 @@ * @file indicators_test.cpp * @author Nanubala Gnana Sai * - * Test file for all the indicators: Epsilon, IGD+. + * Test file for all the indicators: Epsilon, IGD+, IGD. * * ensmallen is free software; you may redistribute it and/or modify it under * the terms of the 3-clause BSD license. You should have received a copy of @@ -17,7 +17,7 @@ using namespace ens; using namespace ens::test; /** - * Calculates the Epsilon metric for the pair of fronts. + * Calculates the Epsilon performance indicator for the pair of fronts. * Tests for data of type double. * The reference numerical results have been taken from hand calculated values. * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/285 @@ -37,7 +37,7 @@ TEST_CASE("EpsilonDoubleTest", "[IndicatorsTest]") } /** - * Calculates the Epsilon metric for the pair of fronts. + * Calculates the Epsilon performance indicator for the pair of fronts. * Tests for data of type float. * The reference numerical results have been taken from hand calculated values. * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/285 @@ -57,7 +57,46 @@ TEST_CASE("EpsilonFloatTest", "[IndicatorsTest]") } /** - * Calculates the IGD+ metric for the pair of fronts. + * Calculates the IGD performance indicator for the pair of fronts. + * Tests for data of type double. + * The reference numerical results have been taken from hand calculated values. + * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/ + * for more. + */ +TEST_CASE("IGDDoubleTest", "[IndicatorsTest]") +{ + arma::cube referenceFront(2, 1, 3); + double tol = 1e-10; + referenceFront.slice(0) = arma::vec{0.11111111, 0.75039705}; + referenceFront.slice(1) = arma::vec{0.22222222, 0.60558677}; + referenceFront.slice(2) = arma::vec{0.33333333, 0.49446993}; + arma::cube front = referenceFront * 1.1; + double igd = IGD::Evaluate(front, referenceFront, 1); + REQUIRE(igd == Approx(0.06666608265617857).margin(tol)); +} + +/** + * Calculates the IGD performance indicator for the pair of fronts. + * Tests for data of type float. + * The reference numerical results have been taken from hand calculated values. + * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/ + * for more. + */ +TEST_CASE("IGDFloatTest", "[IndicatorsTest]") +{ + arma::fcube referenceFront(2, 1, 3); + float tol = 1e-10; + referenceFront.slice(0) = arma::fvec{0.11111111f, 0.75039705f}; + referenceFront.slice(1) = arma::fvec{0.22222222f, 0.60558677f}; + referenceFront.slice(2) = arma::fvec{0.33333333f, 0.49446993f}; + arma::fcube front = referenceFront * 1.1; + float igd = IGD::Evaluate(front, referenceFront, 1); + + REQUIRE(igd == Approx(0.06666608265617857).margin(tol)); +} + +/** + * Calculates the IGD+ performance indicator for the pair of fronts. * Tests for data of type double. * The reference numerical results have been taken from hand calculated values. * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/285 @@ -77,7 +116,7 @@ TEST_CASE("IGDPlusDoubleTest", "[IndicatorsTest]") } /** - * Calculates the IGD+ metric for the pair of fronts. + * Calculates the IGD+ performance indicator for the pair of fronts. * Tests for data of type float. * The reference numerical results have been taken from hand calculated values. * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/285 From 762b14e60defca002ef416b736259bc03b9d7646 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Fri, 26 Jul 2024 23:54:21 +0530 Subject: [PATCH 24/31] style fixes --- include/ensmallen_bits/agemoea/agemoea_impl.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ensmallen_bits/agemoea/agemoea_impl.hpp b/include/ensmallen_bits/agemoea/agemoea_impl.hpp index 4cf21e2b1..c643a7185 100644 --- a/include/ensmallen_bits/agemoea/agemoea_impl.hpp +++ b/include/ensmallen_bits/agemoea/agemoea_impl.hpp @@ -694,8 +694,8 @@ inline void AGEMOEA::SurvivalScoreAssignment( typedef typename MatType::elem_type ElemType; // Calculations for the first front. - if (fNum == 0){ - + if (fNum == 0) + { if (front.size() < numObjectives) { dimension = 1; From d9495a9425bbd1a7b479d403f01cab5364d8d765 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Fri, 26 Jul 2024 23:58:53 +0530 Subject: [PATCH 25/31] Fixed nsga2 documentation --- doc/optimizers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/optimizers.md b/doc/optimizers.md index 12b447216..5d17a19d2 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -2167,7 +2167,7 @@ size equal to that of the starting population. #### Constructors * `NSGA2()` - * `NSGA2(`_`populationSize, maxGenerations, mutationProb, crossoverProb, mutationStrength, epsilon, lowerBound, upperBound`_`)` + * `NSGA2(`_`populationSize, maxGenerations, crossoverProb, mutationProb, mutationStrength, epsilon, lowerBound, upperBound`_`)` #### Attributes From 77c6fe4302d1f459c7c01547a5ca8dd78680184a Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sat, 27 Jul 2024 01:29:02 +0530 Subject: [PATCH 26/31] Added colab link --- tests/indicators_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/indicators_test.cpp b/tests/indicators_test.cpp index 34d31677b..478a1ea2c 100644 --- a/tests/indicators_test.cpp +++ b/tests/indicators_test.cpp @@ -79,7 +79,7 @@ TEST_CASE("IGDDoubleTest", "[IndicatorsTest]") * Calculates the IGD performance indicator for the pair of fronts. * Tests for data of type float. * The reference numerical results have been taken from hand calculated values. - * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/ + * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/399 * for more. */ TEST_CASE("IGDFloatTest", "[IndicatorsTest]") @@ -99,7 +99,7 @@ TEST_CASE("IGDFloatTest", "[IndicatorsTest]") * Calculates the IGD+ performance indicator for the pair of fronts. * Tests for data of type double. * The reference numerical results have been taken from hand calculated values. - * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/285 + * Refer the IPynb notebook in https://github.com/mlpack/ensmallen/pull/399 * for more. */ TEST_CASE("IGDPlusDoubleTest", "[IndicatorsTest]") From af14105947bd8b0109bc515395e1fe959e61f2db Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sat, 27 Jul 2024 07:13:24 +0530 Subject: [PATCH 27/31] increase re tries for zdt3 --- tests/agemoea_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp index 4dbdefa82..de1e2710d 100644 --- a/tests/agemoea_test.cpp +++ b/tests/agemoea_test.cpp @@ -321,7 +321,7 @@ TEST_CASE("AGEMOEADIRICHLETZDT3Test", "[AGEMOEADTest]") typedef decltype(ZDT_THREE.objectiveF1) ObjectiveTypeA; typedef decltype(ZDT_THREE.objectiveF2) ObjectiveTypeB; bool success = true; - for (size_t tries = 0; tries < 2; tries++) + for (size_t tries = 0; tries < 4; tries++) { arma::mat coords = ZDT_THREE.GetInitialPoint(); std::tuple objectives = ZDT_THREE.GetObjectives(); From b1b51d2e729a6cc813c3dd940d31177fd23ad83d Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sat, 27 Jul 2024 11:16:34 +0530 Subject: [PATCH 28/31] ZDT3 test population percentage checks --- tests/agemoea_test.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/agemoea_test.cpp b/tests/agemoea_test.cpp index de1e2710d..406975acb 100644 --- a/tests/agemoea_test.cpp +++ b/tests/agemoea_test.cpp @@ -276,14 +276,13 @@ TEST_CASE("AGEMOEAZDTONETest", "[AGEMOEATest]") */ bool AVariableBoundsCheck(const arma::cube& paretoSet) { - bool inBounds = true; const arma::mat regions{ {0.0, 0.182228780, 0.4093136748, 0.6183967944, 0.8233317983}, {0.0830015349, 0.2577623634, 0.4538821041, 0.6525117038, 0.8518328654} }; - + double notInBounds = 0; for (size_t pointIdx = 0; pointIdx < paretoSet.n_slices; ++pointIdx) { const arma::mat& point = paretoSet.slice(pointIdx); @@ -297,12 +296,12 @@ bool AVariableBoundsCheck(const arma::cube& paretoSet) if (notInRegion0 && notInRegion1 && notInRegion2 && notInRegion3 && notInRegion4) { - inBounds = false; - break; + notInBounds++; } } - return inBounds; + notInBounds = notInBounds / paretoSet.n_slices; + return notInBounds < 0.80; } /** @@ -316,12 +315,12 @@ TEST_CASE("AGEMOEADIRICHLETZDT3Test", "[AGEMOEADTest]") const double lowerBound = 0; const double upperBound = 1; - AGEMOEA opt(20, 500, 0.8, 20, 1e-6, 20, lowerBound, upperBound); + AGEMOEA opt(50, 500, 0.8, 20, 1e-6, 20, lowerBound, upperBound); typedef decltype(ZDT_THREE.objectiveF1) ObjectiveTypeA; typedef decltype(ZDT_THREE.objectiveF2) ObjectiveTypeB; bool success = true; - for (size_t tries = 0; tries < 4; tries++) + for (size_t tries = 0; tries < 2; tries++) { arma::mat coords = ZDT_THREE.GetInitialPoint(); std::tuple objectives = ZDT_THREE.GetObjectives(); From 6db63f344555270faf7ceb9411c67918ba45f779 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Sun, 28 Jul 2024 00:11:05 +0530 Subject: [PATCH 29/31] Documentation changes --- doc/function_types.md | 164 +++++++++++++++++++++--------------------- doc/optimizers.md | 31 ++++++++ 2 files changed, 113 insertions(+), 82 deletions(-) diff --git a/doc/function_types.md b/doc/function_types.md index 5c1b3a2e6..aa267e754 100644 --- a/doc/function_types.md +++ b/doc/function_types.md @@ -884,6 +884,84 @@ arma::cube bestFront = optimizer.ParetoFront(); +### Performance Indicators + +Performance indicators in multiobjective optimization provide essential metrics +for evaluating solution quality, such as convergence to the Pareto front and solution +diversity. These indicators are vital for understanding trade-offs and guiding +algorithm selection. + +The ensmallen library offers three such indicators, aiding in the assessment and comparison +of different optimization methods: + +#### Epsilon + +Epsilon metric is a performance metric used in multi-objective optimization which measures +the smallest factor by which a set of solution objectives must be scaled to dominate a +reference set of solutions. Specifically, given a set of Pareto-optimal solutions, the +epsilon indicator finds the minimum value ϵ such that each solution in the set is at +least as good as every solution in the reference set when the objectives are scaled by ϵ. + +
+Click to collapse/expand example code. + + +```c++ + arma::cube referenceFront(2, 1, 3); + double tol = 1e-10; + referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; + referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; + referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; + arma::cube front = referenceFront * 1.1; + double eps = Epsilon::Evaluate(front, referenceFront); +``` +
+ +#### IGD + +Inverse Generational Distance (IGD) is a performance metric used in multi-objective optimization +to evaluate the quality of a set of solutions relative to a reference set, typically representing +the true Pareto front. IGD measures the average distance from each point in the reference set to +the closest point in the obtained solution set. + +
+Click to collapse/expand example code. + + +```c++ + arma::cube referenceFront(2, 1, 3); + double tol = 1e-10; + referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; + referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; + referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; + arma::cube front = referenceFront * 1.1; + // The third parameter is the power constant in the distance formula. + double igd = IGD::Evaluate(front, referenceFront, 1); +``` +
+ +#### IGD Plus + +IGD Plus (IGD+) is a variant of the Inverse Generational Distance (IGD) metric used in multi-objective +optimization. It refines the traditional IGD metric by incorporating a preference for Pareto-dominance +in the distance calculation. This modification helps IGD+ better reflect both the convergence to the +Pareto front and the diversity of the solution set. + +
+Click to collapse/expand example code. + + +```c++ + arma::cube referenceFront(2, 1, 3); + double tol = 1e-10; + referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; + referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; + referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; + arma::cube front = referenceFront * 1.1; + double igdPlus = IGDPlus::Evaluate(front, referenceFront); +``` +
+ *Note*: all multi-objective function optimizers have both the function `Optimize()` to find the best front, and also the function `ParetoFront()` to return all sets of solutions that are on the front. @@ -893,6 +971,10 @@ The following optimizers can be used with multi-objective functions: - [MOEA/D-DE](#moead) - [AGEMOEA](#agemoea) +#### See also: +* [Performance Assessment of Multiobjective Optimizers: An Analysis and Review](https://sop.tik.ee.ethz.ch/publicationListFiles/ztlf2003a.pdf) +* [Modified Distance Calculation in Generational Distance and Inverted Generational Distance](https://link.springer.com/chapter/10.1007/978-3-319-15892-1_8) + ## Constrained functions A constrained function is an objective function `f(x)` that is also subject to @@ -1195,85 +1277,3 @@ including ensmallen: This can be useful for situations where you know that the checks should be ignored. However, be aware that the code may fail to compile and give more confusing and difficult error messages! - -# Performance Indicators - -## Epsilon - -Epsilon metric is a performance metric used in multi-objective optimization which measures -the smallest factor by which a set of solution objectives must be scaled to dominate a -reference set of solutions. Specifically, given a set of Pareto-optimal solutions, the -epsilon indicator finds the minimum value ϵ such that each solution in the set is at -least as good as every solution in the reference set when the objectives are scaled by ϵ. - -#### Constructors - -* `Epsilon()` - -#### Examples - -```c++ - arma::cube referenceFront(2, 1, 3); - double tol = 1e-10; - referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; - referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; - referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; - arma::cube front = referenceFront * 1.1; - double eps = Epsilon::Evaluate(front, referenceFront); -``` -#### See also: - -* [Performance Assessment of Multiobjective Optimizers: An Analysis and Review](https://sop.tik.ee.ethz.ch/publicationListFiles/ztlf2003a.pdf) - -## IGD - -Inverse Generational Distance (IGD) is a performance metric used in multi-objective optimization -to evaluate the quality of a set of solutions relative to a reference set, typically representing -the true Pareto front. IGD measures the average distance from each point in the reference set to -the closest point in the obtained solution set. - -#### Constructors - -* `IGD()` - -#### Examples - -```c++ - arma::cube referenceFront(2, 1, 3); - double tol = 1e-10; - referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; - referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; - referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; - arma::cube front = referenceFront * 1.1; - // The third parameter is the power constant in the distance formula. - double igd = IGD::Evaluate(front, referenceFront, 1); -``` -#### See also: - -* [Performance Assessment of Multiobjective Optimizers: An Analysis and Review](https://sop.tik.ee.ethz.ch/publicationListFiles/ztlf2003a.pdf) - -## IGD Plus - -IGD Plus (IGD+) is a variant of the Inverse Generational Distance (IGD) metric used in multi-objective -optimization. It refines the traditional IGD metric by incorporating a preference for Pareto-dominance -in the distance calculation. This modification helps IGD+ better reflect both the convergence to the -Pareto front and the diversity of the solution set. - -#### Constructors - -* `IGDPlus()` - -#### Examples - -```c++ - arma::cube referenceFront(2, 1, 3); - double tol = 1e-10; - referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; - referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; - referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; - arma::cube front = referenceFront * 1.1; - double igdPlus = IGDPlus::Evaluate(front, referenceFront); -``` -#### See also: - -* [Modified Distance Calculation in Generational Distance and Inverted Generational Distance](https://link.springer.com/chapter/10.1007/978-3-319-15892-1_8) \ No newline at end of file diff --git a/doc/optimizers.md b/doc/optimizers.md index 5d17a19d2..14082473c 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -556,10 +556,41 @@ arma::cube bestFront = opt.ParetoFront(); +
+Click to collapse/expand example code. + + +```c++ +ZDT3<> ZDT_THREE(300); +const double lowerBound = 0; +const double upperBound = 1; + +AGEMOEA opt(50, 500, 0.8, 20, 1e-6, 20, lowerBound, upperBound); +typedef decltype(ZDT_THREE.objectiveF1) ObjectiveTypeA; +typedef decltype(ZDT_THREE.objectiveF2) ObjectiveTypeB; +bool success = true; +arma::mat coords = ZDT_THREE.GetInitialPoint(); +std::tuple objectives = ZDT_THREE.GetObjectives(); +opt.Optimize(objectives, coords); +const arma::cube bestFront = opt.ParetoFront(); + +NSGA2 opt2(50, 5000, 0.5, 0.5, 1e-3, 1e-6, lowerBound, upperBound); +// obj2 will contain the minimum sum of objectiveA and objectiveB found on the best front. +double obj2 = opt2.Optimize(objectives, coords); + +arma::cube NSGAFront = opt2.ParetoFront(); +// Get the IGD score for NSGA front using AGEMOEA as reference. +double igd = IGD::Evaluate(NSGAFront, bestFront, 1); +std::cout << igd << std::endl; +``` + +
+ #### See also: * [An adaptive evolutionary algorithm based on non-euclidean geometry for many-objective optimization](https://doi.org/10.1145/3321707.3321839) * [Multi-Objective Optimization in Wikipedia](https://en.wikipedia.org/wiki/Multi-objective_optimization) + * [Performance Indicators](#performance-indicators) ## AMSBound From 1aea611154a965ee7e3a554440e023bdfe5272f3 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Mon, 29 Jul 2024 00:16:45 +0530 Subject: [PATCH 30/31] Documentation changes --- doc/function_types.md | 50 ++++++++++++++++++++++--------------------- doc/optimizers.md | 4 +++- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/doc/function_types.md b/doc/function_types.md index aa267e754..b5f282136 100644 --- a/doc/function_types.md +++ b/doc/function_types.md @@ -888,8 +888,7 @@ arma::cube bestFront = optimizer.ParetoFront(); Performance indicators in multiobjective optimization provide essential metrics for evaluating solution quality, such as convergence to the Pareto front and solution -diversity. These indicators are vital for understanding trade-offs and guiding -algorithm selection. +diversity. The ensmallen library offers three such indicators, aiding in the assessment and comparison of different optimization methods: @@ -907,13 +906,14 @@ least as good as every solution in the reference set when the objectives are sca ```c++ - arma::cube referenceFront(2, 1, 3); - double tol = 1e-10; - referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; - referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; - referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; - arma::cube front = referenceFront * 1.1; - double eps = Epsilon::Evaluate(front, referenceFront); +arma::cube referenceFront(2, 1, 3); +double tol = 1e-10; +referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; +referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; +referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; +arma::cube front = referenceFront * 1.1; +// eps is approximately 1.1 +double eps = Epsilon::Evaluate(front, referenceFront); ``` @@ -929,14 +929,15 @@ the closest point in the obtained solution set. ```c++ - arma::cube referenceFront(2, 1, 3); - double tol = 1e-10; - referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; - referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; - referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; - arma::cube front = referenceFront * 1.1; - // The third parameter is the power constant in the distance formula. - double igd = IGD::Evaluate(front, referenceFront, 1); +arma::cube referenceFront(2, 1, 3); +double tol = 1e-10; +referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; +referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; +referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; +arma::cube front = referenceFront * 1.1; +// The third parameter is the power constant in the distance formula. +// IGD is approximately 0.05329 +double igd = IGD::Evaluate(front, referenceFront, 1); ``` @@ -952,13 +953,14 @@ Pareto front and the diversity of the solution set. ```c++ - arma::cube referenceFront(2, 1, 3); - double tol = 1e-10; - referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; - referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; - referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; - arma::cube front = referenceFront * 1.1; - double igdPlus = IGDPlus::Evaluate(front, referenceFront); +arma::cube referenceFront(2, 1, 3); +double tol = 1e-10; +referenceFront.slice(0) = arma::vec{0.01010101, 0.89949622}; +referenceFront.slice(1) = arma::vec{0.02020202, 0.85786619}; +referenceFront.slice(2) = arma::vec{0.03030303, 0.82592234}; +arma::cube front = referenceFront * 1.1; +// IGDPlus is approximately 0.05329 +double igdPlus = IGDPlus::Evaluate(front, referenceFront); ``` diff --git a/doc/optimizers.md b/doc/optimizers.md index 14082473c..d84dbb7e2 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -2182,6 +2182,7 @@ arma::cube bestFront = opt.ParetoFront(); * [MOEA/D-DE Algorithm](https://ieeexplore.ieee.org/document/4633340) * [Multi-objective Functions in Wikipedia](https://en.wikipedia.org/wiki/Test_functions_for_optimization#Test_functions_for_multi-objective_optimization) * [Multi-objective functions](#multi-objective-functions) +* [Performance Indicators](#performance-indicators) ## NSGA2 @@ -2248,7 +2249,8 @@ arma::cube bestFront = opt.ParetoFront(); * [NSGA-II Algorithm](https://www.iitk.ac.in/kangal/Deb_NSGA-II.pdf) * [Multi-objective Functions in Wikipedia](https://en.wikipedia.org/wiki/Test_functions_for_optimization#Test_functions_for_multi-objective_optimization) - * [Multi-objective functions](#multi-objective-functions) + * [Multi-objective functions](#multi-objective-functions) + * [Performance Indicators](#performance-indicators) ## OptimisticAdam From b87cb77fd9965f1d8f936196bca485d69f9bcd85 Mon Sep 17 00:00:00 2001 From: IWNMWE Date: Mon, 29 Jul 2024 00:19:35 +0530 Subject: [PATCH 31/31] Remove extra space --- doc/optimizers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/optimizers.md b/doc/optimizers.md index d84dbb7e2..b5f31c470 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -580,7 +580,7 @@ double obj2 = opt2.Optimize(objectives, coords); arma::cube NSGAFront = opt2.ParetoFront(); // Get the IGD score for NSGA front using AGEMOEA as reference. -double igd = IGD::Evaluate(NSGAFront, bestFront, 1); +double igd = IGD::Evaluate(NSGAFront, bestFront, 1); std::cout << igd << std::endl; ```