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