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