#ifndef NLOPTOPTIMIZER_HPP #define NLOPTOPTIMIZER_HPP #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4244) #pragma warning(disable: 4267) #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #include #include "Optimizer.hpp" namespace Slic3r { namespace opt { namespace detail { // Helper types for NLopt algorithm selection in template contexts template struct NLoptAlg {}; // NLopt can combine multiple algorithms if one is global an other is a local // method. This is how template specializations can be informed about this fact. template struct NLoptAlgComb {}; template struct IsNLoptAlg { static const constexpr bool value = false; }; template struct IsNLoptAlg> { static const constexpr bool value = true; }; template struct IsNLoptAlg> { static const constexpr bool value = true; }; // NLopt can wrap any of its algorithms to use the augmented lagrangian method // for deriving an object function from all equality and inequality constraints // This way one can use algorithms that do not support these constraints natively template struct NLoptAUGLAG {}; template struct IsNLoptAlg>> { static const constexpr bool value = true; }; template struct IsNLoptAlg>> { static const constexpr bool value = true; }; template using NLoptOnly = std::enable_if_t::value, T>; template struct GetNLoptAlg_ { static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; static constexpr nlopt_algorithm Global = NLOPT_NUM_ALGORITHMS; static constexpr bool IsAUGLAG = false; }; template struct GetNLoptAlg_> { static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; static constexpr nlopt_algorithm Global = a; static constexpr bool IsAUGLAG = false; }; template struct GetNLoptAlg_> { static constexpr nlopt_algorithm Local = l; static constexpr nlopt_algorithm Global = g; static constexpr bool IsAUGLAG = false; }; template constexpr nlopt_algorithm GetNLoptAlg_Global = GetNLoptAlg_>::Global; template constexpr nlopt_algorithm GetNLoptAlg_Local = GetNLoptAlg_>::Local; template constexpr bool IsAUGLAG = GetNLoptAlg_>::IsAUGLAG; template struct GetNLoptAlg_> { static constexpr nlopt_algorithm Local = GetNLoptAlg_Local; static constexpr nlopt_algorithm Global = GetNLoptAlg_Global; static constexpr bool IsAUGLAG = true; }; enum class OptDir { MIN, MAX }; // Where to optimize struct NLoptRAII { // Helper RAII class for nlopt_opt nlopt_opt ptr = nullptr; template explicit NLoptRAII(A&&...a) { ptr = nlopt_create(std::forward(a)...); } NLoptRAII(const NLoptRAII&) = delete; NLoptRAII(NLoptRAII&&) = delete; NLoptRAII& operator=(const NLoptRAII&) = delete; NLoptRAII& operator=(NLoptRAII&&) = delete; ~NLoptRAII() { nlopt_destroy(ptr); } }; // Map a generic function to each argument following the mapping function template Fn for_each_argument(Fn &&fn, Args&&...args) { // see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/ (fn(std::forward(args)),...); return fn; } // Call fn on each element of the input tuple tup. template Fn for_each_in_tuple(Fn fn, Tup &&tup) { auto mpfn = [&fn](auto&...pack) { for_each_argument(fn, pack...); }; std::apply(mpfn, tup); return fn; } // Wrap each element of the tuple tup into a wrapper class W and return // a new tuple with each element being of type W where T_i is the type of // i-th element of tup. template class W, class...Args> auto wrap_tup(const std::tuple &tup) { return std::tuple...>(tup); } template> class NLoptOpt { StopCriteria m_stopcr; StopCriteria m_loc_stopcr; OptDir m_dir = OptDir::MIN; static constexpr double ConstraintEps = 1e-6; template struct OptData { Fn fn; NLoptOpt *self = nullptr; nlopt_opt opt_raw = nullptr; OptData(const Fn &f): fn{f} {} OptData(const Fn &f, NLoptOpt *s, nlopt_opt nlopt_raw) : fn{f}, self{s}, opt_raw{nlopt_raw} {} }; template static double optfunc(unsigned n, const double *params, double *gradient, void *data) { assert(n == N); auto tdata = static_cast*>(data); if (tdata->self->m_stopcr.stop_condition()) nlopt_force_stop(tdata->opt_raw); auto funval = to_arr(params); double scoreval = 0.; using RetT = decltype(tdata->fn(funval)); if constexpr (std::is_convertible_v>) { ScoreGradient score = tdata->fn(funval); for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; scoreval = score.score; } else { scoreval = tdata->fn(funval); } return scoreval; } template static double constrain_func(unsigned n, const double *params, double *gradient, void *data) { assert(n == N); auto tdata = static_cast*>(data); auto funval = to_arr(params); return tdata->fn(funval); } template static void set_up(NLoptRAII &nl, const Bounds &bounds, const StopCriteria &stopcr) { std::array lb, ub; for (size_t i = 0; i < N; ++i) { lb[i] = bounds[i].min(); ub[i] = bounds[i].max(); } nlopt_set_lower_bounds(nl.ptr, lb.data()); nlopt_set_upper_bounds(nl.ptr, ub.data()); double abs_diff = stopcr.abs_score_diff(); double rel_diff = stopcr.rel_score_diff(); double stopval = stopcr.stop_score(); if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); if(stopcr.max_iterations() > 0) nlopt_set_maxeval(nl.ptr, stopcr.max_iterations()); } template Result optimize(NLoptRAII &nl, Fn &&fn, const Input &initvals, const std::tuple &equalities, const std::tuple &inequalities) { Result r; OptData data {fn, this, nl.ptr}; auto do_for_each_eq = [this, &nl](auto &arg) { arg.self = this; arg.opt_raw = nl.ptr; using F = decltype(arg.fn); nlopt_add_equality_constraint (nl.ptr, constrain_func, &arg, ConstraintEps); }; auto do_for_each_ineq = [this, &nl](auto &arg) { arg.self = this; arg.opt_raw = nl.ptr; using F = decltype(arg.fn); nlopt_add_inequality_constraint (nl.ptr, constrain_func, &arg, ConstraintEps); }; auto eq_data = wrap_tup(equalities); for_each_in_tuple(do_for_each_eq, eq_data); auto ineq_data = wrap_tup(inequalities); for_each_in_tuple(do_for_each_ineq, ineq_data); switch(m_dir) { case OptDir::MIN: nlopt_set_min_objective(nl.ptr, optfunc, &data); break; case OptDir::MAX: nlopt_set_max_objective(nl.ptr, optfunc, &data); break; } r.optimum = initvals; r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score); return r; } public: template Result optimize(Fn&& f, const Input &initvals, const Bounds& bounds, const std::tuple &equalities, const std::tuple &inequalities) { if constexpr (IsAUGLAG) { NLoptRAII nl_wrap{NLOPT_AUGLAG, N}; set_up(nl_wrap, bounds, get_criteria()); NLoptRAII nl_glob{GetNLoptAlg_Global, N}; set_up(nl_glob, bounds, get_criteria()); nlopt_set_local_optimizer(nl_wrap.ptr, nl_glob.ptr); if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { NLoptRAII nl_loc{GetNLoptAlg_Local, N}; set_up(nl_loc, bounds, m_loc_stopcr); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); return optimize(nl_wrap, std::forward(f), initvals, equalities, inequalities); } else { return optimize(nl_wrap, std::forward(f), initvals, equalities, inequalities); } } else { NLoptRAII nl_glob{GetNLoptAlg_Global, N}; set_up(nl_glob, bounds, get_criteria()); if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { NLoptRAII nl_loc{GetNLoptAlg_Local, N}; set_up(nl_loc, bounds, m_loc_stopcr); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); return optimize(nl_glob, std::forward(f), initvals, equalities, inequalities); } else { return optimize(nl_glob, std::forward(f), initvals, equalities, inequalities); } } assert(false); return {}; } explicit NLoptOpt(const StopCriteria &stopcr_glob = {}) : m_stopcr(stopcr_glob) {} void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } const StopCriteria &get_criteria() const noexcept { return m_stopcr; } void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; } const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } void set_dir(OptDir dir) noexcept { m_dir = dir; } void seed(long s) { nlopt_srand(s); } }; template struct AlgFeatures_ { static constexpr bool SupportsInequalities = false; static constexpr bool SupportsEqualities = false; }; } // namespace detail; template constexpr bool SupportsEqualities = detail::AlgFeatures_>::SupportsEqualities; template constexpr bool SupportsInequalities = detail::AlgFeatures_>::SupportsInequalities; // Optimizers based on NLopt. template class Optimizer> { detail::NLoptOpt m_opt; public: Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } template Result optimize(Func&& func, const Input &initvals, const Bounds& bounds, const std::tuple &eq_constraints = {}, const std::tuple &ineq_constraint = {}) { static_assert(std::tuple_size_v> == 0 || SupportsEqualities, "Equality constraints are not supported."); static_assert(std::tuple_size_v> == 0 || SupportsInequalities, "Inequality constraints are not supported."); return m_opt.optimize(std::forward(func), initvals, bounds, eq_constraints, ineq_constraint); } explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} Optimizer &set_criteria(const StopCriteria &cr) { m_opt.set_criteria(cr); return *this; } const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } void seed(long s) { m_opt.seed(s); } void set_loc_criteria(const StopCriteria &cr) { m_opt.set_loc_criteria(cr); } const StopCriteria &get_loc_criteria() const noexcept { return m_opt.get_loc_criteria(); } }; // Predefinded NLopt algorithms using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; using AlgNLoptCobyla = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; using AlgNLoptORIG_DIRECT = detail::NLoptAlg; using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptAGS = detail::NLoptAlg; using AlgNLoptMLSL_Subplx = detail::NLoptAlgComb; using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb; // To craft auglag algorithms (constraint support through object function transformation) using detail::NLoptAUGLAG; namespace detail { template<> struct AlgFeatures_ { static constexpr bool SupportsInequalities = true; static constexpr bool SupportsEqualities = true; }; template<> struct AlgFeatures_ { static constexpr bool SupportsInequalities = true; static constexpr bool SupportsEqualities = false; }; template<> struct AlgFeatures_ { static constexpr bool SupportsInequalities = true; static constexpr bool SupportsEqualities = false; }; template<> struct AlgFeatures_ { static constexpr bool SupportsInequalities = true; static constexpr bool SupportsEqualities = true; }; template struct AlgFeatures_> { static constexpr bool SupportsInequalities = true; static constexpr bool SupportsEqualities = true; }; } // namespace detail }} // namespace Slic3r::opt #endif // NLOPTOPTIMIZER_HPP