#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 #include #include #include #include #include namespace Slic3r { namespace opt { // A type to hold the complete result of the optimization. template struct Result { int resultcode; std::array optimum; double score; }; // An interval of possible input values for optimization class Bound { double m_min, m_max; public: Bound(double min = std::numeric_limits::min(), double max = std::numeric_limits::max()) : m_min(min), m_max(max) {} double min() const noexcept { return m_min; } double max() const noexcept { return m_max; } }; // Helper types for optimization function input and bounds template using Input = std::array; template using Bounds = std::array; // A type for specifying the stop criteria. Setter methods can be concatenated class StopCriteria { // If the absolute value difference between two scores. double m_abs_score_diff = std::nan(""); // If the relative value difference between two scores. double m_rel_score_diff = std::nan(""); // Stop if this value or better is found. double m_stop_score = std::nan(""); // A predicate that if evaluates to true, the optimization should terminate // and the best result found prior to termination should be returned. std::function m_stop_condition = [] { return false; }; // The max allowed number of iterations. unsigned m_max_iterations = 0; public: StopCriteria & abs_score_diff(double val) { m_abs_score_diff = val; return *this; } double abs_score_diff() const { return m_abs_score_diff; } StopCriteria & rel_score_diff(double val) { m_rel_score_diff = val; return *this; } double rel_score_diff() const { return m_rel_score_diff; } StopCriteria & stop_score(double val) { m_stop_score = val; return *this; } double stop_score() const { return m_stop_score; } StopCriteria & max_iterations(double val) { m_max_iterations = val; return *this; } double max_iterations() const { return m_max_iterations; } template StopCriteria & stop_condition(Fn &&cond) { m_stop_condition = cond; return *this; } bool stop_condition() { return m_stop_condition(); } }; // Helper class to use optimization methods involving gradient. template struct ScoreGradient { double score; std::optional> gradient; ScoreGradient(double s, const std::array &grad) : score{s}, gradient{grad} {} }; // Helper to be used in static_assert. template struct always_false { enum { value = false }; }; // Basic interface to optimizer object template class Optimizer { public: Optimizer(const StopCriteria &) { static_assert (always_false::value, "Optimizer unimplemented for given method!"); } Optimizer &to_min() { return *this; } Optimizer &to_max() { return *this; } Optimizer &set_criteria(const StopCriteria &) { return *this; } StopCriteria get_criteria() const { return {}; }; template Result optimize(Func&& func, const Input &initvals, const Bounds& bounds) { return {}; } // optional for randomized methods: void seed(long /*s*/) {} }; 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; }; template using NLoptOnly = std::enable_if_t::value, T>; // Helper to convert C style array to std::array. The copy should be optimized // away with modern compilers. template auto to_arr(const T *a) { std::array r; std::copy(a, a + N, std::begin(r)); return r; } template auto to_arr(const T (&a) [N]) { return to_arr(static_cast(a)); } enum class OptDir { MIN, MAX }; // Where to optimize struct NLopt { // Helper RAII class for nlopt_opt nlopt_opt ptr = nullptr; template explicit NLopt(A&&...a) { ptr = nlopt_create(std::forward(a)...); } NLopt(const NLopt&) = delete; NLopt(NLopt&&) = delete; NLopt& operator=(const NLopt&) = delete; NLopt& operator=(NLopt&&) = delete; ~NLopt() { nlopt_destroy(ptr); } }; template class NLoptOpt {}; // Optimizers based on NLopt. template class NLoptOpt> { protected: StopCriteria m_stopcr; OptDir m_dir; template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; template static double optfunc(unsigned n, const double *params, double *gradient, void *data) { assert(n >= N); auto tdata = static_cast*>(data); if (std::get<1>(*tdata)->m_stopcr.stop_condition()) nlopt_force_stop(std::get<2>(*tdata)); auto fnptr = std::get<0>(*tdata); auto funval = to_arr(params); double scoreval = 0.; using RetT = decltype((*fnptr)(funval)); if constexpr (std::is_convertible_v>) { ScoreGradient score = (*fnptr)(funval); for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; scoreval = score.score; } else { scoreval = (*fnptr)(funval); } return scoreval; } template void set_up(NLopt &nl, const Bounds& bounds) { 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 = m_stopcr.abs_score_diff(); double rel_diff = m_stopcr.rel_score_diff(); double stopval = m_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(this->m_stopcr.max_iterations() > 0) nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations()); } template Result optimize(NLopt &nl, Fn &&fn, const Input &initvals) { Result r; TOptData data = std::make_tuple(&fn, this, nl.ptr); 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(Func&& func, const Input &initvals, const Bounds& bounds) { NLopt nl{alg, N}; set_up(nl, bounds); return optimize(nl, std::forward(func), initvals); } explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } const StopCriteria &get_criteria() const noexcept { return m_stopcr; } void set_dir(OptDir dir) noexcept { m_dir = dir; } void seed(long s) { nlopt_srand(s); } }; template class NLoptOpt>: public NLoptOpt> { using Base = NLoptOpt>; public: template Result optimize(Fn&& f, const Input &initvals, const Bounds& bounds) { NLopt nl_glob{glob, N}, nl_loc{loc, N}; Base::set_up(nl_glob, bounds); 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); } explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} }; } // namespace detail; // 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) { return m_opt.optimize(std::forward(func), initvals, bounds); } 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); } }; template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } template auto score_gradient(double s, const double (&grad)[N]) { return ScoreGradient(s, detail::to_arr(grad)); } // Predefinded NLopt algorithms that are used in the codebase using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; // TODO: define others if needed... // Helper defs for pre-crafted global and local optimizers that work well. using DefaultGlobalOptimizer = Optimizer; using DefaultLocalOptimizer = Optimizer; }} // namespace Slic3r::opt #endif // NLOPTOPTIMIZER_HPP