1 /* Copyright 2017-2021 PaGMO development team
2 
3 This file is part of the PaGMO library.
4 
5 The PaGMO library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8   * the GNU Lesser General Public License as published by the Free
9     Software Foundation; either version 3 of the License, or (at your
10     option) any later version.
11 
12 or
13 
14   * the GNU General Public License as published by the Free Software
15     Foundation; either version 3 of the License, or (at your option) any
16     later version.
17 
18 or both in parallel, as here.
19 
20 The PaGMO library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the PaGMO library.  If not,
27 see https://www.gnu.org/licenses/. */
28 
29 #if defined(_MSC_VER)
30 
31 // Disable the checked iterators feature in MSVC. There are some warnings
32 // triggered by Boost algos instantiations which we cannot do much about.
33 #define _SCL_SECURE_NO_WARNINGS
34 
35 // Boost's bimap results in some C++17 deprecation warnings in C++17 mode.
36 #define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
37 
38 #endif
39 
40 #include <algorithm>
41 #include <cassert>
42 #include <cmath>
43 #include <exception>
44 #include <initializer_list>
45 #include <iomanip>
46 #include <iostream>
47 #include <iterator>
48 #include <limits>
49 #include <memory>
50 #include <sstream>
51 #include <stdexcept>
52 #include <string>
53 #include <tuple>
54 #include <type_traits>
55 #include <unordered_map>
56 #include <utility>
57 #include <vector>
58 
59 #include <nlopt.h>
60 
61 #include <boost/algorithm/string/classification.hpp>
62 #include <boost/algorithm/string/split.hpp>
63 #include <boost/any.hpp>
64 #include <boost/bimap.hpp>
65 #include <boost/numeric/conversion/cast.hpp>
66 
67 #include <pagmo/algorithm.hpp>
68 #include <pagmo/algorithms/nlopt.hpp>
69 #include <pagmo/algorithms/not_population_based.hpp>
70 #include <pagmo/exceptions.hpp>
71 #include <pagmo/io.hpp>
72 #include <pagmo/population.hpp>
73 #include <pagmo/problem.hpp>
74 #include <pagmo/s11n.hpp>
75 #include <pagmo/type_traits.hpp>
76 #include <pagmo/types.hpp>
77 #include <pagmo/utils/constrained.hpp>
78 
79 namespace pagmo
80 {
81 
82 namespace detail
83 {
84 
85 namespace
86 {
87 
88 // The idea here is to establish a bijection between string name (e.g., "cobyla")
89 // and the enums used in the NLopt C API to refer to the algos (e.g., NLOPT_LN_COBYLA).
90 // We use a bidirectional map so that we can map both string -> enum and enum -> string,
91 // depending on what is needed.
92 using nlopt_names_map_t = boost::bimap<std::string, ::nlopt_algorithm>;
93 
94 // Initialise the mapping between algo names and enums for the supported algorithms.
nlopt_names_map_init()95 nlopt_names_map_t nlopt_names_map_init()
96 {
97     nlopt_names_map_t retval;
98     using value_type = nlopt_names_map_t::value_type;
99     retval.insert(value_type("cobyla", NLOPT_LN_COBYLA));
100     retval.insert(value_type("bobyqa", NLOPT_LN_BOBYQA));
101     retval.insert(value_type("newuoa", NLOPT_LN_NEWUOA));
102     retval.insert(value_type("newuoa_bound", NLOPT_LN_NEWUOA_BOUND));
103     retval.insert(value_type("praxis", NLOPT_LN_PRAXIS));
104     retval.insert(value_type("neldermead", NLOPT_LN_NELDERMEAD));
105     retval.insert(value_type("sbplx", NLOPT_LN_SBPLX));
106     retval.insert(value_type("mma", NLOPT_LD_MMA));
107     retval.insert(value_type("ccsaq", NLOPT_LD_CCSAQ));
108     retval.insert(value_type("slsqp", NLOPT_LD_SLSQP));
109     retval.insert(value_type("lbfgs", NLOPT_LD_LBFGS));
110     retval.insert(value_type("tnewton_precond_restart", NLOPT_LD_TNEWTON_PRECOND_RESTART));
111     retval.insert(value_type("tnewton_precond", NLOPT_LD_TNEWTON_PRECOND));
112     retval.insert(value_type("tnewton_restart", NLOPT_LD_TNEWTON_RESTART));
113     retval.insert(value_type("tnewton", NLOPT_LD_TNEWTON));
114     retval.insert(value_type("var2", NLOPT_LD_VAR2));
115     retval.insert(value_type("var1", NLOPT_LD_VAR1));
116     retval.insert(value_type("auglag", NLOPT_AUGLAG));
117     retval.insert(value_type("auglag_eq", NLOPT_AUGLAG_EQ));
118     return retval;
119 }
120 
121 const nlopt_names_map_t nlopt_names = nlopt_names_map_init();
122 
123 // A map to link a human-readable description to NLopt return codes.
124 using nlopt_result_map_t = std::unordered_map<::nlopt_result, std::string>;
125 
126 const nlopt_result_map_t nlopt_results = {
127     {NLOPT_SUCCESS, "NLOPT_SUCCESS (value = " + std::to_string(NLOPT_SUCCESS) + ", Generic success return value)"},
128     {NLOPT_STOPVAL_REACHED, "NLOPT_STOPVAL_REACHED (value = " + std::to_string(NLOPT_STOPVAL_REACHED)
129                                 + ", Optimization stopped because stopval was reached)"},
130     {NLOPT_FTOL_REACHED, "NLOPT_FTOL_REACHED (value = " + std::to_string(NLOPT_FTOL_REACHED)
131                              + ", Optimization stopped because ftol_rel or ftol_abs was reached)"},
132     {NLOPT_XTOL_REACHED, "NLOPT_XTOL_REACHED (value = " + std::to_string(NLOPT_XTOL_REACHED)
133                              + ", Optimization stopped because xtol_rel or xtol_abs was reached)"},
134     {NLOPT_MAXEVAL_REACHED, "NLOPT_MAXEVAL_REACHED (value = " + std::to_string(NLOPT_MAXEVAL_REACHED)
135                                 + ", Optimization stopped because maxeval was reached)"},
136     {NLOPT_MAXTIME_REACHED, "NLOPT_MAXTIME_REACHED (value = " + std::to_string(NLOPT_MAXTIME_REACHED)
137                                 + ", Optimization stopped because maxtime was reached)"},
138     {NLOPT_FAILURE, "NLOPT_FAILURE (value = " + std::to_string(NLOPT_FAILURE) + ", Generic failure code)"},
139     {NLOPT_INVALID_ARGS, "NLOPT_INVALID_ARGS (value = " + std::to_string(NLOPT_INVALID_ARGS) + ", Invalid arguments)"},
140     {NLOPT_OUT_OF_MEMORY,
141      "NLOPT_OUT_OF_MEMORY (value = " + std::to_string(NLOPT_OUT_OF_MEMORY) + ", Ran out of memory)"},
142     {NLOPT_ROUNDOFF_LIMITED, "NLOPT_ROUNDOFF_LIMITED (value = " + std::to_string(NLOPT_ROUNDOFF_LIMITED)
143                                  + ", Halted because roundoff errors limited progress)"},
144     {NLOPT_FORCED_STOP,
145      "NLOPT_FORCED_STOP (value = " + std::to_string(NLOPT_FORCED_STOP) + ", Halted because of a forced termination)"}};
146 
147 // Convert an NLopt result in a more descriptive string.
nlopt_res2string(::nlopt_result err)148 std::string nlopt_res2string(::nlopt_result err)
149 {
150     return (nlopt_results.find(err) == nlopt_results.end() ? "??" : nlopt_results.at(err));
151 }
152 
153 extern "C" {
154 
155 // Wrappers to connect our objfun/constraints calculation machinery to NLopt's. Declared here,
156 // defined later in order to avoid circular deps.
157 // NOTE: these functions need to be passed to the NLopt C API, and as such they need to be
158 // declared within an 'extern "C"' block (otherwise, it might be UB to pass C++ function pointers
159 // to a C API).
160 // https://www.reddit.com/r/cpp/comments/4fqfy7/using_c11_capturing_lambdas_w_vanilla_c_api/d2b9bh0/
161 double nlopt_objfun_wrapper(unsigned, const double *, double *, void *);
162 void nlopt_ineq_c_wrapper(unsigned, double *, unsigned, const double *, double *, void *);
163 void nlopt_eq_c_wrapper(unsigned, double *, unsigned, const double *, double *, void *);
164 }
165 
166 struct nlopt_obj {
167     // Single entry of the log (objevals, objval, n of unsatisfied const, constr. violation, feasibility).
168     // Same as defined in the nlopt algorithm.
169     using log_line_type = nlopt::log_line_type;
170     // The log.
171     using log_type = std::vector<log_line_type>;
172     // NOTE: this is a wrapper around std::copy() for use in MSVC in conjunction with raw pointers.
173     // In debug mode, MSVC will complain about unchecked iterators unless instructed otherwise.
174     template <typename Int, typename T>
unchecked_copypagmo::detail::__anon54303aaf0111::nlopt_obj175     static void unchecked_copy(Int size, const T *begin, T *dest)
176     {
177         std::copy(begin, begin + size, dest);
178     }
nlopt_objpagmo::detail::__anon54303aaf0111::nlopt_obj179     explicit nlopt_obj(::nlopt_algorithm algo, problem &prob, double stopval, double ftol_rel, double ftol_abs,
180                        double xtol_rel, double xtol_abs, int maxeval, int maxtime, unsigned verbosity)
181         : m_algo(algo), m_prob(prob), m_value(nullptr, ::nlopt_destroy), m_verbosity(verbosity)
182     {
183         // Extract and set problem dimension.
184         const auto n = boost::numeric_cast<unsigned>(prob.get_nx());
185         // Try to init the nlopt_obj.
186         m_value.reset(::nlopt_create(algo, n));
187         if (!m_value) {
188             pagmo_throw(std::runtime_error, "the creation of the nlopt_opt object failed"); // LCOV_EXCL_LINE
189         }
190 
191         // NLopt does not handle MOO.
192         if (prob.get_nobj() != 1u) {
193             pagmo_throw(std::invalid_argument, "NLopt algorithms cannot handle multi-objective optimization");
194         }
195 
196         // This is just a vector_double that is re-used across objfun invocations.
197         // It will hold the current decision vector.
198         m_dv.resize(prob.get_nx());
199 
200         // Handle the various stopping criteria.
201         auto res = ::nlopt_set_stopval(m_value.get(), stopval);
202         if (res != NLOPT_SUCCESS) {
203             // LCOV_EXCL_START
204             pagmo_throw(std::invalid_argument, "could not set the 'stopval' stopping criterion to "
205                                                    + std::to_string(stopval) + " for the NLopt algorithm '"
206                                                    + nlopt_names.right.at(algo)
207                                                    + "', the error is: " + nlopt_res2string(res));
208             // LCOV_EXCL_STOP
209         }
210         res = ::nlopt_set_ftol_rel(m_value.get(), ftol_rel);
211         if (res != NLOPT_SUCCESS) {
212             // LCOV_EXCL_START
213             pagmo_throw(std::invalid_argument, "could not set the 'ftol_rel' stopping criterion to "
214                                                    + std::to_string(ftol_rel) + " for the NLopt algorithm '"
215                                                    + nlopt_names.right.at(algo)
216                                                    + "', the error is: " + nlopt_res2string(res));
217             // LCOV_EXCL_STOP
218         }
219         res = ::nlopt_set_ftol_abs(m_value.get(), ftol_abs);
220         if (res != NLOPT_SUCCESS) {
221             // LCOV_EXCL_START
222             pagmo_throw(std::invalid_argument, "could not set the 'ftol_abs' stopping criterion to "
223                                                    + std::to_string(ftol_abs) + " for the NLopt algorithm '"
224                                                    + nlopt_names.right.at(algo)
225                                                    + "', the error is: " + nlopt_res2string(res));
226             // LCOV_EXCL_STOP
227         }
228         res = ::nlopt_set_xtol_rel(m_value.get(), xtol_rel);
229         if (res != NLOPT_SUCCESS) {
230             // LCOV_EXCL_START
231             pagmo_throw(std::invalid_argument, "could not set the 'xtol_rel' stopping criterion to "
232                                                    + std::to_string(xtol_rel) + " for the NLopt algorithm '"
233                                                    + nlopt_names.right.at(algo)
234                                                    + "', the error is: " + nlopt_res2string(res));
235             // LCOV_EXCL_STOP
236         }
237         res = ::nlopt_set_xtol_abs1(m_value.get(), xtol_abs);
238         if (res != NLOPT_SUCCESS) {
239             // LCOV_EXCL_START
240             pagmo_throw(std::invalid_argument, "could not set the 'xtol_abs' stopping criterion to "
241                                                    + std::to_string(xtol_abs) + " for the NLopt algorithm '"
242                                                    + nlopt_names.right.at(algo)
243                                                    + "', the error is: " + nlopt_res2string(res));
244             // LCOV_EXCL_STOP
245         }
246         res = ::nlopt_set_maxeval(m_value.get(), maxeval);
247         if (res != NLOPT_SUCCESS) {
248             // LCOV_EXCL_START
249             pagmo_throw(std::invalid_argument, "could not set the 'maxeval' stopping criterion to "
250                                                    + std::to_string(maxeval) + " for the NLopt algorithm '"
251                                                    + nlopt_names.right.at(algo)
252                                                    + "', the error is: " + nlopt_res2string(res));
253             // LCOV_EXCL_STOP
254         }
255         res = ::nlopt_set_maxtime(m_value.get(), maxtime);
256         if (res != NLOPT_SUCCESS) {
257             // LCOV_EXCL_START
258             pagmo_throw(std::invalid_argument, "could not set the 'maxtime' stopping criterion to "
259                                                    + std::to_string(maxtime) + " for the NLopt algorithm '"
260                                                    + nlopt_names.right.at(algo)
261                                                    + "', the error is: " + nlopt_res2string(res));
262             // LCOV_EXCL_STOP
263         }
264     }
265 
266     // Set box bounds.
set_boundspagmo::detail::__anon54303aaf0111::nlopt_obj267     void set_bounds()
268     {
269         const auto bounds = m_prob.get_bounds();
270         auto res = ::nlopt_set_lower_bounds(m_value.get(), bounds.first.data());
271         if (res != NLOPT_SUCCESS) {
272             // LCOV_EXCL_START
273             pagmo_throw(std::invalid_argument, "could not set the lower bounds for the NLopt algorithm '"
274                                                    + nlopt_names.right.at(m_algo)
275                                                    + "', the error is: " + nlopt_res2string(res));
276             // LCOV_EXCL_STOP
277         }
278         res = ::nlopt_set_upper_bounds(m_value.get(), bounds.second.data());
279         if (res != NLOPT_SUCCESS) {
280             // LCOV_EXCL_START
281             pagmo_throw(std::invalid_argument, "could not set the upper bounds for the NLopt algorithm '"
282                                                    + nlopt_names.right.at(m_algo)
283                                                    + "', the error is: " + nlopt_res2string(res));
284             // LCOV_EXCL_STOP
285         }
286     }
287 
288     // Set the objfun + gradient.
set_objfunpagmo::detail::__anon54303aaf0111::nlopt_obj289     void set_objfun()
290     {
291         // If needed, init the sparsity pattern.
292         // NOTE: we do it here so that, in case this is a local optimiser,
293         // we don't waste memory (set_objfun() etc. are not called when setting up a local
294         // optimiser).
295         if (m_prob.has_gradient_sparsity()) {
296             m_sp = m_prob.gradient_sparsity();
297         }
298         auto res = ::nlopt_set_min_objective(m_value.get(), nlopt_objfun_wrapper, static_cast<void *>(this));
299         if (res != NLOPT_SUCCESS) {
300             // LCOV_EXCL_START
301             pagmo_throw(std::invalid_argument, "could not set the objective function for the NLopt algorithm '"
302                                                    + nlopt_names.right.at(m_algo)
303                                                    + "', the error is: " + nlopt_res2string(res));
304             // LCOV_EXCL_STOP
305         }
306     }
307 
308     // Inequality constraints.
set_ineq_constraintspagmo::detail::__anon54303aaf0111::nlopt_obj309     void set_ineq_constraints()
310     {
311         if (m_prob.get_nic()) {
312             const auto c_tol = m_prob.get_c_tol();
313             auto res = ::nlopt_add_inequality_mconstraint(
314                 m_value.get(), boost::numeric_cast<unsigned>(m_prob.get_nic()), nlopt_ineq_c_wrapper,
315                 static_cast<void *>(this), c_tol.data() + m_prob.get_nec());
316             if (res != NLOPT_SUCCESS) {
317                 pagmo_throw(std::invalid_argument,
318                             "could not set the inequality constraints for the NLopt algorithm '"
319                                 + nlopt_names.right.at(m_algo) + "', the error is: " + nlopt_res2string(res)
320                                 + "\nThis usually means that the algorithm does not support inequality constraints");
321             }
322         }
323     }
324 
325     // Equality constraints.
set_eq_constraintspagmo::detail::__anon54303aaf0111::nlopt_obj326     void set_eq_constraints()
327     {
328         if (m_prob.get_nec()) {
329             const auto c_tol = m_prob.get_c_tol();
330             auto res = ::nlopt_add_equality_mconstraint(m_value.get(), boost::numeric_cast<unsigned>(m_prob.get_nec()),
331                                                         nlopt_eq_c_wrapper, static_cast<void *>(this), c_tol.data());
332             if (res != NLOPT_SUCCESS) {
333                 pagmo_throw(std::invalid_argument,
334                             "could not set the equality constraints for the NLopt algorithm '"
335                                 + nlopt_names.right.at(m_algo) + "', the error is: " + nlopt_res2string(res)
336                                 + "\nThis usually means that the algorithm does not support equality constraints");
337             }
338         }
339     }
340 
341     // Delete all other ctors/assignment ops.
342     nlopt_obj(const nlopt_obj &) = delete;
343     nlopt_obj(nlopt_obj &&) = delete;
344     nlopt_obj &operator=(const nlopt_obj &) = delete;
345     nlopt_obj &operator=(nlopt_obj &&) = delete;
346 
347     // Data members.
348     ::nlopt_algorithm m_algo;
349     problem &m_prob;
350     sparsity_pattern m_sp;
351     std::unique_ptr<std::remove_pointer<::nlopt_opt>::type, void (*)(::nlopt_opt)> m_value;
352     vector_double m_dv;
353     unsigned m_verbosity;
354     unsigned long m_objfun_counter = 0;
355     log_type m_log;
356     // This exception pointer will be null, unless
357     // an error is raised during the computation of the objfun
358     // or constraints. If not null, it will be re-thrown
359     // in the evolve() method.
360     std::exception_ptr m_eptr;
361 };
362 
nlopt_objfun_wrapper(unsigned dim,const double * x,double * grad,void * f_data)363 double nlopt_objfun_wrapper(unsigned dim, const double *x, double *grad, void *f_data)
364 {
365     // Get *this back from the function data.
366     auto &nlo = *static_cast<nlopt_obj *>(f_data);
367 
368     // NOTE: the idea here is that we wrap everything in a try/catch block,
369     // and, if any exception is thrown, we record it into the nlo object
370     // and re-throw it later. We do this because we are using the NLopt C API,
371     // and if we let exceptions out of here we run in undefined behaviour.
372     // We do the same for the constraints functions.
373     try {
374         // A few shortcuts.
375         auto &p = nlo.m_prob;
376         auto &dv = nlo.m_dv;
377         const auto verb = nlo.m_verbosity;
378         auto &f_count = nlo.m_objfun_counter;
379         auto &log = nlo.m_log;
380 
381         // A couple of sanity checks.
382         assert(dim == p.get_nx());
383         assert(dv.size() == dim);
384 
385         if (grad && !p.has_gradient()) {
386             // If grad is not null, it means we are in an algorithm
387             // that needs the gradient. If the problem does not support it,
388             // we error out.
389             pagmo_throw(std::invalid_argument,
390                         "during an optimization with the NLopt algorithm '"
391                             + nlopt_names.right.at(::nlopt_get_algorithm(nlo.m_value.get()))
392                             + "' an objective function gradient was requested, but the optimisation problem '"
393                             + p.get_name() + "' does not provide it");
394         }
395 
396         // Copy the decision vector in our temporary dv vector_double,
397         // for use in the pagmo API.
398         std::copy(x, x + dim, dv.begin());
399 
400         // Compute fitness.
401         const auto fitness = p.fitness(dv);
402 
403         // Compute gradient, if needed.
404         if (grad) {
405             const auto gradient = p.gradient(dv);
406 
407             if (p.has_gradient_sparsity()) {
408                 // Sparse gradient case.
409                 const auto &sp = nlo.m_sp;
410                 // NOTE: problem::gradient() has already checked that
411                 // the returned vector has size m_gs_dim, i.e., the stored
412                 // size of the sparsity pattern. On the other hand,
413                 // problem::gradient_sparsity() also checks that the returned
414                 // vector has size m_gs_dim, so these two must have the same size.
415                 assert(gradient.size() == sp.size());
416                 auto g_it = gradient.begin();
417 
418                 // First we fill the dense output gradient with zeroes.
419                 std::fill(grad, grad + dim, 0.);
420                 // Then we iterate over the sparsity pattern, and fill in the
421                 // nonzero bits in grad.
422                 for (auto it = sp.begin(); it != sp.end() && it->first == 0u; ++it, ++g_it) {
423                     // NOTE: we just need the gradient of the objfun,
424                     // i.e., those (i,j) pairs in which i == 0. We know that the gradient
425                     // of the objfun, if present, starts at the beginning of sp, as sp is
426                     // sorted in lexicographic fashion.
427                     grad[it->second] = *g_it;
428                 }
429             } else {
430                 // Dense gradient case.
431                 nlopt_obj::unchecked_copy(p.get_nx(), gradient.data(), grad);
432             }
433         }
434 
435         // Update the log if requested.
436         if (verb && !(f_count % verb)) {
437             // Constraints bits.
438             const auto ctol = p.get_c_tol();
439             const auto c1eq
440                 = detail::test_eq_constraints(fitness.data() + 1, fitness.data() + 1 + p.get_nec(), ctol.data());
441             const auto c1ineq = detail::test_ineq_constraints(
442                 fitness.data() + 1 + p.get_nec(), fitness.data() + fitness.size(), ctol.data() + p.get_nec());
443             // This will be the total number of violated constraints.
444             const auto nv = p.get_nc() - c1eq.first - c1ineq.first;
445             // This will be the norm of the violation.
446             const auto l = c1eq.second + c1ineq.second;
447             // Test feasibility.
448             const auto feas = p.feasibility_f(fitness);
449 
450             if (!(f_count / verb % 50u)) {
451                 // Every 50 lines print the column names.
452                 print("\n", std::setw(10), "objevals:", std::setw(15), "objval:", std::setw(15),
453                       "violated:", std::setw(15), "viol. norm:", '\n');
454             }
455             // Print to screen the log line.
456             print(std::setw(10), f_count + 1u, std::setw(15), fitness[0], std::setw(15), nv, std::setw(15), l,
457                   feas ? "" : " i", '\n');
458             // Record the log.
459             log.emplace_back(f_count + 1u, fitness[0], nv, l, feas);
460         }
461 
462         // Update the counter.
463         ++f_count;
464 
465         // Return the objfun value.
466         return fitness[0];
467     } catch (...) {
468         // Store exception, force the stop of the optimisation,
469         // and return a useless value.
470         nlo.m_eptr = std::current_exception();
471         ::nlopt_force_stop(nlo.m_value.get());
472         return HUGE_VAL;
473     }
474 }
475 
nlopt_ineq_c_wrapper(unsigned m,double * result,unsigned dim,const double * x,double * grad,void * f_data)476 void nlopt_ineq_c_wrapper(unsigned m, double *result, unsigned dim, const double *x, double *grad, void *f_data)
477 {
478     // Get *this back from the function data.
479     auto &nlo = *static_cast<nlopt_obj *>(f_data);
480 
481     try {
482         // A few shortcuts.
483         auto &p = nlo.m_prob;
484         auto &dv = nlo.m_dv;
485 
486         // A couple of sanity checks.
487         assert(dim == p.get_nx());
488         assert(dv.size() == dim);
489         assert(m == p.get_nic());
490         (void)m;
491 
492         if (grad && !p.has_gradient()) {
493             // If grad is not null, it means we are in an algorithm
494             // that needs the gradient. If the problem does not support it,
495             // we error out.
496             pagmo_throw(std::invalid_argument, "during an optimization with the NLopt algorithm '"
497                                                    + nlopt_names.right.at(::nlopt_get_algorithm(nlo.m_value.get()))
498                                                    + "' an inequality constraints gradient was requested, but the "
499                                                      "optimisation problem '"
500                                                    + p.get_name() + "' does not provide it");
501         }
502 
503         // Copy the decision vector in our temporary dv vector_double,
504         // for use in the pagmo API.
505         std::copy(x, x + dim, dv.begin());
506 
507         // Compute fitness and write IC to the output.
508         // NOTE: fitness is nobj + nec + nic.
509         const auto fitness = p.fitness(dv);
510         nlopt_obj::unchecked_copy(p.get_nic(), fitness.data() + 1 + p.get_nec(), result);
511 
512         if (grad) {
513             // Handle gradient, if requested.
514             const auto gradient = p.gradient(dv);
515 
516             if (p.has_gradient_sparsity()) {
517                 // Sparse gradient.
518                 const auto &sp = nlo.m_sp;
519                 // NOTE: problem::gradient() has already checked that
520                 // the returned vector has size m_gs_dim, i.e., the stored
521                 // size of the sparsity pattern. On the other hand,
522                 // problem::gradient_sparsity() also checks that the returned
523                 // vector has size m_gs_dim, so these two must have the same size.
524                 assert(gradient.size() == sp.size());
525 
526                 // Let's first fill it with zeroes.
527                 std::fill(grad, grad + p.get_nx() * p.get_nic(), 0.);
528 
529                 // Now we need to go into the sparsity pattern and find where
530                 // the sparsity data for the constraints start.
531                 auto it_sp = std::lower_bound(sp.begin(), sp.end(), sparsity_pattern::value_type(p.get_nec() + 1u, 0u));
532 
533                 // Need to do a bit of horrid overflow checking :/.
534                 using diff_type = std::iterator_traits<decltype(it_sp)>::difference_type;
535                 using udiff_type = std::make_unsigned<diff_type>::type;
536                 if (sp.size() > static_cast<udiff_type>(std::numeric_limits<diff_type>::max())) {
537                     pagmo_throw(std::overflow_error, "Overflow error, the sparsity pattern size is too large.");
538                 }
539                 // This is the index at which the ineq constraints start.
540                 const auto idx = std::distance(sp.begin(), it_sp);
541                 // Grab the start of the gradient data for the ineq constraints.
542                 auto g_it = gradient.data() + idx;
543 
544                 // Then we iterate over the sparsity pattern, and fill in the
545                 // nonzero bits in grad. Run until sp.end() as the IC are at the
546                 // end of the sparsity/gradient vector.
547                 for (; it_sp != sp.end(); ++it_sp, ++g_it) {
548                     grad[(it_sp->first - 1u - p.get_nec()) * p.get_nx() + it_sp->second] = *g_it;
549                 }
550             } else {
551                 // Dense gradient.
552                 nlopt_obj::unchecked_copy(p.get_nic() * p.get_nx(), gradient.data() + p.get_nx() * (1u + p.get_nec()),
553                                           grad);
554             }
555         }
556     } catch (...) {
557         // Store exception, stop optimisation.
558         nlo.m_eptr = std::current_exception();
559         ::nlopt_force_stop(nlo.m_value.get());
560     }
561 }
562 
nlopt_eq_c_wrapper(unsigned m,double * result,unsigned dim,const double * x,double * grad,void * f_data)563 void nlopt_eq_c_wrapper(unsigned m, double *result, unsigned dim, const double *x, double *grad, void *f_data)
564 {
565     // Get *this back from the function data.
566     auto &nlo = *static_cast<nlopt_obj *>(f_data);
567 
568     try {
569         // A few shortcuts.
570         auto &p = nlo.m_prob;
571         auto &dv = nlo.m_dv;
572 
573         // A couple of sanity checks.
574         assert(dim == p.get_nx());
575         assert(dv.size() == dim);
576         assert(m == p.get_nec());
577 
578         if (grad && !p.has_gradient()) {
579             // If grad is not null, it means we are in an algorithm
580             // that needs the gradient. If the problem does not support it,
581             // we error out.
582             pagmo_throw(std::invalid_argument,
583                         "during an optimization with the NLopt algorithm '"
584                             + nlopt_names.right.at(::nlopt_get_algorithm(nlo.m_value.get()))
585                             + "' an equality constraints gradient was requested, but the optimisation problem '"
586                             + p.get_name() + "' does not provide it");
587         }
588 
589         // Copy the decision vector in our temporary dv vector_double,
590         // for use in the pagmo API.
591         std::copy(x, x + dim, dv.begin());
592 
593         // Compute fitness and write EC to the output.
594         // NOTE: fitness is nobj + nec + nic.
595         const auto fitness = p.fitness(dv);
596         nlopt_obj::unchecked_copy(p.get_nec(), fitness.data() + 1, result);
597 
598         if (grad) {
599             // Handle gradient, if requested.
600             const auto gradient = p.gradient(dv);
601 
602             if (p.has_gradient_sparsity()) {
603                 // Sparse gradient case.
604                 const auto &sp = nlo.m_sp;
605                 // NOTE: problem::gradient() has already checked that
606                 // the returned vector has size m_gs_dim, i.e., the stored
607                 // size of the sparsity pattern. On the other hand,
608                 // problem::gradient_sparsity() also checks that the returned
609                 // vector has size m_gs_dim, so these two must have the same size.
610                 assert(gradient.size() == sp.size());
611 
612                 // Let's first fill it with zeroes.
613                 std::fill(grad, grad + p.get_nx() * p.get_nec(), 0.);
614 
615                 // Now we need to go into the sparsity pattern and find where
616                 // the sparsity data for the constraints start.
617                 // NOTE: it_sp could be end() or point to ineq constraints. This should
618                 // be fine: it_sp is a valid iterator in sp, sp has the same
619                 // size as gradient and we do the proper checks below before accessing
620                 // the values pointed to by it_sp/g_it.
621                 auto it_sp = std::lower_bound(sp.begin(), sp.end(), sparsity_pattern::value_type(1u, 0u));
622 
623                 // Need to do a bit of horrid overflow checking :/.
624                 using diff_type = std::iterator_traits<decltype(it_sp)>::difference_type;
625                 using udiff_type = std::make_unsigned<diff_type>::type;
626                 if (sp.size() > static_cast<udiff_type>(std::numeric_limits<diff_type>::max())) {
627                     pagmo_throw(std::overflow_error, "Overflow error, the sparsity pattern size is too large.");
628                 }
629                 // This is the index at which the eq constraints start.
630                 const auto idx = std::distance(sp.begin(), it_sp);
631                 // Grab the start of the gradient data for the eq constraints.
632                 auto g_it = gradient.data() + idx;
633 
634                 // Then we iterate over the sparsity pattern, and fill in the
635                 // nonzero bits in grad. We terminate either at the end of sp, or when
636                 // we encounter the first inequality constraint.
637                 for (; it_sp != sp.end() && it_sp->first < p.get_nec() + 1u; ++it_sp, ++g_it) {
638                     grad[(it_sp->first - 1u) * p.get_nx() + it_sp->second] = *g_it;
639                 }
640             } else {
641                 // Dense gradient.
642                 nlopt_obj::unchecked_copy(p.get_nx() * p.get_nec(), gradient.data() + p.get_nx(), grad);
643             }
644         }
645     } catch (...) {
646         // Store exception, stop optimisation.
647         nlo.m_eptr = std::current_exception();
648         ::nlopt_force_stop(nlo.m_value.get());
649     }
650 }
651 
652 } // namespace
653 
654 } // namespace detail
655 
656 /// Default constructor.
657 /**
658  * The default constructor initialises the pagmo::nlopt algorithm with the ``"cobyla"`` solver.
659  * The individual selection/replacement strategies are those specified by
660  * not_population_based::not_population_based().
661  *
662  * @throws unspecified any exception thrown by pagmo::nlopt(const std::string &).
663  */
nlopt()664 nlopt::nlopt() : nlopt("cobyla") {}
665 
666 /// Constructor from solver name.
667 /**
668  * This constructor will initialise a pagmo::nlopt object which will use the NLopt algorithm specified by
669  * the input string \p algo. The individual selection/replacement strategies are those specified by
670  * not_population_based::not_population_based(). \p algo is translated to an NLopt algorithm type according to the
671  * following table:
672  * \verbatim embed:rst:leading-asterisk
673  *  ================================  ====================================
674  *  ``algo`` string                   NLopt algorithm
675  *  ================================  ====================================
676  *  ``"cobyla"``                      ``NLOPT_LN_COBYLA``
677  *  ``"bobyqa"``                      ``NLOPT_LN_BOBYQA``
678  *  ``"newuoa"``                      ``NLOPT_LN_NEWUOA``
679  *  ``"newuoa_bound"``                ``NLOPT_LN_NEWUOA_BOUND``
680  *  ``"praxis"``                      ``NLOPT_LN_PRAXIS``
681  *  ``"neldermead"``                  ``NLOPT_LN_NELDERMEAD``
682  *  ``"sbplx"``                       ``NLOPT_LN_SBPLX``
683  *  ``"mma"``                         ``NLOPT_LD_MMA``
684  *  ``"ccsaq"``                       ``NLOPT_LD_CCSAQ``
685  *  ``"slsqp"``                       ``NLOPT_LD_SLSQP``
686  *  ``"lbfgs"``                       ``NLOPT_LD_LBFGS``
687  *  ``"tnewton_precond_restart"``     ``NLOPT_LD_TNEWTON_PRECOND_RESTART``
688  *  ``"tnewton_precond"``             ``NLOPT_LD_TNEWTON_PRECOND``
689  *  ``"tnewton_restart"``             ``NLOPT_LD_TNEWTON_RESTART``
690  *  ``"tnewton"``                     ``NLOPT_LD_TNEWTON``
691  *  ``"var2"``                        ``NLOPT_LD_VAR2``
692  *  ``"var1"``                        ``NLOPT_LD_VAR1``
693  *  ``"auglag"``                      ``NLOPT_AUGLAG``
694  *  ``"auglag_eq"``                   ``NLOPT_AUGLAG_EQ``
695  *  ================================  ====================================
696  * \endverbatim
697  * The parameters of the selected algorithm can be specified via the methods of this class.
698  *
699  * \verbatim embed:rst:leading-asterisk
700  * .. seealso::
701  *
702  *    The `NLopt website <https://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/>`__ contains a detailed
703  *    description of each supported solver.
704  *
705  * \endverbatim
706  *
707  * @param algo the name of the NLopt algorithm that will be used by this pagmo::nlopt object.
708  *
709  * @throws std::runtime_error if the NLopt version is not at least 2.
710  * @throws std::invalid_argument if \p algo is not one of the allowed algorithm names.
711  * @throws unspecified any exception thrown by not_population_based::not_population_based().
712  */
nlopt(const std::string & algo)713 nlopt::nlopt(const std::string &algo) : m_algo(algo)
714 {
715     // Check version.
716     int major, minor, bugfix;
717     ::nlopt_version(&major, &minor, &bugfix);
718     if (major < 2) {
719         pagmo_throw(std::runtime_error, "Only NLopt version >= 2 is supported"); // LCOV_EXCL_LINE
720     }
721 
722     // Check the algorithm.
723     if (detail::nlopt_names.left.find(m_algo) == detail::nlopt_names.left.end()) {
724         // The selected algorithm is unknown or not among the supported ones.
725         std::ostringstream oss;
726         std::transform(detail::nlopt_names.left.begin(), detail::nlopt_names.left.end(),
727                        std::ostream_iterator<std::string>(oss, "\n"),
728                        [](const uncvref_t<decltype(*detail::nlopt_names.left.begin())> &v) { return v.first; });
729         pagmo_throw(std::invalid_argument,
730                     "unknown/unsupported NLopt algorithm '" + algo + "'. The supported algorithms are:\n" + oss.str());
731     }
732 }
733 
734 /// Copy constructor.
735 /**
736  * The copy constructor will deep-copy the state of \p other.
737  *
738  * @param other the construction argument.
739  *
740  * @throws unspecified any exception thrown by copying the internal state of \p other.
741  */
nlopt(const nlopt & other)742 nlopt::nlopt(const nlopt &other)
743     : not_population_based(other), m_algo(other.m_algo), m_last_opt_result(other.m_last_opt_result),
744       m_sc_stopval(other.m_sc_stopval), m_sc_ftol_rel(other.m_sc_ftol_rel), m_sc_ftol_abs(other.m_sc_ftol_abs),
745       m_sc_xtol_rel(other.m_sc_xtol_rel), m_sc_xtol_abs(other.m_sc_xtol_abs), m_sc_maxeval(other.m_sc_maxeval),
746       m_sc_maxtime(other.m_sc_maxtime), m_verbosity(other.m_verbosity), m_log(other.m_log),
747       m_loc_opt(other.m_loc_opt ? std::make_unique<nlopt>(*other.m_loc_opt) : nullptr)
748 {
749 }
750 
751 /// Evolve population.
752 /**
753  * This method will select an individual from \p pop, optimise it with the NLopt algorithm specified upon
754  * construction, replace an individual in \p pop with the optimised individual, and finally return \p pop.
755  * The individual selection and replacement criteria can be set via set_selection(const std::string &),
756  * set_selection(population::size_type), set_replacement(const std::string &) and
757  * set_replacement(population::size_type). The NLopt solver will run until one of the stopping criteria
758  * is satisfied, and the return status of the NLopt solver will be recorded (it can be fetched with
759  * get_last_opt_result()).
760  *
761  * @param pop the population to be optimised.
762  *
763  * @return the optimised population.
764  *
765  * @throws std::invalid_argument in the following cases:
766  * - the population's problem is multi-objective,
767  * - the setup of the NLopt algorithm fails (e.g., if the problem is constrained but the selected
768  *   NLopt solver does not support constrained optimisation),
769  * - the selected NLopt solver needs gradients but they are not provided by the population's
770  *   problem,
771  * - the components of the individual selected for optimisation contain NaNs or they are outside
772  *   the problem's bounds.
773  * @throws unspecified any exception thrown by the public interface of pagmo::problem or
774  * pagmo::not_population_based.
775  */
evolve(population pop) const776 population nlopt::evolve(population pop) const
777 {
778     if (!pop.size()) {
779         // In case of an empty pop, just return it.
780         return pop;
781     }
782 
783     auto &prob = pop.get_problem();
784 
785     // Create the nlopt obj.
786     // NOTE: this will check also the problem's properties.
787     detail::nlopt_obj no(detail::nlopt_names.left.at(m_algo), prob, m_sc_stopval, m_sc_ftol_rel, m_sc_ftol_abs,
788                          m_sc_xtol_rel, m_sc_xtol_abs, m_sc_maxeval, m_sc_maxtime, m_verbosity);
789     no.set_bounds();
790     no.set_objfun();
791     no.set_eq_constraints();
792     no.set_ineq_constraints();
793 
794     // Set the local optimiser, if appropriate.
795     if (m_loc_opt) {
796         detail::nlopt_obj no_loc(detail::nlopt_names.left.at(m_loc_opt->m_algo), prob, m_loc_opt->m_sc_stopval,
797                                  m_loc_opt->m_sc_ftol_rel, m_loc_opt->m_sc_ftol_abs, m_loc_opt->m_sc_xtol_rel,
798                                  m_loc_opt->m_sc_xtol_abs, m_loc_opt->m_sc_maxeval, m_loc_opt->m_sc_maxtime, 0);
799         ::nlopt_set_local_optimizer(no.m_value.get(), no_loc.m_value.get());
800     }
801 
802     // Setup of the initial guess. Store also the original fitness
803     // of the selected individual, old_f, for later use.
804     auto sel_xf = select_individual(pop);
805     vector_double initial_guess(std::move(sel_xf.first)), old_f(std::move(sel_xf.second));
806 
807     // Check the initial guess.
808     // NOTE: this should be guaranteed by the population's invariants.
809     assert(initial_guess.size() == prob.get_nx());
810     const auto bounds = prob.get_bounds();
811     for (decltype(bounds.first.size()) i = 0; i < bounds.first.size(); ++i) {
812         if (std::isnan(initial_guess[i])) {
813             pagmo_throw(std::invalid_argument,
814                         "the value of the initial guess at index " + std::to_string(i) + " is NaN");
815         }
816         if (initial_guess[i] < bounds.first[i] || initial_guess[i] > bounds.second[i]) {
817             pagmo_throw(std::invalid_argument, "the value of the initial guess at index " + std::to_string(i)
818                                                    + " is outside the problem's bounds");
819         }
820     }
821 
822     // Run the optimisation and store the status returned by NLopt.
823     double objval;
824     m_last_opt_result = ::nlopt_optimize(no.m_value.get(), initial_guess.data(), &objval);
825     if (m_verbosity) {
826         // Print to screen the result of the optimisation, if we are being verbose.
827         std::cout << "\nOptimisation return status: " << detail::nlopt_res2string(m_last_opt_result) << '\n';
828     }
829     // Replace the log.
830     m_log = std::move(no.m_log);
831 
832     // Handle any exception that might've been thrown.
833     if (no.m_eptr) {
834         std::rethrow_exception(no.m_eptr);
835     }
836 
837     // Compute the new fitness vector.
838     const auto new_f = prob.fitness(initial_guess);
839 
840     // Store the new individual into the population, but only if better.
841     if (compare_fc(new_f, old_f, prob.get_nec(), prob.get_c_tol())) {
842         replace_individual(pop, initial_guess, new_f);
843     }
844 
845     // Return the evolved pop.
846     return pop;
847 }
848 
849 /// Algorithm's name.
850 /**
851  * @return a human-readable name for the algorithm.
852  */
get_name() const853 std::string nlopt::get_name() const
854 {
855     return "NLopt - " + m_algo + ":";
856 }
857 
858 /// Get extra information about the algorithm.
859 /**
860  * @return a human-readable string containing useful information about the algorithm's properties
861  * (e.g., the stopping criteria, the selection/replacement policies, etc.).
862  */
get_extra_info() const863 std::string nlopt::get_extra_info() const
864 {
865     int major, minor, bugfix;
866     ::nlopt_version(&major, &minor, &bugfix);
867     auto retval = "\tNLopt version: " + std::to_string(major) + "." + std::to_string(minor) + "."
868                   + std::to_string(bugfix) + "\n\tSolver: '" + m_algo
869                   + "'\n\tLast optimisation return code: " + detail::nlopt_res2string(m_last_opt_result)
870                   + "\n\tVerbosity: " + std::to_string(m_verbosity) + "\n\tIndividual selection "
871                   + (boost::any_cast<population::size_type>(&m_select)
872                          ? "idx: " + std::to_string(boost::any_cast<population::size_type>(m_select))
873                          : "policy: " + boost::any_cast<std::string>(m_select))
874                   + "\n\tIndividual replacement "
875                   + (boost::any_cast<population::size_type>(&m_replace)
876                          ? "idx: " + std::to_string(boost::any_cast<population::size_type>(m_replace))
877                          : "policy: " + boost::any_cast<std::string>(m_replace))
878                   + "\n\tStopping criteria:\n\t\tstopval:  "
879                   + (m_sc_stopval == -HUGE_VAL ? "disabled" : detail::to_string(m_sc_stopval))
880                   + "\n\t\tftol_rel: " + (m_sc_ftol_rel <= 0. ? "disabled" : detail::to_string(m_sc_ftol_rel))
881                   + "\n\t\tftol_abs: " + (m_sc_ftol_abs <= 0. ? "disabled" : detail::to_string(m_sc_ftol_abs))
882                   + "\n\t\txtol_rel: " + (m_sc_xtol_rel <= 0. ? "disabled" : detail::to_string(m_sc_xtol_rel))
883                   + "\n\t\txtol_abs: " + (m_sc_xtol_abs <= 0. ? "disabled" : detail::to_string(m_sc_xtol_abs))
884                   + "\n\t\tmaxeval:  " + (m_sc_maxeval <= 0. ? "disabled" : detail::to_string(m_sc_maxeval))
885                   + "\n\t\tmaxtime:  " + (m_sc_maxtime <= 0. ? "disabled" : detail::to_string(m_sc_maxtime)) + "\n";
886     if (m_loc_opt) {
887         // Add a tab to the output of the extra_info() of the local opt,
888         // and append the result.
889         retval += "\tLocal optimizer:\n";
890         const auto loc_info = m_loc_opt->get_extra_info();
891         std::vector<std::string> split_v;
892         boost::algorithm::split(split_v, loc_info, boost::algorithm::is_any_of("\n"),
893                                 boost::algorithm::token_compress_on);
894         for (const auto &s : split_v) {
895             retval += "\t" + s + "\n";
896         }
897     }
898     return retval;
899 }
900 
901 /// Set the ``stopval`` stopping criterion.
902 /**
903  * @param stopval the desired value for the ``stopval`` stopping criterion (see get_stopval()).
904  *
905  * @throws std::invalid_argument if \p stopval is NaN.
906  */
set_stopval(double stopval)907 void nlopt::set_stopval(double stopval)
908 {
909     if (std::isnan(stopval)) {
910         pagmo_throw(std::invalid_argument, "The 'stopval' stopping criterion cannot be NaN");
911     }
912     m_sc_stopval = stopval;
913 }
914 
915 /// Set the ``ftol_rel`` stopping criterion.
916 /**
917  * @param ftol_rel the desired value for the ``ftol_rel`` stopping criterion (see get_ftol_rel()).
918  *
919  * @throws std::invalid_argument if \p ftol_rel is NaN.
920  */
set_ftol_rel(double ftol_rel)921 void nlopt::set_ftol_rel(double ftol_rel)
922 {
923     if (std::isnan(ftol_rel)) {
924         pagmo_throw(std::invalid_argument, "The 'ftol_rel' stopping criterion cannot be NaN");
925     }
926     m_sc_ftol_rel = ftol_rel;
927 }
928 
929 /// Set the ``ftol_abs`` stopping criterion.
930 /**
931  * @param ftol_abs the desired value for the ``ftol_abs`` stopping criterion (see get_ftol_abs()).
932  *
933  * @throws std::invalid_argument if \p ftol_abs is NaN.
934  */
set_ftol_abs(double ftol_abs)935 void nlopt::set_ftol_abs(double ftol_abs)
936 {
937     if (std::isnan(ftol_abs)) {
938         pagmo_throw(std::invalid_argument, "The 'ftol_abs' stopping criterion cannot be NaN");
939     }
940     m_sc_ftol_abs = ftol_abs;
941 }
942 
943 /// Set the ``xtol_rel`` stopping criterion.
944 /**
945  * @param xtol_rel the desired value for the ``xtol_rel`` stopping criterion (see get_xtol_rel()).
946  *
947  * @throws std::invalid_argument if \p xtol_rel is NaN.
948  */
set_xtol_rel(double xtol_rel)949 void nlopt::set_xtol_rel(double xtol_rel)
950 {
951     if (std::isnan(xtol_rel)) {
952         pagmo_throw(std::invalid_argument, "The 'xtol_rel' stopping criterion cannot be NaN");
953     }
954     m_sc_xtol_rel = xtol_rel;
955 }
956 
957 /// Set the ``xtol_abs`` stopping criterion.
958 /**
959  * @param xtol_abs the desired value for the ``xtol_abs`` stopping criterion (see get_xtol_abs()).
960  *
961  * @throws std::invalid_argument if \p xtol_abs is NaN.
962  */
set_xtol_abs(double xtol_abs)963 void nlopt::set_xtol_abs(double xtol_abs)
964 {
965     if (std::isnan(xtol_abs)) {
966         pagmo_throw(std::invalid_argument, "The 'xtol_abs' stopping criterion cannot be NaN");
967     }
968     m_sc_xtol_abs = xtol_abs;
969 }
970 
971 /// Set the local optimizer.
972 /**
973  * Some NLopt algorithms rely on other NLopt algorithms as local/subsidiary optimizers.
974  * This method allows to set such local optimizer. By default, no local optimizer is specified.
975  *
976  * \verbatim embed:rst:leading-asterisk
977  * .. note::
978  *
979  *    At the present time, only the ``"auglag"`` and ``"auglag_eq"`` solvers make use
980  *    of a local optimizer. Setting a local optimizer on any other solver will have no effect.
981  *
982  * .. note::
983  *
984  *    The objective function, bounds, and nonlinear-constraint parameters of the local
985  *    optimizer are ignored (as they are provided by the parent optimizer). Conversely, the stopping
986  *    criteria should be specified in the local optimizer. The verbosity of
987  *    the local optimizer is also forcibly set to zero during the optimisation.
988  *
989  * \endverbatim
990  *
991  * @param n the local optimizer that will be used by this pagmo::nlopt algorithm.
992  */
set_local_optimizer(nlopt n)993 void nlopt::set_local_optimizer(nlopt n)
994 {
995     m_loc_opt = std::make_unique<nlopt>(std::move(n));
996 }
997 
998 /// Unset the local optimizer.
999 /**
1000  * After a call to this method, get_local_optimizer() and get_local_optimizer() const will return \p nullptr.
1001  */
unset_local_optimizer()1002 void nlopt::unset_local_optimizer()
1003 {
1004     m_loc_opt.reset(nullptr);
1005 }
1006 
1007 // Save to archive.
1008 template <typename Archive>
save(Archive & ar,unsigned) const1009 void nlopt::save(Archive &ar, unsigned) const
1010 {
1011     detail::to_archive(ar, boost::serialization::base_object<not_population_based>(*this), m_algo, m_last_opt_result,
1012                        m_sc_stopval, m_sc_ftol_rel, m_sc_ftol_abs, m_sc_xtol_rel, m_sc_xtol_abs, m_sc_maxeval,
1013                        m_sc_maxtime, m_verbosity, m_log);
1014     if (m_loc_opt) {
1015         detail::to_archive(ar, true, *m_loc_opt);
1016     } else {
1017         ar << false;
1018     }
1019 }
1020 
1021 // Load from archive.
1022 template <typename Archive>
load(Archive & ar,unsigned)1023 void nlopt::load(Archive &ar, unsigned)
1024 {
1025     detail::from_archive(ar, boost::serialization::base_object<not_population_based>(*this), m_algo, m_last_opt_result,
1026                          m_sc_stopval, m_sc_ftol_rel, m_sc_ftol_abs, m_sc_xtol_rel, m_sc_xtol_abs, m_sc_maxeval,
1027                          m_sc_maxtime, m_verbosity, m_log);
1028     bool with_local;
1029     ar >> with_local;
1030     if (with_local) {
1031         m_loc_opt = std::make_unique<nlopt>();
1032         ar >> *m_loc_opt;
1033     }
1034 }
1035 
1036 } // namespace pagmo
1037 
1038 PAGMO_S11N_ALGORITHM_IMPLEMENT(pagmo::nlopt)
1039