From 3e325d475c08c12cbf793634acc0fd715d5ee514 Mon Sep 17 00:00:00 2001 From: jacobdenobel Date: Thu, 6 Nov 2025 15:26:30 +0100 Subject: [PATCH] center placement --- include/bounds.hpp | 37 ++++++++++++------------------------ include/center_placement.hpp | 7 +++++++ include/common.hpp | 12 ++++++++++++ include/es.hpp | 4 ++-- include/modules.hpp | 1 + include/mutation.hpp | 4 ++++ include/settings.hpp | 25 ++++++++++++++++++++---- src/bounds.cpp | 14 +++++++------- src/center_placement.cpp | 12 +++++++----- src/common.cpp | 26 +++++++++++++++---------- src/interface.cpp | 28 ++++++++++++++++----------- src/mutation.cpp | 14 +++++++++++--- src/parameters.cpp | 2 +- tests/test_c_bounds.py | 20 ++++++++++++++----- 14 files changed, 133 insertions(+), 73 deletions(-) diff --git a/include/bounds.hpp b/include/bounds.hpp index 7495f6b..3edce9a 100644 --- a/include/bounds.hpp +++ b/include/bounds.hpp @@ -21,23 +21,10 @@ namespace bounds struct BoundCorrection { - virtual ~BoundCorrection() = default; - Vector db; - Float diameter; size_t n_out_of_bounds = 0; - bool has_bounds; - BoundCorrection(const Vector &lb, const Vector &ub) : db(ub - lb), - diameter((ub - lb).norm()), - has_bounds(true) - { - //! find a better way - if (!std::isfinite(diameter)) - { - diameter = 10; - has_bounds = false; - } - } + virtual ~BoundCorrection() = default; + BoundCorrection() = default; void correct(const Eigen::Index i, parameters::Parameters &p); @@ -81,7 +68,7 @@ namespace bounds { sampling::Gaussian sampler; - COTN(Eigen::Ref lb, Eigen::Ref ub) : BoundCorrection(lb, ub), sampler(static_cast(lb.size()), rng::normal(0, 1.0 / 3.)) {} + COTN(const size_t d) : BoundCorrection(), sampler(d, rng::normal(0, 1.0 / 3.)) {} Vector correct_x(const Vector &xi, const Mask &oob, const Float sigma, const parameters::Settings &settings) override; }; @@ -97,7 +84,7 @@ namespace bounds { sampling::Uniform sampler; - UniformResample(Eigen::Ref lb, Eigen::Ref ub) : BoundCorrection(lb, ub), sampler(static_cast(lb.size())) {} + UniformResample(const size_t d) : BoundCorrection(), sampler(d) {} Vector correct_x(const Vector &xi, const Mask &oob, const Float sigma, const parameters::Settings &settings) override; }; @@ -116,26 +103,26 @@ namespace bounds Vector correct_x(const Vector &xi, const Mask &oob, const Float sigma, const parameters::Settings &settings) override; }; - inline std::shared_ptr get(const parameters::CorrectionMethod &m, const Vector &lb, const Vector &ub) + inline std::shared_ptr get(const parameters::CorrectionMethod &m, const size_t d) { using namespace parameters; switch (m) { case CorrectionMethod::MIRROR: - return std::make_shared(lb, ub); + return std::make_shared(); case CorrectionMethod::COTN: - return std::make_shared(lb, ub); + return std::make_shared(d); case CorrectionMethod::UNIFORM_RESAMPLE: - return std::make_shared(lb, ub); + return std::make_shared(d); case CorrectionMethod::SATURATE: - return std::make_shared(lb, ub); + return std::make_shared(); case CorrectionMethod::TOROIDAL: - return std::make_shared(lb, ub); + return std::make_shared(); case CorrectionMethod::RESAMPLE: - return std::make_shared(lb, ub); + return std::make_shared(); default: case CorrectionMethod::NONE: - return std::make_shared(lb, ub); + return std::make_shared(); } }; } diff --git a/include/center_placement.hpp b/include/center_placement.hpp index 7d4db9f..413113d 100644 --- a/include/center_placement.hpp +++ b/include/center_placement.hpp @@ -30,6 +30,11 @@ namespace center void operator()(parameters::Parameters &p) override; }; + struct Center : Placement + { + void operator()(parameters::Parameters &p) override; + }; + inline std::shared_ptr get(const parameters::CenterPlacement &p) { @@ -41,6 +46,8 @@ namespace center return std::make_shared(); case CenterPlacement::ZERO: return std::make_shared(); + case CenterPlacement::CENTER: + return std::make_shared
(); default: case CenterPlacement::X0: return std::make_shared(); diff --git a/include/common.hpp b/include/common.hpp index 905a350..0cfff21 100644 --- a/include/common.hpp +++ b/include/common.hpp @@ -128,6 +128,18 @@ inline std::ostream &operator<<(std::ostream &os, const Solution &s) namespace utils { + /** + * @brief Sort an array of indexes idx inplace based + * based on comparing values in v using std::stable_sort + * + * @param v Vector + * @param idx std::vector + * @return void + */ + void sort_index_inplace(const Vector &v, std::vector& idx); + + void sort_index_inplace(const std::vector &v, std::vector& idx); + /** * @brief Return an array of indexes of the sorted array * sort indexes based on comparing values in v using std::stable_sort instead diff --git a/include/es.hpp b/include/es.hpp index ecbd611..617c9c4 100644 --- a/include/es.hpp +++ b/include/es.hpp @@ -21,7 +21,7 @@ namespace es x(x0), f(f0), t(1), budget(budget), target(target), rejection_sampling(modules.bound_correction == parameters::CorrectionMethod::RESAMPLE), sampler(sampling::get(d, modules, 1)), - corrector(bounds::get(modules.bound_correction, Vector::Ones(d) * -5.0, Vector::Ones(d) * 5.0)), + corrector(bounds::get(modules.bound_correction, d)), settings{d} { settings.modules = modules; @@ -74,7 +74,7 @@ namespace es sampler(sampling::get(d, modules, lambda)), sigma_sampler(std::make_shared(d)), rejection_sampling(modules.bound_correction == parameters::CorrectionMethod::RESAMPLE), - corrector(bounds::get(modules.bound_correction, Vector::Ones(d) * -5.0, Vector::Ones(d) * 5.0)), + corrector(bounds::get(modules.bound_correction, d)), settings(d) { // tau = 1.0 / sampler->expected_length(); diff --git a/include/modules.hpp b/include/modules.hpp index e64b4a1..f2685a2 100644 --- a/include/modules.hpp +++ b/include/modules.hpp @@ -85,6 +85,7 @@ namespace parameters X0, ZERO, UNIFORM, + CENTER }; struct Modules diff --git a/include/mutation.hpp b/include/mutation.hpp index ec2d80a..94ee08f 100644 --- a/include/mutation.hpp +++ b/include/mutation.hpp @@ -140,6 +140,10 @@ namespace mutation Vector combined; + std::vector idx; + std::vector oidx; + + using Strategy::Strategy; void adapt(const parameters::Weights& w, std::shared_ptr adaptation, Population& pop, diff --git a/include/settings.hpp b/include/settings.hpp index 810f8ed..ed36cf9 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -19,16 +19,23 @@ namespace parameters size_t mu0; std::optional x0; + Vector lb; Vector ub; + Vector db; + Vector center; + Float diameter; + Float volume; + bool has_bounds; + std::optional cs; std::optional cc; std::optional cmu; std::optional c1; std::optional damps; std::optional acov; + bool verbose; - Float volume; bool one_plus_one; Settings(size_t dim, @@ -60,7 +67,12 @@ namespace parameters mu0(mu.value_or(lambda0 / 2)), x0(x0), lb(lb.value_or(Vector::Ones(dim) * -5)), - ub(ub.value_or(Vector::Ones(dim)* 5)), + ub(ub.value_or(Vector::Ones(dim) * 5)), + db(this->ub - this->lb), + center(this->lb + (db * 0.5)), + diameter(db.norm()), + volume(db.prod()), + has_bounds(true), cs(cs), cc(cc), cmu(cmu), @@ -68,7 +80,6 @@ namespace parameters damps(damps), acov(acov), verbose(verbose), - volume(0.0), one_plus_one(false) { if (modules.mirrored == Mirror::PAIRWISE and lambda0 % 2 != 0) @@ -113,7 +124,13 @@ namespace parameters if (modules.restart_strategy == RestartStrategyType::BIPOP || modules.restart_strategy == RestartStrategyType::IPOP) modules.restart_strategy = RestartStrategyType::RESTART; } - volume = (this->ub.cwiseMin(10 * sigma0) - this->lb.cwiseMax(-10 * sigma0)).prod(); + + if (!std::isfinite(diameter)){ + has_bounds = false; + diameter = 10; + volume = pow(diameter, dim); + center.setConstant(0); + } } }; diff --git a/src/bounds.cpp b/src/bounds.cpp index 31dae20..8bcf2e5 100644 --- a/src/bounds.cpp +++ b/src/bounds.cpp @@ -27,13 +27,13 @@ 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(db), xi); + return (oob).select((xi - settings.lb).cwiseQuotient(settings.db), xi); ; } void BoundCorrection::correct(const Eigen::Index i, parameters::Parameters &p) { - if (!has_bounds) + if (!p.settings.has_bounds) return; const auto oob = is_out_of_bounds(p.pop.X.col(i), p.settings); @@ -53,33 +53,33 @@ namespace bounds { const Vector y = delta_out_of_bounds(xi, oob, settings); return (oob).select( - settings.lb.array() + db.array() * ((y.array() > 0).cast() - (sigma * sampler().array().abs())).abs(), y); + settings.lb.array() + settings.db.array() * ((y.array() > 0).cast() - (sigma * sampler().array().abs())).abs(), y); } Vector Mirror::correct_x(const Vector &xi, const Mask &oob, const Float sigma, const parameters::Settings &settings) { const Vector y = delta_out_of_bounds(xi, oob, settings); return (oob).select( - settings.lb.array() + db.array() * (y.array() - y.array().floor() - y.array().floor().unaryExpr(&modulo2)).abs(), + settings.lb.array() + settings.db.array() * (y.array() - y.array().floor() - y.array().floor().unaryExpr(&modulo2)).abs(), y); } Vector UniformResample::correct_x(const Vector &xi, const Mask &oob, const Float sigma, const parameters::Settings &settings) { - return (oob).select(settings.lb + sampler().cwiseProduct(db), xi); + return (oob).select(settings.lb + sampler().cwiseProduct(settings.db), xi); } Vector Saturate::correct_x(const Vector &xi, const Mask &oob, const Float sigma, const parameters::Settings &settings) { const Vector y = delta_out_of_bounds(xi, oob, settings); return (oob).select( - settings.lb.array() + db.array() * (y.array() > 0).cast(), y); + settings.lb.array() + settings.db.array() * (y.array() > 0).cast(), y); } Vector Toroidal::correct_x(const Vector &xi, const Mask &oob, const Float sigma, const parameters::Settings &settings) { const Vector y = delta_out_of_bounds(xi, oob, settings); return (oob).select( - settings.lb.array() + db.array() * (y.array() - y.array().floor()).abs(), y); + settings.lb.array() + settings.db.array() * (y.array() - y.array().floor()).abs(), y); } } diff --git a/src/center_placement.cpp b/src/center_placement.cpp index 59387e1..e735aed 100644 --- a/src/center_placement.cpp +++ b/src/center_placement.cpp @@ -5,19 +5,21 @@ namespace center { void X0::operator()(parameters::Parameters &p) { - const auto default_x0 = (p.settings.lb + p.settings.ub) / 2.0; - - p.adaptation->m = p.settings.x0.value_or(default_x0); + p.adaptation->m = p.settings.x0.value_or(p.settings.center); } void Uniform::operator()(parameters::Parameters &p) { - // Only works for square spaces - p.adaptation->m = Vector::Random(p.settings.dim) * p.settings.ub; + p.adaptation->m = p.settings.lb + (Vector::Random(p.settings.dim) * p.settings.db); } void Zero::operator()(parameters::Parameters &p) { p.adaptation->m.setZero(); } + + void Center::operator()(parameters::Parameters& p) + { + p.adaptation->m = p.settings.center; + } } diff --git a/src/common.cpp b/src/common.cpp index 0d12936..0417e2f 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -20,31 +20,37 @@ namespace constants namespace utils { - std::vector sort_indexes(const Vector& v) + void sort_index_inplace(const Vector& v, std::vector& idx) { - std::vector idx(v.size()); - std::iota(idx.begin(), idx.end(), 0); - std::stable_sort(idx.begin(), idx.end(), [&v](size_t i1, size_t i2) { return v[i1] < v[i2]; }); - - return idx; } - std::vector sort_indexes(const std::vector& v) + void sort_index_inplace(const std::vector& v, std::vector& idx) { - std::vector idx(v.size()); - std::iota(idx.begin(), idx.end(), 0); - std::stable_sort(idx.begin(), idx.end(), [&v](size_t i1, size_t i2) { return v[i1] < v[i2]; }); + } + + std::vector sort_indexes(const Vector& v) + { + std::vector idx(v.size()); + std::iota(idx.begin(), idx.end(), 0); + sort_index_inplace(v, idx); + return idx; + } + std::vector sort_indexes(const std::vector& v) + { + std::vector idx(v.size()); + std::iota(idx.begin(), idx.end(), 0); + sort_index_inplace(v, idx); return idx; } diff --git a/src/interface.cpp b/src/interface.cpp index 0daaacf..5fbe178 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -100,6 +100,7 @@ void define_options(py::module& main) .value("X0", CenterPlacement::X0) .value("ZERO", CenterPlacement::ZERO) .value("UNIFORM", CenterPlacement::UNIFORM) + .value("CENTER", CenterPlacement::CENTER) .export_values(); } @@ -286,6 +287,9 @@ void define_center_placement(py::module& main) py::class_>(m, "Zero") .def(py::init<>()); + + py::class_>(m, "Center") + .def(py::init<>()); } void define_repelling(py::module& main) @@ -644,6 +648,11 @@ void define_parameters(py::module& main) .def_readwrite("x0", &Settings::x0) .def_readwrite("lb", &Settings::lb) .def_readwrite("ub", &Settings::ub) + .def_readwrite("db", &Settings::db) + .def_readwrite("center", &Settings::center) + .def_readwrite("diameter", &Settings::diameter) + .def_readonly("volume", &Settings::volume) + .def_readwrite("has_bounds", &Settings::has_bounds) .def_readwrite("cs", &Settings::cs) .def_readwrite("cc", &Settings::cc) .def_readwrite("cmu", &Settings::cmu) @@ -651,7 +660,6 @@ void define_parameters(py::module& main) .def_readwrite("damps", &Settings::damps) .def_readwrite("acov", &Settings::acov) .def_readwrite("verbose", &Settings::verbose) - .def_readonly("volume", &Settings::volume) .def_readonly("one_plus_one", &Settings::one_plus_one) .def("__repr__", [] (Settings& settings) { @@ -749,9 +757,7 @@ void define_bounds(py::module& main) using namespace bounds; py::class_>(m, "BoundCorrection") - .def_readwrite("db", &BoundCorrection::db) - .def_readwrite("diameter", &BoundCorrection::diameter) - .def_readwrite("has_bounds", &BoundCorrection::has_bounds) + .def_readonly("n_out_of_bounds", &BoundCorrection::n_out_of_bounds) .def("correct", &BoundCorrection::correct, py::arg("index"), py::arg("parameters")) @@ -761,26 +767,26 @@ void define_bounds(py::module& main) ; py::class_>(m, "Resample") - .def(py::init(), py::arg("lb"), py::arg("ub")); + .def(py::init<>()); py::class_>(m, "NoCorrection") - .def(py::init(), py::arg("lb"), py::arg("ub")); + .def(py::init<>()); py::class_>(m, "COTN") - .def(py::init(), py::arg("lb"), py::arg("ub")) + .def(py::init(), py::arg("dim")) .def_readonly("sampler", &COTN::sampler); py::class_>(m, "Mirror") - .def(py::init(), py::arg("lb"), py::arg("ub")); + .def(py::init<>()); py::class_>(m, "UniformResample") - .def(py::init(), py::arg("lb"), py::arg("ub")); + .def(py::init(), py::arg("dim")); py::class_>(m, "Saturate") - .def(py::init(), py::arg("lb"), py::arg("ub")); + .def(py::init<>()); py::class_>(m, "Toroidal") - .def(py::init(), py::arg("lb"), py::arg("ub")); + .def(py::init<>()); } void define_mutation(py::module& main) diff --git a/src/mutation.cpp b/src/mutation.cpp index c1e949e..c5fcd45 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -34,7 +34,7 @@ namespace mutation p.pop.t(i) = p.stats.t; const auto &zi = (*p.sampler)(); const auto &zi_scaled = p.mutation->tc->scale( - zi, p.bounds->diameter, p.settings.budget, p.stats.evaluations); + 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; @@ -130,8 +130,16 @@ namespace mutation combined.conservativeResize(n + n); combined.head(n) = pop.f.head(n); combined.tail(n) = old_pop.f.head(n); - const auto idx = utils::sort_indexes(combined); - const auto oidx = utils::sort_indexes(idx); + + 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); Float delta_r = 0.0; for (size_t i = 0; i < n; i++) diff --git a/src/parameters.cpp b/src/parameters.cpp index 7f66eb7..92f8bc3 100644 --- a/src/parameters.cpp +++ b/src/parameters.cpp @@ -28,7 +28,7 @@ namespace parameters static_cast(settings.lambda0), static_cast(settings.mu0), settings.budget)), - bounds(bounds::get(settings.modules.bound_correction, settings.lb, settings.ub)), + bounds(bounds::get(settings.modules.bound_correction, settings.dim)), repelling(repelling::get(settings.modules)), center_placement(center::get(settings.modules.center_placement)) { diff --git a/tests/test_c_bounds.py b/tests/test_c_bounds.py index 1af8b1b..bc224b4 100644 --- a/tests/test_c_bounds.py +++ b/tests/test_c_bounds.py @@ -11,18 +11,18 @@ class TestBounds(unittest.TestCase): __bound_fixers = ( bounds.COTN, + bounds.UniformResample, bounds.Mirror, bounds.Saturate, bounds.Toroidal, - bounds.UniformResample, ) __bound_fixers_options = ( options.CorrectionMethod.COTN, + options.CorrectionMethod.UNIFORM_RESAMPLE, options.CorrectionMethod.MIRROR, options.CorrectionMethod.SATURATE, options.CorrectionMethod.TOROIDAL, - options.CorrectionMethod.UNIFORM_RESAMPLE, ) def setUp(self): self.lb, self.ub = np.zeros(2), np.ones(2) * 2 @@ -36,9 +36,13 @@ def setUp(self): self.par.pop.X = self.par.adaptation.m + (self.par.pop.s * self.par.pop.Y) def test_bound_fixers(self): - for boundcntrl, option in zip(self.__bound_fixers, self.__bound_fixers_options): + for i, (boundcntrl, option) in enumerate(zip(self.__bound_fixers, self.__bound_fixers_options)): + if i < 2: + method = boundcntrl(self.lb.size) + else: + method = boundcntrl() + self.par.settings.modules.bound_correction = option - method = boundcntrl(self.lb, self.ub) method.correct(1, self.par) self.assertEqual(method.n_out_of_bounds, 0) method.correct(0, self.par) @@ -50,12 +54,18 @@ def test_bound_fixers(self): self.setUp() def test_do_nothing(self): - method = bounds.NoCorrection(self.lb, self.ub) + method = bounds.NoCorrection() method.correct(0, self.par) self.assertEqual(method.n_out_of_bounds, 1) self.assertFalse(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))) + + def test_center(self): + self.assertTrue(np.all(self.par.settings.center == 1)) + self.assertTrue(np.all(self.par.settings.lb == 0)) + self.assertTrue(np.all(self.par.settings.ub == 2)) + self.assertTrue(np.all(self.par.settings.db == 2)) if __name__ == "__main__":