diff --git a/README.md b/README.md
index c91ac81..2b9afcc 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,57 @@
-# ModularCMAES  [](https://app.codacy.com/gh/IOHprofiler/ModularCMAES?utm_source=github.com&utm_medium=referral&utm_content=IOHprofiler/ModularCMAES&utm_campaign=Badge_Grade) [](https://www.codacy.com/gh/IOHprofiler/ModularCMAES/dashboard?utm_source=github.com&utm_medium=referral&utm_content=IOHprofiler/ModularCMAES&utm_campaign=Badge_Coverage)
+
+
+
+
+
+
+
+
+[](https://app.codacy.com/gh/IOHprofiler/ModularCMAES?utm_source=github.com&utm_medium=referral&utm_content=IOHprofiler/ModularCMAES&utm_campaign=Badge_Grade)
+[](https://www.codacy.com/gh/IOHprofiler/ModularCMAES/dashboard?utm_source=github.com&utm_medium=referral&utm_content=IOHprofiler/ModularCMAES&utm_campaign=Badge_Coverage)
+
+
The **Modular CMA-ES** is a Python and C++ package that provides a modular implementation of the Covariance Matrix Adaptation Evolution Strategy (CMA-ES) algorithm. This package allows you to create various algorithmic variants of CMA-ES by enabling or disabling different modules, offering flexibility and customization in evolutionary optimization. In addition to the CMA-ES, the library includes an implementation of the Matrix Adaptation Evolution Strategy (MA-ES) algorithm, which has similar emprical performance on most problems, but signifanctly lower runtime. All modules implemented are compatible with both the CMA-ES and MA-ES.
This implementation is based on the algorithm introduced in the paper "[Evolving the Structure of Evolution Strategies. (2016)](https://ieeexplore.ieee.org/document/7850138)" by Sander van Rijn et. al. If you would like to cite this work in your research, please cite the paper: "[Tuning as a Means of Assessing the Benefits of New Ideas in Interplay with Existing Algorithmic Modules (2021)](https://doi.org/10.1145/3449726.3463167)" by Jacob de Nobel, Diederick Vermetten, Hao Wang, Carola Doerr and Thomas Bäck.
-This README provides a high level overview of the implemented modules, and provides some usage examples for both the Python-only and the C++-based versions of the framework. A complete API documentation can be found [here](https://modularcmaes.readthedocs.io/) (under construction).
+This README provides a high level overview of the implemented modules, and provides some usage examples for both the Python-only and the C++-based versions of the framework.
## Table of Contents
-- [ModularCMAES ](#modularcmaes---)
- - [Table of Contents](#table-of-contents)
- - [Installation ](#installation-)
- - [Python Installation](#python-installation)
- - [Installation from source](#installation-from-source)
- - [Usage ](#usage-)
- - [C++ Backend ](#c-backend-)
- - [High Level interface ](#high-level-interface-)
- - [Tuning ](#tuning-)
- - [Configuration Space Generation](#configuration-space-generation)
- - [Creating Settings from a Configuration](#creating-settings-from-a-configuration)
- - [Python-only (Legacy) ](#python-only-legacy-)
- - [Ask–Tell Interface ](#asktell-interface-)
- - [Modules ](#modules-)
- - [Matrix Adaptation ](#matrix-adaptation-)
- - [Active Update ](#active-update-)
- - [Elitism ](#elitism-)
- - [Orthogonal Sampling ](#orthogonal-sampling-)
- - [Sequential Selection ](#sequential-selection-)
- - [Threshold Convergence ](#threshold-convergence-)
- - [Sample Sigma ](#sample-sigma-)
- - [Quasi-Gaussian Sampling ](#quasi-gaussian-sampling-)
- - [Recombination Weights ](#recombination-weights-)
- - [Mirrored Sampling ](#mirrored-sampling-)
- - [Step size adaptation](#step-size-adaptation)
- - [Restart Strategy](#restart-strategy)
- - [Bound correction](#bound-correction)
- - [Sample Transformer ](#sample-transformer-)
- - [Repelling Restart ](#repelling-restart-)
- - [Center Placement ](#center-placement-)
- - [Citation ](#citation-)
- - [License ](#license-)
+- [Table of Contents](#table-of-contents)
+- [Installation ](#installation-)
+ - [Python Installation](#python-installation)
+ - [Installation from source](#installation-from-source)
+- [Usage ](#usage-)
+ - [C++ Backend ](#c-backend-)
+ - [High Level interface ](#high-level-interface-)
+ - [Low Level interface ](#low-level-interface-)
+ - [Tuning ](#tuning-)
+ - [Configuration Space Generation](#configuration-space-generation)
+ - [Creating Settings from a Configuration](#creating-settings-from-a-configuration)
+ - [Integer Variables](#integer-variables)
+ - [Python-only (Legacy) ](#python-only-legacy-)
+ - [Ask–Tell Interface ](#asktell-interface-)
+- [Modules ](#modules-)
+ - [Matrix Adaptation ](#matrix-adaptation-)
+ - [Active Update ](#active-update-)
+ - [Elitism ](#elitism-)
+ - [Orthogonal Sampling ](#orthogonal-sampling-)
+ - [Sequential Selection ](#sequential-selection-)
+ - [Threshold Convergence ](#threshold-convergence-)
+ - [Sample Sigma ](#sample-sigma-)
+ - [Quasi-Gaussian Sampling ](#quasi-gaussian-sampling-)
+ - [Recombination Weights ](#recombination-weights-)
+ - [Mirrored Sampling ](#mirrored-sampling-)
+ - [Step size adaptation](#step-size-adaptation)
+ - [Restart Strategy](#restart-strategy)
+ - [Bound correction](#bound-correction)
+ - [Sample Transformer ](#sample-transformer-)
+ - [Repelling Restart ](#repelling-restart-)
+ - [Center Placement ](#center-placement-)
+- [Citation ](#citation-)
+- [License ](#license-)
## Installation
@@ -99,6 +111,27 @@ If you want to work on a development version of the library, you should follow t
For performance, completeness, and modularity, the **C++ backend** is the **recommended interface** for ModularCMAES. It exposes all available modules and options through a direct Python binding.
+#### High Level interface
+
+In addition to the fully specified method described below, we can run an optimization via a friendly `fmin` interface:
+
+```python
+x0 = [0, 1, 2, 3] # Location to start the search from
+sigma0 = 0.234 # Initial estimate of the stepsize, try 0.3 * (ub - lb) if you're unsure
+budget = 100 # Total number of function evaluations
+xopt, fopt, evals, cma = c_maes.fmin(
+ func, x0, sigma0, budget,
+ # We can specify modules and setting values as keyword arguments
+ active=True,
+ target=10.0,
+ cc=0.8
+ matrix_adaptation='NONE'
+)
+```
+Note that the `func`, `x0`, `sigma0` and `budget` arguments are **required**. Modules and settings can be specified via keyword arguments by they corresponding names in the `Modules` and `Settings` objects. Module options, such as `matrix_adaptation` in the above example, can be specified by their name as `str`.
+
+#### Low Level interface
+
To run an optimization, first create a `Modules` object specifying which modules and options to enable. Then construct a `Settings` object (which defines problem dimension and strategy parameters), and pass it through a `Parameters` object to the optimizer:
```python
@@ -144,26 +177,12 @@ while not cma.break_conditions():
This modularity allows experimentation with specific parts of the evolution strategy,
such as custom selection, recombination, or adaptation routines.
-#### High Level interface
-In addition to the fully specified method described above, we can also call the optimizer via a more friendly `fmin` interface:
-```python
-x0 = [0, 1, 2, 3] # Location to start the search from
-sigma0 = 0.234 # Initial estimate of the stepsize, try 0.3 * (ub - lb) if you're unsure
-budget = 100 # Total number of function evaluations
-xopt, fopt, evals, cma = c_maes.fmin(
- func, x0, sigma0, budget,
- # We can specify modules and setting values as keyword arguments
- active=True,
- cc=0.8
-)
-```
-Note that the `func`, `x0`, `sigma0` and `budget` arguments are required. Via keyword arguments, modules and settings can be specified, provided by their names in the `Modules` and `Settings` objects.
---
-#### Tuning
+### Tuning
To facilitate **automated hyperparameter tuning**, ModularCMAES now provides functionality to **create and manipulate configuration spaces** directly compatible with popular optimization and AutoML tools, such as **SMAC**, **BOHB**. This functionality allows users to systematically explore both **algorithmic module combinations** and **numerical hyperparameters** (e.g., population size, learning rates, damping coefficients).
@@ -230,6 +249,25 @@ cma = c_maes.ModularCMAES(parameters)
cma.run(func)
```
+### Integer Variables
+A rudimentary mechanism for dealing with integer variables is implemented, which applies a lower bound on `sigma` for integer coordinates and rounds them to the nearest integer before evaluation, inspired by http://www.cmap.polytechnique.fr/~nikolaus.hansen/marty2024lb+ic-cma.pdf.
+
+An option can be provided to the `Settings` object or `settings_from_dict`/`settings_from_config` functions:
+
+```python
+dim = 5
+settings = Settings(
+ dim,
+ integer_variables=[1, 2] # A list or numpy array of integer indices
+)
+
+settings = settings_from_dict(
+ dim,
+ integer_variables=list(range(dim)) # All variables are integer
+)
+```
+
+
---
### Python-only (Legacy)
@@ -237,6 +275,7 @@ cma.run(func)
> **Legacy notice:**
> The Python-only implementation is **no longer actively developed** and does not include all features of the C++ version.
> It remains available for experimentation and teaching purposes.
+> A complete API documentation can be found [here](https://modularcmaes.readthedocs.io/) (under construction).
The Python interface provides a simple API and includes a convenience `fmin` function for optimizing a single objective function in one call:
diff --git a/banner.png b/banner.png
new file mode 100644
index 0000000..5155f64
Binary files /dev/null and b/banner.png differ
diff --git a/include/bounds.hpp b/include/bounds.hpp
index 3edce9a..1f84670 100644
--- a/include/bounds.hpp
+++ b/include/bounds.hpp
@@ -14,8 +14,6 @@ namespace parameters
namespace bounds
{
- using Mask = Eigen::Array;
-
Mask is_out_of_bounds(const Vector &xi, const Vector &lb, const Vector &ub);
bool any_out_of_bounds(const Vector &xi, const Vector &lb, const Vector &ub);
diff --git a/include/common.hpp b/include/common.hpp
index 0cfff21..9b5183c 100644
--- a/include/common.hpp
+++ b/include/common.hpp
@@ -28,10 +28,13 @@ using Matrix = Eigen::Matrix;
using Vector = Eigen::Matrix;
using Array = Eigen::Array;
using size_to = std::optional;
+using Mask = Eigen::Array;
+using Indices = Eigen::ArrayXi;
template
std::ostream &operator<<(std::ostream &os, const std::vector &x);
+
using FunctionType = std::function;
namespace constants
diff --git a/include/mapping.hpp b/include/mapping.hpp
new file mode 100644
index 0000000..ec574ec
--- /dev/null
+++ b/include/mapping.hpp
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "common.hpp"
+
+namespace parameters
+{
+ struct Parameters;
+}
+
+namespace mapping
+{
+
+ // struct IntegerHandling
+ // {
+ // Float lb_sigma;
+
+ // IntegerHandling(const size_t d, const Float mueff)
+ // : lb_sigma(std::min(0.2, mueff / static_cast(d)))
+ // {
+ // }
+
+ // virtual Array get_effective_sigma(const parameters::Parameters &p, const size_t idx);
+
+ // virtual Vector round_to_integer(const Vector &x, const Indices iidx)
+ // {
+ // auto x_rounded = x;
+ // for (const auto &idx : iidx)
+ // x_rounded[idx] = std::round(x[idx]);
+ // return x_rounded;
+ // }
+ // };
+
+ // struct NoIntegerHandling : IntegerHandling
+ // {
+ // using IntegerHandling::IntegerHandling;
+
+ // // virtual void get_effective_sigma(const parameters::Parameters& p) override {}
+ // // void round_to_integer(Eigen::Ref x, const Indices iidx) override {}
+ // };
+ // Array IntegerHandling::get_effective_sigma(const parameters::Parameters& p, const size_t idx)
+ // {
+ // Array effective_sigma = Array::Constant(p.settings.dim, p.pop.S(idx));
+
+ // const Array& Cdiag = p.adaptation->coordinate_wise_variances;
+ // for (const auto& iidx: p.settings.integer_variables)
+ // {
+ // const Float Cii = std::max(Cdiag[iidx], Float(1e-16));
+ // const Float sigma_from_lb = lb_sigma / std::sqrt(Cii);
+ // effective_sigma[iidx] = std::max(sigma_from_lb, effective_sigma[iidx]);
+ // }
+
+ // return effective_sigma;
+ // }
+
+ using CoordinateTransformType = std::function;
+
+ inline Float identity(Float x) { return x; }
+ inline Float iround(Float x) { return std::round(x); }
+
+ struct CoordinateMapping
+ {
+ size_t d;
+ std::vector transformations;
+
+ CoordinateMapping(const Indices &integer_idx, const size_t d, const Float mueff)
+ : d(d), transformations(d, identity)
+ {
+ for (int i = 0; i < integer_idx.size(); i++)
+ transformations[i] = iround;
+ }
+
+ Vector transform(const Vector &x)
+ {
+ Vector y = x;
+ for (size_t i = 0; i < d; i++)
+ y(i) = transformations[i](x(i));
+ return y;
+ }
+ };
+
+ inline std::shared_ptr get(const Indices &integer_idx, const size_t d, const Float mueff)
+ {
+ return std::make_shared(integer_idx, d, mueff);
+ }
+}
\ No newline at end of file
diff --git a/include/matrix_adaptation.hpp b/include/matrix_adaptation.hpp
index 24642d8..6e8dfa6 100644
--- a/include/matrix_adaptation.hpp
+++ b/include/matrix_adaptation.hpp
@@ -11,12 +11,18 @@ namespace matrix_adaptation
struct Adaptation
{
Vector m, m_old, dm, ps, dz;
+ Vector coordinate_wise_variances;
+
Float dd;
Float expected_length_z;
Adaptation(const size_t dim, const Vector& x0, const Vector& ps, const Float expected_length_z) :
- m(x0), m_old(dim), dm(Vector::Zero(dim)),
- ps(ps), dd(static_cast(dim)),
+ m(x0),
+ m_old(dim),
+ dm(Vector::Zero(dim)),
+ ps(ps),
+ coordinate_wise_variances(Vector::Ones(dim)),
+ dd(static_cast(dim)),
expected_length_z(expected_length_z)
{
}
@@ -50,7 +56,7 @@ namespace matrix_adaptation
virtual Vector compute_y(const Vector&) = 0;
- virtual Vector invert_x(const Vector&, Float sigma);
+ virtual Vector invert_x(const Vector&, Vector sigma);
virtual Vector invert_y(const Vector&) = 0;
@@ -61,6 +67,7 @@ namespace matrix_adaptation
dm.setZero();
ps.setZero();
dz.setZero();
+ coordinate_wise_variances.setOnes();
}
Float distance(const Vector u, const Vector& v)
diff --git a/include/mutation.hpp b/include/mutation.hpp
index 94ee08f..4553391 100644
--- a/include/mutation.hpp
+++ b/include/mutation.hpp
@@ -29,12 +29,12 @@ namespace mutation
{
Float init_threshold = 0.1;
Float decay_factor = 0.995;
- virtual Vector scale(const Vector& zi, const Float diameter, const size_t budget, const size_t evaluations);
+ virtual Vector scale(const Vector &zi, const Float diameter, const size_t budget, const size_t evaluations);
};
struct NoThresholdConvergence : ThresholdConvergence
{
- Vector scale(const Vector& zi, const Float diameter, const size_t budget, const size_t evaluations) override
+ Vector scale(const Vector &zi, const Float diameter, const size_t budget, const size_t evaluations) override
{
return zi;
}
@@ -46,41 +46,38 @@ namespace mutation
size_t seq_cutoff;
public:
- SequentialSelection(const parameters::Mirror& m, const size_t mu, const Float seq_cutoff_factor = 1.0) : seq_cutoff_factor(m == parameters::Mirror::PAIRWISE ? std::max(Float{ 2. }, seq_cutoff_factor) : seq_cutoff_factor),
- seq_cutoff(static_cast(mu* seq_cutoff_factor))
- {}
- virtual bool break_conditions(const size_t i, const Float f, Float fopt, const parameters::Mirror& m);
+ SequentialSelection(const parameters::Mirror &m, const size_t mu, const Float seq_cutoff_factor = 1.0) : seq_cutoff_factor(m == parameters::Mirror::PAIRWISE ? std::max(Float{2.}, seq_cutoff_factor) : seq_cutoff_factor),
+ seq_cutoff(static_cast(mu * seq_cutoff_factor))
+ {
+ }
+ virtual bool break_conditions(const size_t i, const Float f, Float fopt, const parameters::Mirror &m);
};
struct NoSequentialSelection : SequentialSelection
{
using SequentialSelection::SequentialSelection;
- bool break_conditions(const size_t i, const Float f, Float fopt, const parameters::Mirror& m) override { return false; }
+ bool break_conditions(const size_t i, const Float f, Float fopt, const parameters::Mirror &m) override { return false; }
};
struct SigmaSampler
{
sampling::GaussianTransformer sampler;
- SigmaSampler(const Float d) : sampler{ std::make_shared(1) }
- {}
-
- virtual void sample(const Float sigma, Population& pop, const Float tau)
+ SigmaSampler(const Float d) : sampler{std::make_shared(d)}
{
- sampler.sampler->d = pop.s.rows();
- pop.s.noalias() = (sigma * (tau * sampler().array()).exp()).matrix().eval();
}
+
+ void apply_integer_bounds(parameters::Parameters &p);
+
+ virtual void sample(const Float sigma, parameters::Parameters &p);
};
struct NoSigmaSampler : SigmaSampler
{
using SigmaSampler::SigmaSampler;
- void sample(const Float sigma, Population& pop, const Float tau) override
- {
- pop.s.setConstant(sigma);
- }
+ void sample(const Float sigma, parameters::Parameters &p) override;
};
struct Strategy
@@ -92,24 +89,25 @@ namespace mutation
Float s = 0;
Strategy(
- const std::shared_ptr& threshold_covergence,
- const std::shared_ptr& sequential_selection,
- const std::shared_ptr& sigma_sampler,
+ const std::shared_ptr &threshold_covergence,
+ const std::shared_ptr &sequential_selection,
+ const std::shared_ptr &sigma_sampler,
const Float sigma0) : tc(threshold_covergence), sq(sequential_selection), ss(sigma_sampler), sigma(sigma0)
- {}
+ {
+ }
- virtual void mutate(FunctionType& objective, const size_t n_offspring, parameters::Parameters& p);
+ virtual void mutate(FunctionType &objective, const size_t n_offspring, parameters::Parameters &p);
- virtual void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) {};
+ virtual void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) {};
};
struct CSA : Strategy
{
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
struct TPA : Strategy
@@ -120,18 +118,18 @@ namespace mutation
Float b_tpa = 0.0;
Float rank_tpa = 0.0;
- void mutate(FunctionType& objective, const size_t n_offspring, parameters::Parameters& p) override;
+ void mutate(FunctionType &objective, const size_t n_offspring, parameters::Parameters &p) override;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
struct MSR : Strategy
{
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
struct PSR : Strategy
@@ -142,65 +140,60 @@ namespace mutation
std::vector idx;
std::vector oidx;
-
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
struct XNES : Strategy
{
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
struct MXNES : Strategy
{
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
struct LPXNES : Strategy
{
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
-
struct SR : Strategy
{
constexpr static Float tgt_success_ratio = 2.0 / 11.0;
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
};
-
struct SA : Strategy
{
using Strategy::Strategy;
- void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop,
- const Population& old_pop, const parameters::Stats& stats, const size_t lambda) override;
+ void adapt(const parameters::Weights &w, std::shared_ptr adaptation, Population &pop,
+ const Population &old_pop, const parameters::Stats &stats, const size_t lambda) override;
- void mutate(FunctionType& objective, const size_t n_offspring, parameters::Parameters& p) override;
+ void mutate(FunctionType &objective, const size_t n_offspring, parameters::Parameters &p) override;
private:
Float mean_sigma;
};
-
-
- std::shared_ptr get(const parameters::Modules& m, const size_t mu, const Float d, const Float sigma);
+ std::shared_ptr get(const parameters::Modules &m, const size_t mu, const Float d, const Float sigma);
}
\ No newline at end of file
diff --git a/include/parameters.hpp b/include/parameters.hpp
index fd625db..2c140b5 100644
--- a/include/parameters.hpp
+++ b/include/parameters.hpp
@@ -12,6 +12,7 @@
#include "weights.hpp"
#include "repelling.hpp"
#include "center_placement.hpp"
+#include "mapping.hpp"
namespace parameters
{
@@ -39,7 +40,8 @@ namespace parameters
std::shared_ptr bounds;
std::shared_ptr repelling;
std::shared_ptr center_placement;
-
+ std::shared_ptr coordinate_mapping;
+
Parameters(const size_t dim);
Parameters(const Settings &settings);
diff --git a/include/population.hpp b/include/population.hpp
index f6f068a..9a1f1e8 100644
--- a/include/population.hpp
+++ b/include/population.hpp
@@ -5,33 +5,33 @@
struct Population
{
Matrix X;
+ Matrix X_transformed;
Matrix Z;
Matrix Y;
+ Matrix S;
Vector f;
- Vector s;
Vector t;
size_t d;
size_t n;
Population(const size_t d, const size_t n)
- : X(d, n), Z(d, n), Y(d, n), f(Vector::Constant(n, std::numeric_limits::infinity())), s(n), t(n), d(d), n(n) {}
+ : X(d, n), X_transformed(d, n), Z(d, n), Y(d, n), S(d, n), f(Vector::Constant(n, std::numeric_limits::infinity())), t(n), d(d), n(n) {}
- Population(const Matrix &X, const Matrix &Z, const Matrix &Y, const Vector &f, const Vector &s)
- : X(X), Z(Z), Y(Y), f(f), s(s), t(f.rows()), d(X.rows()), n(X.cols()) {}
+ Population(const Matrix &X, const Matrix &Z, const Matrix &Y, const Vector &f, const Matrix &S)
+ : X(X), X_transformed(X), Z(Z), Y(Y), S(S), f(f), t(f.rows()), d(X.rows()), n(X.cols()) {}
Population() : Population(0, 0) {}
void sort();
- Population& operator+=(const Population& other);
+ Population &operator+=(const Population &other);
void resize_cols(const size_t size);
- void keep_only(const std::vector& idx);
+ void keep_only(const std::vector &idx);
size_t n_finite() const;
};
-
std::ostream &operator<<(std::ostream &os, const Population &dt);
\ No newline at end of file
diff --git a/include/settings.hpp b/include/settings.hpp
index ed36cf9..4c34f5d 100644
--- a/include/settings.hpp
+++ b/include/settings.hpp
@@ -22,6 +22,7 @@ namespace parameters
Vector lb;
Vector ub;
+ Indices integer_variables;
Vector db;
Vector center;
Float diameter;
@@ -49,6 +50,7 @@ namespace parameters
std::optional x0 = std::nullopt,
std::optional lb = std::nullopt,
std::optional ub = std::nullopt,
+ std::optional integer_variables = std::nullopt,
std::optional cs = std::nullopt,
std::optional cc = std::nullopt,
std::optional cmu = std::nullopt,
@@ -68,6 +70,7 @@ namespace parameters
x0(x0),
lb(lb.value_or(Vector::Ones(dim) * -5)),
ub(ub.value_or(Vector::Ones(dim) * 5)),
+ integer_variables(integer_variables.value_or(Indices{})),
db(this->ub - this->lb),
center(this->lb + (db * 0.5)),
diameter(db.norm()),
diff --git a/include/weights.hpp b/include/weights.hpp
index 966f998..5d5f76e 100644
--- a/include/weights.hpp
+++ b/include/weights.hpp
@@ -17,6 +17,7 @@ namespace parameters
Float expected_length_z;
Float expected_length_ps;
Float beta;
+ Float int_lb_sigma;
Weights(const size_t dim, const size_t mu, const size_t lambda, const Settings &settings,
const Float expected_length_z);
diff --git a/scripts/matrix/get_data.py b/scripts/matrix/get_data.py
index e0860e1..4a1f546 100644
--- a/scripts/matrix/get_data.py
+++ b/scripts/matrix/get_data.py
@@ -1,7 +1,7 @@
from time import perf_counter
import warnings
import numpy as np
-import modcma.c_maes as modcma
+import modcma.c_maes as ccma
import ioh
import pandas as pd
@@ -43,7 +43,7 @@ def run_modma(problem: ioh.ProblemType,
# modules.ssa = modcma.options.StepSizeAdaptation.CSA
# modules.restart_strategy = modcma.options.RestartStrategy.STOP
- settings = modcma.Settings(
+ settings = ccma.Settings(
problem.meta_data.n_variables,
x0=x0,
modules=modules,
@@ -55,7 +55,7 @@ def run_modma(problem: ioh.ProblemType,
budget=problem.meta_data.n_variables * BUDGET,
)
- cma = modcma.ModularCMAES(settings)
+ cma = ccma.ModularCMAES(settings)
start = perf_counter()
while not cma.break_conditions():
if cma.p.criteria.any():
@@ -71,14 +71,14 @@ def run_modma(problem: ioh.ProblemType,
class RestartCollector:
- def __init__(self, strategy = modcma.options.RestartStrategy.STOP):
- modules = modcma.parameters.Modules()
+ def __init__(self, strategy = ccma.options.RestartStrategy.STOP):
+ modules = ccma.parameters.Modules()
modules.restart_strategy = strategy
- settings = modcma.Settings(
+ settings = ccma.Settings(
2,
modules=modules,
)
- cma = modcma.ModularCMAES(settings)
+ cma = ccma.ModularCMAES(settings)
self.names = [x.name for x in cma.p.criteria.items]
self.reset()
@@ -106,7 +106,7 @@ def collect(name, module_name, option):
problem.attach_logger(logger)
runs = []
for i in range(N_REPEATS):
- modcma.utils.set_seed(21 + fid * d * i)
+ ccma.utils.set_seed(21 + fid * d * i)
collector.reset()
run_modma(problem, np.zeros(d), collector, module_name, option)
# print(name, fid, d, problem.state.current_best_internal.y, problem.state.evaluations)
@@ -116,13 +116,13 @@ def collect(name, module_name, option):
def make_modules(module_name, option):
- modules = modcma.parameters.Modules()
- modules.restart_strategy = modcma.options.RestartStrategy.STOP
+ modules = ccma.parameters.Modules()
+ modules.restart_strategy = ccma.options.RestartStrategy.STOP
setattr(modules, module_name, option)
return modules
def collect_modcma():
- options = modcma.options.MatrixAdaptationType.__members__
+ options = ccma.options.MatrixAdaptationType.__members__
del options['COVARIANCE_NO_EIGV']
with Pool(6) as p:
@@ -139,7 +139,7 @@ def run_pycma(problem: ioh.ProblemType, x0: np.ndarray):
options['maxfevals'] = problem.meta_data.n_variables * BUDGET
cma = pycma.CMAEvolutionStrategy(x0, 2.0, options=options)
- settings = modcma.Settings(problem.meta_data.n_variables)
+ settings = ccma.Settings(problem.meta_data.n_variables)
assert settings.lambda0 == cma.sp.popsize
start = perf_counter()
@@ -191,5 +191,5 @@ def collect_pycma():
# p2.join()
# collect_modcma()
- mods = modcma.parameters.Modules()
+ mods = ccma.parameters.Modules()
\ No newline at end of file
diff --git a/scripts/matrix/test_bbob5d.py b/scripts/matrix/test_bbob5d.py
index 9a2a963..7c53a45 100644
--- a/scripts/matrix/test_bbob5d.py
+++ b/scripts/matrix/test_bbob5d.py
@@ -1,6 +1,6 @@
from time import perf_counter
import ioh
-import modcma.c_maes as modcma
+import modcma.c_maes as ccma
import iohinspector as ins
import matplotlib.colors as mcolors
import numpy as np
@@ -18,10 +18,10 @@ def inner(*args, **kwargs):
@timeit
def run_modma(f: ioh.ProblemType, dim: int, n_evaluations):
- modules = modcma.parameters.Modules()
+ modules = ccma.parameters.Modules()
# modules.restart_strategy = modcma.options.RestartStrategy.IPOP
# modules.active = True
- settings = modcma.Settings(
+ settings = ccma.Settings(
dim,
budget=n_evaluations,
target=f.optimum.y + 1e-8,
@@ -31,7 +31,7 @@ def run_modma(f: ioh.ProblemType, dim: int, n_evaluations):
modules=modules,
verbose=False
)
- cma = modcma.ModularCMAES(settings)
+ cma = ccma.ModularCMAES(settings)
cma.run(f)
return cma
diff --git a/scripts/matrix/test_timing.py b/scripts/matrix/test_timing.py
index 59e6519..b23a3e8 100644
--- a/scripts/matrix/test_timing.py
+++ b/scripts/matrix/test_timing.py
@@ -9,7 +9,7 @@
import numpy as np
from modcma import ModularCMAES
-import modcma.c_maes as modcma
+import modcma.c_maes as ccma
import cma as pycma
import ioh
from fcmaes import optimizer, retry
diff --git a/scripts/matrix/test_timing_modules.py b/scripts/matrix/test_timing_modules.py
index 92e6cce..64e21ff 100644
--- a/scripts/matrix/test_timing_modules.py
+++ b/scripts/matrix/test_timing_modules.py
@@ -2,7 +2,7 @@
import warnings
import numpy as np
-import modcma.c_maes as modcma
+import modcma.c_maes as ccma
import ioh
import pandas as pd
import matplotlib.pyplot as plt
@@ -12,10 +12,10 @@
np.random.seed(12)
-def run_modma(problem: ioh.ProblemType, x0: np.ndarray, matrix_adaptation = modcma.options.COVARIANCE, max_generations=1000):
- modules = modcma.parameters.Modules()
+def run_modma(problem: ioh.ProblemType, x0: np.ndarray, matrix_adaptation = ccma.options.COVARIANCE, max_generations=1000):
+ modules = ccma.parameters.Modules()
modules.matrix_adaptation = matrix_adaptation
- settings = modcma.Settings(
+ settings = ccma.Settings(
problem.meta_data.n_variables,
x0=x0,
modules=modules,
@@ -25,7 +25,7 @@ def run_modma(problem: ioh.ProblemType, x0: np.ndarray, matrix_adaptation = modc
max_generations=max_generations
)
- cma = modcma.ModularCMAES(settings)
+ cma = ccma.ModularCMAES(settings)
start = perf_counter()
cma.run(problem)
@@ -45,7 +45,7 @@ def run_pycma(problem: ioh.ProblemType, x0: np.ndarray, max_generations=1000):
pprint(options)
cma = pycma.CMAEvolutionStrategy(x0, 2.0, options=options)
- settings = modcma.Settings(problem.meta_data.n_variables)
+ settings = ccma.Settings(problem.meta_data.n_variables)
assert settings.lambda0 == cma.sp.popsize
np.random.seed(1)
start = perf_counter()
@@ -64,7 +64,7 @@ def collect():
dims = 2, 3, 5, 10, 20, 40, 100, 200, 500, 1000
n_repeats = 15
- options = modcma.options.MatrixAdaptationType.__members__
+ options = ccma.options.MatrixAdaptationType.__members__
del options['COVARIANCE_NO_EIGV']
pprint(options)
diff --git a/setup.py b/setup.py
index 7fc541e..11d943c 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
with open("README.md", "r", encoding="Latin-1") as fh:
long_description = fh.read()
-__version__ = "1.1.0"
+__version__ = "1.2.0"
if platform.system() in ("Linux", "Darwin"):
os.environ["CC"] = "g++"
diff --git a/src/bounds.cpp b/src/bounds.cpp
index 8bcf2e5..5a14417 100644
--- a/src/bounds.cpp
+++ b/src/bounds.cpp
@@ -28,7 +28,6 @@ namespace bounds
Vector BoundCorrection::delta_out_of_bounds(const Vector &xi, const Mask &oob, const parameters::Settings &settings) const
{
return (oob).select((xi - settings.lb).cwiseQuotient(settings.db), xi);
- ;
}
void BoundCorrection::correct(const Eigen::Index i, parameters::Parameters &p)
@@ -44,7 +43,8 @@ namespace bounds
return;
p.pop.X.col(i) = correct_x(p.pop.X.col(i), oob, p.mutation->sigma, p.settings);
- p.pop.Y.col(i) = p.adaptation->invert_x(p.pop.X.col(i), p.pop.s(i));
+ p.pop.X_transformed.col(i) = p.pop.X.col(i);
+ p.pop.Y.col(i) = p.adaptation->invert_x(p.pop.X.col(i), p.pop.S.col(i));
p.pop.Z.col(i) = p.adaptation->invert_y(p.pop.Y.col(i));
}
}
diff --git a/src/interface.cpp b/src/interface.cpp
index 934327b..9428eb6 100644
--- a/src/interface.cpp
+++ b/src/interface.cpp
@@ -6,12 +6,11 @@
#include
#include
#include
-#include
+#include
#include "c_maes.hpp"
#include "to_string.hpp"
#include "es.hpp"
-
namespace py = pybind11;
PYBIND11_MAKE_OPAQUE(restart::vCriteria);
@@ -341,6 +340,7 @@ void define_matrix_adaptation(py::module &main)
.def_readwrite("ps", &Adaptation::ps)
.def_readwrite("dz", &Adaptation::dz)
.def_readwrite("dd", &Adaptation::dd)
+ .def_readwrite("coordinate_wise_variances", &Adaptation::coordinate_wise_variances)
.def_readwrite("expected_length_z", &Adaptation::expected_length_z)
.def("adapt_evolution_paths", &Adaptation::adapt_evolution_paths,
py::arg("pop"),
@@ -604,7 +604,7 @@ void define_parameters(py::module &main)
py::class_>(m, "Settings")
.def(py::init, std::optional, size_to, size_to, std::optional,
std::optional, std::optional, std::optional,
- std::optional, std::optional,
+ std::optional, std::optional, std::optional,
std::optional, std::optional, std::optional,
std::optional, std::optional, std::optional,
bool, bool>(),
@@ -619,6 +619,7 @@ void define_parameters(py::module &main)
py::arg("x0") = std::nullopt,
py::arg("lb") = std::nullopt,
py::arg("ub") = std::nullopt,
+ py::arg("integer_variables") = std::nullopt,
py::arg("cs") = std::nullopt,
py::arg("cc") = std::nullopt,
py::arg("cmu") = std::nullopt,
@@ -626,8 +627,7 @@ void define_parameters(py::module &main)
py::arg("damps") = std::nullopt,
py::arg("acov") = std::nullopt,
py::arg("verbose") = false,
- py::arg("always_compute_eigv") = false
- )
+ py::arg("always_compute_eigv") = false)
.def_readonly("dim", &Settings::dim)
.def_readonly("modules", &Settings::modules)
.def_readwrite("target", &Settings::target)
@@ -668,6 +668,7 @@ void define_parameters(py::module &main)
ss << " x0: " << to_string(settings.x0);
ss << " lb: " << settings.lb.transpose();
ss << " ub: " << settings.ub.transpose();
+ ss << " integer_variables: " << settings.integer_variables.transpose();
ss << " cs: " << to_string(settings.cs);
ss << " cc: " << to_string(settings.cc);
ss << " cmu: " << to_string(settings.cmu);
@@ -688,6 +689,10 @@ void define_parameters(py::module &main)
std::shared_ptr,
std::shared_ptr>;
+ py::class_>(m, "CoordinateMapping")
+ .def(py::init())
+ .def("transform", &mapping::CoordinateMapping::transform, py::arg("x"));
+
py::class_>(main, "Parameters")
.def(py::init(), py::arg("dimension"))
.def(py::init(), py::arg("settings"))
@@ -738,7 +743,8 @@ void define_parameters(py::module &main)
.def_readwrite("restart_strategy", &Parameters::restart_strategy)
.def_readwrite("repelling", &Parameters::repelling)
.def_readwrite("bounds", &Parameters::bounds)
- .def_readwrite("center_placement", &Parameters::center_placement);
+ .def_readwrite("center_placement", &Parameters::center_placement)
+ .def_readwrite("coordinate_mapping", &Parameters::coordinate_mapping);
}
void define_bounds(py::module &main)
@@ -811,7 +817,8 @@ void define_mutation(py::module &main)
py::class_>(m, "SigmaSampler")
.def(py::init(), py::arg("dimension"))
- .def("sample", &SigmaSampler::sample, py::arg("sigma"), py::arg("population"), py::arg("tau"));
+ .def("apply_integer_bounds", &SigmaSampler::apply_integer_bounds)
+ .def("sample", &SigmaSampler::sample, py::arg("sigma"), py::arg("parameters"));
py::class_>(m, "NoSigmaSampler")
.def(py::init(), py::arg("dimension"));
@@ -864,17 +871,18 @@ void define_population(py::module &main)
{
py::class_(main, "Population")
.def(py::init(), py::arg("dimension"), py::arg("n"))
- .def(py::init(), py::arg("X"), py::arg("Z"), py::arg("Y"), py::arg("f"), py::arg("s"))
+ .def(py::init(), py::arg("X"), py::arg("Z"), py::arg("Y"), py::arg("f"), py::arg("S"))
.def("sort", &Population::sort)
.def("resize_cols", &Population::resize_cols, py::arg("size"))
.def("keep_only", &Population::keep_only, py::arg("idx"))
.def_property_readonly("n_finite", &Population::n_finite)
.def("__add__", &Population::operator+=, py::arg("other"))
.def_readwrite("X", &Population::X)
+ .def_readwrite("X_transformed", &Population::X_transformed)
.def_readwrite("Z", &Population::Z)
.def_readwrite("Y", &Population::Y)
+ .def_readwrite("S", &Population::S)
.def_readwrite("f", &Population::f)
- .def_readwrite("s", &Population::s)
.def_readwrite("d", &Population::d)
.def_readwrite("n", &Population::n)
.def_readwrite("t", &Population::t);
diff --git a/src/mapping.cpp b/src/mapping.cpp
new file mode 100644
index 0000000..0d1f0fc
--- /dev/null
+++ b/src/mapping.cpp
@@ -0,0 +1,8 @@
+#include "mapping.hpp"
+#include "parameters.hpp"
+
+namespace mapping
+{
+
+
+}
\ No newline at end of file
diff --git a/src/matrix_adaptation.cpp b/src/matrix_adaptation.cpp
index 9d4509c..eff9b06 100644
--- a/src/matrix_adaptation.cpp
+++ b/src/matrix_adaptation.cpp
@@ -5,9 +5,9 @@ namespace matrix_adaptation
using namespace parameters;
- Vector Adaptation::invert_x(const Vector& xi, const Float sigma)
+ Vector Adaptation::invert_x(const Vector& xi, const Vector sigma)
{
- return (xi - m) / sigma;
+ return ((xi - m).array() / sigma.array()).matrix();
}
static void one_plus_one_path_update(
@@ -31,7 +31,7 @@ namespace matrix_adaptation
void Adaptation::adapt_evolution_paths(const Population& pop, const Weights& w,
const Stats& stats, const parameters::Settings& settings, const size_t lambda, const size_t mu)
{
- const auto sigma = pop.s.mean();
+ const auto sigma = pop.S.mean();
dm = (m - m_old) / sigma;
dz = pop.Z.leftCols(mu) * w.positive.head(mu);
adapt_evolution_paths_inner(pop, w, stats, settings, mu, lambda);
@@ -106,6 +106,7 @@ namespace matrix_adaptation
C = old_c + rank_one + rank_mu;
C = 0.5 * (C + C.transpose().eval());
+ coordinate_wise_variances.noalias() = C.diagonal();
}
bool CovarianceAdaptation::perform_eigendecomposition(const Settings& settings)
@@ -136,6 +137,7 @@ namespace matrix_adaptation
d.noalias() = d.cwiseSqrt().eval();
inv_root_C.noalias() = B * d.cwiseInverse().asDiagonal() * B.transpose();
A.noalias() = B * d.asDiagonal();
+
return true;
}
@@ -220,6 +222,7 @@ namespace matrix_adaptation
c(j) = (decay_c * c(j)) + (w.c1 * pow(pc(j), 2)) + (w.cmu * rank_mu);
c(j) = std::max(c(j), Float{ 1e-12 });
d(j) = std::sqrt(c(j));
+ coordinate_wise_variances(j) = c(j);
}
return true;
@@ -288,6 +291,8 @@ namespace matrix_adaptation
+ ((popY * (tau_m * weights).asDiagonal()) * (popZ.transpose() * M_inv));
else
outdated_M_inv = true; // Rely on moore penrose pseudo-inv (only when needed)
+
+ coordinate_wise_variances.noalias() = (M.array().square().rowwise().sum()).matrix();
return true;
}
@@ -345,7 +350,7 @@ namespace matrix_adaptation
for (size_t i = 0; i < pop.Y.cols() - mu; i++)
Eigen::internal::llt_rank_update_lower(A, pop.Y.col(mu + i), w.cmu * w.negative(i));
-
+ coordinate_wise_variances.noalias() = (A.array().square().rowwise().sum()).matrix();
return true;
}
@@ -387,6 +392,7 @@ namespace matrix_adaptation
C = (1.0 - tc_inv) * C + (tc_inv * Y);
C = 0.5 * (C + C.transpose().eval());
+ coordinate_wise_variances.noalias() = C.diagonal();
const Eigen::LLT chol(C);
if (chol.info() != Eigen::Success)
@@ -397,7 +403,7 @@ namespace matrix_adaptation
return false;
}
A = chol.matrixL();
-
+
return true;
}
@@ -508,6 +514,7 @@ namespace matrix_adaptation
A *= (w.cc * G).exp();
outdated_A_inv = true;
+ coordinate_wise_variances.noalias() = (A.array().square().rowwise().sum()).matrix();
return true;
}
diff --git a/src/mutation.cpp b/src/mutation.cpp
index c5fcd45..6861dfd 100644
--- a/src/mutation.cpp
+++ b/src/mutation.cpp
@@ -20,9 +20,38 @@ namespace mutation
return (f < fopt) and (i >= seq_cutoff) and (m != parameters::Mirror::PAIRWISE or i % 2 == 0);
}
+ void SigmaSampler::apply_integer_bounds(parameters::Parameters &p)
+ {
+ const Array &Cdiag = p.adaptation->coordinate_wise_variances;
+ for (const auto &iidx : p.settings.integer_variables)
+ {
+ const Float Cii = std::max(Cdiag[iidx], Float(1e-16));
+ const Float lb_sigma = p.weights.int_lb_sigma / std::sqrt(Cii);
+
+ for (size_t i = 0; i < p.pop.n; i++)
+ p.pop.S(iidx, i) = std::max(p.pop.S(iidx, i), lb_sigma);
+ }
+ }
+
+ void SigmaSampler::sample(const Float sigma, parameters::Parameters &p)
+ {
+ for (size_t i = 0; i < p.pop.n; i++)
+ {
+ const auto z = sampler();
+ p.pop.S.col(i) = (sigma * (p.weights.beta * z.array()).exp()).matrix();
+ }
+ apply_integer_bounds(p);
+ }
+
+ void NoSigmaSampler::sample(const Float sigma, parameters::Parameters &p)
+ {
+ p.pop.S.setConstant(sigma);
+ apply_integer_bounds(p);
+ }
+
void Strategy::mutate(FunctionType &objective, const size_t n_offspring, parameters::Parameters &p)
{
- ss->sample(sigma, p.pop, p.weights.beta);
+ ss->sample(sigma, p);
p.bounds->n_out_of_bounds = 0;
p.repelling->prepare_sampling(p);
@@ -37,14 +66,17 @@ namespace mutation
zi, p.settings.diameter, p.settings.budget, p.stats.evaluations);
p.pop.Z.col(i).noalias() = zi_scaled;
p.pop.Y.col(i).noalias() = p.adaptation->compute_y(p.pop.Z.col(i));
- p.pop.X.col(i).noalias() = p.pop.Y.col(i) * p.pop.s(i) + p.adaptation->m;
+ p.pop.X.col(i).array() = p.pop.Y.col(i).array() * p.pop.S.col(i).array() + p.adaptation->m.array();
p.bounds->correct(i, p);
+
} while (
(p.settings.modules.bound_correction == parameters::CorrectionMethod::RESAMPLE &&
n_rej++ < 5 * p.settings.dim && p.bounds->is_out_of_bounds(p.pop.X.col(i), p.settings).any()) ||
p.repelling->is_rejected(p.pop.X.col(i), p));
- p.pop.f(i) = objective(p.pop.X.col(i));
+ p.pop.X_transformed.col(i) = p.coordinate_mapping->transform(p.pop.X.col(i));
+
+ p.pop.f(i) = objective(p.pop.X_transformed.col(i));
p.stats.evaluations++;
if (sq->break_conditions(i, p.pop.f(i), p.stats.global_best.y, p.settings.modules.mirrored))
{
@@ -69,8 +101,12 @@ namespace mutation
{
Strategy::mutate(objective, n_offspring_, p);
- const auto f_pos = objective(p.adaptation->m + (p.mutation->sigma * p.adaptation->dm));
- const auto f_neg = objective(p.adaptation->m + (p.mutation->sigma * -p.adaptation->dm));
+ const auto x_pos = p.coordinate_mapping->transform(p.adaptation->m + (p.mutation->sigma * p.adaptation->dm));
+ const auto x_neg = p.coordinate_mapping->transform(p.adaptation->m + (p.mutation->sigma * -p.adaptation->dm));
+ const auto f_pos = objective(x_pos);
+ const auto f_neg = objective(x_neg);
+ p.stats.update_best(x_pos, f_pos);
+ p.stats.update_best(x_neg, f_neg);
p.stats.evaluations += 2;
this->rank_tpa = f_neg < f_pos ? -a_tpa : a_tpa + b_tpa;
}
@@ -131,13 +167,14 @@ namespace mutation
combined.head(n) = pop.f.head(n);
combined.tail(n) = old_pop.f.head(n);
- if (idx.size() != n) {
+ if (idx.size() != n)
+ {
idx.resize(n);
oidx.resize(n);
std::iota(idx.begin(), idx.end(), 0);
std::iota(oidx.begin(), oidx.end(), 0);
}
-
+
utils::sort_index_inplace(combined, idx);
utils::sort_index_inplace(idx, oidx);
@@ -182,7 +219,9 @@ namespace mutation
Population &pop,
const Population &old_pop, const parameters::Stats &stats, const size_t lambda)
{
- const Float rel_log = (pop.s.array() / sigma).log().matrix().dot(w.clipped());
+ const auto logS = (pop.S.array() / sigma).log();
+ const Vector per_sample = logS.colwise().mean().transpose();
+ const Float rel_log = per_sample.dot(w.clipped());
sigma *= std::exp(w.cs * rel_log);
}
@@ -196,15 +235,17 @@ namespace mutation
void SA::mutate(FunctionType &objective, const size_t n_offspring, parameters::Parameters &p)
{
Strategy::mutate(objective, n_offspring, p);
- mean_sigma = std::exp(p.pop.s.array().log().mean());
+ mean_sigma = std::exp(p.pop.S.array().log().mean());
}
void SA::adapt(const parameters::Weights &w, std::shared_ptr adaptation,
Population &pop,
const Population &old_pop, const parameters::Stats &stats, const size_t lambda)
{
- const auto &sigma_l = pop.s.topRows(w.positive.rows());
- sigma = std::exp((w.positive.array() * sigma_l.array().log()).sum()) / mean_sigma;
+ const auto &sigma_l = pop.S.topRows(w.positive.rows());
+ const auto &log_sigma_dim = sigma_l.array().log().rowwise().mean();
+ const Float log_sigma = (w.positive.array() * log_sigma_dim).sum();
+ sigma = std::exp(log_sigma) / mean_sigma;
}
std::shared_ptr get(const parameters::Modules &m, const size_t mu, const Float d, const Float sigma)
diff --git a/src/parameters.cpp b/src/parameters.cpp
index 92f8bc3..02748c9 100644
--- a/src/parameters.cpp
+++ b/src/parameters.cpp
@@ -30,7 +30,8 @@ namespace parameters
settings.budget)),
bounds(bounds::get(settings.modules.bound_correction, settings.dim)),
repelling(repelling::get(settings.modules)),
- center_placement(center::get(settings.modules.center_placement))
+ center_placement(center::get(settings.modules.center_placement)),
+ coordinate_mapping(mapping::get(settings.integer_variables, settings.dim, weights.mueff))
{
criteria.reset(*this);
}
@@ -51,7 +52,8 @@ namespace parameters
}
stats.solutions.push_back(stats.current_best);
stats.evaluations++;
- stats.centers.emplace_back(adaptation->m, objective(adaptation->m), stats.t - 1, stats.evaluations);
+ const auto mean = coordinate_mapping->transform(adaptation->m);
+ stats.centers.emplace_back(mean, objective(mean), stats.t - 1, stats.evaluations);
stats.update_best(stats.centers.back().x, stats.centers.back().y);
stats.has_improved = false;
repelling->update_archive(objective, *this);
@@ -80,7 +82,6 @@ namespace parameters
mutation->sigma = std::min(std::max(mutation->sigma, restart::MinSigma::tolerance), restart::MaxSigma::tolerance);
successfull_adaptation = adaptation->adapt_matrix(weights, settings.modules, pop, mu, settings, stats);
-
criteria.update(*this);
stats.t++;
}
diff --git a/src/population.cpp b/src/population.cpp
index 6475a62..6fa679a 100644
--- a/src/population.cpp
+++ b/src/population.cpp
@@ -4,20 +4,22 @@ void Population::sort()
{
const auto idx = utils::sort_indexes(f);
X = X(Eigen::all, idx).eval();
+ X_transformed = X_transformed(Eigen::all, idx).eval();
Z = Z(Eigen::all, idx).eval();
Y = Y(Eigen::all, idx).eval();
+ S = S(Eigen::all, idx).eval();
f = f(idx).eval();
- s = s(idx).eval();
t = t(idx).eval();
}
Population& Population::operator+=(const Population& other)
{
utils::hstack(X, other.X);
+ utils::hstack(X_transformed, other.X_transformed);
utils::hstack(Y, other.Y);
utils::hstack(Z, other.Z);
+ utils::hstack(S, other.S);
utils::concat(f, other.f);
- utils::concat(s, other.s);
utils::concat(t, other.t);
n += other.n;
return *this;
@@ -27,10 +29,11 @@ void Population::resize_cols(const size_t size)
{
n = std::min(size, static_cast(X.cols()));
X.conservativeResize(d, n);
+ X_transformed.conservativeResize(d, n);
Y.conservativeResize(d, n);
Z.conservativeResize(d, n);
+ S.conservativeResize(d, n);
f.conservativeResize(n);
- s.conservativeResize(n);
t.conservativeResize(n);
}
@@ -38,10 +41,11 @@ void Population::resize_cols(const size_t size)
void Population::keep_only(const std::vector& idx)
{
X = X(Eigen::all, idx).eval();
+ X_transformed = X_transformed(Eigen::all, idx).eval();
Z = Z(Eigen::all, idx).eval();
Y = Y(Eigen::all, idx).eval();
+ S = S(Eigen::all, idx).eval();
f = f(idx).eval();
- s = s(idx).eval();
t = t(idx).eval();
n = idx.size();
}
@@ -56,7 +60,7 @@ std::ostream& operator<<(std::ostream& os, const Population& p)
os
<< "Population"
<< "\nx=\n"
- << p.X
+ << p.X_transformed
<< "\ny=\n"
<< p.Y
<< "\nf=\n"
diff --git a/src/repelling.cpp b/src/repelling.cpp
index 8ef48ac..0a3a5b4 100644
--- a/src/repelling.cpp
+++ b/src/repelling.cpp
@@ -33,7 +33,7 @@ namespace repelling
for (size_t k = 1; k < n_evals + 1; k++)
{
const Float a = static_cast(k) / (static_cast(n_evals) + 1.0);
- const Vector x = v.x + a * (u.x - v.x);
+ const Vector x = p.coordinate_mapping->transform(v.x + a * (u.x - v.x));
const Float y = f(x);
p.stats.evaluations++;
if (max_f < y)
diff --git a/src/selection.cpp b/src/selection.cpp
index c27f912..567d2cd 100644
--- a/src/selection.cpp
+++ b/src/selection.cpp
@@ -22,7 +22,7 @@ namespace selection
p.pop.resize_cols(p.lambda);
p.stats.current_avg = p.pop.f.array().mean();
- p.stats.update_best(p.pop.X(Eigen::all, 0), p.pop.f(0));
+ p.stats.update_best(p.pop.X_transformed(Eigen::all, 0), p.pop.f(0));
}
void Pairwise::operator()(parameters::Parameters& p) const
@@ -45,7 +45,7 @@ namespace selection
{
for (Eigen::Index i = 0; i < static_cast(p.old_pop.n); i++)
{
- p.old_pop.Y.col(i).noalias() = p.adaptation->invert_x(p.old_pop.X.col(i), p.old_pop.s(i));
+ p.old_pop.Y.col(i).noalias() = p.adaptation->invert_x(p.old_pop.X.col(i), p.old_pop.S.col(i));
p.old_pop.Z.col(i).noalias() = p.adaptation->invert_y(p.old_pop.Y.col(i));
}
}
diff --git a/src/weights.cpp b/src/weights.cpp
index 53a5422..91913ea 100644
--- a/src/weights.cpp
+++ b/src/weights.cpp
@@ -162,6 +162,8 @@ namespace parameters
beta = 1.0 / std::sqrt(2.0 * mueff);
if (settings.modules.ssa == StepSizeAdaptation::LPXNES)
beta = std::log(2.0) / (std::sqrt(d) * std::log(d));
+
+ int_lb_sigma = std::min(0.2, mueff / d);
}
diff --git a/tests/test_c_bounds.py b/tests/test_c_bounds.py
index bc224b4..72700de 100644
--- a/tests/test_c_bounds.py
+++ b/tests/test_c_bounds.py
@@ -27,13 +27,14 @@ class TestBounds(unittest.TestCase):
def setUp(self):
self.lb, self.ub = np.zeros(2), np.ones(2) * 2
self.par = Parameters(parameters.Settings(2, lambda0=2, lb=self.lb, ub=self.ub))
- self.par.pop.s = np.ones(2) * 2
+ self.par.pop.S = np.ones((2, 2)) * 2
self.par.adaptation.m = np.ones(2) * 0.1
Z = np.ones((2, 2)) * 0.9
Z[0, 0] *= 2
self.par.pop.Z = Z
self.par.pop.Y = self.par.pop.Z.copy()
- self.par.pop.X = self.par.adaptation.m + (self.par.pop.s * self.par.pop.Y)
+ self.par.pop.X_transformed = self.par.pop.X = self.par.adaptation.m + (self.par.pop.S * self.par.pop.Y)
+
def test_bound_fixers(self):
for i, (boundcntrl, option) in enumerate(zip(self.__bound_fixers, self.__bound_fixers_options)):
@@ -47,7 +48,6 @@ def test_bound_fixers(self):
self.assertEqual(method.n_out_of_bounds, 0)
method.correct(0, self.par)
self.assertEqual(method.n_out_of_bounds, 1)
-
self.assertTrue(np.all(self.par.pop.X <= 2))
self.assertTrue(np.all(np.isclose(self.par.pop.Y.ravel()[1:], 0.9)))
self.assertTrue(np.all(np.isclose(self.par.pop.X.ravel()[1:], 1.9)))
diff --git a/tests/test_c_criteria.py b/tests/test_c_criteria.py
index 29df4cf..22b0373 100644
--- a/tests/test_c_criteria.py
+++ b/tests/test_c_criteria.py
@@ -2,16 +2,16 @@
import unittest
-import modcma.c_maes as modcma
+import modcma.c_maes as ccma
-class MyCriterion(modcma.restart.Criterion):
+class MyCriterion(ccma.restart.Criterion):
def __init__(self, name):
super().__init__(f"MyCriterion{name}")
- def on_reset(self, par: modcma.Parameters):
+ def on_reset(self, par: ccma.Parameters):
"""Called when a restart happens (also at the start)"""
- def update(self, par: modcma.Parameters):
+ def update(self, par: ccma.Parameters):
"""Called after each iteration, needs to modify self.met"""
self.met = True
@@ -19,15 +19,15 @@ def update(self, par: modcma.Parameters):
class TestCriteria(unittest.TestCase):
def setUp(self):
- mod = modcma.parameters.Modules()
- mod.restart_strategy = modcma.options.RestartStrategy.RESTART
- settings = modcma.Settings(
+ mod = ccma.parameters.Modules()
+ mod.restart_strategy = ccma.options.RestartStrategy.RESTART
+ settings = ccma.Settings(
dim=3,
budget=1000,
verbose=True,
modules=mod
)
- self.cma = modcma.ModularCMAES(settings)
+ self.cma = ccma.ModularCMAES(settings)
def test_modify(self):
self.cma.p.criteria.items = self.cma.p.criteria.items[1:3]
diff --git a/tests/test_c_integer.py b/tests/test_c_integer.py
new file mode 100644
index 0000000..e71d6c4
--- /dev/null
+++ b/tests/test_c_integer.py
@@ -0,0 +1,32 @@
+import unittest
+from modcma import c_maes
+import numpy as np
+
+def sphere(x):
+ return sum(round(xi + 7)**2 for xi in x)
+
+
+class TestInteger(unittest.TestCase):
+ def test_int(self):
+ dim = 10
+ settings = c_maes.settings_from_dict(
+ dim,
+ integer_variables=list(range(dim)),
+ target=0,
+ lb=[-50] * dim,
+ ub=[50] * dim,
+ # active=True,
+ # lambda0=1,
+ sample_transformation='LAPLACE',
+ ssa="SA"
+ )
+ cma = c_maes.ModularCMAES(settings)
+ c_maes.utils.set_seed(100)
+ while not cma.break_conditions():
+ cma.mutate(sphere)
+ cma.select()
+ cma.recombine()
+ cma.adapt()
+
+ self.assertEqual(cma.p.stats.global_best.y, 0.0)
+
diff --git a/tests/test_c_mutation.py b/tests/test_c_mutation.py
index a73a79c..45bcfeb 100644
--- a/tests/test_c_mutation.py
+++ b/tests/test_c_mutation.py
@@ -15,37 +15,41 @@
class TestMutation(unittest.TestCase):
def setUp(self):
- self.pop = Population(2, 2)
- self.pop.Z = np.ones((2, 2)) * 0.5
+
+ pop = Population(2, 2)
+ pop.Z = np.ones((2, 2)) * 0.5
+ self.par = Parameters(2)
+ self.par.pop = pop
+ self.par.weights.beta = 1
def test_sigma_sampler(self):
ss = mutation.SigmaSampler(2)
noss = mutation.NoSigmaSampler(2)
- ss.sample(2.0, self.pop, 1)
- self.assertFalse(np.all(self.pop.s == 2.0))
- noss.sample(2.0, self.pop, 1)
- self.assertTrue(np.all(self.pop.s == 2.0))
+ ss.sample(2.0, self.par)
+ self.assertFalse(np.all(self.par.pop.S == 2.0))
+ noss.sample(2.0, self.par)
+ self.assertTrue(np.all(self.par.pop.S == 2.0))
def test_threshold_convergence(self):
tc = mutation.ThresholdConvergence()
notc = mutation.NoThresholdConvergence()
- notc.scale(self.pop.Z[0], 10, 100, 2)
- self.assertTrue(np.all(self.pop.Z == 0.5))
+ notc.scale(self.par.pop.Z[0], 10, 100, 2)
+ self.assertTrue(np.all(self.par.pop.Z == 0.5))
budget = 100
evals = 2
diam = 10
t = tc.init_threshold * diam * pow((budget - evals) / budget, tc.decay_factor)
- norm = np.linalg.norm(self.pop.Z, axis=0)
+ norm = np.linalg.norm(self.par.pop.Z, axis=0)
Z = np.ones((2, 2))
- Z[0] = tc.scale(self.pop.Z[0], diam, budget, evals)
- Z[1] = tc.scale(self.pop.Z[1], diam, budget, evals)
- self.pop.Z = Z
+ Z[0] = tc.scale(self.par.pop.Z[0], diam, budget, evals)
+ Z[1] = tc.scale(self.par.pop.Z[1], diam, budget, evals)
+ self.par.pop.Z = Z
expected_z = 0.5 * ((t + (t - norm)) / norm)
- self.assertTrue(np.isclose(self.pop.Z, expected_z).all())
- self.assertTrue(np.isclose(self.pop.Z, scale_with_threshold(np.ones(2) * .5, t)).all())
+ self.assertTrue(np.isclose(self.par.pop.Z, expected_z).all())
+ self.assertTrue(np.isclose(self.par.pop.Z, scale_with_threshold(np.ones(2) * .5, t)).all())
def get_cma(self, ssa, adapt_sigma=True):
modules = parameters.Modules()
@@ -113,8 +117,11 @@ def test_adapt_xnes(self):
def test_adapt_lpxnes(self):
cma = self.get_cma(options.StepSizeAdaptation.LPXNES)
+
w = cma.p.weights.weights.clip(0)[: cma.p.pop.n]
- z = np.exp(cma.p.weights.cs * (w @ np.log(cma.p.pop.s)))
+
+ z = np.exp(cma.p.weights.cs * (w @ np.log(cma.p.pop.S).mean(axis=0)))
+
sigma = np.power(cma.p.settings.sigma0, 1 - cma.p.weights.cs) * z
self.assertTrue(np.isclose(cma.p.mutation.sigma, sigma))
diff --git a/tests/test_c_population.py b/tests/test_c_population.py
index 125caa7..ee1e61d 100644
--- a/tests/test_c_population.py
+++ b/tests/test_c_population.py
@@ -27,20 +27,19 @@ def set_params(self):
self.Y = np.dot(self.B, self.D * self.Z)
self.X = self.xmean + (self._sigma * self.Y)
self.f = np.array([sum(i) for i in self.X.T])
- self.s = np.ones(self._lambda) * self._sigma
- self.pop = Population(self.X, self.Z, self.Y, self.f, self.s)
+ self.S = np.ones((self._dim, self._lambda)) * self._sigma
+ self.pop = Population(self.X, self.Z, self.Y, self.f, self.S)
def test_sort(self):
"""Test sorting behaviour."""
self.pop.sort()
rank = np.argsort(self.f)
- for e in ("X", "Y", "Z",):
+ for e in ("X", "Y", "Z", "S"):
self.assertListEqual(
getattr(self, e)[:, rank].tolist(), getattr(self.pop, e).tolist()
)
self.assertListEqual(self.f[rank].tolist(), self.pop.f.tolist())
- self.assertListEqual(self.s[rank].tolist(), self.pop.s.tolist())
def test_keeponly(self):
"""Test keeponly behaviour."""
@@ -54,8 +53,8 @@ def test_resize(self):
self.pop.resize_cols(2)
self.assertEqual(self.pop.n, 2)
self.assertEqual(self.pop.f.size, 2)
- self.assertEqual(self.pop.s.size, 2)
self.assertEqual(self.pop.X.shape[1], 2)
+ self.assertEqual(self.pop.S.shape[1], 2)
self.assertEqual(self.pop.Y.shape[1], 2)
self.assertEqual(self.pop.Z.shape[1], 2)
@@ -67,7 +66,7 @@ def test_n_finite(self):
def test_add(self):
"""Test addition."""
- self.pop = self.pop + Population(self.X, self.Z, self.Y, self.f, self.s)
+ self.pop = self.pop + Population(self.X, self.Z, self.Y, self.f, self.S)
self.assertEqual(
self.pop.X.shape,
(
diff --git a/tests/test_c_tuning.py b/tests/test_c_tuning.py
index 79e1d9c..32810d7 100644
--- a/tests/test_c_tuning.py
+++ b/tests/test_c_tuning.py
@@ -43,7 +43,7 @@ def test_from_dict(self):
def test_fmin(self):
- xopt, fopt, evals, es = c_maes.fmin(sphere, [1, 2], 0.2, 100, active=True)
+ xopt, fopt, evals, es = c_maes.fmin(sphere, [1, 2], 0.2, 100, active=True, matrix_adaptation='NONE')
self.assertLess(fopt, 1e-4)
self.assertLessEqual(evals, 100)
self.assertEqual(sphere(xopt), fopt)