From bbd4a524b3ed9d12f313a9629a48404a5e0180fd Mon Sep 17 00:00:00 2001 From: jacobdenobel Date: Thu, 17 Apr 2025 01:24:33 +0200 Subject: [PATCH 1/2] why am I still working. At least it works --- include/c_maes.hpp | 6 + include/common.hpp | 6 +- include/parameters.hpp | 16 +- include/restart.hpp | 174 --------------- include/restart_criteria.hpp | 144 ++++++++++++ include/restart_strategy.hpp | 77 +++++++ include/stats.hpp | 1 - modcma/c_maes/cmaescpp/__init__.pyi | 94 +++++--- modcma/c_maes/cmaescpp/es.pyi | 49 +++++ modcma/c_maes/cmaescpp/matrix_adaptation.pyi | 44 ++-- modcma/c_maes/cmaescpp/parameters.pyi | 2 + modcma/c_maes/cmaescpp/restart.pyi | 139 ++++++------ setup.py | 2 +- src/c_maes.cpp | 31 ++- src/common.cpp | 4 +- src/interface.cpp | 178 +++++++-------- src/matrix_adaptation.cpp | 4 - src/mutation.cpp | 7 +- src/parameters.cpp | 53 ++--- src/restart.cpp | 220 ------------------- src/restart_criteria.cpp | 214 ++++++++++++++++++ src/restart_strategy.cpp | 61 +++++ tests/test_c_adaptation.py | 2 +- 23 files changed, 851 insertions(+), 677 deletions(-) delete mode 100644 include/restart.hpp create mode 100644 include/restart_criteria.hpp create mode 100644 include/restart_strategy.hpp create mode 100644 modcma/c_maes/cmaescpp/es.pyi delete mode 100644 src/restart.cpp create mode 100644 src/restart_criteria.cpp create mode 100644 src/restart_strategy.cpp diff --git a/include/c_maes.hpp b/include/c_maes.hpp index cd91faa..8d9c933 100644 --- a/include/c_maes.hpp +++ b/include/c_maes.hpp @@ -12,6 +12,12 @@ struct ModularCMAES void recombine() const; + void select() const; + + void adapt() const; + + void mutate(FunctionType &objective) const; + bool step(FunctionType& objective) const; void operator()(FunctionType& objective) const; diff --git a/include/common.hpp b/include/common.hpp index 6000208..aae8627 100644 --- a/include/common.hpp +++ b/include/common.hpp @@ -22,7 +22,7 @@ #include #include -using Float = long double; +using Float = double; using Matrix = Eigen::Matrix; using Vector = Eigen::Matrix; using Array = Eigen::Array; @@ -35,9 +35,9 @@ using FunctionType = std::function; namespace constants { - extern Float tolup_sigma; + extern Float max_dsigma; + extern Float min_dsigma; extern Float tol_condition_cov; - extern Float tol_min_sigma; extern Float stagnation_quantile; extern Float sigma_threshold; extern size_t cache_max_doubles; diff --git a/include/parameters.hpp b/include/parameters.hpp index 7d01c90..1f4d0a5 100644 --- a/include/parameters.hpp +++ b/include/parameters.hpp @@ -4,7 +4,8 @@ #include "mutation.hpp" #include "population.hpp" #include "matrix_adaptation.hpp" -#include "restart.hpp" +#include "restart_strategy.hpp" +#include "restart_criteria.hpp" #include "sampling.hpp" #include "stats.hpp" #include "selection.hpp" @@ -16,6 +17,8 @@ namespace parameters { struct Parameters { + bool successfull_adaptation; + size_t lambda; size_t mu; @@ -26,23 +29,26 @@ namespace parameters Population pop; Population old_pop; + restart::Criteria criteria; std::shared_ptr sampler; std::shared_ptr adaptation; std::shared_ptr mutation; std::shared_ptr selection; - std::shared_ptr restart; + std::shared_ptr restart_strategy; std::shared_ptr bounds; std::shared_ptr repelling; std::shared_ptr center_placement; Parameters(const size_t dim); + Parameters(const Settings &settings); - void adapt(FunctionType& objective); + + void start(FunctionType& objective); - void perform_restart(FunctionType& objective, const std::optional &sigma = std::nullopt); + void adapt(); - bool invalid_state() const; + void perform_restart(FunctionType& objective, const std::optional &sigma = std::nullopt); }; } diff --git a/include/restart.hpp b/include/restart.hpp deleted file mode 100644 index 9c90771..0000000 --- a/include/restart.hpp +++ /dev/null @@ -1,174 +0,0 @@ -#pragma once - -#include "common.hpp" -#include "modules.hpp" - -namespace parameters -{ - struct Parameters; -} - -namespace restart -{ - class RestartCriteria - { - void update(const parameters::Parameters &p); - - public: - Float sigma0; - size_t last_restart; - size_t max_iter; - size_t max_flat_fitness; - size_t n_bin; - size_t n_stagnation; - size_t flat_fitness_index; - - Eigen::Array flat_fitnesses; - std::vector median_fitnesses; - std::vector best_fitnesses; - - size_t time_since_restart; - Float recent_improvement; - size_t n_flat_fitness; - Float d_sigma; - - Float tolx_condition; - Vector tolx_vector; - - Float root_max_d; - Float condition_c; - Vector effect_coord; - Vector effect_axis; - - bool any = false; - - RestartCriteria(const Float sigma0, const Float d, const Float lambda, const size_t t) - : sigma0(sigma0), - last_restart(t), - max_iter(static_cast(100 + 50 * std::pow((d + 3), 2.0) / std::sqrt(lambda))), - max_flat_fitness(static_cast(std::ceil(d / 3))), - n_bin(10 + static_cast(std::ceil(30 * d / lambda))), - n_stagnation(static_cast(std::min(static_cast(120 + (30 * d / lambda)), 20000))), - flat_fitness_index(static_cast(std::round(.1 + lambda / 4))), - flat_fitnesses(Eigen::Array::Constant(static_cast(d), 0)), - median_fitnesses{}, - best_fitnesses{}, - time_since_restart(0), - recent_improvement(0.), - n_flat_fitness(0), - d_sigma(0.), - tolx_condition(0.), - tolx_vector{static_cast(d * 2)}, - root_max_d(0.), - condition_c(0.), - effect_coord(static_cast(d)), - effect_axis(static_cast(d)) - { - median_fitnesses.reserve(max_iter); - best_fitnesses.reserve(max_iter); - } - - bool exceeded_max_iter() const; - - bool no_improvement() const; - - bool flat_fitness() const; - - bool tolx() const; - - bool tolupsigma() const; - - bool conditioncov() const; - - bool noeffectaxis() const; - - bool noeffectcoor() const; - - bool stagnation() const; - - bool min_sigma() const; - - bool operator()(const parameters::Parameters &p); - }; - - struct Strategy - { - RestartCriteria criteria; - - Strategy(const Float sigma0, const Float d, const Float lambda) : criteria{sigma0, d, lambda, 0} {} - - void evaluate(FunctionType& objective, parameters::Parameters &p); - - virtual void restart(FunctionType& objective, parameters::Parameters &) = 0; - }; - - struct None : Strategy - { - using Strategy::Strategy; - void restart(FunctionType& objective, parameters::Parameters &p) override {} - }; - - struct Stop : Strategy - { - using Strategy::Strategy; - void restart(FunctionType& objective, parameters::Parameters &p) override {} - }; - - struct Restart : Strategy - { - using Strategy::Strategy; - void restart(FunctionType& objective, parameters::Parameters &) override; - }; - - struct IPOP : Strategy - { - Float ipop_factor = 2.0; - using Strategy::Strategy; - void restart(FunctionType& objective, parameters::Parameters &) override; - }; - - struct BIPOP : Strategy - { - - size_t lambda_init; - Float mu_factor; - size_t budget; - - size_t lambda_large = 0; - size_t lambda_small = 0; - size_t budget_small = 0; - size_t budget_large = 0; - size_t used_budget = 0; - - BIPOP( - const Float sigma0, const Float d, const Float lambda, const Float mu, const size_t budget) : Strategy(sigma0, d, lambda), lambda_init(static_cast(lambda)), mu_factor(mu / lambda), budget(budget) - { - } - - void restart(FunctionType& objective, parameters::Parameters &) override; - - bool large() const - { - return budget_large >= budget_small and budget_large > 0; - } - }; - - inline std::shared_ptr get(const parameters::RestartStrategyType s, const Float sigma0, const Float d, const Float lambda, const Float mu, const size_t budget) - { - using namespace parameters; - switch (s) - { - case RestartStrategyType::RESTART: - return std::make_shared(sigma0, d, lambda); - case RestartStrategyType::IPOP: - return std::make_shared(sigma0, d, lambda); - case RestartStrategyType::BIPOP: - return std::make_shared(sigma0, d, lambda, mu, budget); - case RestartStrategyType::STOP: - return std::make_shared(sigma0, d, lambda); - default: - case RestartStrategyType::NONE: - return std::make_shared(sigma0, d, lambda); - } - } -} \ No newline at end of file diff --git a/include/restart_criteria.hpp b/include/restart_criteria.hpp new file mode 100644 index 0000000..eb6dea7 --- /dev/null +++ b/include/restart_criteria.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "common.hpp" +#include "modules.hpp" + +namespace parameters +{ + struct Parameters; +} + +namespace restart +{ + struct Criterion { + bool met; + std::string name; + size_t last_restart; + + Criterion(const std::string& name): met(false), name(name) {} + + void reset(const parameters::Parameters &p); + + virtual void update(const parameters::Parameters &p) = 0; + + virtual void on_reset(const parameters::Parameters &p){}; + }; + + using vCriteria = std::vector>; + + struct Criteria { + Criteria(const vCriteria& c): items(c){} + + void update(const parameters::Parameters &p) + { + any = false; + for (const auto& c: items) + { + c->update(p); + any = any or c->met; + } + } + + void reset(const parameters::Parameters &p) + { + for (const auto& c: items) + c->reset(p); + } + + vCriteria items; + bool any; + + static Criteria get(const parameters::Modules modules); + }; + + + struct ExceededMaxIter: Criterion + { + size_t max_iter; + ExceededMaxIter(): Criterion("ExceededMaxIter"){} + void update(const parameters::Parameters &p) override; + void on_reset(const parameters::Parameters &p) override; + }; + + struct NoImprovement: Criterion + { + size_t n_bin; + std::vector best_fitnesses; + NoImprovement(): Criterion("NoImprovement"){} + void update(const parameters::Parameters &p) override; + void on_reset(const parameters::Parameters &p) override; + }; + + struct SigmaOutOfBounds: Criterion + { + SigmaOutOfBounds(): Criterion("SigmaOutOfBounds"){} + void update(const parameters::Parameters &p) override; + }; + + struct UnableToAdapt: Criterion + { + UnableToAdapt(): Criterion("UnableToAdapt"){} + void update(const parameters::Parameters &p) override; + }; + + struct FlatFitness: Criterion + { + size_t max_flat_fitness; + size_t flat_fitness_index; + Eigen::Array flat_fitnesses; + + FlatFitness(): Criterion("FlatFitness"){} + void update(const parameters::Parameters &p) override; + void on_reset(const parameters::Parameters &p) override; + }; + + struct TolX: Criterion + { + Vector tolx_vector; + TolX(): Criterion("TolX"){} + void update(const parameters::Parameters &p) override; + void on_reset(const parameters::Parameters &p) override; + }; + + + struct MaxDSigma: Criterion + { + MaxDSigma(): Criterion("MaxDSigma"){} + void update(const parameters::Parameters &p) override; + }; + + struct MinDSigma: Criterion + { + MinDSigma(): Criterion("MinDSigma"){} + void update(const parameters::Parameters &p) override; + }; + + + struct ConditionC: Criterion + { + ConditionC(): Criterion("ConditionC"){} + void update(const parameters::Parameters &p) override; + }; + + struct NoEffectAxis: Criterion + { + NoEffectAxis(): Criterion("NoEffectAxis"){} + void update(const parameters::Parameters &p) override; + }; + + struct NoEffectCoord: Criterion + { + NoEffectCoord(): Criterion("NoEffectCoord"){} + void update(const parameters::Parameters &p) override; + }; + + struct Stagnation: Criterion + { + size_t n_stagnation; + std::vector median_fitnesses; + std::vector best_fitnesses; + Stagnation(): Criterion("Stagnation"){} + void update(const parameters::Parameters &p) override; + void on_reset(const parameters::Parameters &p) override; + }; +} \ No newline at end of file diff --git a/include/restart_strategy.hpp b/include/restart_strategy.hpp new file mode 100644 index 0000000..f024523 --- /dev/null +++ b/include/restart_strategy.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "common.hpp" +#include "modules.hpp" +#include "restart_criteria.hpp" + +namespace parameters +{ + struct Parameters; +} + +namespace restart +{ + struct Strategy + { + virtual Float update(parameters::Parameters &p); + }; + + struct IPOP : Strategy + { + Float ipop_factor = 2.0; + Float update(parameters::Parameters &) override; + }; + + struct BIPOP : Strategy + { + size_t lambda_init; + Float mu_factor; + size_t budget; + + size_t lambda_large = 0; + size_t lambda_small = 0; + size_t budget_small = 0; + size_t budget_large = 0; + size_t used_budget = 0; + + BIPOP( + const Float lambda, const Float mu, const size_t budget) : + Strategy(), + lambda_init(static_cast(lambda)), + mu_factor(mu / lambda), + budget(budget) + { + } + + Float update(parameters::Parameters &) override; + + bool large() const + { + return budget_large >= budget_small and budget_large > 0; + } + }; + + namespace strategy { + inline std::shared_ptr get( + const parameters::Modules modules, + const Float lambda, + const Float mu, + const size_t budget + ) + { + using namespace parameters; + switch (modules.restart_strategy) + { + case RestartStrategyType::IPOP: + return std::make_shared(); + case RestartStrategyType::BIPOP: + return std::make_shared(lambda, mu, budget); + default: + case RestartStrategyType::STOP: + case RestartStrategyType::NONE: + case RestartStrategyType::RESTART: + return std::make_shared(); + } + } + } +} \ No newline at end of file diff --git a/include/stats.hpp b/include/stats.hpp index 461b981..9aa108d 100644 --- a/include/stats.hpp +++ b/include/stats.hpp @@ -31,7 +31,6 @@ namespace parameters has_improved = true; } success_ratio = (1 - cs) * success_ratio + (cs * has_improved); - } }; } diff --git a/modcma/c_maes/cmaescpp/__init__.pyi b/modcma/c_maes/cmaescpp/__init__.pyi index 3cbe737..b731cbe 100644 --- a/modcma/c_maes/cmaescpp/__init__.pyi +++ b/modcma/c_maes/cmaescpp/__init__.pyi @@ -1,14 +1,21 @@ from typing import Any, Callable, List, Optional, Union, overload, ClassVar import numpy -from . import matrix_adaptation, sampling, parameters, mutation, restart, repelling, center +from . import ( + matrix_adaptation, + sampling, + parameters, + mutation, + restart, + repelling, + center, +) class Solution: x: numpy.ndarray y: float t: int e: int - class constants: cache_max_doubles: ClassVar[int] = ... @@ -21,7 +28,6 @@ class constants: tolup_sigma: ClassVar[float] = ... def __init__(self, *args, **kwargs) -> None: ... - class ModularCMAES: @overload def __init__(self, parameters: Parameters) -> None: ... @@ -40,35 +46,6 @@ class ModularCMAES: @property def p(self) -> Parameters: ... -class Parameters: - adaptation: Union[ - matrix_adaptation.MatrixAdaptation, - matrix_adaptation.CovarianceAdaptation, - matrix_adaptation.NoAdaptation, - matrix_adaptation.SeperableAdaptation - ] - bounds: Any - center_placement: center.Placement - lamb: int - mu: int - mutation: mutation.Strategy - old_pop: Population - pop: Population - repelling: repelling.Repelling - restart: restart.Strategy - sampler: sampling.Sampler - selection: Any - settings: parameters.Settings - stats: parameters.Stats - weights: parameters.Weights - - @overload - def __init__(self, dimension: int) -> None: ... - @overload - def __init__(self, settings: parameters.Settings) -> None: ... - def adapt(self, objective: Callable[[numpy.ndarray], float]) -> None: ... - def perform_restart(self, objective: Callable[[numpy.ndarray], float], sigma: Optional[float] = ...) -> None: ... - class Population: X: numpy.ndarray Y: numpy.ndarray @@ -91,6 +68,57 @@ class Population: def keep_only(self, idx: List[int]) -> None: ... def resize_cols(self, size: int) -> None: ... def sort(self) -> None: ... - def __add__(self, other: Population) -> Population: ... + def __add__(self, other: "Population") -> "Population": ... @property def n_finite(self) -> int: ... + +class Parameters: + adaptation: ( + matrix_adaptation.MatrixAdaptation + | matrix_adaptation.CovarianceAdaptation + | matrix_adaptation.SeperableAdaptation + | matrix_adaptation.OnePlusOneAdaptation + | matrix_adaptation.NoAdaptation + ) + bounds: Any + center_placement: center.Placement + criteria: restart.Criteria + lamb: int + mu: int + mutation: mutation.Strategy + old_pop: Population + pop: Population + repelling: repelling.Repelling + restart_strategy: restart.Strategy + sampler: sampling.Sampler + selection: Any + settings: parameters.Settings + stats: parameters.Stats + weights: parameters.Weights + @overload + def __init__(self, dimension: int) -> None: ... + @overload + def __init__(self, settings: parameters.Settings) -> None: ... + def adapt(self) -> None: ... + def perform_restart( + self, + objective: Callable[[numpy.ndarray[numpy.float64[m, 1]]], float], + sigma: float | None = ..., + ) -> None: ... + def start( + self, objective: Callable[[numpy.ndarray[numpy.float64[m, 1]]], float] + ) -> None: ... + +class constants: + cache_max_doubles: ClassVar[int] = ... + cache_min_samples: ClassVar[int] = ... + cache_samples: ClassVar[bool] = ... + clip_sigma: ClassVar[bool] = ... + lb_sigma: ClassVar[float] = ... + max_dsigma: ClassVar[float] = ... + min_dsigma: ClassVar[float] = ... + sigma_threshold: ClassVar[float] = ... + stagnation_quantile: ClassVar[float] = ... + tol_condition_cov: ClassVar[float] = ... + ub_sigma: ClassVar[float] = ... + def __init__(self, *args, **kwargs) -> None: ... diff --git a/modcma/c_maes/cmaescpp/es.pyi b/modcma/c_maes/cmaescpp/es.pyi new file mode 100644 index 0000000..f2be5f5 --- /dev/null +++ b/modcma/c_maes/cmaescpp/es.pyi @@ -0,0 +1,49 @@ +import modcma.c_maes.cmaescpp.bounds +import modcma.c_maes.cmaescpp.parameters +import modcma.c_maes.cmaescpp.sampling +import numpy +from typing import Callable + +class MuCommaLambdaES: + S: numpy.ndarray[numpy.float64[m, n]] + X: numpy.ndarray[numpy.float64[m, n]] + budget: int + corrector: modcma.c_maes.cmaescpp.bounds.BoundCorrection + d: int + e: int + f: numpy.ndarray + f_min: float + lamb: int + m: numpy.ndarray + mu: int + mu_inv: float + rejection_sampling: bool + sampler: modcma.c_maes.cmaescpp.sampling.Sampler + sigma: numpy.ndarray + sigma_sampler: modcma.c_maes.cmaescpp.sampling.Sampler + t: int + target: float + tau: float + tau_i: float + x_min: numpy.ndarray + def __init__(self, d: int, x0: numpy.ndarray, sigma0: float = ..., budget: int = ..., target: float = ..., modules: modcma.c_maes.cmaescpp.parameters.Modules = ...) -> None: ... + def sample(self, arg0: numpy.ndarray) -> numpy.ndarray: ... + def step(self, arg0: Callable[[numpy.ndarray], float]) -> None: ... + def __call__(self, arg0: Callable[[numpy.ndarray], float]) -> None: ... + +class OnePlusOneES: + budget: int + corrector: modcma.c_maes.cmaescpp.bounds.BoundCorrection + d: int + decay: float + f: float + rejection_sampling: bool + sampler: modcma.c_maes.cmaescpp.sampling.Sampler + sigma: float + t: int + target: float + x: numpy.ndarray + def __init__(self, d: int, x0: numpy.ndarray, f0: float, sigma0: float = ..., budget: int = ..., target: float = ..., modules: modcma.c_maes.cmaescpp.parameters.Modules = ...) -> None: ... + def sample(self) -> numpy.ndarray: ... + def step(self, arg0: Callable[[numpy.ndarray], float]) -> None: ... + def __call__(self, arg0: Callable[[numpy.ndarray], float]) -> None: ... diff --git a/modcma/c_maes/cmaescpp/matrix_adaptation.pyi b/modcma/c_maes/cmaescpp/matrix_adaptation.pyi index 742a5c9..40afdb4 100644 --- a/modcma/c_maes/cmaescpp/matrix_adaptation.pyi +++ b/modcma/c_maes/cmaescpp/matrix_adaptation.pyi @@ -1,38 +1,21 @@ import modcma.c_maes.cmaescpp -import modcma.c_maes.cmaescpp.mutation import numpy class Adaptation: - chiN: float dd: float dm: numpy.ndarray + expected_length_z: float inv_C: numpy.ndarray m: numpy.ndarray m_old: numpy.ndarray ps: numpy.ndarray - def __init__(self, *args, **kwargs) -> None: ... - def adapt_evolution_paths( - self, - pop: modcma.c_maes.cmaescpp.Population, - weights, - mutation: modcma.c_maes.cmaescpp.mutation.Strategy, - stats, - mu: int, - lamb: int, - ) -> None: ... - def adapt_matrix( - self, - weights, - modules, - population: modcma.c_maes.cmaescpp.Population, - mu: int, - settings, - ) -> bool: ... - def restart(self, settings) -> None: ... + def adapt_evolution_paths(self, pop: modcma.c_maes.cmaescpp.Population, weights, mutation: modcma.c_maes.cmaescpp.mutation.Strategy, stats, mu: int, lamb: int) -> None: ... + def adapt_matrix(self, weights, modules, population: modcma.c_maes.cmaescpp.Population, mu: int, settings, stats) -> bool: ... def compute_y(self, zi: numpy.ndarray) -> numpy.ndarray: ... def invert_x(self, xi: numpy.ndarray, sigma: float) -> numpy.ndarray: ... def invert_y(self, yi: numpy.ndarray) -> numpy.ndarray: ... + def restart(self, settings) -> None: ... class CovarianceAdaptation(Adaptation): B: numpy.ndarray @@ -41,19 +24,20 @@ class CovarianceAdaptation(Adaptation): hs: bool inv_root_C: numpy.ndarray pc: numpy.ndarray - def __init__(self, dimension: int, x0: numpy.ndarray) -> None: ... - def adapt_covariance_matrix( - self, weights, modules, population: modcma.c_maes.cmaescpp.Population, mu: int - ) -> None: ... + def __init__(self, dimension: int, x0: numpy.ndarray, expected_length_z: float) -> None: ... + def adapt_covariance_matrix(self, weights, modules, population: modcma.c_maes.cmaescpp.Population, mu: int) -> None: ... def perform_eigendecomposition(self, stats) -> bool: ... class MatrixAdaptation(Adaptation): M: numpy.ndarray M_inv: numpy.ndarray - def __init__(self, dimension: int, x0: numpy.ndarray) -> None: ... - -class SeperableAdaptation(CovarianceAdaptation): - def __init__(self, dimension: int, x0: numpy.ndarray) -> None: ... + def __init__(self, dimension: int, x0: numpy.ndarray, expected_length_z: float) -> None: ... class NoAdaptation(Adaptation): - def __init__(self, dimension: int, x0: numpy.ndarray) -> None: ... + def __init__(self, dimension: int, x0: numpy.ndarray, expected_length_z: float) -> None: ... + +class OnePlusOneAdaptation(CovarianceAdaptation): + def __init__(self, dimension: int, x0: numpy.ndarray, expected_length_z: float) -> None: ... + +class SeperableAdaptation(CovarianceAdaptation): + def __init__(self, dimension: int, x0: numpy.ndarray, expected_length_z: float) -> None: ... diff --git a/modcma/c_maes/cmaescpp/parameters.pyi b/modcma/c_maes/cmaescpp/parameters.pyi index 004a3fd..a538a9f 100644 --- a/modcma/c_maes/cmaescpp/parameters.pyi +++ b/modcma/c_maes/cmaescpp/parameters.pyi @@ -71,7 +71,9 @@ class Stats: current_best: Solution evaluations: int global_best: Solution + has_improved: bool solutions: list[Solution] + success_ratio: float t: int def __init__(self) -> None: ... diff --git a/modcma/c_maes/cmaescpp/restart.pyi b/modcma/c_maes/cmaescpp/restart.pyi index 375913e..42c90e6 100644 --- a/modcma/c_maes/cmaescpp/restart.pyi +++ b/modcma/c_maes/cmaescpp/restart.pyi @@ -1,5 +1,24 @@ import numpy -from typing import Callable + +class Strategy: + def __init__(self, *args, **kwargs) -> None: ... + def update(self, parameters) -> float: ... + +class Criterion: + last_restart: int + met: bool + name: str + def __init__(self, *args, **kwargs) -> None: ... + def reset(self, parameters) -> None: ... + def update(self, parameters) -> None: ... + +class Criteria: + items: list[Criterion] + def __init__(self, *args, **kwargs) -> None: ... + def reset(self, parameters) -> None: ... + def update(self, parameters) -> None: ... + @property + def any(self) -> bool: ... class BIPOP(Strategy): budget: int @@ -9,83 +28,57 @@ class BIPOP(Strategy): lambda_large: int lambda_small: int mu_factor: float - def __init__(self, sigma: float, dimension: float, lamb: float, mu: float, budget: int) -> None: ... + def __init__(self, *args, **kwargs) -> None: ... def large(self) -> bool: ... - def restart(self, objective: Callable[[numpy.ndarray], float], parameters) -> None: ... @property def used_budget(self) -> int: ... +class ConditionC(Criterion): + def __init__(self) -> None: ... + +class ExceededMaxIter(Criterion): + max_iter: int + def __init__(self) -> None: ... + +class FlatFitness(Criterion): + flat_fitness_index: int + flat_fitnesses: numpy.ndarray[numpy.int32[m, 1]] + max_flat_fitness: int + def __init__(self) -> None: ... + class IPOP(Strategy): ipop_factor: float - def __init__(self, sigma: float, dimension: float, lamb: float) -> None: ... - def restart(self, objective: Callable[[numpy.ndarray], float], parameters) -> None: ... - -class NoRestart(Strategy): - def __init__(self, sigma: float, dimension: float, lamb: float) -> None: ... - def restart(self, objective: Callable[[numpy.ndarray], float], parameters) -> None: ... - -class Restart(Strategy): - def __init__(self, sigma: float, dimension: float, lamb: float) -> None: ... - def restart(self, objective: Callable[[numpy.ndarray], float], parameters) -> None: ... - -class RestartCriteria: - def __init__(self, sigma: float, dimension: float, lamb: float, time: int) -> None: ... - def conditioncov(self) -> bool: ... - def exceeded_max_iter(self) -> bool: ... - def flat_fitness(self) -> bool: ... - def no_improvement(self) -> bool: ... - def noeffectaxis(self) -> bool: ... - def noeffectcoor(self) -> bool: ... - def stagnation(self) -> bool: ... - def tolupsigma(self) -> bool: ... - def tolx(self) -> bool: ... - def __call__(self, parameters) -> bool: ... - @property - def any(self) -> bool: ... - @property - def best_fitnesses(self) -> list[float]: ... - @property - def condition_c(self) -> float: ... - @property - def d_sigma(self) -> float: ... - @property - def effect_axis(self) -> numpy.ndarray: ... - @property - def effect_coord(self) -> numpy.ndarray: ... - @property - def flat_fitness_index(self) -> int: ... - @property - def flat_fitnesses(self) -> numpy.ndarray[numpy.int32[m, 1]]: ... - @property - def last_restart(self) -> int: ... - @property - def max_iter(self) -> int: ... - @property - def median_fitnesses(self) -> list[float]: ... - @property - def n_bin(self) -> int: ... - @property - def n_flat_fitness(self) -> int: ... - @property - def n_stagnation(self) -> int: ... - @property - def recent_improvement(self) -> float: ... - @property - def root_max_d(self) -> float: ... - @property - def sigma0(self) -> float: ... - @property - def time_since_restart(self) -> int: ... - @property - def tolx_condition(self) -> float: ... - @property - def tolx_vector(self) -> numpy.ndarray: ... + def __init__(self, *args, **kwargs) -> None: ... -class Stop(Strategy): - def __init__(self, sigma: float, dimension: float, lamb: float) -> None: ... - def restart(self, objective: Callable[[numpy.ndarray], float], parameters) -> None: ... +class MaxDSigma(Criterion): + def __init__(self) -> None: ... -class Strategy: - criteria: RestartCriteria - def __init__(self, *args, **kwargs) -> None: ... - def evaluate(self, objective: Callable[[numpy.ndarray], float], parameters) -> None: ... +class MinDSigma(Criterion): + def __init__(self) -> None: ... + +class NoEffectAxis(Criterion): + def __init__(self) -> None: ... + +class NoEffectCoord(Criterion): + def __init__(self) -> None: ... + +class NoImprovement(Criterion): + best_fitnesses: list[float] + n_bin: int + def __init__(self) -> None: ... + +class SigmaOutOfBounds(Criterion): + def __init__(self) -> None: ... + +class Stagnation(Criterion): + best_fitnesses: list[float] + median_fitnesses: list[float] + n_stagnation: int + def __init__(self) -> None: ... + +class TolX(Criterion): + tolx_vector: numpy.ndarray[numpy.float64[m, 1]] + def __init__(self) -> None: ... + +class UnableToAdapt(Criterion): + def __init__(self) -> None: ... diff --git a/setup.py b/setup.py index 98fa87d..0633d5a 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ if platform.system() in ("Linux", "Darwin"): os.environ["CC"] = "g++" os.environ["CXX"] = "g++" - flags = ["-O3", "-fno-math-errno", ] #"-fopenmp" + flags = ["-O0", "-fno-math-errno", ] #"-fopenmp" if platform.system() == "Darwin": flags.append("-mmacosx-version-min=10.15") else: diff --git a/src/c_maes.cpp b/src/c_maes.cpp index e7bad1a..8244ead 100644 --- a/src/c_maes.cpp +++ b/src/c_maes.cpp @@ -7,16 +7,31 @@ void ModularCMAES::recombine() const positive); } -bool ModularCMAES::step(FunctionType& objective) const +void ModularCMAES::mutate(FunctionType &objective) const { + p->start(objective); p->mutation->mutate(objective, p->lambda, *p); +} + +void ModularCMAES::select() const +{ p->selection->select(*p); +} +void ModularCMAES::adapt() const +{ + p->adapt(); +} + +bool ModularCMAES::step(FunctionType& objective) const +{ + mutate(objective); + select(); recombine(); - p->adapt(objective); - if (p->stats.t % (p->settings.dim * 2) == 0 and p->settings.verbose) - std::cout << p->stats << " (mu, lambda, sigma): " << p->mu - << ", " << p->lambda << ", " << p->mutation->sigma << '\n'; + adapt(); + // if (p->stats.t % (p->settings.dim * 2) == 0 and p->settings.verbose) + // std::cout << p->stats << " (mu, lambda, sigma): " << p->mu + // << ", " << p->lambda << ", " << p->mutation->sigma << '\n'; return !break_conditions(); } @@ -24,8 +39,8 @@ void ModularCMAES::operator()(FunctionType& objective) const { while (step(objective)); - if (p->settings.verbose) - std::cout << p->stats << '\n'; + // if (p->settings.verbose) + // std::cout << p->stats << '\n'; } bool ModularCMAES::break_conditions() const @@ -34,6 +49,6 @@ bool ModularCMAES::break_conditions() const const auto budget_used_up = p->stats.evaluations >= p->settings.budget; const auto exceed_gens = p->settings.max_generations and p->stats.t >= p->settings.max_generations; const auto restart_strategy_criteria = p->settings.modules.restart_strategy == parameters::RestartStrategyType::STOP - and p->restart->criteria.any; + and p->criteria.any; return exceed_gens or target_reached or budget_used_up or restart_strategy_criteria; } diff --git a/src/common.cpp b/src/common.cpp index 117bde3..9498511 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -11,9 +11,9 @@ std::ostream& operator<<(std::ostream& os, const std::vector& x) namespace constants { - Float tolup_sigma = std::pow(10., 20.); + Float max_dsigma = std::pow(10., 20.); + Float min_dsigma = 1e-8; Float tol_condition_cov = pow(10., 14.); - Float tol_min_sigma = 1e-8; Float stagnation_quantile = 0.3; Float sigma_threshold = 1e-4; size_t cache_max_doubles = 2'000'000; diff --git a/src/interface.cpp b/src/interface.cpp index 2d01fc4..24789ec 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -636,7 +636,8 @@ void define_parameters(py::module &main) py::class_>(main, "Parameters") .def(py::init(), py::arg("dimension")) .def(py::init(), py::arg("settings")) - .def("adapt", &Parameters::adapt, py::arg("objective")) + .def("adapt", &Parameters::adapt) + .def("start", &Parameters::start, py::arg("objective")) .def("perform_restart", &Parameters::perform_restart, py::arg("objective"), py::arg("sigma") = std::nullopt) .def_readwrite("settings", &Parameters::settings) @@ -665,6 +666,7 @@ void define_parameters(py::module &main) { self.adaptation = adaptation; }) + .def_readwrite("criteria", &Parameters::criteria) .def_readwrite("stats", &Parameters::stats) .def_readwrite("weights", &Parameters::weights) .def_readwrite("pop", &Parameters::pop) @@ -672,7 +674,7 @@ void define_parameters(py::module &main) .def_readwrite("sampler", &Parameters::sampler) .def_readwrite("mutation", &Parameters::mutation) .def_readwrite("selection", &Parameters::selection) - .def_readwrite("restart", &Parameters::restart) + .def_readwrite("restart_strategy", &Parameters::restart_strategy) .def_readwrite("repelling", &Parameters::repelling) .def_readwrite("bounds", &Parameters::bounds) .def_readwrite("center_placement", &Parameters::center_placement); @@ -889,23 +891,23 @@ void define_constants(py::module &m) { py::class_(m, "constants") .def_property_static( - "tolup_sigma", + "max_dsigma", [](py::object) - { return constants::tolup_sigma; }, + { return constants::max_dsigma; }, [](py::object, Float a) - { constants::tolup_sigma = a; }) + { constants::max_dsigma = a; }) .def_property_static( - "tol_condition_cov", + "min_dsigma", [](py::object) - { return constants::tol_condition_cov; }, + { return constants::min_dsigma; }, [](py::object, Float a) - { constants::tol_condition_cov = a; }) + { constants::min_dsigma = a; }) .def_property_static( - "tol_min_sigma", + "tol_condition_cov", [](py::object) - { return constants::tol_min_sigma; }, + { return constants::tol_condition_cov; }, [](py::object, Float a) - { constants::tol_min_sigma = a; }) + { constants::tol_condition_cov = a; }) .def_property_static( "stagnation_quantile", [](py::object) @@ -954,87 +956,93 @@ void define_constants(py::module &m) [](py::object) { return constants::ub_sigma; }, [](py::object, Float a) - { constants::ub_sigma = a; }) - ; + { constants::ub_sigma = a; }); } -void define_restart(py::module &main) +void define_restart_criteria(py::module &main) { auto m = main.def_submodule("restart"); using namespace restart; - py::class_(m, "RestartCriteria") - .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb"), py::arg("time")) - .def("exceeded_max_iter", &RestartCriteria::exceeded_max_iter) - .def("no_improvement", &RestartCriteria::no_improvement) - .def("flat_fitness", &RestartCriteria::flat_fitness) - .def("tolx", &RestartCriteria::tolx) - .def("tolupsigma", &RestartCriteria::tolupsigma) - .def("conditioncov", &RestartCriteria::conditioncov) - .def("noeffectaxis", &RestartCriteria::noeffectaxis) - .def("noeffectcoor", &RestartCriteria::noeffectcoor) - .def("stagnation", &RestartCriteria::stagnation) - .def_readonly("sigma0", &RestartCriteria::sigma0) - .def_readonly("last_restart", &RestartCriteria::last_restart) - .def_readonly("max_iter", &RestartCriteria::max_iter) - .def_readonly("n_bin", &RestartCriteria::n_bin) - .def_readonly("n_stagnation", &RestartCriteria::n_stagnation) - .def_readonly("flat_fitness_index", &RestartCriteria::flat_fitness_index) - .def_readonly("flat_fitnesses", &RestartCriteria::flat_fitnesses) - .def_readonly("median_fitnesses", &RestartCriteria::median_fitnesses) - .def_readonly("best_fitnesses", &RestartCriteria::best_fitnesses) - .def_readonly("time_since_restart", &RestartCriteria::time_since_restart) - .def_readonly("recent_improvement", &RestartCriteria::recent_improvement) - .def_readonly("n_flat_fitness", &RestartCriteria::n_flat_fitness) - .def_readonly("d_sigma", &RestartCriteria::d_sigma) - .def_readonly("tolx_condition", &RestartCriteria::tolx_condition) - .def_readonly("tolx_vector", &RestartCriteria::tolx_vector) - .def_readonly("root_max_d", &RestartCriteria::root_max_d) - .def_readonly("condition_c", &RestartCriteria::condition_c) - .def_readonly("effect_coord", &RestartCriteria::effect_coord) - .def_readonly("effect_axis", &RestartCriteria::effect_axis) - .def_readonly("any", &RestartCriteria::any) - .def("__call__", &RestartCriteria::operator(), py::arg("parameters")) - .def("__repr__", [](const RestartCriteria &res) - { - std::stringstream ss; - ss << std::boolalpha; - ss << ""; - return ss.str(); }); + py::class_>(m, "Criterion") + .def("reset", &Criterion::reset, py::arg("parameters")) + .def("update", &Criterion::update, py::arg("parameters")) + .def_readwrite("met", &Criterion::met) + .def_readwrite("name", &Criterion::name) + .def_readwrite("last_restart", &Criterion::last_restart) + .def("__repr__", [](Criterion &self) + { return "<" + self.name + " met: " + std::to_string(self.met) + ">"; }); - py::class_>(m, "Strategy") - .def("evaluate", &Strategy::evaluate, py::arg("objective"), py::arg("parameters")) - .def_readwrite("criteria", &Strategy::criteria); + py::class_>(m, "ExceededMaxIter") + .def(py::init<>()) + .def_readwrite("max_iter", &ExceededMaxIter::max_iter); + + py::class_>(m, "NoImprovement") + .def(py::init<>()) + .def_readwrite("n_bin", &NoImprovement::n_bin) + .def_readwrite("best_fitnesses", &NoImprovement::best_fitnesses); + + py::class_>(m, "SigmaOutOfBounds") + .def(py::init<>()); + + py::class_>(m, "UnableToAdapt") + .def(py::init<>()); + + py::class_>(m, "FlatFitness") + .def(py::init<>()) + .def_readwrite("max_flat_fitness", &FlatFitness::max_flat_fitness) + .def_readwrite("flat_fitness_index", &FlatFitness::flat_fitness_index) + .def_readwrite("flat_fitnesses", &FlatFitness::flat_fitnesses); - py::class_>(m, "NoRestart") - .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb")) - .def("restart", &None::restart, py::arg("objective"), py::arg("parameters")); + py::class_>(m, "TolX") + .def(py::init<>()) + .def_readwrite("tolx_vector", &TolX::tolx_vector); + + py::class_>(m, "MaxDSigma") + .def(py::init<>()); - py::class_>(m, "Stop") - .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb")) - .def("restart", &Stop::restart, py::arg("objective"), py::arg("parameters")); + py::class_>(m, "MinDSigma") + .def(py::init<>()); - py::class_>(m, "Restart") - .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb")) - .def("restart", &Restart::restart, py::arg("objective"), py::arg("parameters")); + py::class_>(m, "ConditionC") + .def(py::init<>()); + + py::class_>(m, "NoEffectAxis") + .def(py::init<>()); + + py::class_>(m, "NoEffectCoord") + .def(py::init<>()); + + py::class_>(m, "Stagnation") + .def(py::init<>()) + .def_readwrite("n_stagnation", &Stagnation::n_stagnation) + .def_readwrite("median_fitnesses", &Stagnation::median_fitnesses) + .def_readwrite("best_fitnesses", &Stagnation::best_fitnesses); + + py::class_(m, "Criteria") + .def_readwrite("items", &Criteria::items) + .def("reset", &Criteria::reset, py::arg("parameters")) + .def("update", &Criteria::update, py::arg("parameters")) + .def_readonly("any", &Criteria::any); +} + +void define_restart_strategy(py::module &main) +{ + auto m = main.def_submodule("restart"); + using namespace restart; + + py::class_>(m, "Strategy") + // .def("evaluate", &Strategy::evaluate, py::arg("objective"), py::arg("parameters")) + // .def_readwrite("criteria", &Strategy::criteria) + .def("update", &Strategy::update, py::arg("parameters")); + ; py::class_>(m, "IPOP") - .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb")) - .def("restart", &IPOP::restart, py::arg("objective"), py::arg("parameters")) + // .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb")) .def_readwrite("ipop_factor", &IPOP::ipop_factor); py::class_>(m, "BIPOP") - .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb"), py::arg("mu"), py::arg("budget")) - .def("restart", &BIPOP::restart, py::arg("objective"), py::arg("parameters")) + // .def(py::init(), py::arg("sigma"), py::arg("dimension"), py::arg("lamb"), py::arg("mu"), py::arg("budget")) .def("large", &BIPOP::large) .def_readwrite("mu_factor", &BIPOP::mu_factor) .def_readwrite("lambda_init", &BIPOP::lambda_init) @@ -1053,16 +1061,9 @@ void define_cmaes(py::module &m) .def(py::init(), py::arg("dimension")) .def(py::init(), py::arg("settings")) .def("recombine", &ModularCMAES::recombine) - .def( - "mutate", [](ModularCMAES &self, FunctionType &f) - { self.p->mutation->mutate(f, self.p->lambda, *self.p); }, - py::arg("objective")) - .def("select", [](ModularCMAES &self) - { self.p->selection->select(*self.p); }) - .def( - "adapt", [](ModularCMAES &self, FunctionType &f) - { self.p->adapt(f); }, - py::arg("objective")) + .def("mutate", &ModularCMAES::mutate, py::arg("objective")) + .def("select", &ModularCMAES::select) + .def("adapt", &ModularCMAES::adapt) .def("step", &ModularCMAES::step, py::arg("objective")) .def("__call__", &ModularCMAES::operator(), py::arg("objective")) .def("run", &ModularCMAES::operator(), py::arg("objective")) @@ -1160,7 +1161,8 @@ PYBIND11_MODULE(cmaescpp, m) define_population(m); define_samplers(m); define_mutation(m); - define_restart(m); + define_restart_criteria(m); + define_restart_strategy(m); define_matrix_adaptation(m); define_center_placement(m); define_repelling(m); diff --git a/src/matrix_adaptation.cpp b/src/matrix_adaptation.cpp index 5396e49..ef90444 100644 --- a/src/matrix_adaptation.cpp +++ b/src/matrix_adaptation.cpp @@ -43,14 +43,10 @@ namespace matrix_adaptation leftCols(mu).transpose()); } - - //std::cout << C << std::endl; C = old_c + rank_one + rank_mu; C = C.triangularView().toDenseMatrix() + C.triangularView().toDenseMatrix().transpose(); - - //std::cout << C << std::endl; } bool CovarianceAdaptation::perform_eigendecomposition(const Settings& settings) diff --git a/src/mutation.cpp b/src/mutation.cpp index e45053f..f82a3f9 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -33,22 +33,18 @@ namespace mutation ss->sample(sigma, p.pop); p.bounds->n_out_of_bounds = 0; p.repelling->prepare_sampling(p); - for (Eigen::Index i = 0; i < static_cast(n_offspring); ++i) { size_t n_rej = 0; do { p.pop.Z.col(i) = p.mutation->tc->scale((*p.sampler)(), p.bounds->diameter, p.settings.budget, p.stats.evaluations); - p.pop.Y.col(i) = p.adaptation->compute_y(p.pop.Z.col(i)); - p.pop.X.col(i) = p.pop.Y.col(i) * p.pop.s(i) + p.adaptation->m; - 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)).any()) || p.repelling->is_rejected(p.pop.X.col(i), p)); - + p.pop.f(i) = objective(p.pop.X.col(i)); p.stats.evaluations++; if (sq->break_conditions(i, p.pop.f(i), p.stats.global_best.y, p.settings.modules.mirrored)) @@ -183,6 +179,7 @@ namespace mutation const Float expected_z) { using namespace parameters; + auto tc = m.threshold_convergence ? std::make_shared() : std::make_shared(); diff --git a/src/parameters.cpp b/src/parameters.cpp index 2340d6e..bb174e6 100644 --- a/src/parameters.cpp +++ b/src/parameters.cpp @@ -2,12 +2,15 @@ namespace parameters { - Parameters::Parameters(const Settings &settings) : lambda(settings.lambda0), + Parameters::Parameters(const Settings &settings) : successfull_adaptation(true), + lambda(settings.lambda0), mu(settings.mu0), settings(settings), + stats{}, weights(settings.dim, settings.mu0, settings.lambda0, settings), pop(settings.dim, settings.lambda0), old_pop(settings.dim, settings.lambda0), + criteria(restart::Criteria::get(settings.modules)), sampler(sampling::get(settings.dim, settings.modules, settings.lambda0)), adaptation(matrix_adaptation::get(settings.modules, settings.dim, settings.x0.value_or(Vector::Zero(settings.dim)), @@ -19,10 +22,8 @@ namespace parameters settings.cs, sampler->expected_length())), selection(std::make_shared(settings.modules)), - restart(restart::get( - settings.modules.restart_strategy, - settings.sigma0, - static_cast(settings.dim), + restart_strategy(restart::strategy::get( + settings.modules, static_cast(settings.lambda0), static_cast(settings.mu0), settings.budget)), @@ -30,6 +31,7 @@ namespace parameters repelling(repelling::get(settings.modules)), center_placement(center::get(settings.modules.center_placement)) { + criteria.reset(*this); } Parameters::Parameters(const size_t dim) : Parameters(Settings(dim, {})) @@ -39,9 +41,8 @@ namespace parameters void Parameters::perform_restart(FunctionType &objective, const std::optional &sigma) { stats.solutions.push_back(stats.current_best); - stats.evaluations++; - stats.centers.emplace_back(adaptation->m, objective(adaptation->m), stats.t, stats.evaluations); + stats.centers.emplace_back(adaptation->m, objective(adaptation->m), 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); @@ -58,37 +59,32 @@ namespace parameters settings.cs, sampler->expected_length()); adaptation->restart(settings); (*center_placement)(*this); - restart->criteria = restart::RestartCriteria(sigma.value_or(settings.sigma0), settings.dim, lambda, stats.t); + criteria.reset(*this); stats.current_best = {}; } - bool Parameters::invalid_state() const + void Parameters::adapt() { + adaptation->adapt_evolution_paths(pop, weights, mutation, stats, mu, lambda); + mutation->adapt(weights, adaptation, pop, old_pop, stats, lambda); + if (constants::clip_sigma) mutation->sigma = std::min(std::max(mutation->sigma, constants::lb_sigma), constants::ub_sigma); - - const bool sigma_out_of_bounds = constants::lb_sigma > mutation->sigma or mutation->sigma > constants::ub_sigma; - if (sigma_out_of_bounds && settings.verbose) - { - std::cout << "sigma out of bounds: " << mutation->sigma << " restarting\n"; - } - return sigma_out_of_bounds; + successfull_adaptation = adaptation->adapt_matrix(weights, settings.modules, pop, mu, settings, stats); + + criteria.update(*this); + stats.t++; } - void Parameters::adapt(FunctionType &objective) + void Parameters::start(FunctionType &objective) { - adaptation->adapt_evolution_paths(pop, weights, mutation, stats, mu, lambda); - mutation->adapt(weights, adaptation, pop, old_pop, stats, lambda); - - auto successfull_adaptation = adaptation->adapt_matrix(weights, settings.modules, pop, mu, settings, stats); - - if (!successfull_adaptation or invalid_state()) - perform_restart(objective); - old_pop = pop; - restart->evaluate(objective, *this); - stats.t++; + if (criteria.any) + { + const auto sig = restart_strategy->update(*this); + perform_restart(objective, sig); + } } } @@ -99,6 +95,5 @@ std::ostream &operator<<(std::ostream &os, const parameters::Stats &s) << " t=" << s.t << " e=" << s.evaluations << " best=" << s.global_best - << " improved=" << std::boolalpha << s.has_improved - ; + << " improved=" << std::boolalpha << s.has_improved; } diff --git a/src/restart.cpp b/src/restart.cpp deleted file mode 100644 index 77c27a7..0000000 --- a/src/restart.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include "restart.hpp" -#include "parameters.hpp" -#include "matrix_adaptation.hpp" - -#include - -namespace restart -{ - //! max - min, for the last n elements of a vector - Float ptp_tail(const std::vector &v, const size_t n) - { - const auto na = std::min(v.size(), n); - if (na == 1) - { - return v[0]; - } - - const Float min = *std::min_element(v.end() - na, v.end()); - const Float max = *std::max_element(v.end() - na, v.end()); - return max - min; - } - - // TODO: this is duplicate code - Float median(const Vector &x) - { - if (x.size() % 2 == 0) - return (x(x.size() / 2) + x(x.size() / 2 - 1)) / 2.0; - return x(x.size() / 2); - } - - Float median(const std::vector &v, const size_t from, const size_t to) - { - const size_t n = to - from; - if (n % 2 == 0) - return (v[from + (n / 2)] + v[from + (n / 2) - 1]) / 2.0; - return v[from + (n / 2)]; - } - - void RestartCriteria::update(const parameters::Parameters &p) - { - flat_fitnesses(p.stats.t % p.settings.dim) = p.pop.f(0) == p.pop.f(flat_fitness_index); - median_fitnesses.push_back(median(p.pop.f)); - best_fitnesses.push_back(p.pop.f(0)); - - time_since_restart = p.stats.t - last_restart; - recent_improvement = ptp_tail(best_fitnesses, n_bin); - n_flat_fitness = static_cast(flat_fitnesses.sum()); - - d_sigma = p.mutation->sigma / sigma0; - tolx_condition = 10e-12 * sigma0; - - if (p.settings.modules.matrix_adaptation == parameters::MatrixAdaptationType::COVARIANCE || - p.settings.modules.matrix_adaptation == parameters::MatrixAdaptationType::SEPERABLE) - { - using namespace matrix_adaptation; - const std::shared_ptr dynamic = std::dynamic_pointer_cast( - p.adaptation); - - tolx_vector.head(p.settings.dim) = dynamic->C.diagonal() * d_sigma; - tolx_vector.tail(p.settings.dim) = dynamic->pc * d_sigma; - - root_max_d = std::sqrt(dynamic->d.maxCoeff()); - condition_c = pow(dynamic->d.maxCoeff(), 2.0) / pow(dynamic->d.minCoeff(), 2); - - effect_coord = 0.2 * p.mutation->sigma * dynamic->C.diagonal().cwiseSqrt(); - - const Eigen::Index t = p.stats.t % p.settings.dim; - effect_axis = 0.1 * p.mutation->sigma * std::sqrt(dynamic->d(t)) * dynamic->B.col(t); - } - } - - bool RestartCriteria::exceeded_max_iter() const - { - return max_iter < time_since_restart; - } - - bool RestartCriteria::no_improvement() const - { - return time_since_restart > n_bin and recent_improvement == 0; - } - - bool RestartCriteria::flat_fitness() const - { - return time_since_restart > static_cast(flat_fitnesses.size()) and n_flat_fitness > max_flat_fitness; - } - - bool RestartCriteria::tolx() const - { - return (tolx_vector.array() < tolx_condition).all(); - } - - bool RestartCriteria::tolupsigma() const - { - return d_sigma > constants::tolup_sigma * root_max_d; - } - - bool RestartCriteria::conditioncov() const - { - return condition_c > constants::tol_condition_cov; - } - - bool RestartCriteria::noeffectaxis() const - { - return (effect_axis.array() == 0).all(); - } - - bool RestartCriteria::noeffectcoor() const - { - return (effect_coord.array() == 0).all(); - } - - bool RestartCriteria::min_sigma() const - { - return d_sigma < constants::tol_min_sigma; - } - - bool RestartCriteria::stagnation() const - { - const size_t pt = static_cast(constants::stagnation_quantile * time_since_restart); - return time_since_restart > n_stagnation and ((median(best_fitnesses, pt, time_since_restart) >= median( - best_fitnesses, 0, pt)) and - (median(median_fitnesses, pt, time_since_restart) >= median(median_fitnesses, 0, pt))); - } - - bool RestartCriteria::operator()(const parameters::Parameters &p) - { - update(p); - any = exceeded_max_iter() or no_improvement() or stagnation() or min_sigma(); - if (p.settings.lambda0 > 1) - { - any = any or flat_fitness(); - } - if (p.settings.modules.matrix_adaptation == parameters::MatrixAdaptationType::COVARIANCE) - { - any = any or tolx() or tolupsigma() or conditioncov() or noeffectaxis() or noeffectcoor(); - } - - if (any) - { - if (p.settings.verbose) - { - std::cout << "restart criteria: " << p.stats.t << " ("; - std::cout << time_since_restart << std::boolalpha; - std::cout << ") flat_fitness: " << flat_fitness(); - std::cout << " exeeded_max_iter: " << exceeded_max_iter(); - std::cout << " no_improvement: " << no_improvement(); - std::cout << " tolx: " << tolx(); - std::cout << " tolupsigma: " << tolupsigma(); - std::cout << " conditioncov: " << conditioncov(); - std::cout << " noeffectaxis: " << noeffectaxis(); - std::cout << " noeffectcoor: " << noeffectcoor(); - std::cout << " min_sigma: " << min_sigma(); - std::cout << " stagnation: " << stagnation() << '\n'; - } - return true; - } - return false; - } - - void Strategy::evaluate(FunctionType &objective, parameters::Parameters &p) - { - if (criteria(p)) - { - restart(objective, p); - } - } - - void Restart::restart(FunctionType &objective, parameters::Parameters &p) - { - p.perform_restart(objective); - } - - void IPOP::restart(FunctionType &objective, parameters::Parameters &p) - { - const size_t max_lambda = static_cast(std::pow(p.settings.dim * p.lambda, 2)); - if (p.mu < max_lambda) - { - p.mu *= static_cast(ipop_factor); - p.lambda *= static_cast(ipop_factor); - } - p.perform_restart(objective); - } - - void BIPOP::restart(FunctionType &objective, parameters::Parameters &p) - { - static std::uniform_real_distribution<> dist; - - const auto last_used_budget = p.stats.evaluations - used_budget; - used_budget += last_used_budget; - const auto remaining_budget = budget - used_budget; - - if (!lambda_large) - { - lambda_large = lambda_init * 2; - budget_small = remaining_budget / 2; - budget_large = remaining_budget - budget_small; - } - else if (large()) - { - budget_large -= last_used_budget; - lambda_large *= 2; - } - else - { - budget_small -= last_used_budget; - } - - lambda_small = static_cast(std::floor( - static_cast(lambda_init) * std::pow(.5 / static_cast(lambda_large) / lambda_init, - std::pow(dist(rng::GENERATOR), 2)))); - - if (lambda_small % 2 != 0) - lambda_small++; - - p.lambda = std::max(size_t{2}, large() ? lambda_large : lambda_small); - p.mu = std::max(Float{1.0}, p.lambda * mu_factor); - p.perform_restart(objective, - large() ? p.settings.sigma0 : p.settings.sigma0 * std::pow(10, -2 * dist(rng::GENERATOR))); - } -} diff --git a/src/restart_criteria.cpp b/src/restart_criteria.cpp new file mode 100644 index 0000000..2ad8715 --- /dev/null +++ b/src/restart_criteria.cpp @@ -0,0 +1,214 @@ +#include +#include "restart_criteria.hpp" +#include "parameters.hpp" + +namespace +{ + //! max - min, for the last n elements of a vector + Float ptp_tail(const std::vector &v, const size_t n) + { + const auto na = std::min(v.size(), n); + if (na == 1) + { + return v[0]; + } + + const Float min = *std::min_element(v.end() - na, v.end()); + const Float max = *std::max_element(v.end() - na, v.end()); + return max - min; + } + + // TODO: this is duplicate code + Float median(const Vector &x) + { + if (x.size() % 2 == 0) + return (x(x.size() / 2) + x(x.size() / 2 - 1)) / 2.0; + return x(x.size() / 2); + } + + Float median(const std::vector &v, const size_t from, const size_t to) + { + const size_t n = to - from; + if (n % 2 == 0) + return (v[from + (n / 2)] + v[from + (n / 2) - 1]) / 2.0; + return v[from + (n / 2)]; + } +} + +namespace restart +{ + void Criterion::reset(const parameters::Parameters &p) + { + last_restart = p.stats.t; + met = false; + on_reset(p); + } + + void ExceededMaxIter::on_reset(const parameters::Parameters &p) + { + max_iter = static_cast(100 + 50 * std::pow((static_cast(p.settings.dim) + 3), 2.0) / std::sqrt(static_cast(p.lambda))); + } + + void ExceededMaxIter::update(const parameters::Parameters &p) + { + const auto time_since_restart = p.stats.t - last_restart; + met = max_iter < time_since_restart; + } + + void NoImprovement::on_reset(const parameters::Parameters &p) + { + n_bin = 10 + static_cast(std::ceil(30 * static_cast(p.settings.dim) / static_cast(p.lambda))); + } + + void NoImprovement::update(const parameters::Parameters &p) + { + const size_t time_since_restart = p.stats.t - last_restart; + best_fitnesses.push_back(p.pop.f(0)); + const auto recent_improvement = ptp_tail(best_fitnesses, n_bin); + met = time_since_restart > n_bin and recent_improvement == 0; + } + + void SigmaOutOfBounds::update(const parameters::Parameters &p) + { + met = constants::lb_sigma > p.mutation->sigma or p.mutation->sigma > constants::ub_sigma; + } + + void UnableToAdapt::update(const parameters::Parameters &p) + { + met = !p.successfull_adaptation or !std::isfinite(p.mutation->sigma); + } + + void FlatFitness::update(const parameters::Parameters &p) + { + const size_t time_since_restart = p.stats.t - last_restart; + flat_fitnesses(p.stats.t % p.settings.dim) = p.pop.f(0) == p.pop.f(flat_fitness_index); + const size_t n_flat_fitness = static_cast(flat_fitnesses.sum()); + met = time_since_restart > static_cast(flat_fitnesses.size()) and n_flat_fitness > max_flat_fitness; + } + + void FlatFitness::on_reset(const parameters::Parameters &p) + { + flat_fitnesses = Eigen::Array::Constant(p.settings.dim, 0); + max_flat_fitness = static_cast(std::ceil(static_cast(p.settings.dim) / 3)); + flat_fitness_index = static_cast(std::round(.1 + static_cast(p.lambda) / 4)); + } + + void TolX::update(const parameters::Parameters &p) + { + // TODO: This should be another sigma0, the one that has been used to restart + if (const auto dynamic = std::dynamic_pointer_cast(p.adaptation)) + { + const Float d_sigma = p.mutation->sigma / p.settings.sigma0; + const Float tolx_condition = 10e-12 * p.settings.sigma0; + tolx_vector.head(p.settings.dim) = dynamic->C.diagonal() * d_sigma; + tolx_vector.tail(p.settings.dim) = dynamic->pc * d_sigma; + met = (tolx_vector.array() < tolx_condition).all(); + } + } + + void TolX::on_reset(const parameters::Parameters &p) + { + tolx_vector = Vector::Ones(p.settings.dim * 2); + } + + void MaxDSigma::update(const parameters::Parameters &p) + { + const Float d_sigma = p.mutation->sigma / p.settings.sigma0; + Float root_max_d = 1.0; + if (const auto dynamic = std::dynamic_pointer_cast(p.adaptation)) + { + root_max_d = std::sqrt(dynamic->d.maxCoeff()); + } + met = d_sigma > constants::max_dsigma * root_max_d; + } + + void MinDSigma::update(const parameters::Parameters &p) + { + const Float d_sigma = p.mutation->sigma / p.settings.sigma0; + + Float root_min_d = 1.0; + if (const auto dynamic = std::dynamic_pointer_cast(p.adaptation)) + { + root_min_d = std::sqrt(dynamic->d.minCoeff()); + } + met = d_sigma < constants::min_dsigma * root_min_d; + } + + void ConditionC::update(const parameters::Parameters &p) + { + if (const auto dynamic = std::dynamic_pointer_cast(p.adaptation)) + { + const Float condition_c = pow(dynamic->d.maxCoeff(), 2.0) / pow(dynamic->d.minCoeff(), 2); + met = condition_c > constants::tol_condition_cov; + } + } + + void NoEffectAxis::update(const parameters::Parameters &p) + { + if (const auto dynamic = std::dynamic_pointer_cast(p.adaptation)) + { + const Eigen::Index t = p.stats.t % p.settings.dim; + const auto effect_axis = 0.1 * p.mutation->sigma * std::sqrt(dynamic->d(t)) * dynamic->B.col(t); + met = (effect_axis.array() == 0).all(); + } + } + + void NoEffectCoord::update(const parameters::Parameters &p) + { + if (const auto dynamic = std::dynamic_pointer_cast(p.adaptation)) + { + const auto effect_coord = 0.2 * p.mutation->sigma * dynamic->C.diagonal().cwiseSqrt(); + met = (effect_coord.array() == 0).all(); + } + } + + void Stagnation::update(const parameters::Parameters &p) + { + const size_t time_since_restart = p.stats.t - last_restart; + const size_t pt = static_cast(constants::stagnation_quantile * time_since_restart); + median_fitnesses.push_back(median(p.pop.f)); + best_fitnesses.push_back(p.pop.f(0)); + + const bool best_better = median(best_fitnesses, pt, time_since_restart) >= median(best_fitnesses, 0, pt); + const bool median_better = median(median_fitnesses, pt, time_since_restart) >= median(median_fitnesses, 0, pt); + + met = time_since_restart > n_stagnation and (best_better and median_better); + } + + void Stagnation::on_reset(const parameters::Parameters &p) + { + const auto d = static_cast(p.settings.dim); + const auto lambda = static_cast(p.lambda); + n_stagnation = (static_cast(std::min(static_cast(120 + (30 * d / lambda)), 20000))); + median_fitnesses = {}; + best_fitnesses = {}; + } + + Criteria Criteria::get(const parameters::Modules modules) + { + vCriteria criteria { + std::make_shared() + }; + + if (modules.restart_strategy >= parameters::RestartStrategyType::RESTART) + { + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + } + + if (modules.matrix_adaptation == parameters::MatrixAdaptationType::COVARIANCE || + modules.matrix_adaptation == parameters::MatrixAdaptationType::SEPERABLE) + { + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + criteria.push_back(std::make_shared()); + } + return Criteria(criteria); + } +} \ No newline at end of file diff --git a/src/restart_strategy.cpp b/src/restart_strategy.cpp new file mode 100644 index 0000000..9f6c1b8 --- /dev/null +++ b/src/restart_strategy.cpp @@ -0,0 +1,61 @@ +#include "restart_strategy.hpp" +#include "parameters.hpp" +#include "matrix_adaptation.hpp" + + + +namespace restart +{ + + Float Strategy::update(parameters::Parameters &p) + { + return p.settings.sigma0; + } + + Float IPOP::update(parameters::Parameters &p) + { + const size_t max_lambda = static_cast(std::pow(p.settings.dim * p.lambda, 2)); + if (p.mu < max_lambda) + { + p.mu *= static_cast(ipop_factor); + p.lambda *= static_cast(ipop_factor); + } + return p.settings.sigma0; + } + + Float BIPOP::update(parameters::Parameters &p) + { + static std::uniform_real_distribution<> dist; + + const auto last_used_budget = p.stats.evaluations - used_budget; + used_budget += last_used_budget; + const auto remaining_budget = budget - used_budget; + + if (!lambda_large) + { + lambda_large = lambda_init * 2; + budget_small = remaining_budget / 2; + budget_large = remaining_budget - budget_small; + } + else if (large()) + { + budget_large -= last_used_budget; + lambda_large *= 2; + } + else + { + budget_small -= last_used_budget; + } + + lambda_small = static_cast(std::floor( + static_cast(lambda_init) * std::pow(.5 / static_cast(lambda_large) / lambda_init, + std::pow(dist(rng::GENERATOR), 2)))); + + if (lambda_small % 2 != 0) + lambda_small++; + + p.lambda = std::max(size_t{2}, large() ? lambda_large : lambda_small); + p.mu = std::max(Float{1.0}, p.lambda * mu_factor); + return large() ? p.settings.sigma0 : p.settings.sigma0 * std::pow(10, -2 * dist(rng::GENERATOR)); + } +} diff --git a/tests/test_c_adaptation.py b/tests/test_c_adaptation.py index 400818c..b39cac4 100644 --- a/tests/test_c_adaptation.py +++ b/tests/test_c_adaptation.py @@ -29,7 +29,7 @@ def test_matrix_adaptation(self): new_M = ((0.5 * cma.p.weights.cmu * cma.p.weights.positive) * cma.p.pop.Y[:, :cma.p.mu]).dot(cma.p.pop.Z[:, :cma.p.mu].T) M = old_M + scaled_ps + new_M - cma.adapt(sum) + cma.adapt() self.assertTrue(np.all(np.isclose(cma.p.adaptation.M, M))) From bf78166170abf34db945b51fe55716ae5c4627ae Mon Sep 17 00:00:00 2001 From: jacobdenobel Date: Thu, 17 Apr 2025 01:31:13 +0200 Subject: [PATCH 2/2] readme --- README.md | 48 ++++++++++++++++++++++++++---------------------- setup.py | 4 ++-- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 01bccee..fb2b482 100644 --- a/README.md +++ b/README.md @@ -8,27 +8,31 @@ This README provides a high level overview of the implemented modules, and provi ## Table of Contents -1. [Installation](#installation) -2. [Usage](#usage) - - [Python only interface](#python-only) - - [Ask-Tell Interface](#ask-tell) - - [C++ Backend](#c-backend) -3. [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) - - [Base Sampler](#base-sampler) - - [Recombination Weights](#recombination-weights) - - [Mirrored Sampling](#mirrored-sampling) - - [Step size adaptation](#step-size-adaptation) - - [Restart Strategy](#restart-strategy) - - [Bound Correction](#bound-correction) -4. [Citation](#citation) -5. [License](#license) +- [ModularCMAES ](#modularcmaes---) + - [Table of Contents](#table-of-contents) + - [Installation ](#installation-) + - [Python Installation](#python-installation) + - [Installation from source](#installation-from-source) + - [Usage ](#usage-) + - [Python-only ](#python-only-) + - [Ask-Tell Interface ](#ask-tell-interface-) + - [C++ Backend ](#c-backend-) + - [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) + - [Citation ](#citation-) + - [License ](#license-) ## Installation @@ -184,7 +188,7 @@ while not cma.break_conditions(): cma.mutate(func) cma.select() cma.recombine() - cma.adapt(func) + cma.adapt() ``` ## Modules diff --git a/setup.py b/setup.py index 0633d5a..c87dfce 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.0.9" +__version__ = "1.0.11" ext = Pybind11Extension( "modcma.c_maes.cmaescpp", @@ -22,7 +22,7 @@ if platform.system() in ("Linux", "Darwin"): os.environ["CC"] = "g++" os.environ["CXX"] = "g++" - flags = ["-O0", "-fno-math-errno", ] #"-fopenmp" + flags = ["-O3", "-fno-math-errno", ] #"-fopenmp" if platform.system() == "Darwin": flags.append("-mmacosx-version-min=10.15") else: