diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 3859217da8..87aec4b361 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -13,7 +13,7 @@ #include -#include +#include "Optimizer.hpp" namespace Slic3r { namespace opt { @@ -64,12 +64,36 @@ struct NLopt { // Helper RAII class for nlopt_opt template class NLoptOpt {}; +// 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; +} + +template +Fn for_each_in_tuple(Fn fn, const std::tuple &tup) +{ + auto arg = std::tuple_cat(std::make_tuple(fn), tup); + auto mpfn = [](auto fn, auto...pack) { + return for_each_argument(fn, pack...); + }; + std::apply(mpfn, arg); + + return fn; +} + // Optimizers based on NLopt. template class NLoptOpt> { protected: StopCriteria m_stopcr; OptDir m_dir = OptDir::MIN; + static constexpr double ConstraintEps = 1e-6; + template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; @@ -78,7 +102,7 @@ protected: double *gradient, void *data) { - assert(n >= N); + assert(n == N); auto tdata = static_cast*>(data); @@ -101,6 +125,21 @@ protected: 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 &fnptr = std::get<0>(*tdata); + auto funval = to_arr(params); + + return (*fnptr)(funval); + } + template void set_up(NLopt &nl, const Bounds& bounds) { @@ -125,13 +164,30 @@ protected: nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); } - template - Result optimize(NLopt &nl, Fn &&fn, const Input &initvals) + template + Result optimize(NLopt &nl, Fn &&fn, const Input &initvals, + const std::tuple &equalities, + const std::tuple &inequalities) { Result r; TOptData data = std::make_tuple(&fn, this, nl.ptr); + auto do_for_each_eq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_equality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + auto do_for_each_ineq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_inequality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + for_each_in_tuple(do_for_each_eq, equalities); + for_each_in_tuple(do_for_each_ineq, inequalities); + switch(m_dir) { case OptDir::MIN: nlopt_set_min_objective(nl.ptr, optfunc, &data); break; @@ -147,15 +203,18 @@ protected: public: - template + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl{alg, N}; set_up(nl, bounds); - return optimize(nl, std::forward(func), initvals); + return optimize(nl, std::forward(func), initvals, + equalities, inequalities); } explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} @@ -173,10 +232,12 @@ class NLoptOpt>: public NLoptOpt> using Base = NLoptOpt>; public: - template + template Result optimize(Fn&& f, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl_glob{glob, N}, nl_loc{loc, N}; @@ -184,7 +245,8 @@ public: Base::set_up(nl_loc, bounds); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); - return Base::optimize(nl_glob, std::forward(f), initvals); + return Base::optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); } explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} @@ -201,12 +263,16 @@ 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 + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {}) { - return m_opt.optimize(std::forward(func), initvals, bounds); + return m_opt.optimize(std::forward(func), initvals, bounds, + eq_constraints, + ineq_constraint); } explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} @@ -225,7 +291,9 @@ public: using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptCobyla = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptMLSL = detail::NLoptAlgComb; }} // namespace Slic3r::opt diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index bf95d9ee07..6212a5f59d 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -12,6 +12,15 @@ namespace Slic3r { namespace opt { +template +using FloatingOnly = std::enable_if_t::value, O>; + +template> +constexpr T NaN = std::numeric_limits::quiet_NaN(); + +constexpr float NaNf = NaN; +constexpr double NaNd = NaN; + // A type to hold the complete result of the optimization. template struct Result { int resultcode; // Method dependent @@ -79,7 +88,7 @@ public: double stop_score() const { return m_stop_score; } - StopCriteria & max_iterations(double val) + StopCriteria & max_iterations(unsigned val) { m_max_iterations = val; return *this; } @@ -137,16 +146,25 @@ public: // For each dimension an interval (Bound) has to be given marking the bounds // for that dimension. // + // Optionally, some constraints can be given in the form of double(Input) + // functors. The parameters eq_constraints and ineq_constraints can be used + // to add equality and inequality (<= 0) constraints to the optimization. + // Note that it is up the the particular method if it accepts these + // constraints. + // // initvals have to be within the specified bounds, otherwise its undefined // behavior. // // Func can return a score of type double or optionally a ScoreGradient // class to indicate the function gradient for a optimization methods that // make use of the gradient. - template + template Result optimize(Func&& /*func*/, const Input &/*initvals*/, - const Bounds& /*bounds*/) { return {}; } + const Bounds& /*bounds*/, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {} + ) { return {}; } // optional for randomized methods: void seed(long /*s*/) {}