1 #ifndef NLOPTOPTIMIZER_HPP 2 #define NLOPTOPTIMIZER_HPP 3 4 #ifdef _MSC_VER 5 #pragma warning(push) 6 #pragma warning(disable: 4244) 7 #pragma warning(disable: 4267) 8 #endif 9 #include <nlopt.h> 10 #ifdef _MSC_VER 11 #pragma warning(pop) 12 #endif 13 14 #include <utility> 15 16 #include <libslic3r/Optimize/Optimizer.hpp> 17 18 namespace Slic3r { namespace opt { 19 20 namespace detail { 21 22 // Helper types for NLopt algorithm selection in template contexts 23 template<nlopt_algorithm alg> struct NLoptAlg {}; 24 25 // NLopt can combine multiple algorithms if one is global an other is a local 26 // method. This is how template specializations can be informed about this fact. 27 template<nlopt_algorithm gl_alg, nlopt_algorithm lc_alg = NLOPT_LN_NELDERMEAD> 28 struct NLoptAlgComb {}; 29 30 template<class M> struct IsNLoptAlg { 31 static const constexpr bool value = false; 32 }; 33 34 template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAlg<a>> { 35 static const constexpr bool value = true; 36 }; 37 38 template<nlopt_algorithm a1, nlopt_algorithm a2> 39 struct IsNLoptAlg<NLoptAlgComb<a1, a2>> { 40 static const constexpr bool value = true; 41 }; 42 43 template<class M, class T = void> 44 using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>; 45 46 47 enum class OptDir { MIN, MAX }; // Where to optimize 48 49 struct NLopt { // Helper RAII class for nlopt_opt 50 nlopt_opt ptr = nullptr; 51 NLoptSlic3r::opt::detail::NLopt52 template<class...A> explicit NLopt(A&&...a) 53 { 54 ptr = nlopt_create(std::forward<A>(a)...); 55 } 56 57 NLopt(const NLopt&) = delete; 58 NLopt(NLopt&&) = delete; 59 NLopt& operator=(const NLopt&) = delete; 60 NLopt& operator=(NLopt&&) = delete; 61 ~NLoptSlic3r::opt::detail::NLopt62 ~NLopt() { nlopt_destroy(ptr); } 63 }; 64 65 template<class Method> class NLoptOpt {}; 66 67 // Optimizers based on NLopt. 68 template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> { 69 protected: 70 StopCriteria m_stopcr; 71 OptDir m_dir; 72 73 template<class Fn> using TOptData = 74 std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>; 75 76 template<class Fn, size_t N> optfunc(unsigned n,const double * params,double * gradient,void * data)77 static double optfunc(unsigned n, const double *params, 78 double *gradient, 79 void *data) 80 { 81 assert(n >= N); 82 83 auto tdata = static_cast<TOptData<Fn>*>(data); 84 85 if (std::get<1>(*tdata)->m_stopcr.stop_condition()) 86 nlopt_force_stop(std::get<2>(*tdata)); 87 88 auto fnptr = std::get<0>(*tdata); 89 auto funval = to_arr<N>(params); 90 91 double scoreval = 0.; 92 using RetT = decltype((*fnptr)(funval)); 93 if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) { 94 ScoreGradient<N> score = (*fnptr)(funval); 95 for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; 96 scoreval = score.score; 97 } else { 98 scoreval = (*fnptr)(funval); 99 } 100 101 return scoreval; 102 } 103 104 template<size_t N> set_up(NLopt & nl,const Bounds<N> & bounds)105 void set_up(NLopt &nl, const Bounds<N>& bounds) 106 { 107 std::array<double, N> lb, ub; 108 109 for (size_t i = 0; i < N; ++i) { 110 lb[i] = bounds[i].min(); 111 ub[i] = bounds[i].max(); 112 } 113 114 nlopt_set_lower_bounds(nl.ptr, lb.data()); 115 nlopt_set_upper_bounds(nl.ptr, ub.data()); 116 117 double abs_diff = m_stopcr.abs_score_diff(); 118 double rel_diff = m_stopcr.rel_score_diff(); 119 double stopval = m_stopcr.stop_score(); 120 if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); 121 if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); 122 if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); 123 124 if(this->m_stopcr.max_iterations() > 0) 125 nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations()); 126 } 127 128 template<class Fn, size_t N> optimize(NLopt & nl,Fn && fn,const Input<N> & initvals)129 Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals) 130 { 131 Result<N> r; 132 133 TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr); 134 135 switch(m_dir) { 136 case OptDir::MIN: 137 nlopt_set_min_objective(nl.ptr, optfunc<Fn, N>, &data); break; 138 case OptDir::MAX: 139 nlopt_set_max_objective(nl.ptr, optfunc<Fn, N>, &data); break; 140 } 141 142 r.optimum = initvals; 143 r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score); 144 145 return r; 146 } 147 148 public: 149 150 template<class Func, size_t N> optimize(Func && func,const Input<N> & initvals,const Bounds<N> & bounds)151 Result<N> optimize(Func&& func, 152 const Input<N> &initvals, 153 const Bounds<N>& bounds) 154 { 155 NLopt nl{alg, N}; 156 set_up(nl, bounds); 157 158 return optimize(nl, std::forward<Func>(func), initvals); 159 } 160 NLoptOpt(StopCriteria stopcr={})161 explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} 162 set_criteria(const StopCriteria & cr)163 void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } get_criteria() const164 const StopCriteria &get_criteria() const noexcept { return m_stopcr; } set_dir(OptDir dir)165 void set_dir(OptDir dir) noexcept { m_dir = dir; } 166 seed(long s)167 void seed(long s) { nlopt_srand(s); } 168 }; 169 170 template<nlopt_algorithm glob, nlopt_algorithm loc> 171 class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>> 172 { 173 using Base = NLoptOpt<NLoptAlg<glob>>; 174 public: 175 176 template<class Fn, size_t N> optimize(Fn && f,const Input<N> & initvals,const Bounds<N> & bounds)177 Result<N> optimize(Fn&& f, 178 const Input<N> &initvals, 179 const Bounds<N>& bounds) 180 { 181 NLopt nl_glob{glob, N}, nl_loc{loc, N}; 182 183 Base::set_up(nl_glob, bounds); 184 Base::set_up(nl_loc, bounds); 185 nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); 186 187 return Base::optimize(nl_glob, std::forward<Fn>(f), initvals); 188 } 189 NLoptOpt(StopCriteria stopcr={})190 explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} 191 }; 192 193 } // namespace detail; 194 195 // Optimizers based on NLopt. 196 template<class M> class Optimizer<M, detail::NLoptOnly<M>> { 197 detail::NLoptOpt<M> m_opt; 198 199 public: 200 to_max()201 Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } to_min()202 Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } 203 204 template<class Func, size_t N> optimize(Func && func,const Input<N> & initvals,const Bounds<N> & bounds)205 Result<N> optimize(Func&& func, 206 const Input<N> &initvals, 207 const Bounds<N>& bounds) 208 { 209 return m_opt.optimize(std::forward<Func>(func), initvals, bounds); 210 } 211 Optimizer(StopCriteria stopcr={})212 explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} 213 set_criteria(const StopCriteria & cr)214 Optimizer &set_criteria(const StopCriteria &cr) 215 { 216 m_opt.set_criteria(cr); return *this; 217 } 218 get_criteria() const219 const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } 220 seed(long s)221 void seed(long s) { m_opt.seed(s); } 222 }; 223 224 // Predefinded NLopt algorithms 225 using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>; 226 using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>; 227 using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>; 228 using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>; 229 using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>; 230 231 }} // namespace Slic3r::opt 232 233 #endif // NLOPTOPTIMIZER_HPP 234