From a87a53747ae47fdf8b603eef74a438cf5c89c6ca Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Mon, 10 Apr 2017 01:00:13 +0200 Subject: [PATCH 01/20] Initial commit for auglag support. --- include/pagmo/algorithms/nlopt.hpp | 219 ++++++++++++++++++----------- pygmo/expose_algorithms.cpp | 9 ++ 2 files changed, 143 insertions(+), 85 deletions(-) diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index dc81ae351..100134066 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -58,6 +58,7 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include #include @@ -116,6 +117,8 @@ inline typename nlopt_data<>::names_map_t nlopt_names_map() retval.insert(value_type("tnewton", NLOPT_LD_TNEWTON)); retval.insert(value_type("var2", NLOPT_LD_VAR2)); retval.insert(value_type("var1", NLOPT_LD_VAR1)); + retval.insert(value_type("auglag", NLOPT_AUGLAG)); + retval.insert(value_type("auglag_eq", NLOPT_AUGLAG_EQ)); return retval; } @@ -178,7 +181,7 @@ struct nlopt_obj { using data = nlopt_data<>; explicit nlopt_obj(::nlopt_algorithm algo, problem &prob, double stopval, double ftol_rel, double ftol_abs, double xtol_rel, double xtol_abs, int maxeval, int maxtime, unsigned verbosity) - : m_prob(prob), m_value(nullptr, ::nlopt_destroy), m_verbosity(verbosity) + : m_algo(algo), m_prob(prob), m_value(nullptr, ::nlopt_destroy), m_verbosity(verbosity) { // If needed, init the sparsity pattern. if (prob.has_gradient_sparsity()) { @@ -224,8 +227,76 @@ struct nlopt_obj { // It will hold the current decision vector. m_dv.resize(prob.get_nx()); - // Set the objfun + gradient. - res = ::nlopt_set_min_objective( + // Handle the various stopping criteria. + res = ::nlopt_set_stopval(m_value.get(), stopval); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the 'stopval' stopping criterion to " + + std::to_string(stopval) + " for the NLopt algorithm '" + + data::names.right.at(algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + res = ::nlopt_set_ftol_rel(m_value.get(), ftol_rel); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the 'ftol_rel' stopping criterion to " + + std::to_string(ftol_rel) + " for the NLopt algorithm '" + + data::names.right.at(algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + res = ::nlopt_set_ftol_abs(m_value.get(), ftol_abs); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the 'ftol_abs' stopping criterion to " + + std::to_string(ftol_abs) + " for the NLopt algorithm '" + + data::names.right.at(algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + res = ::nlopt_set_xtol_rel(m_value.get(), xtol_rel); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the 'xtol_rel' stopping criterion to " + + std::to_string(xtol_rel) + " for the NLopt algorithm '" + + data::names.right.at(algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + res = ::nlopt_set_xtol_abs1(m_value.get(), xtol_abs); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the 'xtol_abs' stopping criterion to " + + std::to_string(xtol_abs) + " for the NLopt algorithm '" + + data::names.right.at(algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + res = ::nlopt_set_maxeval(m_value.get(), maxeval); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the 'maxeval' stopping criterion to " + + std::to_string(maxeval) + " for the NLopt algorithm '" + + data::names.right.at(algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + res = ::nlopt_set_maxtime(m_value.get(), maxtime); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the 'maxtime' stopping criterion to " + + std::to_string(maxtime) + " for the NLopt algorithm '" + + data::names.right.at(algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + } + + // Set the objfun + gradient. + void set_objfun() + { + auto res = ::nlopt_set_min_objective( m_value.get(), [](unsigned dim, const double *x, double *grad, void *f_data) -> double { // Get *this back from the function data. @@ -343,20 +414,19 @@ struct nlopt_obj { if (res != NLOPT_SUCCESS) { // LCOV_EXCL_START pagmo_throw(std::invalid_argument, "could not set the objective function for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " + + data::names.right.at(m_algo) + "', the error is: " + nlopt_res2string(res)); // LCOV_EXCL_STOP } + } - // Vector-valued constraints. - const auto nic = boost::numeric_cast(prob.get_nic()); - const auto nec = boost::numeric_cast(prob.get_nec()); - const auto c_tol = prob.get_c_tol(); - - // Inequality. - if (nic) { - res = ::nlopt_add_inequality_mconstraint( - m_value.get(), nic, + // Inequality constraints. + void set_ineq_constraints() + { + if (m_prob.get_nic()) { + const auto c_tol = m_prob.get_c_tol(); + auto res = ::nlopt_add_inequality_mconstraint( + m_value.get(), boost::numeric_cast(m_prob.get_nic()), [](unsigned m, double *result, unsigned dim, const double *x, double *grad, void *f_data) { // Get *this back from the function data. auto &nlo = *static_cast(f_data); @@ -444,19 +514,23 @@ struct nlopt_obj { ::nlopt_force_stop(nlo.m_value.get()); } }, - static_cast(this), c_tol.data() + nec); + static_cast(this), c_tol.data() + m_prob.get_nec()); if (res != NLOPT_SUCCESS) { pagmo_throw(std::invalid_argument, "could not set the inequality constraints for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " + nlopt_res2string(res) + + data::names.right.at(m_algo) + "', the error is: " + nlopt_res2string(res) + "\nThis usually means that the algorithm does not support inequality constraints"); } } + } - // Equality. - if (nec) { - res = ::nlopt_add_equality_mconstraint( - m_value.get(), nec, + // Equality constraints. + void set_eq_constraints() + { + if (m_prob.get_nec()) { + const auto c_tol = m_prob.get_c_tol(); + auto res = ::nlopt_add_equality_mconstraint( + m_value.get(), boost::numeric_cast(m_prob.get_nec()), [](unsigned m, double *result, unsigned dim, const double *x, double *grad, void *f_data) { // Get *this back from the function data. auto &nlo = *static_cast(f_data); @@ -551,75 +625,10 @@ struct nlopt_obj { if (res != NLOPT_SUCCESS) { pagmo_throw(std::invalid_argument, "could not set the equality constraints for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " + nlopt_res2string(res) + + data::names.right.at(m_algo) + "', the error is: " + nlopt_res2string(res) + "\nThis usually means that the algorithm does not support equality constraints"); } } - - // Handle the various stopping criteria. - res = ::nlopt_set_stopval(m_value.get(), stopval); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the 'stopval' stopping criterion to " - + std::to_string(stopval) + " for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - res = ::nlopt_set_ftol_rel(m_value.get(), ftol_rel); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the 'ftol_rel' stopping criterion to " - + std::to_string(ftol_rel) + " for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - res = ::nlopt_set_ftol_abs(m_value.get(), ftol_abs); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the 'ftol_abs' stopping criterion to " - + std::to_string(ftol_abs) + " for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - res = ::nlopt_set_xtol_rel(m_value.get(), xtol_rel); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the 'xtol_rel' stopping criterion to " - + std::to_string(xtol_rel) + " for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - res = ::nlopt_set_xtol_abs1(m_value.get(), xtol_abs); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the 'xtol_abs' stopping criterion to " - + std::to_string(xtol_abs) + " for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - res = ::nlopt_set_maxeval(m_value.get(), maxeval); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the 'maxeval' stopping criterion to " - + std::to_string(maxeval) + " for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - res = ::nlopt_set_maxtime(m_value.get(), maxtime); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the 'maxtime' stopping criterion to " - + std::to_string(maxtime) + " for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } } // Delete all other ctors/assignment ops. @@ -629,6 +638,7 @@ struct nlopt_obj { nlopt_obj &operator=(nlopt_obj &&) = delete; // Data members. + ::nlopt_algorithm m_algo; problem &m_prob; sparsity_pattern m_sp; std::unique_ptr::type, void (*)(::nlopt_opt)> m_value; @@ -804,6 +814,16 @@ class nlopt + "'. The supported algorithms are:\n" + oss.str()); } } + nlopt(const nlopt &other) + : m_algo(other.m_algo), m_select(other.m_select), m_replace(other.m_replace), + m_rselect_seed(other.m_rselect_seed), m_e(other.m_e), m_last_opt_result(other.m_last_opt_result), + m_sc_stopval(other.m_sc_stopval), m_sc_ftol_rel(other.m_sc_ftol_rel), m_sc_ftol_abs(other.m_sc_ftol_abs), + m_sc_xtol_rel(other.m_sc_xtol_rel), m_sc_xtol_abs(other.m_sc_xtol_abs), m_sc_maxeval(other.m_sc_maxeval), + m_sc_maxtime(other.m_sc_maxtime), m_verbosity(other.m_verbosity), m_log(other.m_log), + m_loc_opt(other.m_loc_opt ? detail::make_unique(*other.m_loc_opt) : nullptr) + { + } + nlopt(nlopt &&) = default; /// Set the seed for the ``"random"`` selection/replacement policies. /** * @param seed the value that will be used to seed the random number generator used by the ``"random"`` @@ -955,6 +975,17 @@ class nlopt // NOTE: this will check also the problem's properties. nlopt_obj no(nlopt_data::names.left.at(m_algo), prob, m_sc_stopval, m_sc_ftol_rel, m_sc_ftol_abs, m_sc_xtol_rel, m_sc_xtol_abs, m_sc_maxeval, m_sc_maxtime, m_verbosity); + no.set_objfun(); + no.set_eq_constraints(); + no.set_ineq_constraints(); + + // Set the local optimiser, if appropriate. + if (m_loc_opt) { + nlopt_obj no_loc(nlopt_data::names.left.at(m_loc_opt->m_algo), prob, m_loc_opt->m_sc_stopval, + m_loc_opt->m_sc_ftol_rel, m_loc_opt->m_sc_ftol_abs, m_loc_opt->m_sc_xtol_rel, + m_loc_opt->m_sc_xtol_abs, m_loc_opt->m_sc_maxeval, m_loc_opt->m_sc_maxtime, 0); + ::nlopt_set_local_optimizer(no.m_value.get(), no_loc.m_value.get()); + } // Setup of the initial guess. vector_double initial_guess; @@ -1302,6 +1333,22 @@ class nlopt { m_sc_maxtime = n; } + void unset_local_optimizer() + { + m_loc_opt.reset(nullptr); + } + void set_local_optimizer(const nlopt &n) + { + m_loc_opt = detail::make_unique(n); + } + const nlopt *get_local_optimizer() const + { + return m_loc_opt.get(); + } + nlopt *get_local_optimizer() + { + return m_loc_opt.get(); + } private: std::string m_algo; @@ -1321,6 +1368,8 @@ class nlopt // Verbosity/log. unsigned m_verbosity = 0; mutable log_type m_log; + // Local/subsidiary optimizer. + std::unique_ptr m_loc_opt; }; } diff --git a/pygmo/expose_algorithms.cpp b/pygmo/expose_algorithms.cpp index 30052c903..5b8dfb801 100644 --- a/pygmo/expose_algorithms.cpp +++ b/pygmo/expose_algorithms.cpp @@ -404,6 +404,15 @@ void expose_algorithms() nlopt_.def("get_last_opt_result", lcast([](const nlopt &n) { return static_cast(n.get_last_opt_result()); }), nlopt_get_last_opt_result_docstring().c_str()); nlopt_.def("get_solver_name", &nlopt::get_solver_name, nlopt_get_solver_name_docstring().c_str()); + nlopt_.add_property("local_optimizer", bp::make_function(lcast([](nlopt &n) { return n.get_local_optimizer(); }), + bp::return_internal_reference<>()), + lcast([](nlopt &n, const nlopt *ptr) { + if (ptr) { + n.set_local_optimizer(*ptr); + } else { + n.unset_local_optimizer(); + } + })); #endif } } From eadb0d43bf5c0169077f5060957c55c9d16f2da4 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Mon, 10 Apr 2017 18:21:04 +0200 Subject: [PATCH 02/20] WIP bits. --- include/pagmo/algorithms/nlopt.hpp | 163 +++++++++++++++++++++-------- tests/nlopt.cpp | 107 +++++++++++++++++++ 2 files changed, 226 insertions(+), 44 deletions(-) diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index 100134066..5f160c7a3 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -34,6 +34,8 @@ see https://www.gnu.org/licenses/. */ #if defined(PAGMO_WITH_NLOPT) #include +#include +#include #include #include #include @@ -183,11 +185,6 @@ struct nlopt_obj { double xtol_rel, double xtol_abs, int maxeval, int maxtime, unsigned verbosity) : m_algo(algo), m_prob(prob), m_value(nullptr, ::nlopt_destroy), m_verbosity(verbosity) { - // If needed, init the sparsity pattern. - if (prob.has_gradient_sparsity()) { - m_sp = prob.gradient_sparsity(); - } - // Extract and set problem dimension. const auto n = boost::numeric_cast(prob.get_nx()); // Try to init the nlopt_obj. @@ -204,25 +201,6 @@ struct nlopt_obj { // Variable to hold the result of various operations. ::nlopt_result res; - // Box bounds. - const auto bounds = prob.get_bounds(); - res = ::nlopt_set_lower_bounds(m_value.get(), bounds.first.data()); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the lower bounds for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - res = ::nlopt_set_upper_bounds(m_value.get(), bounds.second.data()); - if (res != NLOPT_SUCCESS) { - // LCOV_EXCL_START - pagmo_throw(std::invalid_argument, "could not set the upper bounds for the NLopt algorithm '" - + data::names.right.at(algo) + "', the error is: " - + nlopt_res2string(res)); - // LCOV_EXCL_STOP - } - // This is just a vector_double that is re-used across objfun invocations. // It will hold the current decision vector. m_dv.resize(prob.get_nx()); @@ -293,9 +271,38 @@ struct nlopt_obj { } } + // Set box bounds. + void set_bounds() + { + const auto bounds = m_prob.get_bounds(); + auto res = ::nlopt_set_lower_bounds(m_value.get(), bounds.first.data()); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the lower bounds for the NLopt algorithm '" + + data::names.right.at(m_algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + res = ::nlopt_set_upper_bounds(m_value.get(), bounds.second.data()); + if (res != NLOPT_SUCCESS) { + // LCOV_EXCL_START + pagmo_throw(std::invalid_argument, "could not set the upper bounds for the NLopt algorithm '" + + data::names.right.at(m_algo) + "', the error is: " + + nlopt_res2string(res)); + // LCOV_EXCL_STOP + } + } + // Set the objfun + gradient. void set_objfun() { + // If needed, init the sparsity pattern. + // NOTE: we do it here so that, in case this is a local optimiser, + // we don't waste memory (set_objfun() etc. are not called when setting up a local + // optimiser). + if (m_prob.has_gradient_sparsity()) { + m_sp = m_prob.gradient_sparsity(); + } auto res = ::nlopt_set_min_objective( m_value.get(), [](unsigned dim, const double *x, double *grad, void *f_data) -> double { @@ -824,6 +831,7 @@ class nlopt { } nlopt(nlopt &&) = default; + nlopt &operator=(nlopt &&) = default; /// Set the seed for the ``"random"`` selection/replacement policies. /** * @param seed the value that will be used to seed the random number generator used by the ``"random"`` @@ -975,6 +983,7 @@ class nlopt // NOTE: this will check also the problem's properties. nlopt_obj no(nlopt_data::names.left.at(m_algo), prob, m_sc_stopval, m_sc_ftol_rel, m_sc_ftol_abs, m_sc_xtol_rel, m_sc_xtol_abs, m_sc_maxeval, m_sc_maxtime, m_verbosity); + no.set_bounds(); no.set_objfun(); no.set_eq_constraints(); no.set_ineq_constraints(); @@ -1123,24 +1132,36 @@ class nlopt { int major, minor, bugfix; ::nlopt_version(&major, &minor, &bugfix); - return "\tNLopt version: " + std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(bugfix) - + "\n\tLast optimisation return code: " + detail::nlopt_res2string(m_last_opt_result) + "\n\tVerbosity: " - + std::to_string(m_verbosity) + "\n\tIndividual selection " - + (boost::any_cast(&m_select) - ? "idx: " + std::to_string(boost::any_cast(m_select)) - : "policy: " + boost::any_cast(m_select)) - + "\n\tIndividual replacement " - + (boost::any_cast(&m_replace) - ? "idx: " + std::to_string(boost::any_cast(m_replace)) - : "policy: " + boost::any_cast(m_replace)) - + "\n\tStopping criteria:\n\t\tstopval: " - + (m_sc_stopval == -HUGE_VAL ? "disabled" : detail::to_string(m_sc_stopval)) + "\n\t\tftol_rel: " - + (m_sc_ftol_rel <= 0. ? "disabled" : detail::to_string(m_sc_ftol_rel)) + "\n\t\tftol_abs: " - + (m_sc_ftol_abs <= 0. ? "disabled" : detail::to_string(m_sc_ftol_abs)) + "\n\t\txtol_rel: " - + (m_sc_xtol_rel <= 0. ? "disabled" : detail::to_string(m_sc_xtol_rel)) + "\n\t\txtol_abs: " - + (m_sc_xtol_abs <= 0. ? "disabled" : detail::to_string(m_sc_xtol_abs)) + "\n\t\tmaxeval: " - + (m_sc_maxeval <= 0. ? "disabled" : detail::to_string(m_sc_maxeval)) + "\n\t\tmaxtime: " - + (m_sc_maxtime <= 0. ? "disabled" : detail::to_string(m_sc_maxtime)) + "\n"; + auto retval = "\tNLopt version: " + std::to_string(major) + "." + std::to_string(minor) + "." + + std::to_string(bugfix) + "\n\tSolver: '" + m_algo + "'\n\tLast optimisation return code: " + + detail::nlopt_res2string(m_last_opt_result) + "\n\tVerbosity: " + std::to_string(m_verbosity) + + "\n\tIndividual selection " + + (boost::any_cast(&m_select) + ? "idx: " + std::to_string(boost::any_cast(m_select)) + : "policy: " + boost::any_cast(m_select)) + + "\n\tIndividual replacement " + + (boost::any_cast(&m_replace) + ? "idx: " + std::to_string(boost::any_cast(m_replace)) + : "policy: " + boost::any_cast(m_replace)) + + "\n\tStopping criteria:\n\t\tstopval: " + + (m_sc_stopval == -HUGE_VAL ? "disabled" : detail::to_string(m_sc_stopval)) + "\n\t\tftol_rel: " + + (m_sc_ftol_rel <= 0. ? "disabled" : detail::to_string(m_sc_ftol_rel)) + "\n\t\tftol_abs: " + + (m_sc_ftol_abs <= 0. ? "disabled" : detail::to_string(m_sc_ftol_abs)) + "\n\t\txtol_rel: " + + (m_sc_xtol_rel <= 0. ? "disabled" : detail::to_string(m_sc_xtol_rel)) + "\n\t\txtol_abs: " + + (m_sc_xtol_abs <= 0. ? "disabled" : detail::to_string(m_sc_xtol_abs)) + "\n\t\tmaxeval: " + + (m_sc_maxeval <= 0. ? "disabled" : detail::to_string(m_sc_maxeval)) + "\n\t\tmaxtime: " + + (m_sc_maxtime <= 0. ? "disabled" : detail::to_string(m_sc_maxtime)) + "\n"; + if (m_loc_opt) { + retval += "\tLocal optimizer:\n"; + const auto loc_info = m_loc_opt->get_extra_info(); + std::vector split_v; + boost::algorithm::split(split_v, loc_info, boost::algorithm::is_any_of("\n"), + boost::algorithm::token_compress_on); + for (const auto &s : split_v) { + retval += "\t" + s + "\n"; + } + } + return retval; } /// Get the optimisation log. /** @@ -1337,9 +1358,9 @@ class nlopt { m_loc_opt.reset(nullptr); } - void set_local_optimizer(const nlopt &n) + void set_local_optimizer(nlopt n) { - m_loc_opt = detail::make_unique(n); + m_loc_opt = detail::make_unique(std::move(n)); } const nlopt *get_local_optimizer() const { @@ -1349,6 +1370,58 @@ class nlopt { return m_loc_opt.get(); } + template + void save(Archive &ar) const + { + ar(m_algo); + if (boost::any_cast(&m_select)) { + // NOTE: true -> string, false -> idx. + ar(true); + ar(boost::any_cast(m_select)); + } else { + ar(false); + ar(boost::any_cast(m_select)); + } + if (boost::any_cast(&m_replace)) { + // NOTE: true -> string, false -> idx. + ar(true); + ar(boost::any_cast(m_replace)); + } else { + ar(false); + ar(boost::any_cast(m_replace)); + } + ar(m_rselect_seed, m_e, m_last_opt_result, m_sc_stopval, m_sc_ftol_rel, m_sc_ftol_abs, m_sc_xtol_rel, + m_sc_xtol_abs, m_sc_maxeval, m_sc_maxtime, m_verbosity, m_log, m_loc_opt); + } + template + void load(Archive &ar) + { + nlopt tmp; + ar(tmp.m_algo); + bool flag; + std::string str; + population::size_type idx; + ar(flag); + if (flag) { + ar(str); + tmp.m_select = str; + } else { + ar(idx); + tmp.m_select = idx; + } + ar(flag); + if (flag) { + ar(str); + tmp.m_replace = str; + } else { + ar(idx); + tmp.m_replace = idx; + } + ar(tmp.m_rselect_seed, tmp.m_e, tmp.m_last_opt_result, tmp.m_sc_stopval, tmp.m_sc_ftol_rel, tmp.m_sc_ftol_abs, + tmp.m_sc_xtol_rel, tmp.m_sc_xtol_abs, tmp.m_sc_maxeval, tmp.m_sc_maxtime, tmp.m_verbosity, tmp.m_log, + tmp.m_loc_opt); + *this = std::move(tmp); + } private: std::string m_algo; @@ -1373,6 +1446,8 @@ class nlopt }; } +PAGMO_REGISTER_ALGORITHM(pagmo::nlopt) + #else // PAGMO_WITH_NLOPT #error The nlopt.hpp header was included, but pagmo was not compiled with NLopt support diff --git a/tests/nlopt.cpp b/tests/nlopt.cpp index e093bdcce..b1c672f52 100644 --- a/tests/nlopt.cpp +++ b/tests/nlopt.cpp @@ -30,10 +30,12 @@ see https://www.gnu.org/licenses/. */ #include #include +#include #include #include #include #include +#include #include #include #include @@ -47,6 +49,7 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include using namespace pagmo; @@ -263,3 +266,107 @@ BOOST_AUTO_TEST_CASE(nlopt_set_sc) } a.set_maxtime(123); } + +BOOST_AUTO_TEST_CASE(nlopt_serialization) +{ + for (auto r : {"best", "worst", "random"}) { + for (auto s : {"best", "worst", "random"}) { + auto n = nlopt{"slsqp"}; + n.set_replacement(r); + n.set_selection(s); + algorithm algo{n}; + algo.set_verbosity(5); + auto pop = population(hs71{}, 10); + algo.evolve(pop); + auto s_log = algo.extract()->get_log(); + // Store the string representation of p. + std::stringstream ss; + auto before_text = boost::lexical_cast(algo); + // Now serialize, deserialize and compare the result. + { + cereal::JSONOutputArchive oarchive(ss); + oarchive(algo); + } + // Change the content of p before deserializing. + algo = algorithm{null_algorithm{}}; + { + cereal::JSONInputArchive iarchive(ss); + iarchive(algo); + } + auto after_text = boost::lexical_cast(algo); + BOOST_CHECK_EQUAL(before_text, after_text); + BOOST_CHECK(s_log == algo.extract()->get_log()); + } + } + for (auto r : {0u, 4u, 7u}) { + for (auto s : {0u, 4u, 7u}) { + auto n = nlopt{"slsqp"}; + n.set_replacement(r); + n.set_selection(s); + algorithm algo{n}; + algo.set_verbosity(5); + auto pop = population(hs71{}, 10); + algo.evolve(pop); + auto s_log = algo.extract()->get_log(); + // Store the string representation of p. + std::stringstream ss; + auto before_text = boost::lexical_cast(algo); + // Now serialize, deserialize and compare the result. + { + cereal::JSONOutputArchive oarchive(ss); + oarchive(algo); + } + // Change the content of p before deserializing. + algo = algorithm{null_algorithm{}}; + { + cereal::JSONInputArchive iarchive(ss); + iarchive(algo); + } + auto after_text = boost::lexical_cast(algo); + BOOST_CHECK_EQUAL(before_text, after_text); + BOOST_CHECK(s_log == algo.extract()->get_log()); + } + } +} + +BOOST_AUTO_TEST_CASE(nlopt_loc_opt) +{ + nlopt n{"auglag"}; + n.set_local_optimizer(nlopt{"lbfgs"}); + BOOST_CHECK(n.get_local_optimizer()); + BOOST_CHECK(static_cast(n).get_local_optimizer()); + // Test serialization. + algorithm algo{n}; + std::stringstream ss; + auto before_text = boost::lexical_cast(algo); + // Now serialize, deserialize and compare the result. + { + cereal::JSONOutputArchive oarchive(ss); + oarchive(algo); + } + // Change the content of p before deserializing. + algo = algorithm{null_algorithm{}}; + { + cereal::JSONInputArchive iarchive(ss); + iarchive(algo); + } + auto after_text = boost::lexical_cast(algo); + BOOST_CHECK_EQUAL(before_text, after_text); + // Test small evolution. + auto pop = population{hs71{}, 1}; + pop.set_x(0, {2., 2., 2., 2.}); + pop.get_problem().set_c_tol({1E-6, 1E-6}); + algo.evolve(pop); + BOOST_CHECK(algo.extract()->get_last_opt_result() >= 0); + // Unset the local optimizer. + algo.extract()->unset_local_optimizer(); + BOOST_CHECK(!algo.extract()->get_local_optimizer()); + algo.evolve(pop); + BOOST_CHECK(algo.extract()->get_last_opt_result() == NLOPT_INVALID_ARGS); + // Auglag inside auglag. + algo.extract()->set_local_optimizer(nlopt{"auglag"}); + algo.extract()->get_local_optimizer()->set_local_optimizer(nlopt{"lbfgs"}); + algo.set_verbosity(1); + std::cout << algo << '\n'; + algo.evolve(pop); +} From f644c5bc9d0682525258217c940962302d730484 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Mon, 10 Apr 2017 22:49:14 +0200 Subject: [PATCH 03/20] Testing and doc bits on the C++ side. --- include/pagmo/algorithms/nlopt.hpp | 83 ++++++++++++++++++++++++++---- tests/nlopt.cpp | 77 +++++++++++++++------------ 2 files changed, 117 insertions(+), 43 deletions(-) diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index 5f160c7a3..2ecd6e52e 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -198,15 +198,12 @@ struct nlopt_obj { pagmo_throw(std::invalid_argument, "NLopt algorithms cannot handle multi-objective optimization"); } - // Variable to hold the result of various operations. - ::nlopt_result res; - // This is just a vector_double that is re-used across objfun invocations. // It will hold the current decision vector. m_dv.resize(prob.get_nx()); // Handle the various stopping criteria. - res = ::nlopt_set_stopval(m_value.get(), stopval); + auto res = ::nlopt_set_stopval(m_value.get(), stopval); if (res != NLOPT_SUCCESS) { // LCOV_EXCL_START pagmo_throw(std::invalid_argument, "could not set the 'stopval' stopping criterion to " @@ -680,7 +677,8 @@ struct nlopt_obj { * - SLSQP, * - low-storage BFGS, * - preconditioned truncated Newton, - * - shifted limited-memory variable-metric. + * - shifted limited-memory variable-metric, + * - augmented Lagrangian algorithm. * * The desired NLopt solver is selected upon construction of a pagmo::nlopt algorithm. Various properties * of the solver (e.g., the stopping criteria) can be configured after construction via methods provided @@ -782,6 +780,8 @@ class nlopt * ``"tnewton"`` ``NLOPT_LD_TNEWTON`` * ``"var2"`` ``NLOPT_LD_VAR2`` * ``"var1"`` ``NLOPT_LD_VAR1`` + * ``"auglag"`` ``NLOPT_AUGLAG`` + * ``"auglag_eq"`` ``NLOPT_AUGLAG_EQ`` * ================================ ==================================== * \endverbatim * The parameters of the selected algorithm can be specified via the methods of this class. @@ -821,6 +821,14 @@ class nlopt + "'. The supported algorithms are:\n" + oss.str()); } } + /// Copy constructor. + /** + * The copy constructor will deep-copy the state of \p other. + * + * @param other the construction argument. + * + * @throws unspecified any exception thrown by copying the internal state of \p other. + */ nlopt(const nlopt &other) : m_algo(other.m_algo), m_select(other.m_select), m_replace(other.m_replace), m_rselect_seed(other.m_rselect_seed), m_e(other.m_e), m_last_opt_result(other.m_last_opt_result), @@ -830,7 +838,12 @@ class nlopt m_loc_opt(other.m_loc_opt ? detail::make_unique(*other.m_loc_opt) : nullptr) { } + /// Move constructor. nlopt(nlopt &&) = default; + /// Move assignment operator. + /** + * @return a reference to \p this. + */ nlopt &operator=(nlopt &&) = default; /// Set the seed for the ``"random"`` selection/replacement policies. /** @@ -1354,22 +1367,66 @@ class nlopt { m_sc_maxtime = n; } - void unset_local_optimizer() - { - m_loc_opt.reset(nullptr); - } + /// Set the local optimizer. + /** + * Some NLopt algorithms rely on other NLopt algorithms as local/subsidiary optimizers. + * This method allows to set such local optimizer. By default, no local optimizer is specified. + * + * **NOTE**: at the present time, only the ``"auglag"`` and ``"auglag_eq"`` solvers make use + * of a local optimizer. Setting a local optimizer on any other solver will have no effect. + * + * **NOTE**: the objective function, bounds, and nonlinear-constraint parameters of the local + * optimizer are ignored (as they are provided by the parent optimizer). The verbosity of + * the local optimizer is also forcibly set to zero during the optimisation. + * + * @param n the local optimizer that will be used by this pagmo::nlopt algorithm. + */ void set_local_optimizer(nlopt n) { m_loc_opt = detail::make_unique(std::move(n)); } + /// Get the local optimizer. + /** + * This method returns a raw const pointer to the local optimizer, if it has been set via set_local_optimizer(). + * Otherwise, \p nullptr will be returned. + * + * **NOTE**: the returned value is a raw non-owning pointer: the lifetime of the pointee is tied to the lifetime + * of \p this, and \p delete must never be called on the pointer. + * + * @return a const pointer to the local optimizer. + */ const nlopt *get_local_optimizer() const { return m_loc_opt.get(); } + /// Get the local optimizer. + /** + * This method returns a raw pointer to the local optimizer, if it has been set via set_local_optimizer(). + * Otherwise, \p nullptr will be returned. + * + * **NOTE**: the returned value is a raw non-owning pointer: the lifetime of the pointee is tied to the lifetime + * of \p this, and \p delete must never be called on the pointer. + * + * @return a pointer to the local optimizer. + */ nlopt *get_local_optimizer() { return m_loc_opt.get(); } + /// Unset the local optimizer. + /** + * After a call to this method, get_local_optimizer() and get_local_optimizer() const will return \p nullptr. + */ + void unset_local_optimizer() + { + m_loc_opt.reset(nullptr); + } + /// Save to archive. + /** + * @param ar the target archive. + * + * @throws unspecified any exception thrown by the serialization of primitive types. + */ template void save(Archive &ar) const { @@ -1393,6 +1450,14 @@ class nlopt ar(m_rselect_seed, m_e, m_last_opt_result, m_sc_stopval, m_sc_ftol_rel, m_sc_ftol_abs, m_sc_xtol_rel, m_sc_xtol_abs, m_sc_maxeval, m_sc_maxtime, m_verbosity, m_log, m_loc_opt); } + /// Load from archive. + /** + * In case of exceptions, \p this will be unaffected. + * + * @param ar the source archive. + * + * @throws unspecified any exception thrown by the deserialization of primitive types. + */ template void load(Archive &ar) { diff --git a/tests/nlopt.cpp b/tests/nlopt.cpp index b1c672f52..6b1f851c7 100644 --- a/tests/nlopt.cpp +++ b/tests/nlopt.cpp @@ -331,42 +331,51 @@ BOOST_AUTO_TEST_CASE(nlopt_serialization) BOOST_AUTO_TEST_CASE(nlopt_loc_opt) { - nlopt n{"auglag"}; + for (const auto &str : {"auglag", "auglag_eq"}) { + nlopt n{str}; + n.set_local_optimizer(nlopt{"slsqp"}); + BOOST_CHECK(n.get_local_optimizer()); + BOOST_CHECK(static_cast(n).get_local_optimizer()); + // Test serialization. + algorithm algo{n}; + std::stringstream ss; + auto before_text = boost::lexical_cast(algo); + // Now serialize, deserialize and compare the result. + { + cereal::JSONOutputArchive oarchive(ss); + oarchive(algo); + } + // Change the content of p before deserializing. + algo = algorithm{null_algorithm{}}; + { + cereal::JSONInputArchive iarchive(ss); + iarchive(algo); + } + auto after_text = boost::lexical_cast(algo); + BOOST_CHECK_EQUAL(before_text, after_text); + // Test small evolution. + auto pop = population{hs71{}, 1}; + pop.set_x(0, {2., 2., 2., 2.}); + pop.get_problem().set_c_tol({1E-6, 1E-6}); + algo.evolve(pop); + BOOST_CHECK(algo.extract()->get_last_opt_result() >= 0); + // Unset the local optimizer. + algo.extract()->unset_local_optimizer(); + BOOST_CHECK(!algo.extract()->get_local_optimizer()); + algo.evolve(pop); + BOOST_CHECK(algo.extract()->get_last_opt_result() == NLOPT_INVALID_ARGS); + // Auglag inside auglag. Not sure if this is supposed to work, it gives an error + // currently. + algo.extract()->set_local_optimizer(nlopt{str}); + algo.extract()->get_local_optimizer()->set_local_optimizer(nlopt{"lbfgs"}); + algo.evolve(pop); + BOOST_CHECK(algo.extract()->get_last_opt_result() < 0); + } + // Check setting a local opt does not do anythig for normal solvers. + nlopt n{"slsqp"}; n.set_local_optimizer(nlopt{"lbfgs"}); - BOOST_CHECK(n.get_local_optimizer()); - BOOST_CHECK(static_cast(n).get_local_optimizer()); - // Test serialization. algorithm algo{n}; - std::stringstream ss; - auto before_text = boost::lexical_cast(algo); - // Now serialize, deserialize and compare the result. - { - cereal::JSONOutputArchive oarchive(ss); - oarchive(algo); - } - // Change the content of p before deserializing. - algo = algorithm{null_algorithm{}}; - { - cereal::JSONInputArchive iarchive(ss); - iarchive(algo); - } - auto after_text = boost::lexical_cast(algo); - BOOST_CHECK_EQUAL(before_text, after_text); - // Test small evolution. - auto pop = population{hs71{}, 1}; - pop.set_x(0, {2., 2., 2., 2.}); - pop.get_problem().set_c_tol({1E-6, 1E-6}); + auto pop = population{rosenbrock{20}, 1}; algo.evolve(pop); BOOST_CHECK(algo.extract()->get_last_opt_result() >= 0); - // Unset the local optimizer. - algo.extract()->unset_local_optimizer(); - BOOST_CHECK(!algo.extract()->get_local_optimizer()); - algo.evolve(pop); - BOOST_CHECK(algo.extract()->get_last_opt_result() == NLOPT_INVALID_ARGS); - // Auglag inside auglag. - algo.extract()->set_local_optimizer(nlopt{"auglag"}); - algo.extract()->get_local_optimizer()->set_local_optimizer(nlopt{"lbfgs"}); - algo.set_verbosity(1); - std::cout << algo << '\n'; - algo.evolve(pop); } From b94df3c6403f08c05073bce3a4a041208f8ebc18 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 00:11:23 +0200 Subject: [PATCH 04/20] nlopt: change the evolve() logic to re-insert the evolved individual only if it is "better" than the original one. This gets rid of a reinsertion issue when the NLopt algo stops immediately (in such a case, fitness would have been set to inf previously). --- include/pagmo/algorithms/nlopt.hpp | 63 ++++++++++++++---------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index 2ecd6e52e..a57483b3a 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -990,7 +990,6 @@ class nlopt } auto &prob = pop.get_problem(); - const auto nc = prob.get_nc(); // Create the nlopt obj. // NOTE: this will check also the problem's properties. @@ -1010,17 +1009,21 @@ class nlopt } // Setup of the initial guess. - vector_double initial_guess; + vector_double initial_guess, old_f; if (boost::any_cast(&m_select)) { const auto &s_select = boost::any_cast(m_select); if (s_select == "best") { initial_guess = pop.get_x()[pop.best_idx()]; + old_f = pop.get_f()[pop.best_idx()]; } else if (s_select == "worst") { initial_guess = pop.get_x()[pop.worst_idx()]; + old_f = pop.get_f()[pop.worst_idx()]; } else { assert(s_select == "random"); std::uniform_int_distribution dist(0, pop.size() - 1u); - initial_guess = pop.get_x()[dist(m_e)]; + const auto idx = dist(m_e); + initial_guess = pop.get_x()[idx]; + old_f = pop.get_f()[idx]; } } else { const auto idx = boost::any_cast(m_select); @@ -1030,6 +1033,7 @@ class nlopt + std::to_string(pop.size())); } initial_guess = pop.get_x()[idx]; + old_f = pop.get_f()[idx]; } // Check the initial guess. // NOTE: this should be guaranteed by the population's invariants. @@ -1061,41 +1065,32 @@ class nlopt std::rethrow_exception(no.m_eptr); } - // Store the new individual into the population. - if (boost::any_cast(&m_replace)) { - const auto &s_replace = boost::any_cast(m_replace); - if (s_replace == "best") { - if (nc) { - pop.set_x(pop.best_idx(), initial_guess); - } else { - pop.set_xf(pop.best_idx(), initial_guess, {fitness}); - } - } else if (s_replace == "worst") { - if (nc) { - pop.set_x(pop.worst_idx(), initial_guess); + // Compute the new fitness vector. + const auto new_f = pop.get_problem().fitness(initial_guess); + // Compare to the old fitness. + const bool comp = compare_fc(new_f, old_f, pop.get_problem().get_nec(), pop.get_problem().get_c_tol()); + + // Store the new individual into the population, but only if better. + if (comp) { + if (boost::any_cast(&m_replace)) { + const auto &s_replace = boost::any_cast(m_replace); + if (s_replace == "best") { + pop.set_xf(pop.best_idx(), initial_guess, new_f); + } else if (s_replace == "worst") { + pop.set_xf(pop.worst_idx(), initial_guess, new_f); } else { - pop.set_xf(pop.worst_idx(), initial_guess, {fitness}); + assert(s_replace == "random"); + std::uniform_int_distribution dist(0, pop.size() - 1u); + pop.set_xf(dist(m_e), initial_guess, new_f); } } else { - assert(s_replace == "random"); - std::uniform_int_distribution dist(0, pop.size() - 1u); - if (nc) { - pop.set_x(dist(m_e), initial_guess); - } else { - pop.set_xf(dist(m_e), initial_guess, {fitness}); + const auto idx = boost::any_cast(m_replace); + if (idx >= pop.size()) { + pagmo_throw(std::invalid_argument, "cannot replace the individual at index " + std::to_string(idx) + + " after evolution: the population has a size of only " + + std::to_string(pop.size())); } - } - } else { - const auto idx = boost::any_cast(m_replace); - if (idx >= pop.size()) { - pagmo_throw(std::invalid_argument, "cannot replace the individual at index " + std::to_string(idx) - + " after evolution: the population has a size of only " - + std::to_string(pop.size())); - } - if (nc) { - pop.set_x(idx, initial_guess); - } else { - pop.set_xf(idx, initial_guess, {fitness}); + pop.set_xf(idx, initial_guess, new_f); } } From 862f5de1e43ce79f8869b319e06712ea54ff70c8 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 00:13:50 +0200 Subject: [PATCH 05/20] Vaiorus python bits for auglag. --- pygmo/docstrings.cpp | 34 +++++++++++++++++++++++++++++++++- pygmo/docstrings.hpp | 1 + pygmo/expose_algorithms.cpp | 3 ++- pygmo/test.py | 22 +++++++++++++++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/pygmo/docstrings.cpp b/pygmo/docstrings.cpp index e2fdd4c69..4ce03c3f6 100644 --- a/pygmo/docstrings.cpp +++ b/pygmo/docstrings.cpp @@ -3618,7 +3618,8 @@ NLopt algorithms is: * SLSQP, * low-storage BFGS, * preconditioned truncated Newton, -* shifted limited-memory variable-metric. +* shifted limited-memory variable-metric, +* augmented Lagrangian algorithm. The desired NLopt solver is selected upon construction of an :class:`~pygmo.core.nlopt` algorithm. Various properties of the solver (e.g., the stopping criteria) can be configured via class attributes. Note that multiple @@ -3670,6 +3671,8 @@ translation table: ``"tnewton"`` ``NLOPT_LD_TNEWTON`` ``"var2"`` ``NLOPT_LD_VAR2`` ``"var1"`` ``NLOPT_LD_VAR1`` +``"auglag"`` ``NLOPT_AUGLAG`` +``"auglag_eq"`` ``NLOPT_AUGLAG_EQ`` ================================ ==================================== The parameters of the selected solver can be configured via the attributes of this class. @@ -4008,4 +4011,33 @@ Get the name of the NLopt solver used during construction. )"; } +std::string nlopt_local_optimizer_docstring() +{ + return R"(Local optimizer. + +Some NLopt algorithms rely on other NLopt algorithms as local/subsidiary optimizers. +This property, of type :class:`~pygmo.core.nlopt`, allows to set such local optimizer. +By default, no local optimizer is specified, and the property is set to ``None``. + +.. note:: + + At the present time, only the ``"auglag"`` and ``"auglag_eq"`` solvers make use + of a local optimizer. Setting a local optimizer on any other solver will have no effect. + +.. note:: + + The objective function, bounds, and nonlinear-constraint parameters of the local + optimizer are ignored (as they are provided by the parent optimizer). The verbosity of + the local optimizer is also forcibly set to zero during the optimisation. + +Returns: + :class:`~pygmo.core.nlopt`: the local optimizer, or ``None`` if not set + +Raises: + unspecified: any exception thrown by failures at the intersection between C++ and Python + (e.g., type conversion errors, mismatched function signatures, etc.), when setting the property + +)"; +} + } // namespace diff --git a/pygmo/docstrings.hpp b/pygmo/docstrings.hpp index 3dbc821dc..d4335f8c5 100644 --- a/pygmo/docstrings.hpp +++ b/pygmo/docstrings.hpp @@ -157,6 +157,7 @@ std::string nlopt_set_random_sr_seed_docstring(); std::string nlopt_get_log_docstring(); std::string nlopt_get_last_opt_result_docstring(); std::string nlopt_get_solver_name_docstring(); +std::string nlopt_local_optimizer_docstring(); // utilities // hypervolume diff --git a/pygmo/expose_algorithms.cpp b/pygmo/expose_algorithms.cpp index 5b8dfb801..3025d8d7d 100644 --- a/pygmo/expose_algorithms.cpp +++ b/pygmo/expose_algorithms.cpp @@ -412,7 +412,8 @@ void expose_algorithms() } else { n.unset_local_optimizer(); } - })); + }), + nlopt_local_optimizer_docstring().c_str()); #endif } } diff --git a/pygmo/test.py b/pygmo/test.py index ee0ca0f51..dafa5cf67 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -380,7 +380,7 @@ def runTest(self): from .core import nlopt, algorithm, luksan_vlcek1, problem, population n = nlopt() self.assertEqual(n.get_solver_name(), "cobyla") - n = nlopt(solver = "slsqp") + n = nlopt(solver="slsqp") self.assertEqual(n.get_solver_name(), "slsqp") self.assertRaises(ValueError, lambda: nlopt("dsadsa")) @@ -474,6 +474,26 @@ def _(): pop = algo.evolve(pop) self.assertTrue(len(algo.extract(nlopt).get_log()) != 0) + # Pickling. + from pickle import dumps, loads + algo = algorithm(nlopt("slsqp")) + algo.set_verbosity(5) + prob = problem(luksan_vlcek1(20)) + prob.c_tol = [1E-6] * 18 + pop = population(prob, 20) + algo.evolve(pop) + self.assertEqual(str(algo), str(loads(dumps(algo)))) + self.assertEqual(algo.extract(nlopt).get_log(), loads( + dumps(algo)).extract(nlopt).get_log()) + + # Local optimizer. + self.assertTrue(nlopt("slsqp").local_optimizer is None) + self.assertTrue(nlopt("auglag").local_optimizer is None) + n = nlopt("auglag") + loc = nlopt("slsqp") + n.local_optimizer = loc + self.assertFalse(n.local_optimizer is None) + class null_problem_test_case(_ut.TestCase): """Test case for the null problem From ec17e8b0d667121c1eb58c59b8415c5550ed4268 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 00:25:43 +0200 Subject: [PATCH 06/20] More python testing bits. --- include/pagmo/algorithms/nlopt.hpp | 3 ++- pygmo/test.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index a57483b3a..8c4df05d9 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -1008,7 +1008,8 @@ class nlopt ::nlopt_set_local_optimizer(no.m_value.get(), no_loc.m_value.get()); } - // Setup of the initial guess. + // Setup of the initial guess. Store also the original fitness + // of the selected individual, old_f, for later use. vector_double initial_guess, old_f; if (boost::any_cast(&m_select)) { const auto &s_select = boost::any_cast(m_select); diff --git a/pygmo/test.py b/pygmo/test.py index 17cf20a7e..157253a8a 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -493,6 +493,22 @@ def _(): loc = nlopt("slsqp") n.local_optimizer = loc self.assertFalse(n.local_optimizer is None) + self.assertEqual(str(algorithm(loc)), str( + algorithm(n.local_optimizer))) + pop = population(prob, 20, seed=4) + algo = algorithm(n) + algo.evolve(pop) + self.assertTrue(algo.extract(nlopt).get_last_opt_result() >= 0) + n = nlopt("auglag_eq") + loc = nlopt("slsqp") + n.local_optimizer = loc + self.assertFalse(n.local_optimizer is None) + self.assertEqual(str(algorithm(loc)), str( + algorithm(n.local_optimizer))) + pop = population(prob, 20, seed=4) + algo = algorithm(n) + algo.evolve(pop) + self.assertTrue(algo.extract(nlopt).get_last_opt_result() >= 0) class null_problem_test_case(_ut.TestCase): From cdce11da3f7ae340aaafc3745a5eb5634d12766a Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 00:32:43 +0200 Subject: [PATCH 07/20] Some small test additions. --- pygmo/test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pygmo/test.py b/pygmo/test.py index 157253a8a..baa9d3831 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -510,6 +510,18 @@ def _(): algo.evolve(pop) self.assertTrue(algo.extract(nlopt).get_last_opt_result() >= 0) + # Refcount. + import sys + n = nlopt("auglag") + loc = nlopt("slsqp") + n.local_optimizer = loc + old_rc = sys.getrefcount(n) + foo = n.local_optimizer + self.assertEqual(old_rc + 1, sys.getrefcount(n)) + del n + self.assertTrue(len(str(foo)) != 0) + del foo + class null_problem_test_case(_ut.TestCase): """Test case for the null problem From e20a3fbbc2d19cf089e169c2d1ab9b66c156d902 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 01:44:03 +0200 Subject: [PATCH 08/20] Couple of test fixes. --- pygmo/test.py | 2 ++ tests/nlopt.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/pygmo/test.py b/pygmo/test.py index baa9d3831..75b57447d 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -518,6 +518,8 @@ def _(): old_rc = sys.getrefcount(n) foo = n.local_optimizer self.assertEqual(old_rc + 1, sys.getrefcount(n)) + if sys.version_info[0] < 3: + return del n self.assertTrue(len(str(foo)) != 0) del foo diff --git a/tests/nlopt.cpp b/tests/nlopt.cpp index 6b1f851c7..4fa13fdca 100644 --- a/tests/nlopt.cpp +++ b/tests/nlopt.cpp @@ -26,6 +26,12 @@ You should have received copies of the GNU General Public License and the GNU Lesser General Public License along with the PaGMO library. If not, see https://www.gnu.org/licenses/. */ +#if defined(_MSC_VER) + +#define _SCL_SECURE_NO_WARNINGS + +#endif + #define BOOST_TEST_MODULE nlopt_test #include From e23373e6f971fe8fe3984052beb2ec7f37fe8347 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 02:49:35 +0200 Subject: [PATCH 09/20] Another stab at py27. --- pygmo/test.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pygmo/test.py b/pygmo/test.py index 75b57447d..7a9284505 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -512,15 +512,13 @@ def _(): # Refcount. import sys - n = nlopt("auglag") + nl = nlopt("auglag") loc = nlopt("slsqp") - n.local_optimizer = loc - old_rc = sys.getrefcount(n) - foo = n.local_optimizer - self.assertEqual(old_rc + 1, sys.getrefcount(n)) - if sys.version_info[0] < 3: - return - del n + nl.local_optimizer = loc + old_rc = sys.getrefcount(nl) + foo = nl.local_optimizer + self.assertEqual(old_rc + 1, sys.getrefcount(nl)) + del nl self.assertTrue(len(str(foo)) != 0) del foo From 06e7f98a7185e34afceb33fd779554f66f65f2cd Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 21:46:04 +0200 Subject: [PATCH 10/20] A couple of small bits. --- .../docs/python/algorithms/py_algorithms.rst | 2 +- pygmo/docstrings.cpp | 26 +++++++++---------- tests/nlopt.cpp | 3 +++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/doc/sphinx/docs/python/algorithms/py_algorithms.rst b/doc/sphinx/docs/python/algorithms/py_algorithms.rst index 559b995d6..b58d486fe 100644 --- a/doc/sphinx/docs/python/algorithms/py_algorithms.rst +++ b/doc/sphinx/docs/python/algorithms/py_algorithms.rst @@ -76,5 +76,5 @@ Algorithms exposed from C++ ------------------------------------------------------------- -.. autoclass:: pygmo.core.nlopt +.. autoclass:: pygmo.nlopt :members: diff --git a/pygmo/docstrings.cpp b/pygmo/docstrings.cpp index 7eab4abd6..df432a966 100644 --- a/pygmo/docstrings.cpp +++ b/pygmo/docstrings.cpp @@ -3621,20 +3621,20 @@ NLopt algorithms is: * shifted limited-memory variable-metric, * augmented Lagrangian algorithm. -The desired NLopt solver is selected upon construction of an :class:`~pygmo.core.nlopt` algorithm. Various properties +The desired NLopt solver is selected upon construction of an :class:`~pygmo.nlopt` algorithm. Various properties of the solver (e.g., the stopping criteria) can be configured via class attributes. Note that multiple stopping criteria can be active at the same time: the optimisation will stop as soon as at least one stopping criterion -is satisfied. By default, only the ``xtol_rel`` stopping criterion is active (see :attr:`~pygmo.core.nlopt.xtol_rel`). +is satisfied. By default, only the ``xtol_rel`` stopping criterion is active (see :attr:`~pygmo.nlopt.xtol_rel`). All NLopt solvers support only single-objective optimisation, and, as usual in pagmo, minimisation is always assumed. The gradient-based algorithms require the optimisation problem to provide a gradient. Some solvers support equality and/or inequality constaints. In order to support pagmo's population-based optimisation model, the ``evolve()`` method will select -a single individual from the input :class:`~pygmo.core.population` to be optimised by the NLopt solver. +a single individual from the input :class:`~pygmo.population` to be optimised by the NLopt solver. The optimised individual will then be inserted back into the population at the end of the optimisation. -The selection and replacement strategies can be configured via the :attr:`~pygmo.core.nlopt.selection` -and :attr:`~pygmo.core.nlopt.replacement` attributes. +The selection and replacement strategies can be configured via the :attr:`~pygmo.nlopt.selection` +and :attr:`~pygmo.nlopt.replacement` attributes. .. note:: @@ -3646,7 +3646,7 @@ and :attr:`~pygmo.core.nlopt.replacement` attributes. The `NLopt website `_ contains a detailed description of each supported solver. -This constructor will initialise an :class:`~pygmo.core.nlopt` object which will use the NLopt algorithm specified by +This constructor will initialise an :class:`~pygmo.nlopt` object which will use the NLopt algorithm specified by the input string *solver*, the ``"best"`` individual selection strategy and the ``"best"`` individual replacement strategy. *solver* is translated to an NLopt algorithm type according to the following translation table: @@ -3685,7 +3685,7 @@ See also the docs of the C++ class :cpp:class:`pagmo::nlopt`. description of each supported solver. Args: - solver (``str``): the name of the NLopt algorithm that will be used by this :class:`~pygmo.core.nlopt` object + solver (``str``): the name of the NLopt algorithm that will be used by this :class:`~pygmo.nlopt` object Raises: RuntimeError: if the NLopt version is not at least 2 @@ -3885,7 +3885,7 @@ If the attribute is a string, it must be one of ``"best"``, ``"worst"`` and ``"r * ``"worst"`` will select the worst individual in the population, * ``"random"`` will randomly choose one individual in the population. -:func:`~pygmo.core.nlopt.set_random_sr_seed()` can be used to seed the random number generator +:func:`~pygmo.nlopt.set_random_sr_seed()` can be used to seed the random number generator used by the ``"random"`` policy. If the attribute is an integer, it represents the index (in the population) of the individual that is selected @@ -3917,7 +3917,7 @@ If the attribute is a string, it must be one of ``"best"``, ``"worst"`` and ``"r * ``"worst"`` will select the worst individual in the population, * ``"random"`` will randomly choose one individual in the population. -:func:`~pygmo.core.nlopt.set_random_sr_seed()` can be used to seed the random number generator +:func:`~pygmo.nlopt.set_random_sr_seed()` can be used to seed the random number generator used by the ``"random"`` policy. If the attribute is an integer, it represents the index (in the population) of the individual that will be @@ -3944,8 +3944,8 @@ Set the seed for the ``"random"`` selection/replacement policies. Args: seed (``int``): the value that will be used to seed the random number generator used by the ``"random"`` - election/replacement policies (see :attr:`~pygmo.core.nlopt.selection` and - :attr:`~pygmo.core.nlopt.replacement`) + election/replacement policies (see :attr:`~pygmo.nlopt.selection` and + :attr:`~pygmo.nlopt.replacement`) Raises: OverflowError: if the attribute is set to an integer which is negative or too large @@ -4016,7 +4016,7 @@ std::string nlopt_local_optimizer_docstring() return R"(Local optimizer. Some NLopt algorithms rely on other NLopt algorithms as local/subsidiary optimizers. -This property, of type :class:`~pygmo.core.nlopt`, allows to set such local optimizer. +This property, of type :class:`~pygmo.nlopt`, allows to set such local optimizer. By default, no local optimizer is specified, and the property is set to ``None``. .. note:: @@ -4031,7 +4031,7 @@ By default, no local optimizer is specified, and the property is set to ``None`` the local optimizer is also forcibly set to zero during the optimisation. Returns: - :class:`~pygmo.core.nlopt`: the local optimizer, or ``None`` if not set + :class:`~pygmo.nlopt`: the local optimizer, or ``None`` if not set Raises: unspecified: any exception thrown by failures at the intersection between C++ and Python diff --git a/tests/nlopt.cpp b/tests/nlopt.cpp index 4fa13fdca..f45e62528 100644 --- a/tests/nlopt.cpp +++ b/tests/nlopt.cpp @@ -28,6 +28,9 @@ see https://www.gnu.org/licenses/. */ #if defined(_MSC_VER) +// Disable the checked iterators feature in MSVC. We want it for the source code +// (so it should not be disabled in the headers), but dealing with it in the tests is +// not as useful and quite painful. #define _SCL_SECURE_NO_WARNINGS #endif From a2b8c687dcee2d7b1b812eb36ef00cc67e66fdfa Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 21:50:17 +0200 Subject: [PATCH 11/20] Fix a test that could rarely fail, if the ipcluster states changes during pickling. --- pygmo/_island_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pygmo/_island_test.py b/pygmo/_island_test.py index 0f8235f99..e87919549 100644 --- a/pygmo/_island_test.py +++ b/pygmo/_island_test.py @@ -293,4 +293,7 @@ def run_basic_tests(self): self.assertEqual(str(isl3), str(isl)) # Pickle. - self.assertEqual(str(loads(dumps(isl))), str(isl)) + pisl = loads(dumps(isl)) + self.assertEqual(str(pisl.get_population()), str(isl.get_population())) + self.assertEqual(str(pisl.get_algorithm()), str(isl.get_algorithm())) + self.assertEqual(str(pisl.get_name()), str(isl.get_name())) From d28b09a404de67bca15ecb23a67d7ab7d46265ec Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 22:15:48 +0200 Subject: [PATCH 12/20] Another test addition. --- pygmo/test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pygmo/test.py b/pygmo/test.py index 7a9284505..4a6db52eb 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -1132,6 +1132,7 @@ def run_evolve_tests(self): def run_access_tests(self): from . import archipelago, de, rosenbrock + import sys a = archipelago(5, algo=de(), prob=rosenbrock(), pop_size=10) i0, i1, i2 = a[0], a[1], a[2] a.push_back(algo=de(), prob=rosenbrock(), size=11) @@ -1151,6 +1152,17 @@ def run_access_tests(self): self.assertTrue(isl.get_algorithm().is_(de)) self.assertTrue(isl.get_population().problem.is_(rosenbrock)) self.assertEqual(len(isl.get_population()), 10) + # Check refcount when returning internal ref. + a = archipelago(5, algo=de(), prob=rosenbrock(), pop_size=10) + old_rc = sys.getrefcount(a) + i0, i1, i2, i3 = a[0], a[1], a[2], a[3] + self.assertEqual(sys.getrefcount(a) - 4, old_rc) + del a + self.assertTrue(str(i0) != "") + self.assertTrue(str(i1) != "") + self.assertTrue(str(i2) != "") + self.assertTrue(str(i3) != "") + del i0, i1, i2, i3 def run_push_back_tests(self): from . import archipelago, de, rosenbrock From 18556c43770b9d28f307e19f7862294c05cca554 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 22:42:28 +0200 Subject: [PATCH 13/20] Additional doc bits. --- include/pagmo/algorithms/nlopt.hpp | 10 +++++----- pygmo/docstrings.cpp | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index 8c4df05d9..377b864cc 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -692,7 +692,8 @@ struct nlopt_obj { * * In order to support pagmo's population-based optimisation model, nlopt::evolve() will select * a single individual from the input pagmo::population to be optimised by the NLopt solver. - * The optimised individual will then be inserted back into the population at the end of the optimisation. + * If the optimisation produces a better individual (as established by pagmo::compare_fc()), + * the optimised individual will be inserted back into the population. * The selection and replacement strategies can be configured via set_selection(const std::string &), * set_selection(population::size_type), set_replacement(const std::string &) and * set_replacement(population::size_type). @@ -1068,11 +1069,9 @@ class nlopt // Compute the new fitness vector. const auto new_f = pop.get_problem().fitness(initial_guess); - // Compare to the old fitness. - const bool comp = compare_fc(new_f, old_f, pop.get_problem().get_nec(), pop.get_problem().get_c_tol()); // Store the new individual into the population, but only if better. - if (comp) { + if (compare_fc(new_f, old_f, pop.get_problem().get_nec(), pop.get_problem().get_c_tol())) { if (boost::any_cast(&m_replace)) { const auto &s_replace = boost::any_cast(m_replace); if (s_replace == "best") { @@ -1372,7 +1371,8 @@ class nlopt * of a local optimizer. Setting a local optimizer on any other solver will have no effect. * * **NOTE**: the objective function, bounds, and nonlinear-constraint parameters of the local - * optimizer are ignored (as they are provided by the parent optimizer). The verbosity of + * optimizer are ignored (as they are provided by the parent optimizer). Conversely, the stopping + * criteria should be specified in the local optimizer. The verbosity of * the local optimizer is also forcibly set to zero during the optimisation. * * @param n the local optimizer that will be used by this pagmo::nlopt algorithm. diff --git a/pygmo/docstrings.cpp b/pygmo/docstrings.cpp index df432a966..949aecdf9 100644 --- a/pygmo/docstrings.cpp +++ b/pygmo/docstrings.cpp @@ -3632,7 +3632,8 @@ Some solvers support equality and/or inequality constaints. In order to support pagmo's population-based optimisation model, the ``evolve()`` method will select a single individual from the input :class:`~pygmo.population` to be optimised by the NLopt solver. -The optimised individual will then be inserted back into the population at the end of the optimisation. +If the optimisation produces a better individual (as established by :func:`~pygmo.compare_fc()`), +the optimised individual will be inserted back into the population. The selection and replacement strategies can be configured via the :attr:`~pygmo.nlopt.selection` and :attr:`~pygmo.nlopt.replacement` attributes. @@ -4027,7 +4028,8 @@ By default, no local optimizer is specified, and the property is set to ``None`` .. note:: The objective function, bounds, and nonlinear-constraint parameters of the local - optimizer are ignored (as they are provided by the parent optimizer). The verbosity of + optimizer are ignored (as they are provided by the parent optimizer). Conversely, the stopping + criteria should be specified in the local optimizer.The verbosity of the local optimizer is also forcibly set to zero during the optimisation. Returns: From 093c1a6a081e4919562fba8050af02bf992c0e0d Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 11 Apr 2017 22:50:06 +0200 Subject: [PATCH 14/20] Another minor bit. --- include/pagmo/algorithms/nlopt.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index 377b864cc..0eb338d4d 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -1160,6 +1160,8 @@ class nlopt + (m_sc_maxeval <= 0. ? "disabled" : detail::to_string(m_sc_maxeval)) + "\n\t\tmaxtime: " + (m_sc_maxtime <= 0. ? "disabled" : detail::to_string(m_sc_maxtime)) + "\n"; if (m_loc_opt) { + // Add a tab to the output of the extra_info() of the local opt, + // and append the result. retval += "\tLocal optimizer:\n"; const auto loc_info = m_loc_opt->get_extra_info(); std::vector split_v; From e6b5953a6e6e80ce2fe1981ecc784955adeed0e4 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 12 Apr 2017 15:33:46 +0200 Subject: [PATCH 15/20] A few doc fixes. --- .../docs/python/algorithms/py_algorithms.rst | 33 ++++++++++--------- .../docs/python/tutorials/nlopt_basics.rst | 8 ++--- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/doc/sphinx/docs/python/algorithms/py_algorithms.rst b/doc/sphinx/docs/python/algorithms/py_algorithms.rst index 9474746ef..cbbe8d9ea 100644 --- a/doc/sphinx/docs/python/algorithms/py_algorithms.rst +++ b/doc/sphinx/docs/python/algorithms/py_algorithms.rst @@ -16,16 +16,16 @@ Heuristic Optimization ========================================================== ========================================= =============== =================================================================== Common Name Name in PyGMO Type Comments ========================================================== ========================================= =============== =================================================================== -The null algorithm :class:`pygmo.null_algorithm` SM-CU Exposed from C++, Used for testing purposes, does nothing +The null algorithm :class:`pygmo.null_algorithm` SM-CU Exposed from C++, used for initialization purposes, does nothing Differential Evolution (DE) :class:`pygmo.de` S-U Exposed from C++ Self-adaptive DE (jDE and iDE) :class:`pygmo.sade` S-U Exposed from C++ Self-adaptive DE (de_1220 aka pDE) :class:`pygmo.de1220` S-U Exposed from C++ Particle Swarm Optimization (PSO) :class:`pygmo.pso` S-U Exposed from C++ (N+1)-ES Simple Evolutionary Algorithm :class:`pygmo.sea` S-U (sto) Exposed from C++ -Corana's Simulated Annealing (SA) :class:`pygmo.sa_corana` S-U Exposed from C++ +Corana's Simulated Annealing (SA) :class:`pygmo.simulated_annealing` S-U Exposed from C++ Artificial Bee Colony (ABC) :class:`pygmo.bee_colony` S-U Exposed from C++ Covariance Matrix Adaptation Evo. Strategy (CMA-ES) :class:`pygmo.cmaes` S-U Exposed from C++ -Non-dominated Sorting GA (NSGA2) :class:`pygmo.nsga_II` M-U Exposed from C++ +Non-dominated Sorting GA (NSGA2) :class:`pygmo.nsga2` M-U Exposed from C++ Multi-objective EA vith Decomposition (MOEA/D) :class:`pygmo.moead` M-U Exposed from C++ ========================================================== ========================================= =============== =================================================================== @@ -43,19 +43,20 @@ Local optimization ====================================================== ========================================= =============== ===================================================================== Common Name Name in PyGMO Type Comments ====================================================== ========================================= =============== ===================================================================== -Compass Search (CS) :class:`pygmo.cs` S-CU Exposed from C++ -COBYLA (from nlopt) :class:`pygmo.nlopt` S-CU Exposed from C++ -BOBYQA (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ -NEWUOA + bound constraints (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ -PRAXIS (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ -Nelder-Mead simplex (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ -sbplx (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ -MMA (Method of Moving Asymptotes) (from nlopt) :class:`pygmo.nlopt` S-CU Exposed from C++ -CCSA (from nlopt) :class:`pygmo.nlopt` S-CU Exposed from C++ -SLSQP (from nlopt) :class:`pygmo.nlopt` S-CU Exposed from C++ -low-storage BFGS (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ -preconditioned truncated Newton (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ -shifted limited-memory variable-metric (from nlopt) :class:`pygmo.nlopt` S-U Exposed from C++ +Compass Search (CS) :class:`pygmo.compass_search` S-CU Exposed from C++ +COBYLA (from NLopt) :class:`pygmo.nlopt` S-CU Exposed from C++ +BOBYQA (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +NEWUOA + bound constraints (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +PRAXIS (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +Nelder-Mead simplex (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +sbplx (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +MMA (Method of Moving Asymptotes) (from NLopt) :class:`pygmo.nlopt` S-CU Exposed from C++ +CCSA (from NLopt) :class:`pygmo.nlopt` S-CU Exposed from C++ +SLSQP (from NLopt) :class:`pygmo.nlopt` S-CU Exposed from C++ +low-storage BFGS (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +preconditioned truncated Newton (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +Shifted limited-memory variable-metric (from NLopt) :class:`pygmo.nlopt` S-U Exposed from C++ +Augmented Lagrangian algorithm (from NLopt) :class:`pygmo.nlopt` S-CU Exposed from C++ ====================================================== ========================================= =============== ===================================================================== ---------------------------------------------------------------------------------------------------------------------- diff --git a/doc/sphinx/docs/python/tutorials/nlopt_basics.rst b/doc/sphinx/docs/python/tutorials/nlopt_basics.rst index d49d0ef6e..bf236ace2 100644 --- a/doc/sphinx/docs/python/tutorials/nlopt_basics.rst +++ b/doc/sphinx/docs/python/tutorials/nlopt_basics.rst @@ -5,7 +5,7 @@ A first tutorial on the use of NLopt solvers In this tutorial we show the basic usage pattern of :class:`pygmo.nlopt`. This user defined algorithm (UDA) wraps the NLopt library making it easily accessible via the pygmo common -:class:`pygmo.algorithm` interface. Let see how this miracle occur. +:class:`pygmo.algorithm` interface. Let us see how this miracle occurs. I have the gradient ^^^^^^^^^^^^^^^^^^^ @@ -34,9 +34,9 @@ I have the gradient maxeval: disabled maxtime: disabled -In a few lines we have constructed a :class:`pygmo.algorithm` containing the slsqp solver from +In a few lines we have constructed a :class:`pygmo.algorithm` containing the ``"slsqp"`` solver from NLopt. For a list of solvers availbale via the NLopt library check the docs of :class:`~pygmo.nlopt`. -In this tutorial we will make use of slsqp, a Sequential Quadratic Programming algorithm suited for +In this tutorial we will make use of ``"slsqp"``, a Sequential Quadratic Programming algorithm suited for generic Non Linear Programming problems (i.e. non linearly constrained single objective problems). All the stopping criteria used by the NLopt library are available via dedicated attributes, so that we may, for @@ -53,7 +53,7 @@ Let us algo activate some verbosity as to store a log and have a screen output: >>> algo.set_verbosity(1) We now need a problem to solve. Let us start with one of the UDPs provided in pygmo. The -:class:`pygmo.luksan_vlcek1` provides a classic, scalable, equally constrained problem. It +:class:`pygmo.luksan_vlcek1` problem is a classic, scalable, equality-constrained problem. It also has its gradient implemented so that we do not have to worry about that for the moment. .. doctest:: From ad8ca7090654e914d8e283203b8e1b6c8e3e44c0 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 12 Apr 2017 21:35:09 +0200 Subject: [PATCH 16/20] Doc/test fix. --- doc/sphinx/docs/python/tutorials/nlopt_basics.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/sphinx/docs/python/tutorials/nlopt_basics.rst b/doc/sphinx/docs/python/tutorials/nlopt_basics.rst index 6330f1426..c8ce0d574 100644 --- a/doc/sphinx/docs/python/tutorials/nlopt_basics.rst +++ b/doc/sphinx/docs/python/tutorials/nlopt_basics.rst @@ -15,12 +15,13 @@ I have the gradient >>> import pygmo as pg >>> uda = pg.nlopt("slsqp") >>> algo = pg.algorithm(uda) - >>> print(algo) # doctest: +NORMALIZE_WHITESPACE + >>> print(algo) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Algorithm name: NLopt - slsqp [deterministic] Thread safety: basic Extra info: - NLopt version: 2.4.2 + NLopt version: ... + Solver: 'slsqp' Last optimisation return code: NLOPT_SUCCESS (value = 1, Generic success return value) Verbosity: 0 Individual selection policy: best From 5158bb2caf28eabc72c650d6dc522ee8743121f7 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 12 Apr 2017 21:39:49 +0200 Subject: [PATCH 17/20] Implement helper methods to get the champions' fitnesses/dvs in the archipelago. --- include/pagmo/island.hpp | 30 ++++++++++++++++++++++++++++ pygmo/__init__.py | 2 +- pygmo/core.cpp | 21 +++++++++++++++++++- pygmo/docstrings.cpp | 32 ++++++++++++++++++++++++++++++ pygmo/docstrings.hpp | 2 ++ pygmo/test.py | 42 +++++++++++++++++++++++++++++++--------- tests/archipelago.cpp | 23 ++++++++++++++++++++++ 7 files changed, 141 insertions(+), 11 deletions(-) diff --git a/include/pagmo/island.hpp b/include/pagmo/island.hpp index b67efbe26..4d0d35f11 100644 --- a/include/pagmo/island.hpp +++ b/include/pagmo/island.hpp @@ -1383,6 +1383,36 @@ class archipelago stream(os, t); return os; } + /// Get the fitness vectors of the islands' champions. + /** + * @return a collection of the fitness vectors of the islands' champions. + * + * @throws unspecified any exception thrown by population::champion_f() or + * by memory errors in standard containers. + */ + std::vector get_champions_f() const + { + std::vector retval; + for (const auto &isl_ptr : m_islands) { + retval.emplace_back(isl_ptr->get_population().champion_f()); + } + return retval; + } + /// Get the decision vectors of the islands' champions. + /** + * @return a collection of the decision vectors of the islands' champions. + * + * @throws unspecified any exception thrown by population::champion_x() or + * by memory errors in standard containers. + */ + std::vector get_champions_x() const + { + std::vector retval; + for (const auto &isl_ptr : m_islands) { + retval.emplace_back(isl_ptr->get_population().champion_x()); + } + return retval; + } /// Save to archive. /** * This method will save to \p ar the islands of the archipelago. diff --git a/pygmo/__init__.py b/pygmo/__init__.py index cf7dadcab..1993bf3d3 100644 --- a/pygmo/__init__.py +++ b/pygmo/__init__.py @@ -469,7 +469,7 @@ def _archi_init(self, n=0, **kwargs): >>> archi.evolve() >>> archi.wait() - >>> res = [isl.get_population().champion_f for isl in archi] + >>> res = archi.get_champions_f() >>> res #doctest: +SKIP [array([ 475165.1020545]), array([ 807090.7156793]), diff --git a/pygmo/core.cpp b/pygmo/core.cpp index 9398055cd..552f7c079 100644 --- a/pygmo/core.cpp +++ b/pygmo/core.cpp @@ -809,5 +809,24 @@ BOOST_PYTHON_MODULE(core) .def("__getitem__", lcast([](archipelago &archi, archipelago::size_type n) -> island & { return archi[n]; }), pygmo::archipelago_getitem_docstring().c_str(), bp::return_internal_reference<>()) // NOTE: docs for push_back() are in the Python reimplementation. - .def("_push_back", lcast([](archipelago &archi, const island &isl) { archi.push_back(isl); })); + .def("_push_back", lcast([](archipelago &archi, const island &isl) { archi.push_back(isl); })) + // Champions. + .def("get_champions_f", lcast([](const archipelago &archi) -> bp::list { + bp::list retval; + auto fs = archi.get_champions_f(); + for (const auto &f : fs) { + retval.append(pygmo::v_to_a(f)); + } + return retval; + }), + pygmo::archipelago_get_champions_f_docstring().c_str()) + .def("get_champions_x", lcast([](const archipelago &archi) -> bp::list { + bp::list retval; + auto xs = archi.get_champions_x(); + for (const auto &x : xs) { + retval.append(pygmo::v_to_a(x)); + } + return retval; + }), + pygmo::archipelago_get_champions_x_docstring().c_str()); } diff --git a/pygmo/docstrings.cpp b/pygmo/docstrings.cpp index 0509d90a1..7bf23db86 100644 --- a/pygmo/docstrings.cpp +++ b/pygmo/docstrings.cpp @@ -3954,6 +3954,38 @@ inserted via :func:`~pygmo.archipelago.push_back()`). )"; } +std::string archipelago_get_champions_f_docstring() +{ + return R"(get_champions_f() + +Get the fitness vectors of the islands' champions. + +Returns: + ``list`` of 1D NumPy float arrays: the fitness vectors of the islands' champions + +Raises: + unspecified: any exception thrown by failures at the intersection between C++ and Python (e.g., type conversion errors, + mismatched function signatures, etc.) + +)"; +} + +std::string archipelago_get_champions_x_docstring() +{ + return R"(get_champions_x() + +Get the decision vectors of the islands' champions. + +Returns: + ``list`` of 1D NumPy float arrays: the decision vectors of the islands' champions + +Raises: + unspecified: any exception thrown by failures at the intersection between C++ and Python (e.g., type conversion errors, + mismatched function signatures, etc.) + +)"; +} + std::string nlopt_docstring() { return R"(__init__(solver = "cobyla") diff --git a/pygmo/docstrings.hpp b/pygmo/docstrings.hpp index 806848860..ad2779ae0 100644 --- a/pygmo/docstrings.hpp +++ b/pygmo/docstrings.hpp @@ -219,6 +219,8 @@ std::string archipelago_busy_docstring(); std::string archipelago_wait_docstring(); std::string archipelago_get_docstring(); std::string archipelago_getitem_docstring(); +std::string archipelago_get_champions_f_docstring(); +std::string archipelago_get_champions_x_docstring(); } #endif diff --git a/pygmo/test.py b/pygmo/test.py index 3c501e2aa..bfdd779b7 100644 --- a/pygmo/test.py +++ b/pygmo/test.py @@ -606,8 +606,10 @@ class con_utils_test_case(_ut.TestCase): def runTest(self): from .core import compare_fc, sort_population_con - compare_fc(f1 = [1,1,1], f2 = [1,2.1,-1.2], nec = 1, tol = [0]*2) - sort_population_con(input_f = [[1.2,0.1,-1],[0.2,1.1,1.1],[2,-0.5,-2]], nec = 1, tol = [1e-8]*2) + compare_fc(f1=[1, 1, 1], f2=[1, 2.1, -1.2], nec=1, tol=[0] * 2) + sort_population_con( + input_f=[[1.2, 0.1, -1], [0.2, 1.1, 1.1], [2, -0.5, -2]], nec=1, tol=[1e-8] * 2) + class global_rng_test_case(_ut.TestCase): """Test case for the global random number generator @@ -616,14 +618,15 @@ class global_rng_test_case(_ut.TestCase): def runTest(self): from .core import set_global_rng_seed, population, ackley - set_global_rng_seed(seed = 32) - pop = population(prob = ackley(5), size = 20) + set_global_rng_seed(seed=32) + pop = population(prob=ackley(5), size=20) f1 = pop.champion_f - set_global_rng_seed(seed = 32) - pop = population(prob = ackley(5), size = 20) + set_global_rng_seed(seed=32) + pop = population(prob=ackley(5), size=20) f2 = pop.champion_f self.assertTrue(f1 == f2) + class hypervolume_test_case(_ut.TestCase): """Test case for the hypervolume utilities @@ -1069,6 +1072,7 @@ def runTest(self): self.run_push_back_tests() self.run_io_tests() self.run_pickle_tests() + self.run_champions_tests() def run_init_tests(self): from . import archipelago, de, rosenbrock, population, null_problem, thread_island, mp_island @@ -1262,6 +1266,26 @@ def run_pickle_tests(self): pop_size=10, udi=mp_island()) self.assertEqual(repr(a), repr(loads(dumps(a)))) + def run_champions_tests(self): + from . import archipelago, de, rosenbrock, zdt + from numpy import ndarray + a = archipelago(5, algo=de(), prob=rosenbrock(), pop_size=10) + cf = a.get_champions_f() + self.assertEqual(type(cf), list) + self.assertEqual(len(cf), 5) + self.assertEqual(type(cf[0]), ndarray) + cx = a.get_champions_x() + self.assertEqual(type(cx), list) + self.assertEqual(len(cx), 5) + self.assertEqual(type(cx[0]), ndarray) + a.push_back(algo=de(), prob=rosenbrock(10), size=20) + cx = a.get_champions_x() + self.assertEqual(len(cx[4]), 2) + self.assertEqual(len(cx[5]), 10) + a.push_back(algo=de(), prob=zdt(), size=20) + self.assertRaises(ValueError, lambda: a.get_champions_x()) + self.assertRaises(ValueError, lambda: a.get_champions_f()) + def run_test_suite(): """Run the full test suite. @@ -1286,9 +1310,9 @@ def run_test_suite(): suite.addTest(archipelago_test_case()) suite.addTest(null_problem_test_case()) suite.addTest(hypervolume_test_case()) - suite.addTest(mo_utils_test_case()) - suite.addTest(con_utils_test_case()) - suite.addTest(global_rng_test_case()) + suite.addTest(mo_utils_test_case()) + suite.addTest(con_utils_test_case()) + suite.addTest(global_rng_test_case()) suite.addTest(estimate_sparsity_test_case()) suite.addTest(estimate_gradient_test_case()) try: diff --git a/tests/archipelago.cpp b/tests/archipelago.cpp index b22edd153..e0a1df39f 100644 --- a/tests/archipelago.cpp +++ b/tests/archipelago.cpp @@ -53,6 +53,7 @@ see https://www.gnu.org/licenses/. */ #include #include #include +#include #include #include #include @@ -378,3 +379,25 @@ BOOST_AUTO_TEST_CASE(archipelago_iterator_tests) } BOOST_CHECK(archi.begin() + 4 == archi.end()); } + +BOOST_AUTO_TEST_CASE(archipelago_champion_tests) +{ + archipelago archi; + BOOST_CHECK(archi.get_champions_f().empty()); + BOOST_CHECK(archi.get_champions_x().empty()); + archi.push_back(de{}, rosenbrock{}, 20); + archi.push_back(de{}, rosenbrock{}, 20); + archi.push_back(de{}, rosenbrock{}, 20); + BOOST_CHECK_EQUAL(archi.get_champions_f().size(), 3u); + BOOST_CHECK_EQUAL(archi.get_champions_x().size(), 3u); + for (auto i = 0u; i < 3u; ++i) { + BOOST_CHECK(archi[i].get_population().champion_x() == archi.get_champions_x()[i]); + BOOST_CHECK(archi[i].get_population().champion_f() == archi.get_champions_f()[i]); + } + archi.push_back(de{}, rosenbrock{10}, 20); + BOOST_CHECK(archi.get_champions_x()[2].size() == 2u); + BOOST_CHECK(archi.get_champions_x()[3].size() == 10u); + archi.push_back(de{}, zdt{}, 20); + BOOST_CHECK_THROW(archi.get_champions_f(), std::invalid_argument); + BOOST_CHECK_THROW(archi.get_champions_x(), std::invalid_argument); +} From 44b53ba8d97d1e7f1ba5c3a3bdf624e426e59ec9 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 12 Apr 2017 22:17:59 +0200 Subject: [PATCH 18/20] NLopt: fevals -> objevals, fitness -> objval, for log, documentation, etc. --- doc/sphinx/docs/images/nlopt_basic_lv1.png | Bin 23773 -> 39367 bytes .../docs/python/tutorials/nlopt_basics.rst | 59 ++++++++--------- include/pagmo/algorithms/nlopt.hpp | 31 ++++----- pygmo/docstrings.cpp | 62 +++++++++--------- 4 files changed, 76 insertions(+), 76 deletions(-) diff --git a/doc/sphinx/docs/images/nlopt_basic_lv1.png b/doc/sphinx/docs/images/nlopt_basic_lv1.png index 5754d9a009b890e57c9840d89594de8157a66902..81f1213dcbcb61a4e15c055ccf285712097a18a6 100644 GIT binary patch literal 39367 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%0DXN|V&D>fYWn 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�<@lYA;pJ;so^|F(B_Ncp3{K` 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 R9FKx=k%tn3sSi}&{(rG#y6*r0 literal 23773 zcmeFZbyStz*Dk#2Zt0Yi?oJhHK}EV-y1PR`X$)c`NUOATmvl*YcX!vfHa^eqyyqS7 zH@@$$^VeYv#)f;}vDRE`&Uwu_uX(?Jp)8AqPKFMFK(OTHq+dZG@Bt7A+&n5WIKn?L zy$n7OouA0RMg>1UsHS1ye>4X#|ZWd_gyl_5(1%t$V)$d?UB4ad=b@uk) ztS6HgpC&U_#y&8FOzJ`GBLUsK@op0>t=49V>R(ry{x#K$1?lP5T6!;y%ruCH)gC{? z%|e1tlfw$3&t!^G-t5_nMre}5u(XJ)G8p5JU+qccOY;U zFTl6ITOa-3i~kP@tgUw&UWBJ+M%m3x;P&=blZ~jP8>~hwqk|B|r_Z19sHmbwHj`|P z&;!_GzJ7gfYMMD=1{O_@*VNn`9}mySjWqiO#0Jja-@maku-awA*ohx3;^2d~UZDHF)i|xP zvhvxQ+|b`?4UtRwiGhJ4A|fW}Ddc>UrvZ~&JQfoZL&d`4uZd88${wJ+K1Jy{U13PT z=~oikUv64LSM%woHv1_v5QB!RD?g6dKBTFs$-~o=+o%~)z-Iij|y!v3kDi*c4W-C49=Pz4GmZkCxi8t^| z{PX99-`&+5uj}UP)9s1inwk{n{O53g(DX$3KJC_1Wv<45)PA9CP~-1;?*q{=1ol>lN(ebenR)+t?Pcn%RNA>PJv)bJ?p`Fpc$pR*{7?D-|f)kiO1 zzHFMiJr$hw-V^7jTE>UW`d!)9U#ybPdahB*L{nikepHd@d`qm4+IW3&^gb-i@L;i3 z*L}v-_Dr$z5t0-W9~*6q5ndpcq$mY8dbc$h{6f7C-Ggi}HXZF}MxOj{S4Q%t{J#&l zUW?DH&)}lpkWdkJBqZ2r=A*v_OY3*KGbL+l%f6QEtlYxw?(W_eLE6&yRg@flzS41t zY<6!npBw=h({Qct>yCaQ>kG&>gpCM8{91ftVXw1NVurjP(aU|IP_vhQG{g>Ow?Ge9~mw$;Q7d-s) z6o)^#!fr++Jv|*FFL z(VQbyH|B6bi((IFqMN7JmCO&5SY?kh!!_1O-fTGt$tj5beuoe$RAGR#=!X~cOgi)C z{PL2=qz#kGa~ZGNbxY>;>j%!OJ!!vw#fk3Keq`4vhwS(8_W!B#c7O8Z$$1O)t<;Mb zxI@{`d8;n_43Gsn{O-(tVP&f^V18_Ne?AH6GC}Cf#1INt#qlRjqu{ zdBnlFrG>Nw1>2@Yp;2y5i~Q2kPXPfC z1qFq5V8$j^_!K*1wTbLSI+`;vPnSOnQbg}eyoKVYSJ84%7^tk>IXB-1IZ0^7_qBJT zFz5b6h$v>JOGnTuDMSrLuu;btl9fIDv+RfHe!x>@j2By?p{DjsTbr!T>!i86yX;o7 z+-8CYrT{;G{!Ds+u5#A@lUJ-kM**$~Nn{9X*+FPL!7l-6Yw*HJhlBsbt?>Zl;~|w2p9?3qz1f+}{zs6M6kxzzM#^Z1JP}$0ffBpZl0P^Fqay`Q&hYRoSv0T$=wzcZ^YS9b4E(?T{{5TL zR|anX_yI)cTgjZ1TTO7;Rk&u6#MeUqu8cmrX%@KIU0re7Aaa8Ab@FLHlg)A}w$+=m zw#wEdk&L%If7-upWYAW5+VI8B`1E;Tg|;+}i5@1DxDa2Vvq08K*Lz!tPuIE@#EUH> zrSTk%3&9%O+ahnyd+ zw<1&;8Ww$N#ly?Vgp9kO2jt{H#^DP*5Jsjb^CndrgHASlDp;wnn0KkdTi3U3Jf;QjVlM-~wV?)T+0EKAvz zZ+PgD-xBm+KeqK9va34qt#;n3B*8~PS~4#wcN}?_97k7sy`z6y#!XOzkVgbFmN*Qj zOk#g(^sI34gb9%vvsEEt;z*4|f3RFmftXDRsV}r1u}P9WVC`W3(LC1DHbO^NX=YRU zgWdkeg;{NpjbOK57ea)3_f&g3*V45F;^mu{ukg74xA9r&edc$q)XMj$EK{*IP^HhI zUcD%Ei*=dDh3M5zSK4Tj?BQ$Lt^D>u=G_1{xSb}}D29`@He6O=(y&&m5KohoY7b%sr%5BEQ z4o!`?jHfuOxjVw!!qx2#*21r~S_XGKAAHd`TByd9(2ajAtzlmfhG8+epK?4#4mRulCFTZrim!fYmW#)KNVy2&CYMm_3yW9CV2#b= z*=U{A0X%|PZu_}<4=*o#Lc-S6+hZn3x$RUN60vSTMTOu@jr&@P_ih?MB?~1LbAF=0 zj4yHeUtky+8M*Aue!beQiJsKHX(*~1Mq*nutrfS?m0(Nm4t?ufnf{KmVr}12K|tOz zRMY?Xp8>{i~iM}in-OSczxsM z5VYmxaJJTS+I37X za@PB5r{ZE}&rjCeoUy*Xt*rj~P%&His{#Pr4RPykL{}2sPl;I4NH6F+lrY{Q?%b8Tg_Lp$i&-R}w|olEFksCwU?E zIAxe0!YU3ebSO{@l3B8A%*qk3)w_ zg%L92m8g@GU~c$^7+HV%tt}2^qy?t+^S*+1Z?w##IL)<*MzG{^**Tn$-Q289V#Iv+no0 zB9KWN0Uw_TXhn53S!he3>Ty`=m5$Kb!;T4f%ekkMEOG`Igh(=5^QS5iNAMX@$jwJ- zV^#CiR#p>(w|VLqCSI;4N`-b}{I00Erw{fwzSxZJaHfCDOG^Evc)?ur>FpLTWdF>? zMAH=Fh$r(XA{w23+<+JDnFwEami$bS`Q}(5_3Z2{n?e0`2*-R=FbXfg)hsM5WZG~V zh5C^rR$2acx8|R=@I_o8x@A^`oVEM#ot>SqJ?wE{oc$a+x}H3y?;T03?-4jXt-H9m z=y5WlLCR~c)N<9@+NzW+6kF$gcG!%;iTmKe@N7d{r=a7S>eXe1(mZr#dgoP%o z7s+r?Nj`muI5L&}!w6xN+M1**QMsB!hmtrUp^)EA4}~bOofLk`b+QTQleXG)v!vx! zM~(b=mZ!{ab*|elWxq&=@ID-qM&eg)b=0%W5l>IggPOUUC|PRX2O!ew6JDc0M@K*3 z$Vsi-$cB1=@S$p_e9B|1j|@RtVA`ph9c!jx@Dj{2@B4Qe4vr|%-OAwH!urdQ zGh%k_-+5Xk9!rsea&mHpyVF%li9BzC1tFlIz?3pHq|qt2dC18bom)`-!MdQTP1s&Y zP=#)|H5uWfAI`{1OfmMLI$0o>1?z)t9({THz*Z|ITH#SW3Vv*{q|w)$l&P)855@jK z_oeJPD9MmXtg&`GStZHPaY`6ULc}l3fX9HTwt-DIT%&mz7tQZ=5h}mZ&qXRYlFvEt zVu?Jj5854_otNG-rO=3qLW_#HcI(dceSCa0%WX(;B+h9vWTV^K+e5)*l86m_2Dalw z%anOB#*K@U|G+22#5ArCWF!jOQyJzxen3jv334Q!+r{n5l^~wrO%U_s8Pj#{m2b$X zu1?zA+Ozx@VPE(uU6d|ClX zQ3hcz*kC;=;x!8iBUTLD+_7cWWAHIhXn0{ECy4q#efqTDf+M*Pox5KBH0HBgwdQfT zkuz0$YJRYR99+ny56PN{ISs}`J@d!OLV0Ksd2k6=G)`kfA{{CC#AsM(pWpK^yi`D*&DbY(=_$37LKI&}(e$5uS{-kw-xMleDm~ zj~k_M`>*{ZW_)@QxHB^|`7TOQQc{2mELXF%Z8r|w^^F({(}^%)(26M@MGfAtPBUCP z;0__5lisxf9CQXuJ-S6z@IUbSM=%VPJ2dHSo448Ur0d(87!;z(Gj zIEP23x~aRETW4#OD*hgj@&J~#ou)`z z#jm%TtYg+iY~L(T@KpX*ICWR%k2R_fg0io8KwPj)_)3adkw3$*8l`{#42oB(Jz}#T zF)8nlEIr_>;)ZaJnyOvuC%-5@uPbh_Am$V-pdxL0jy3?2O1xDHcxy2qKR$JIJr@L+JCMK0{-`1_*@JyfMxl^pHV_-{N;{AdBO#k{$hF`CZ5(0Q{gjnPKlYx3+%d=kwQK z<}B~-`aP-G>)*J@hm6x!2Wo$ZZ57Hrfj(Uv?3w}o>CwPFr7jGb6LLD-Y(Er!GmBd# z#dBn8tZIUYI%v(A8qIZL-XfjPr%z3?<+FWbk}C%b)h?sE zlJyAkrh@i%Bm}J&H^HR1o6*mWUPvoH?Y9S!-2fJSetJ3jF9EV8DxB*#c@3HdGv~=N zf%{@RUGN~+SOSs|AQ$mUTW!o?daOl8*OZ1Xy_!diC1&BJ-p1;dk#QF7qRsKOI>ZKV zMrx2T3aPm?GxzuwAgeB z_1p##Fp*t&jE9Azno1uRzxb~H1WzOVpN)sF%7@r&!X84dOobYqUq-@@{p6j{S$(hV z_sU=IdJ|i1lqJD^7FEmf+g#4Ln+;twWP}`9(`I%Joz1yH47&nS#V#i&^DA3R@7Fg? z+$?D+-=)lfa6X+}F8Rq09vz}UDQ*fqu9q8kd1E*k$oxjROz5d>?qI(<>W(C8cFFkc zFvqNoCM)e~<} zjPLD#9`kAkuS4P8aakMubfP>Hhl#Vz-0FHpAQul@t)Bbc%wiWAkmbaump!WyEt#ed zR8Pazn<4h@Z6~{Z!Z$%eIV$d6NEhWB*qCZ%CopH1HRXKaW+Nv~E|I~9d(e%}Y`Qi`(|(viVw*~V1?sOy zDdvQ8poJ*JSlC7iHjkFBT-t|v*9RqEsrF~2+N>P0Pe5R)RaMo{`Gr-VnK9bFC;x5= z%D5J|&{OjKU{dQyDUUaE@7IuINT-e{tr@mw`^_p^azSLyo*Odq<1GNzw8A$IEq<-- z$2bQ|$@PzuhnT`bW@q-#oa_h7!n<-|VoywF&s-U7sGNMMQ15b#c4_ zDXh8rxps_hH(+By&brp`2sP+h40@*+?qd8OvG8>rM)*RbUCGz%#2p>2a3oPFt+rz+ zh)|4LBED+dJjW3E+8LYVC6G-G6Oeii(IH6aO5RES=(T#tK^uVP8!VJ#kK2+_EnpSf z%^}hJE7eJ>T09;L1)JZ-8Rbt+C?_!v+REbii+87(Lb-OUdu}TS^OC9{$(>>GP0)Up zCk{Zn`~%i-7nc8wsq|-eL~$_bR)my?1KlvC@PQZ<1rbMr3bkc1GF4pHT4r< zr^w_J&5pqIE}1>=nkkZ@Tylh#YpsgHFm@D0vO4Oi62+(MTjbkw>Dpj3S6`W_0v3fxAqv!e!FNgvm`_QAS z{RU#JKYpFGvj5uC-&w*G4(E*nT$L*xLSjZioiFuF{6>8#Q1CJuYplK1ZP3B0?bh-D zITy3$d;+$q%E19u{CdjGN55{SDLJdg!+FFH!ia-uzI$@;oC^AAF!A4eODm_domm8O zwW5Z#k}{=O9EBK-J!!6!!AL88-wMS!tWha{scMm9Uqo#`C-MQ9E%SH=V;1oSLWqvE zV2m7r8dcD=ZF^00i2#3!Cxtj1z8@9>2ND$O>FV}MhbO#hEjV)dv-(hLcciCa)O_u& zcn^o-Z@zyQE!NygD1uUP^ajh#2R$ldYX(WWIiberoPB<%cbZHz@p_QO@5YD+%KM>M zd;TmQd$A9#ZqkL@8qXr@Ut4F1#GqAlpeJ{W@|uXWeY*DHu2+T{mq$$1i7V}h>&f-p z;?A|`1r0<4Yp=6J1FN-rhX3q6(b)gG{#lu<;T2QSE>_2s+qFmGdoDNR;~LaFbQX5t zVm&~f^Y*+|8Q8W(T6mA6FE2xoD_3X#EGGSH^=WMz7Xps?i*B*TPv({|Ub#mGa-TKs zLz@)C$t#98hoAVB7!rVL724_9a-@z75ADyR{w^Xo@rK9u-t2v8@gnBy0kW0!l(lH!WIQ<$n#spt2Q<(H5=d z9pi&-F~3@STKb*<3F69aiB@(seZBUU@K@*NvQ&_Y=;()zFXVFlWcR4*Lr}abHu;}v z|Hrxv_EBKVUCp8f)il6I-R|Xj8;~4XXPEsN>L11zrLCE1nqKktG`pA;sH%sKH0LiF zdpI~r!uyosmL;hCI}qdarw^}HKL5)FxO%v;S9F2!or6Eh6RCM!G?Ht5t~y_{z;NS~ zV)oom5?JMYx85WX(JKsA$ol3|UmnJlb4$XbX&b9z!lU(nfP{3il&i4KMNV<{d~s`* zEftrINJJ`{IC6qc>i$g%{O$Q1<#sP8$~XTo>SzOr1m`PDZY%E4e|^aB_v2fUb_@K4 zQmdCs?NV zk-?olLBd)>Y>{mg#%cV8!wzTL)NtA+9dZZWPZYPeZ4>PI{O z0TzqPA5NPlJ!5|1ZvKog2^`?s^&{~k@HkJ>NySj?DmOM75mX;6* zP~Pr`b|Vk4%H@|1UjAoGQ+}$O9XGc-VT~y}I6r`kCnJzaZ?kF+L;JvLop80I4@yqT zbu)oAz0I~u#tA*Q_k4+1RO_PTAh4nCyX26p zrO&rgY?#H-DmF%1Sy|~xl{A2R@%uC3uDFQNa_S2MTZ*kwls<37clujm3oI5GJ~x%c zbY1;OGEJ3gC4n68_hF*r$Xf81gun{B0;HLmEp6S!S@2BN~EX_do?z z04d~~7&&NeV2=!MP#Y{XT;d;?kD76SiGvqI{-cX!v zV56Op5$O6-l^G1L>`^sR}HqiZ{FgcU1tSWSTP^Hhm!d_TTM&aqQ80NFUz z9AySEF^cfk<~RmLWudjQF?~8lMr2M-PJDcPIRyotv{!f=eAht>L6VJ6ryd?K@9&y# zbNxCW+xLRE(Ly|N-O=ceZP`D$Ys#gI4sA#qYrY{j>`ILw>I$uB$@1s^Qu%`|h|oVM z-q>iv=8^EYUs*YvJM{BZ|3>RsbaMaBl1Y&kyH5#Sw9cG-dfv~`?L%o1wjAsLLPEmd zt*zYlv$Y@V8{Q1if*eRItQ-#Vg<;|0CS9MHm)F(=lL@G)Rcvgw(F(V>w@)wmr8DUc zkAu;0TAoftp){qx*7WH=pm}3oC+@8BLc_L4-A{OyBtz2xfqSiahIM|k;ptey(pt55 z3$XR8jFp#^sFdhsghjiq9>Ul}a~Kz*Qw5`Lxa&(zsv4LJ-6(ww9sM6cjicw>fq>uD ztwfPvBSu7txTw|G2vp{kpaS^|nopj4_DH^<)^EGeozzD6ev+Abkkf2>+b`9C0TmFP zSP1Z)Yuc=eDc?LlxQ%ZN360R|qZ}^}W>E6CX8+88qRXGoQ1#`RRcgCQ6{Id=uderFE8K1%FA0aaS|?vGJ=a((X`AIh1^sp zi4k-saYd6*fk32-ag_5!8QtT&1?dij_oLBoc6MvMZDIUuf5@4A2wny0M_Hb+CdtCk zhDb?~t*WKSEGQpWf~0ad(){PWR~L_%Tb4gaix4qsjeR>2qKGQlL_a^BvWNN<UW{Ko|pUTNw!Mka$2a^qM9>?!*`9I z(Ln~$qxpj0Idu=vl`iYJ1ss+nzZo*IRgER+iS0^|IOcMn76(@vDLt5ImGwBOd3{@C zD*6ykUy1CS?TvI&dJA&nw{J8y0^}eU9T~N@wwC&b8R=33}E47}b)HJ^|>o36!rB&eoKzYtiq(oXmHE{v3Z_#Fw{Ca|15 zoC&@hU^IZ7*PYgLcuNi}TEbX@*PovZLfbHBe;O}o{(+cKHU-AFWS=cuNs@P$L8PPJ zhTl?4M{(!OPO^+&#^TB0?AX`rkvfnnUC_B~&1}XO{3tn++_Rkr@EpK&O6%a&6J(0q-lOeIlB9bU&c^|1nS1j=@wTLKbH3=OW=up)1*dZ!`TkpTE9Ts)WmdeL4oh`9#3_` znXkq5HnCM+zL1TGrj9*_v7J1jMtyx!avY0HyUR+jD8asydvjfKtBaSPs3*k5tvXOT zv-79Ie52L6t6gq|pDQYMXz_8+uep{5z9r56mkym2VI&h)1|^~u5!SPsPqox2!SR9 zI>+G)--4|8BIj5e-2NH$kjGhjDUpF{W(kb;7<;-#jXtCwT2s$k9c10LFqIS0TI%j= z=|Wo3Sh(nY2p9v9bAN;F`CMD!{1>HiYiTeFKO>NzL^r0 zWgscRmBZ2aBMNMfH!smUH$t_4yors1xlZXGv$7}CCtS{SN(p9c#3g&8Cb`vxRKawi zP**MB>Og24BlIX?Q$*CgZ?2<0=un+QKhxwDjF`DLzk%~!g^b`CvaWd^W#z3saGsHS zDK}Kz0npwqRzXZA4xShQ*6i+N@sZ5fsVyE24&1ZfoHs_v?KDv<*xpegMCz&DUUAh5 z4`?c^v0Zpn9`xB}gl(=qT}`8#7WPK~rFYvGm2&O(v+MiOkL0jtWs)yd29h@&*&I{(C_HV0NA&2q}CX~MJRb;e=cZpD=;jf0p%_`vw zBQN(iZeAo0EGwR%AFk|GL;`X{*P-5p*ujL#8%{(=HQ1spUi;LvtZ+6LYCN99h&%{{ zN(N}ikj%6&Xg#m)0cHAHpBo1@BF##P;4zI|>!s#r$sb`XMi+{-rwgmmky$2QsW_+F zCP5vn2m31-&nI6lgWxzNNdz%~_{)y@!D_IrPDvO6Op>utOiq~7m(0_*4ALqAmt=BY z1o~0IX8r+(K|_Am0IN)S)9Zai46UtvsTSmEW|i-S_5N7&)Ye_^zrh6ir*r{Rv6 zhbM{85&i2FuYhckDF7FtwF z^KI_hO{4^J6xP(xpK&tO2Tm0S!-Q=fjnQ#DkhZ0%xn($||J(^&)h9Hb5Gy~Uvt)bWwX8re3oyLKWRTpNd7n1y?{y@I7 zyj$rfF+tR5fam`V>8UrRNwHHO-H`-YDT@?&w!gbdZq2;x$+p@)w9H{->Q8=jaox0F zGQ#4CrjqsH8bh%sl{W4Wlcp}^Y!%SBU`v6ScDfv$J`!A zy1SG^pZ{>^Q;7M5G~7A3>GZyL;k(}v+BSAv*Nh2?n5qWI4Y-e{gZp#b53a(m;P)@P ze3!qxPSpJ_H}&_h>U^k$YgO)_rrjeUIM96b$5@qx&Es~!?dN9JZ--G{ne&z zl8~o`t?#~$O&geBMcL*}XuNS#qs@Qv_ggli%0)HQ@Qj+Qh>B^0<%@oSmm2nhH9WCI zsxTWPOz3W(P!Fk5!9ms;s=E4Pa1P+d_D^YTrTfcW;OUB$?*hES$>XO+ozJ({n1AH) z*RaGA-HS)~5NH>dmOzOmZej&6nh7wVw>;irp^Fy6LG2CpZH~)D6AbV9!n;wV0{E>* zW8&hPKM=C^Z~*Fhav~H(Uyz+8b?}2af7bD>-U5Z?y!+Kie*69Vn=jZmtZLZq zK~Cx~?{g% z9gwgYm6JO_5m~Fq@Uiu04qDo;<7M#%eRm}@C;_&;-9CbwS_`{h}ed3-&1@MT8+dna@cJ%k#3o%JZq>PV`6X;J&OhEGV>ynn2O(m|j zc~g8(wE%}_l%oL(jG8WtZK~%)Y(M-QsqtXrCg!WLlN#-F3rA3ZDG7gr4m`zTYj~o7 zt#(UsNC+aJAIon~36I6RV%Y<0dVB2Jg!Uwb9!B_%($v}A^ZgX6flcH1Os90G{vp|Efw zpas4&QP2GhF!5-C-c+VFsM@_yS4WltplHtbjPmI_OyQM_e(^I3a4lA0M@Pq(8+_oH zjkNNKs2YqP;SA5JB#A#zz-exFQFLv`4Ls>e!nd zuMgyi`GYzbD3|lq^6>KN9E$=zd%FNjHu+ln$x^}O+$+n2zzF`L<8AM6(vwi=1X8I0 zAfU7wTMkwM%7%IO7xw4%<^KR^_UFKCrnPgBb|8s5C2ENWH+a{EnJtOpiad~+F#@^$ zoM9s!NN`Y4(S*(N$_l?oN@}YAlUx{uUIz{5cy;!@ucjS!r;eQ<2}*+x;viIjLjE@< zr2xds!-LCdS?&v`!K_0MAn*V#XImt>g|Qj%c2<7A`qY3-=uvRZbBfFmR7WIGYYP%r zIm^RvMYXlbpFTZ!^(x}WiKV3_SgM%VShz+QPh%^;B0s}vA z)3u0C+NWmPvLlSY+zgkxT0WbE-ghFfqHip=&a9on&tGKK{K&6>>c)1wzqfVT6XFC{ ze`dylOPkRpCco)r{JywvM=9k290w|;ZIs6DIly$Q?gKFr*}Vy~L;xJ+bU+h*yTV^v zX4L>}3STitO zY&#%e_i)Hc-=Cj# z-r0m#3U`qIiJkuG(GqGSd>A{Pyq3H9`U)6COzMBY!Q+9z4CYfe58>c=+`fcAd6Z zI$20*d2Co%2PxkyEyq)x-|{uqZV)yW7Fs}BxxKk^-pGoN`}h%|@vHl6r@jBxlsYR+ zF1%!vCN_8$?%&uKGiUfkArja7EtOSOpr0c!I$EXWU}Gq!#ARbpomm-n^3_Z$Lh;`u zOD3N!9@9?F1l;~sVZ$n*eKXP0rVkwW-CV||q|7chgANaon))U`T|pN7KSYj$^-1?8 zm#plX(s}*QLoso2o!6g$_sA70f2ONo&TRlDYRS}#9RN}Z9D5CSel7h(;Yc+zeaXVk z34E5Xavy*P3{D`qA1v-3c_DCFY;eE^(41dIoebJ80RPT>sV#DNcz7SylQEp zUZ@XPhM<+o#p*114ElC!NchF;PRWlvfNdNx!645xwoTMHKCTH&Q%eXM!TCs`-!nkR z)6jDzgbLbctE5WgYh?hlk+ZtC(H8tMl6_JC){WClt&CBF{_EG)G`yT%b!}}0DJcj8 z0|O9KE7*zBnFzW070v^SxpJ&|^MM$bJX`5-=JCUp8y7P{9P@zm_x1GVs86)8DxL^5 zZwa@The3-C3GDA;+Mb(aplvLgxoHi`gQZCEzrA3hDA8oY@;5Lrn44Avs#=+8qVwCf z?~(a#g}2AP5tfV)@a^K&?m$I+YYK=J3uYn zFbt)n8qwN0YWJUjPMQRMYa&2yVrNkTrx8+4(e6SiJ+CivXRdF`U&3zuqC^Lx!az?S zGE-BQ0~kGW&ajiFzIx`(8JM~W52ArV0$NDffHg4BYN8X0o`zCFN`I>*z{SA)9wzY-HSl31i?^STRS5) zU!4_DNE^Fzmz1>Ts+^gY+N15i3j-Gp7aydiCmKAvHO9H$XU#I5<}K7g}JP)V#dB(Q87W0!ce5M_vTWYXe2klI5(0bBP)? z^gXjW*Ch5E5iIn2IMrUi1`-4Gndznx4Fliit8gNAV_^1c58BA->FK%LcXhLs5}S8t zYS6H;kt5XWs;jFBB(6@z40t{E4XHfl;W#Si;lU3e12*>NPei7JR9Ai%pxdGlTpgXq z?bQDA;RH<+iz{}#HEbJ*eO9cM`gLCXJLyCiS7&?nZZC7HSN|I_>8na1^mSxFim*{g~dq0 zS)r9G7#LBJym1NN1ZpBozAL~w7X5y))&6uJupp46ED0&y_$-Z#9-f?>goK3fiBRF= zKe4m3`&h_pJIRNPL)FtOIM*Y2dk{V32G^e=4vmY8d#7P;ZeFrQ3>e@)w`66FOSEDA zAbM3_# zd_h&e0F@|#8Km^4hdyQE@km=d=l(;+04j@AnxizXbjLfl5^DUZakmFa(q}d{Y$(_i zjSCK3#=pZx^A0kD{wIJU>wYx;rhYP|d$Ctb%&k+(N1WgCWl|wf6c};a|L)9xqTH5T zUtiw`5Jy2LA&=uCEXz3wyCKl&e_3Th=6^|Lz$989?ZGCjvgG9sYsOd^arH@xvjytg z1k@Msw?69F*AYTMn_v`$a8PP0b-&mVX20Z}PwoQ^eSKvG1$1U+<^XOqVBp?HV4ARJ zYL%ED08qybsJVdq+zE)8-@kvK2NcyepJ5MjBvQ|POWrOGS7hj0T^{<8$kGjRd~#x| ziO&ECPFk1+jzQ1P~a`X}CQpg#7~=iavtq zVK7T^1c?nm`9XdhAUm_N%jU1ynOxOTz$%gPN?(U%&!JtXZThbFbr50XEOT z=inJASwLV^&O|^u1v*K~$oS5E)-z`W9RyvmOQ3NIs}V5gVBu61*~WL^+fu!Eg|Vq6 zFiB>b>}wB@bt>#uK#}1gFed+8BO{H(!!?`=GDXz(B5#2F9|QS=t|4x-t_QSqbe@2> zK2hyzZ9U2WRG7)ASQD_G`AMada)FC2>6f4beVm{bEjcrj9!RV&=HXKhz&4drQL*nO z1CiCTMFGVufdLNr6$XoD#a+zAiX6&tdj32-0zwt=sG3zy3>zC8FwT4}kN_a?=ag*% zTN-Pyf8?^HA$x{+>rh9gx4d1}rQcXqR%SC@8GEud<~-jB$KC?QGuURQ%vBv2Z`+^s zEpKw!Dl^0KB}5k#uiWQ+1z}`-fKtz|u3+t79}^QNv`;{fVTU{>_FnV;cd5;7?Ax-J z{d&Tpz4|C1Ul;^fa4@R|wXg3S{-Jtscxcr-307p8&kiu>oMpH{`;f!lZ;A0dq7ub8 z?DPrt02Q$VL=b?nY5fYZF04~uPyufCtDXQ`1R%)#`Vb9Dr#>a!vDleFR#>4- zE#NN0l94#w$;TohAcjrVm>+XvqcYVP1HthyVbJsiKVLI*K_RAV>BK+y@*WMk82Wmh zbJwf5Q(&JzlK=QP)AAnoiBv+PcQFqK-YHpa?J#Q4Q3l=vG8FfPd0ek^?^l}oy?~vS z2=*fcmItAwOs?teI(``&P%K&0k@)w{@PhQxMrDmjzR$K<9|_h_I6B>*`c(=r#u>3t zkqsBT*wN@fTN4cV%2Plw4;>F5gSMpS1v1Hr_m5+81g$5>n8(@6a_o0ANhWT>i@*O4 z4-GmpSj1Jz7~HY(_hGbSksp48W&j{dNwavMC_>DzPV!jg%+LlVRpX~{n%_EUoY4P{ zpr;qD%`}DqTJk9OC)mx7GXESNpPg9;%+RhRuP>RVP;ifjfA0b7xfvQzZv=gGAiM=g zsPDec>OBxYmp4?Wx6f-Xs0UZDFKFzQZ0`D`ONJfm^804CSNgH_|Te!SxcSF9| zE2qHL7*5cQ6e9S0Zx%qMdAOkW9i;NX^Z{yM`Wp(!Yq5QKr{4Hho^*T%+ynXV+1sFp z@mg#K;4H)5dRo}o_ldv3_ZME(jw|~q0jDTmmi~&U3fcy`;!dK(4i$2hfJg(o5gTFd ztU#~Esh59@Pefl=x99uIjIiivCINvjpbKXz6CLQ)WLx7O4!~_usPomIIfOtcEMV^i zm}vs>1#lY5jb`xk2smPmpi{=b*%9<|tLJM!12%JOtZ;3@A~ox~oC!VHs?Nd!q|2%D zq{AaqU_E!a|q`-X@AuM(?O*&Zhc2P5bh2n$1nb&dibqrEc;wq##p_e@(^1})rkWIQIc|Gza+ zXB*5pP6MEL7T31`eu3`FC!pIBgP4P!le2KdPZ0FGTR1L}nDBI*d82oTy#|_O%qUob z0jXS$Q-u90U)+Xs)_2Uf&-DU~1J_7yU)uzVFHJRj_1&PDi7W*gL&LXF(a?;>i;O_% zfy1N(yglB=x^a+?aD@S`}_iew(Z`N3cV=NulN{*eIcSPw(w{GPzqtli@MVmM@# z`cg7+k1Ox?h3knKm;2}~P zJp3hPq|3p2?ZZAzwod$9;d3aBo10cuGj^?QZRI)uLgsdLbz%EQb*~J9)Ke+wF9yil zqs7G2v#KqMGPkspFOpJJowM#Z4)i${u=>gP`PGBkRjs12@nWw(Hw29fMSyl4-ab#w>CFX10ew% z3>`)qyJ7Tost$6j;aw*~$AA@uNN|3GYN(_XL4ntYVwWmRao`oQQf9zVnq^qi` zL|ivNnfIsUcXcUb<>Xl3T%J_;qLV-Zq#WnI>3;}EA9+9NJXus2XAQ^RX1+gN0#(=0 z$oc;L(a7kilQ<3JTla;3D#dUrIyN(}uy$@rNuQb8wcl0e11<=p^2Ui%Ab6WCgY>r* zDtN<)=5SUo4XgVgrZEGe6a>PyUECAiOXK|pxfmaw1_B{E4gNH09>e>5t;~p81-Tgg z{^01PY0PMjHoFDcIM4k}bai;;UTQ^RYYbo-Bi-MJgM&p$lESB$G=wQw6CMs10`Y)8 zmPjZ4jSss(N>Xkk{~SDM>ueAcfBpUtcFprDI&E1v4K6H-{re+v$NOO+ib;<*dpS@f zq#=k9NZe%7V`Bz+*6^rAT`ZsX5L5`HHXDo`4ff1=S9=A{lO3vTFxnFXZAf(J6Nc+eaj7STc*P9%b*Xs-_$VLyiaPc5u z#muP4<$2|3=pgWrfbLTM;Ng@kQu=?s;;=>5D*nh6j7SE%v@(4fFTa;l#gk(eWuV2)zcLPfZ++o_39o|?uOCq0Hog9$`;r0y=aj5Kq18Y%zkgL#b9W7rfm zR*I&MQG03NLSX|f*jNB}sA;%RN3ldkLxoM#;rUL|V-h6HzyIh(>)9Y*sH9-x&Hu#4Uov;fYA>t7(><2G^mI2idyW~Ma2 z<(Jn-{e)Ch1;7c-g`mSh*2%I>=^u62sU8Tj%wsq@fijzzWDE`tmKrt%vDPJI#ub;y1Lcqal|WvmxSz<-nf`v3Q9^EfER7df-bP)p;H`iAKsN+ zAUg3Ci{pL;dev(a74b4dB`A$w}=!r|t1#@+%(jdZL(FytHb`6JuUf737Uq*T(4Q<|OYP zLVHISc7Snn894)X-{o+*bIb4D(8i~sjqQC2unaEIQA&gUJNd)@eg~YrU8LznxScgajWy9R$6E4<2}od5Tei z&o|%=4~*)_K*y6F(_12)A#U?r(?SIJ_&ieE9?Jh0Q=R$$ZmI)3v!}n()I7gTzmA9+ z;Y-xR7HQ-xfSa!KuK4xqS8d_Z;UT>|Xm%a~gt1RhXfA*^mLL`PGcWJ8msj;@p+P^) z)cSpMm;NrpC~Mt5#t*FZtNsW|LpS+KY?x=8c;=$!>gxL8!v~n_1z{90aS+H*Z9~3N z!-e+`aMf1u+VvGayI;eNDKPyI7hBtk;0fPSnH=(@!V>--8JMl8U`uPKBpVfcMfH?r z+`#qb4w8MZu*%#LWzqDOE%i~7-{Ds8)m-`ispQJTp-}gG#_j{fG z>AGGs@B2Q_JkRI8@6YFcKKHfC)li`U1u4BuZmOQ!ef{&%G5m+=W7on?ISI z$|JPxv*^*7Y+<;yVtA~Mvp@V^^m9kZ&0vFg^(jqA&dBUwH15|wgdaI8$_=j-$$qUt z_>A0J=f1w4SgcQe-IUL_y=;J+`1!<5wO}U`gL9m#>?eJTn#*H7;*JbMpczHHnzNWp zl?}P}+VY@#d#XJ@h7G;iA2fe9XKCi$mYzt!<^I0TM(O2qiygggoL)|1AB&Muhqss8 z^N<;nVm;~^kO`D;-f%)nut>LLr_Xghp|u1U^5*?|)X}f+)9aSe$Y8(a&500q;DWv- zwq*pS*`cM`7fT+`-TzA0I_K72Rk1K42dM}$gOsK#>S;43)kn~+(@tyb@VRWa*nl62 zSU>1rH?3SLw)rZt(B7%O$YTeQ zm-tEUA&QyqIrdLUqLS-Md_H<&ua-KTE)b_;no1RRqT=>y=E8}86HxBNfYn6^`G8rI zR8<2YHak8yCm7|!%{}hCI$*uTcPCleQ^8g2qeAkwa9v&9U^p;uap6Kfp!?S(D+f~- zMlGvn?t9Cx9{r`RvXeKE82!zDP-~y$cPN%ne!7*Qd0Kxbaw|0x4(anJ9+XC zn=c$FCppi!TXeIH!u+r85A9;rR!*S?UilY*w)$*&IjH}NdZA6(U!|VF(LRIh3}Dv$ z;lqb;Q2SM0h>(a#%XGjYW#u``7C;Y;{gA-mx0|Z);mz<#FgNjzTPcl=`hc_ex(F_- z8(}^97VleHx`5^{cWak~>vEc!wiXtw4a0;-kBXe@Bkf;;Y!*a<`S>7ca_n(Kw8-k3 zCM=)=@i1?(lt~nI=WhWV@reluPtw!V3u<({hK^~iPL&12iE3#B;BGo;6&?$43=j{z zYJ4`(fF3hXDl2Wk9x5DKL32JlIX!HiL8VgN353O(q5_6&GW>WkQ;$o6ed{^amqxqt zbpjoBKCZj;;5WHYn4WWWo6LS5wcD)jx*2?Du=BPl}Ghk}g zay^>c+pWya69b*aL{Z^szHY??v7ZEsgh{7u`O{UZBwFTHQP%eMiX?g=D9izqE-o$> zHvJ{e3{Uz?JWR`lvP-d@U0q2q4RH4R{c6qss}1bbtZNo&9{GiZ3z(*Rd#hG{422Uq9N>oG?8rWd3*L7{_+*dQ}kkhZ*4C{P1aG zAO>=lLr{F%x0~wgN5aYW*OSL29GU5nJ>Rbqds7g5{*&6?qwJ zOpG)`Ayl%Jecgh5_>2or69pFN_Y7 z3P&6QGw1rs(z>qmrkI?qlq)YrGU#x}JwzH7-Rd7+a~i?*(8tpON~c`a^6^)cD^n@# zAQrk`naPiiicW0$bI0Og^pqij;3FEPw_h4@3Zg>TN&Z^na(6F&q@6zlXV$`%P_W%@ z^FxtuPyM2dJxBdJdTmu;gQ~cC8dY-#`99u>$cE?I&R+CuJj);>@cOwTS*6W4YaZ{@ z)2i1q51fg(``*13b8z40b?=X!J_vNy?1t@$LZOD$R%W)!K#UZOTu`n`3}+W2p>8Kx zmt)DxSDgH`k%>hqumh5lGV)%iX!cXB`H-2TTtpBQC9H>x|HU5U{lXg%^!{&OiD<=S z9y-jY7jJH5<>n&;0r?3nltd40D!hXV;)Ua$G6qpmQL!0P5_b2UKt8@Sw%(0@FfXxoqF#%6A2MkL&=0)r1P_y8VAs_O6i5y z)FcjslPCnzq5b>oG!rGj#~^Cr=@IpfD8?t^5zI!kOV_pM*zbLfsU;#Xt~Z9S#SS-jILYOb{IV6V#*dk)_Vs@tU}pp^}>Y^u5PE0rxHiWe%}lYnwib!(HD?k_tHeG>26+8`lK>d&#Ebv)Md z|MYhI>~|oL)9mAApxBpJR5aohRv>>`4O?SKEwpLQjsuO<1FmvNtur-sB?Bf*-O63D zPZJZb>ip)9fUyqfsP%HW)oX5T{S36o%K(c^cy+&xwQI3bIu5b1u|uBFMxR&9TAyVC z*<=BqyL)$s*7C?rS<6^V3q?`%s86;sjA7P;ME3;wL40LpMb^^Q6AXNmp?+z_a1QTp zUS4LzRTpbX#(j8dBEn8J`3J{GF6$z~08CYLp zN>1V6fs+rX^UDn11cK3I9;DKPD}q8oIZ&RpN$I?BZ4q9W8MM90jqU`V-=8^;2Ru)Q z62k!J=T|PBM#tYx}1Gf4MrIeIg#?#FL8C`x<~waQvvJWo%Tm?hp8kSc%pUOPzowskx1nqN#$G=Umb9ymM1 zZ(rN$#ZTa7J*egau@>aJ2mVgLU)ihYbNm389edEUus4=p^yiH13SrLQmu@3soG9TO zccY?4C0-dhfB>}4kYaT_C5GlhyVfjIJB0ZRei*m5bUaCGjE}(0@l6<1$A9FxMsa8~ z8te1<4*1WsVz?dvS!qes4xwe}K&3(hQF<(ncr37xf~y6EEGaQj`e|1lxW3sV zBtVPfMi!{?5A7exR5QO8_+T^QvpYL+TCBAn;D*jFZ{v6(()@v zWM$BfB_u4Io184{4--Qm*&@3mDK#}8u2ipmZvncs6HFt8cE1&pukk5le7bsMzQA$) zEXcmEu8u|kUMzs`T>%JQ!IOogBN%N20~8%=>vE~l()ifpi|8-8kksqg2BT=_934hvKN%$YmN(2oUeWP2g` z!ULnHZ)cajbJwnm^V9S`@Cr}^#qCOfP4Yar>4?Q+Y~e`Hr9JwSg-#A(>;4DS)qAgE zGaA8xGKQ0fKgG73%Wjgas-`9iT>%=w3twHg^6-cQDah{Sds153IaSIzlyomjnz6n# zyhQL_oU&nnXQoo{{J8Ugchla+Y=eXt!Gp#rR;sTQp!#f_j>~|Mq{Q2Y6hr?aOmQ?Q z@vVcOq802%C{Q2+x>3_EW0?Hx`qGT{umH+W_|XLCzRc&2wGM{K3Lk*)QFWrA8s5H5 zvEPMS-n(UAc(~8!cRPWnz(Z@@r3YJ3q0w?qC=Lc#k#-yYTAdB(09y))0zkk5gq?*Y z2%wHIGrA!s2VKts#(#GSB2bp5WKf`2nbkRyqM(>G|Hu> zs%lqp4`?Txjg1X-(wqk)TNM?Ra$sDCL%dOBU5L~i43jWsLg*@eSbqP0-Gj#I9U!YD z5{bnC3wUU)q>;Rc2<{Tsr`+Tf6cPsseFx%>Wr09WT1^K}vCH5P1Kv+W>1G0H68e)q z3{_u8@<7j_*EUEaN%4gy&+ODgeQ+vP)DPSZbKmYN7_VZSnx9<8IfW{8@mb zHj~i*3CX-A%&xj8E!*^o{x#CIzaF|lb5J%sZ5$Md^N_0o-`)*owh-o#01qt{eZK8l zfej(hmR{*s*?ZLIWc+;fY={Yj+g=DtG6uJ9-9mODQ2VcmW%w>xgn$Dv+ya8A zWZ0T_hIYsW2v1f6UJeeA9$ouOw9*c<4u!DehIqUemM+Ajs!1P$Uga zqH=oW=rEi!?im}@=4Mj_+b3Y;%x06&tkL@0lTYHbao*k~P^A50fDPMN6IwztjA=T_ z1nG*Xxc1{xPHyg3Mx4b~uCaUe2&dI63vP#0J@I1~XJ`O+4=)2iuOiyvJ8P>Rj% z?4E;vuE*EzyrCu7_Fn?3zzN1{0t8pnvuE!@`Bzjm3vH0p^rL3t8iekf0@}~ufY0#3 z;hiV~H|ckdpI!g~IS+So2bZhwLC3@nUy5<}@Th=ppHuq!!Nng8jg5O@1PGo$4e9ae zZPZZvo+DQ?F?Qrkzy#z14R}IV`}zmi>x<7r=b{}i0}lqPyLXP!1Uw){YraEIn^lAA zFpWWN*><^hb#PV{Y>&Q-jd?(0ZK{%U1hl^vfMHkw-6#WV;TXD-BWskJ4L((~OpsUr zfK8AX2B9h!Rwp!6vGP}ak4RucnB_?4WlnbX_~>mZOJn1Ep+C}wm!~whP-3%qp+uov j+{5uN64U>c%d(^t+QJnA6OHWvU{D69j87JwaEkapybJxt diff --git a/doc/sphinx/docs/python/tutorials/nlopt_basics.rst b/doc/sphinx/docs/python/tutorials/nlopt_basics.rst index c8ce0d574..221d6cefa 100644 --- a/doc/sphinx/docs/python/tutorials/nlopt_basics.rst +++ b/doc/sphinx/docs/python/tutorials/nlopt_basics.rst @@ -73,40 +73,35 @@ The lines above can be shortened and are equivalent to: >>> pop = pg.population(pg.luksan_vlcek1(dim = 20), size = 1) >>> pop.problem.c_tol = [1e-8] * pop.problem.get_nc() -.. image:: ../../images/nlopt_basic_lv1.png - :scale: 80 % - :alt: slsqp performance - :align: right - We now solve this problem by writing: .. doctest:: >>> pop = algo.evolve(pop) # doctest: +SKIP - fevals: fitness: violated: viol. norm: - 1 250153 18 2619.51 i - 2 132280 18 931.767 i - 3 26355.2 18 357.548 i - 4 14509 18 140.055 i - 5 77119 18 378.603 i - 6 9104.25 18 116.19 i - 7 9104.25 18 116.19 i - 8 2219.94 18 42.8747 i - 9 947.637 18 16.7015 i - 10 423.519 18 7.73746 i - 11 82.8658 18 1.39111 i - 12 34.2733 15 0.227267 i - 13 11.9797 11 0.0309227 i - 14 42.7256 7 0.27342 i - 15 1.66949 11 0.042859 i - 16 1.66949 11 0.042859 i - 17 0.171358 7 0.00425765 i - 18 0.00186583 5 0.000560166 i - 19 1.89265e-06 3 4.14711e-06 i - 20 1.28773e-09 0 0 - 21 7.45125e-14 0 0 - 22 3.61388e-18 0 0 - 23 1.16145e-23 0 0 + objevals: objval: violated: viol. norm: + 1 250153 18 2619.51 i + 2 132280 18 931.767 i + 3 26355.2 18 357.548 i + 4 14509 18 140.055 i + 5 77119 18 378.603 i + 6 9104.25 18 116.19 i + 7 9104.25 18 116.19 i + 8 2219.94 18 42.8747 i + 9 947.637 18 16.7015 i + 10 423.519 18 7.73746 i + 11 82.8658 18 1.39111 i + 12 34.2733 15 0.227267 i + 13 11.9797 11 0.0309227 i + 14 42.7256 7 0.27342 i + 15 1.66949 11 0.042859 i + 16 1.66949 11 0.042859 i + 17 0.171358 7 0.00425765 i + 18 0.00186583 5 0.000560166 i + 19 1.89265e-06 3 4.14711e-06 i + 20 1.28773e-09 0 0 + 21 7.45125e-14 0 0 + 22 3.61388e-18 0 0 + 23 1.16145e-23 0 0 Optimisation return status: NLOPT_XTOL_REACHED (value = 4, Optimization stopped because xtol_rel or xtol_abs was reached) @@ -119,9 +114,13 @@ shown here. >>> from matplotlib import pyplot as plt # doctest: +SKIP >>> plt.semilogy([line[0] for line in log], [line[1] for line in log], label = "obj") # doctest: +SKIP >>> plt.semilogy([line[0] for line in log], [line[3] for line in log], label = "con") # doctest: +SKIP - >>> plt.xlabel("fevals") # doctest: +SKIP + >>> plt.xlabel("objevals") # doctest: +SKIP >>> plt.ylabel("value") # doctest: +SKIP >>> plt.show() # doctest: +SKIP +.. image:: ../../images/nlopt_basic_lv1.png + :scale: 100 % + :alt: slsqp performance + I do not have the gradient ^^^^^^^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/include/pagmo/algorithms/nlopt.hpp b/include/pagmo/algorithms/nlopt.hpp index 0eb338d4d..d796996ae 100644 --- a/include/pagmo/algorithms/nlopt.hpp +++ b/include/pagmo/algorithms/nlopt.hpp @@ -158,7 +158,7 @@ inline std::string nlopt_res2string(::nlopt_result err) } struct nlopt_obj { - // Single entry of the log (feval, fitness, n of unsatisfied const, constr. violation, feasibility). + // Single entry of the log (objevals, objval, n of unsatisfied const, constr. violation, feasibility). using log_line_type = std::tuple; // The log. using log_type = std::vector; @@ -327,11 +327,12 @@ struct nlopt_obj { // If grad is not null, it means we are in an algorithm // that needs the gradient. If the problem does not support it, // we error out. - pagmo_throw(std::invalid_argument, - "during an optimization with the NLopt algorithm '" - + data::names.right.at(::nlopt_get_algorithm(nlo.m_value.get())) - + "' a fitness gradient was requested, but the optimisation problem '" - + p.get_name() + "' does not provide it"); + pagmo_throw( + std::invalid_argument, + "during an optimization with the NLopt algorithm '" + + data::names.right.at(::nlopt_get_algorithm(nlo.m_value.get())) + + "' an objective function gradient was requested, but the optimisation problem '" + + p.get_name() + "' does not provide it"); } // Copy the decision vector in our temporary dv vector_double, @@ -391,8 +392,8 @@ struct nlopt_obj { if (!(f_count / verb % 50u)) { // Every 50 lines print the column names. - print("\n", std::setw(10), "fevals:", std::setw(15), "fitness:", std::setw(15), "violated:", - std::setw(15), "viol. norm:", '\n'); + print("\n", std::setw(10), "objevals:", std::setw(15), "objval:", std::setw(15), + "violated:", std::setw(15), "viol. norm:", '\n'); } // Print to screen the log line. print(std::setw(10), f_count + 1u, std::setw(15), fitness[0], std::setw(15), nv, std::setw(15), @@ -1053,8 +1054,8 @@ class nlopt } // Run the optimisation and store the status returned by NLopt. - double fitness; - m_last_opt_result = ::nlopt_optimize(no.m_value.get(), initial_guess.data(), &fitness); + double objval; + m_last_opt_result = ::nlopt_optimize(no.m_value.get(), initial_guess.data(), &objval); if (m_verbosity) { // Print to screen the result of the optimisation, if we are being verbose. std::cout << "\nOptimisation return status: " << detail::nlopt_res2string(m_last_opt_result) << '\n'; @@ -1114,11 +1115,11 @@ class nlopt * * Example (verbosity 5): * @code{.unparsed} - * fevals: fitness: violated: viol. norm: - * 1 47.9474 1 2.07944 i - * 6 17.1986 2 0.150557 i - * 11 17.014 0 0 - * 16 17.014 0 0 + * objevals: objval: violated: viol. norm: + * 1 47.9474 1 2.07944 i + * 6 17.1986 2 0.150557 i + * 11 17.014 0 0 + * 16 17.014 0 0 * @endcode * The ``i`` at the end of some rows indicates that the decision vector is infeasible. Feasibility * is checked against the problem's tolerance. diff --git a/pygmo/docstrings.cpp b/pygmo/docstrings.cpp index 7bf23db86..0e4885f09 100644 --- a/pygmo/docstrings.cpp +++ b/pygmo/docstrings.cpp @@ -4096,37 +4096,37 @@ See also the docs of the C++ class :cpp:class:`pagmo::nlopt`. >>> prob.c_tol = [1E-6] * 18 # Set constraints tolerance to 1E-6 >>> pop = population(prob, 20) >>> pop = algo.evolve(pop) # doctest: +SKIP - fevals: fitness: violated: viol. norm: - 1 95959.4 18 538.227 i - 2 89282.7 18 5177.42 i - 3 75580 18 464.206 i - 4 75580 18 464.206 i - 5 77737.6 18 1095.94 i - 6 41162 18 350.446 i - 7 41162 18 350.446 i - 8 67881 18 362.454 i - 9 30502.2 18 249.762 i - 10 30502.2 18 249.762 i - 11 7266.73 18 95.5946 i - 12 4510.3 18 42.2385 i - 13 2400.66 18 35.2507 i - 14 34051.9 18 749.355 i - 15 1657.41 18 32.1575 i - 16 1657.41 18 32.1575 i - 17 1564.44 18 12.5042 i - 18 275.987 14 6.22676 i - 19 232.765 12 12.442 i - 20 161.892 15 4.00744 i - 21 161.892 15 4.00744 i - 22 17.6821 11 1.78909 i - 23 7.71103 5 0.130386 i - 24 6.24758 4 0.00736759 i - 25 6.23325 1 5.12547e-05 i - 26 6.2325 0 0 - 27 6.23246 0 0 - 28 6.23246 0 0 - 29 6.23246 0 0 - 30 6.23246 0 0 + objevals: objval: violated: viol. norm: + 1 95959.4 18 538.227 i + 2 89282.7 18 5177.42 i + 3 75580 18 464.206 i + 4 75580 18 464.206 i + 5 77737.6 18 1095.94 i + 6 41162 18 350.446 i + 7 41162 18 350.446 i + 8 67881 18 362.454 i + 9 30502.2 18 249.762 i + 10 30502.2 18 249.762 i + 11 7266.73 18 95.5946 i + 12 4510.3 18 42.2385 i + 13 2400.66 18 35.2507 i + 14 34051.9 18 749.355 i + 15 1657.41 18 32.1575 i + 16 1657.41 18 32.1575 i + 17 1564.44 18 12.5042 i + 18 275.987 14 6.22676 i + 19 232.765 12 12.442 i + 20 161.892 15 4.00744 i + 21 161.892 15 4.00744 i + 22 17.6821 11 1.78909 i + 23 7.71103 5 0.130386 i + 24 6.24758 4 0.00736759 i + 25 6.23325 1 5.12547e-05 i + 26 6.2325 0 0 + 27 6.23246 0 0 + 28 6.23246 0 0 + 29 6.23246 0 0 + 30 6.23246 0 0 Optimisation return status: NLOPT_XTOL_REACHED (value = 4, Optimization stopped because xtol_rel or xtol_abs was reached) From be0cfec8d4f530f664172fc1129b407847328b80 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 12 Apr 2017 22:30:44 +0200 Subject: [PATCH 19/20] Test fixes for clang warning. [skip appveyor] --- tests/archipelago.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/archipelago.cpp b/tests/archipelago.cpp index e0a1df39f..142bdb3ee 100644 --- a/tests/archipelago.cpp +++ b/tests/archipelago.cpp @@ -385,19 +385,19 @@ BOOST_AUTO_TEST_CASE(archipelago_champion_tests) archipelago archi; BOOST_CHECK(archi.get_champions_f().empty()); BOOST_CHECK(archi.get_champions_x().empty()); - archi.push_back(de{}, rosenbrock{}, 20); - archi.push_back(de{}, rosenbrock{}, 20); - archi.push_back(de{}, rosenbrock{}, 20); + archi.push_back(de{}, rosenbrock{}, 20u); + archi.push_back(de{}, rosenbrock{}, 20u); + archi.push_back(de{}, rosenbrock{}, 20u); BOOST_CHECK_EQUAL(archi.get_champions_f().size(), 3u); BOOST_CHECK_EQUAL(archi.get_champions_x().size(), 3u); for (auto i = 0u; i < 3u; ++i) { BOOST_CHECK(archi[i].get_population().champion_x() == archi.get_champions_x()[i]); BOOST_CHECK(archi[i].get_population().champion_f() == archi.get_champions_f()[i]); } - archi.push_back(de{}, rosenbrock{10}, 20); + archi.push_back(de{}, rosenbrock{10}, 20u); BOOST_CHECK(archi.get_champions_x()[2].size() == 2u); BOOST_CHECK(archi.get_champions_x()[3].size() == 10u); - archi.push_back(de{}, zdt{}, 20); + archi.push_back(de{}, zdt{}, 20u); BOOST_CHECK_THROW(archi.get_champions_f(), std::invalid_argument); BOOST_CHECK_THROW(archi.get_champions_x(), std::invalid_argument); } From 48c50106ea17f0a56dc630fa55f049c0a10fc2da Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 12 Apr 2017 22:44:21 +0200 Subject: [PATCH 20/20] Missing type trait in docs. [skip ci] --- doc/sphinx/docs/cpp/miscellanea/type_traits.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/sphinx/docs/cpp/miscellanea/type_traits.rst b/doc/sphinx/docs/cpp/miscellanea/type_traits.rst index 938a8317c..8b94ee7e0 100644 --- a/doc/sphinx/docs/cpp/miscellanea/type_traits.rst +++ b/doc/sphinx/docs/cpp/miscellanea/type_traits.rst @@ -76,5 +76,8 @@ Type traits and enums used in PaGMO .. doxygenclass:: pagmo::override_has_set_seed :members: +.. doxygenclass:: pagmo::has_run_evolve + :members: + .. doxygenclass:: pagmo::is_udi :members: