1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2020 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19
20 #include "libmesh/libmesh_common.h"
21
22 #if defined(LIBMESH_HAVE_NLOPT) && !defined(LIBMESH_USE_COMPLEX_NUMBERS)
23
24
25 // Local Includes
26 #include "libmesh/dof_map.h"
27 #include "libmesh/libmesh_logging.h"
28 #include "libmesh/numeric_vector.h"
29 #include "libmesh/nlopt_optimization_solver.h"
30 #include "libmesh/sparse_matrix.h"
31 #include "libmesh/int_range.h"
32 #include "libmesh/utility.h"
33
34 namespace libMesh
35 {
36
__libmesh_nlopt_objective(unsigned n,const double * x,double * gradient,void * data)37 double __libmesh_nlopt_objective(unsigned n,
38 const double * x,
39 double * gradient,
40 void * data)
41 {
42 LOG_SCOPE("objective()", "NloptOptimizationSolver");
43
44 // ctx should be a pointer to the solver (it was passed in as void *)
45 NloptOptimizationSolver<Number> * solver =
46 static_cast<NloptOptimizationSolver<Number> *> (data);
47
48 OptimizationSystem & sys = solver->system();
49
50 // We'll use current_local_solution below, so let's ensure that it's consistent
51 // with the vector x that was passed in.
52 for (auto i : index_range(*sys.solution))
53 sys.solution->set(i, x[i]);
54
55 // Make sure the solution vector is parallel-consistent
56 sys.solution->close();
57
58 // Impose constraints on X
59 sys.get_dof_map().enforce_constraints_exactly(sys);
60
61 // Update sys.current_local_solution based on X
62 sys.update();
63
64 Real objective;
65 if (solver->objective_object != nullptr)
66 {
67 objective =
68 solver->objective_object->objective(*(sys.current_local_solution), sys);
69 }
70 else
71 {
72 libmesh_error_msg("Objective function not defined in __libmesh_nlopt_objective");
73 }
74
75 // If the gradient has been requested, fill it in
76 if (gradient)
77 {
78 if (solver->gradient_object != nullptr)
79 {
80 solver->gradient_object->gradient(*(sys.current_local_solution), *(sys.rhs), sys);
81
82 // we've filled up sys.rhs with the gradient data, now copy it
83 // to the nlopt data structure
84 libmesh_assert(sys.rhs->size() == n);
85
86 std::vector<double> grad;
87 sys.rhs->localize_to_one(grad);
88 for (unsigned int i = 0; i < n; ++i)
89 gradient[i] = grad[i];
90 }
91 else
92 libmesh_error_msg("Gradient function not defined in __libmesh_nlopt_objective");
93 }
94
95 // Increment the iteration count.
96 solver->get_iteration_count()++;
97
98 // Possibly print the current value of the objective function
99 if (solver->verbose)
100 libMesh::out << objective << std::endl;
101
102 return objective;
103 }
104
105
106
__libmesh_nlopt_equality_constraints(unsigned m,double * result,unsigned n,const double * x,double * gradient,void * data)107 void __libmesh_nlopt_equality_constraints(unsigned m,
108 double * result,
109 unsigned n,
110 const double * x,
111 double * gradient,
112 void * data)
113 {
114 LOG_SCOPE("equality_constraints()", "NloptOptimizationSolver");
115
116 libmesh_assert(data);
117
118 // data should be a pointer to the solver (it was passed in as void *)
119 NloptOptimizationSolver<Number> * solver =
120 static_cast<NloptOptimizationSolver<Number> *> (data);
121
122 OptimizationSystem & sys = solver->system();
123
124 // We'll use current_local_solution below, so let's ensure that it's consistent
125 // with the vector x that was passed in.
126 libmesh_error_msg_if(sys.solution->size() != n,
127 "Error: Input vector x has different length than sys.solution!");
128
129 for (auto i : index_range(*sys.solution))
130 sys.solution->set(i, x[i]);
131 sys.solution->close();
132
133 // Impose constraints on the solution vector
134 sys.get_dof_map().enforce_constraints_exactly(sys);
135
136 // Update sys.current_local_solution based on the solution vector
137 sys.update();
138
139 // Call the user's equality constraints function if there is one.
140 OptimizationSystem::ComputeEqualityConstraints * eco = solver->equality_constraints_object;
141 if (eco)
142 {
143 eco->equality_constraints(*sys.current_local_solution,
144 *sys.C_eq,
145 sys);
146
147 sys.C_eq->close();
148
149 // Copy the values out of eq_constraints into 'result'.
150 // TODO: Even better would be if we could use 'result' directly
151 // as the storage of eq_constraints. Perhaps a serial-only
152 // NumericVector variant which supports this option?
153 for (unsigned int i = 0; i < m; ++i)
154 result[i] = (*sys.C_eq)(i);
155
156 // If gradient != nullptr, then the Jacobian matrix of the equality
157 // constraints has been requested. The incoming 'gradient'
158 // array is of length m*n and d(c_i)/d(x_j) = gradient[n*i+j].
159 if (gradient)
160 {
161 OptimizationSystem::ComputeEqualityConstraintsJacobian * eco_jac =
162 solver->equality_constraints_jacobian_object;
163
164 if (eco_jac)
165 {
166 eco_jac->equality_constraints_jacobian(*sys.current_local_solution,
167 *sys.C_eq_jac,
168 sys);
169
170 sys.C_eq_jac->close();
171
172 // copy the Jacobian data to the gradient array
173 for (numeric_index_type i=0; i<m; i++)
174 for (const auto & dof_index : sys.eq_constraint_jac_sparsity[i])
175 gradient[n*i+dof_index] = (*sys.C_eq_jac)(i,dof_index);
176 }
177 else
178 libmesh_error_msg("Jacobian function not defined in __libmesh_nlopt_equality_constraints");
179 }
180
181 }
182 else
183 libmesh_error_msg("Constraints function not defined in __libmesh_nlopt_equality_constraints");
184 }
185
186
__libmesh_nlopt_inequality_constraints(unsigned m,double * result,unsigned n,const double * x,double * gradient,void * data)187 void __libmesh_nlopt_inequality_constraints(unsigned m,
188 double * result,
189 unsigned n,
190 const double * x,
191 double * gradient,
192 void * data)
193 {
194 LOG_SCOPE("inequality_constraints()", "NloptOptimizationSolver");
195
196 libmesh_assert(data);
197
198 // data should be a pointer to the solver (it was passed in as void *)
199 NloptOptimizationSolver<Number> * solver =
200 static_cast<NloptOptimizationSolver<Number> *> (data);
201
202 OptimizationSystem & sys = solver->system();
203
204 // We'll use current_local_solution below, so let's ensure that it's consistent
205 // with the vector x that was passed in.
206 libmesh_error_msg_if(sys.solution->size() != n, "Error: Input vector x has different length than sys.solution!");
207
208 for (auto i : index_range(*sys.solution))
209 sys.solution->set(i, x[i]);
210 sys.solution->close();
211
212 // Impose constraints on the solution vector
213 sys.get_dof_map().enforce_constraints_exactly(sys);
214
215 // Update sys.current_local_solution based on the solution vector
216 sys.update();
217
218 // Call the user's inequality constraints function if there is one.
219 OptimizationSystem::ComputeInequalityConstraints * ineco = solver->inequality_constraints_object;
220 if (ineco)
221 {
222 ineco->inequality_constraints(*sys.current_local_solution,
223 *sys.C_ineq,
224 sys);
225
226 sys.C_ineq->close();
227
228 // Copy the values out of ineq_constraints into 'result'.
229 // TODO: Even better would be if we could use 'result' directly
230 // as the storage of ineq_constraints. Perhaps a serial-only
231 // NumericVector variant which supports this option?
232 for (unsigned int i = 0; i < m; ++i)
233 result[i] = (*sys.C_ineq)(i);
234
235 // If gradient != nullptr, then the Jacobian matrix of the equality
236 // constraints has been requested. The incoming 'gradient'
237 // array is of length m*n and d(c_i)/d(x_j) = gradient[n*i+j].
238 if (gradient)
239 {
240 OptimizationSystem::ComputeInequalityConstraintsJacobian * ineco_jac =
241 solver->inequality_constraints_jacobian_object;
242
243 if (ineco_jac)
244 {
245 ineco_jac->inequality_constraints_jacobian(*sys.current_local_solution,
246 *sys.C_ineq_jac,
247 sys);
248
249 sys.C_ineq_jac->close();
250
251 // copy the Jacobian data to the gradient array
252 for (numeric_index_type i=0; i<m; i++)
253 for (const auto & dof_index : sys.ineq_constraint_jac_sparsity[i])
254 gradient[n*i+dof_index] = (*sys.C_ineq_jac)(i,dof_index);
255 }
256 else
257 libmesh_error_msg("Jacobian function not defined in __libmesh_nlopt_inequality_constraints");
258 }
259
260 }
261 else
262 libmesh_error_msg("Constraints function not defined in __libmesh_nlopt_inequality_constraints");
263 }
264
265 //---------------------------------------------------------------------
266
267
268
269 //---------------------------------------------------------------------
270 // NloptOptimizationSolver<> methods
271 template <typename T>
NloptOptimizationSolver(OptimizationSystem & system_in)272 NloptOptimizationSolver<T>::NloptOptimizationSolver (OptimizationSystem & system_in)
273 :
274 OptimizationSolver<T>(system_in),
275 _opt(nullptr),
276 _result(NLOPT_SUCCESS),
277 _iteration_count(0),
278 _constraints_tolerance(1.e-8)
279 {
280 // The nlopt interfaces all use unsigned int as their index type, so
281 // don't risk using the NloptOptimizationSolver with a libmesh that
282 // is configured to use 64-bit indices. We can detect this at
283 // configure time, so it should not be possible to actually reach
284 // this error message... it's here just in case.
285 libmesh_error_msg_if(sizeof(dof_id_type) != sizeof(unsigned int),
286 "The NloptOptimizationSolver should not be used with dof_id_type != unsigned int.");
287 }
288
289
290
291 template <typename T>
~NloptOptimizationSolver()292 NloptOptimizationSolver<T>::~NloptOptimizationSolver ()
293 {
294 this->clear ();
295 }
296
297
298
299 template <typename T>
clear()300 void NloptOptimizationSolver<T>::clear ()
301 {
302 if (this->initialized())
303 {
304 this->_is_initialized = false;
305
306 nlopt_destroy(_opt);
307 }
308 }
309
310
311
312 template <typename T>
init()313 void NloptOptimizationSolver<T>::init ()
314 {
315 // Initialize the data structures if not done so already.
316 if (!this->initialized())
317 {
318 this->_is_initialized = true;
319
320 // By default, use the LD_SLSQP solver
321 std::string nlopt_algorithm_name = "LD_SLSQP";
322
323 if (libMesh::on_command_line("--nlopt-algorithm"))
324 nlopt_algorithm_name = libMesh::command_line_next ("--nlopt-algorithm",
325 nlopt_algorithm_name);
326
327 // Convert string to an nlopt algorithm type
328 _opt = nlopt_create(libmesh_map_find(_nlopt_algorithms, nlopt_algorithm_name),
329 this->system().solution->size());
330 }
331 }
332
333
334
335 template <typename T>
solve()336 void NloptOptimizationSolver<T>::solve ()
337 {
338 LOG_SCOPE("solve()", "NloptOptimizationSolver");
339
340 this->init ();
341
342 unsigned int nlopt_size = this->system().solution->size();
343
344 // We have to have an objective function
345 libmesh_assert( this->objective_object );
346
347 // Set routine for objective and (optionally) gradient evaluation
348 {
349 nlopt_result ierr =
350 nlopt_set_min_objective(_opt,
351 __libmesh_nlopt_objective,
352 this);
353 libmesh_error_msg_if(ierr < 0, "NLopt failed to set min objective: " << ierr);
354 }
355
356 if (this->lower_and_upper_bounds_object)
357 {
358 // Need to actually compute the bounds vectors first
359 this->lower_and_upper_bounds_object->lower_and_upper_bounds(this->system());
360
361 std::vector<Real> nlopt_lb(nlopt_size);
362 std::vector<Real> nlopt_ub(nlopt_size);
363 for (unsigned int i = 0; i < nlopt_size; ++i)
364 {
365 nlopt_lb[i] = this->system().get_vector("lower_bounds")(i);
366 nlopt_ub[i] = this->system().get_vector("upper_bounds")(i);
367 }
368
369 nlopt_set_lower_bounds(_opt, nlopt_lb.data());
370 nlopt_set_upper_bounds(_opt, nlopt_ub.data());
371 }
372
373 // If we have an equality constraints object, tell NLopt about it.
374 if (this->equality_constraints_object)
375 {
376 // NLopt requires a vector to specify the tolerance for each constraint.
377 // NLopt makes a copy of this vector internally, so it's safe for us to
378 // let it go out of scope.
379 std::vector<double> equality_constraints_tolerances(this->system().C_eq->size(),
380 _constraints_tolerance);
381
382 // It would be nice to call the C interface directly, at least it should return an error
383 // code we could parse... unfortunately, there does not seem to be a way to extract
384 // the underlying nlopt_opt object from the nlopt::opt class!
385 nlopt_result ierr =
386 nlopt_add_equality_mconstraint(_opt,
387 equality_constraints_tolerances.size(),
388 __libmesh_nlopt_equality_constraints,
389 this,
390 equality_constraints_tolerances.data());
391
392 libmesh_error_msg_if(ierr < 0, "NLopt failed to add equality constraint: " << ierr);
393 }
394
395 // If we have an inequality constraints object, tell NLopt about it.
396 if (this->inequality_constraints_object)
397 {
398 // NLopt requires a vector to specify the tolerance for each constraint
399 std::vector<double> inequality_constraints_tolerances(this->system().C_ineq->size(),
400 _constraints_tolerance);
401
402 nlopt_add_inequality_mconstraint(_opt,
403 inequality_constraints_tolerances.size(),
404 __libmesh_nlopt_inequality_constraints,
405 this,
406 inequality_constraints_tolerances.data());
407 }
408
409 // Set a relative tolerance on the optimization parameters
410 nlopt_set_ftol_rel(_opt, this->objective_function_relative_tolerance);
411
412 // Set the maximum number of allowed objective function evaluations
413 nlopt_set_maxeval(_opt, this->max_objective_function_evaluations);
414
415 // Reset internal iteration counter
416 this->_iteration_count = 0;
417
418 // Perform the optimization
419 std::vector<Real> x(nlopt_size);
420 Real min_val = 0.;
421 _result = nlopt_optimize(_opt, x.data(), &min_val);
422
423 if (_result < 0)
424 libMesh::out << "NLopt failed!" << std::endl;
425 else
426 libMesh::out << "NLopt obtained optimal value: "
427 << min_val
428 << " in "
429 << this->get_iteration_count()
430 << " iterations."
431 << std::endl;
432 }
433
434
435 template <typename T>
print_converged_reason()436 void NloptOptimizationSolver<T>::print_converged_reason()
437 {
438 libMesh::out << "NLopt optimization solver convergence/divergence reason: "
439 << this->get_converged_reason() << std::endl;
440 }
441
442
443
444 template <typename T>
get_converged_reason()445 int NloptOptimizationSolver<T>::get_converged_reason()
446 {
447 return static_cast<int>(_result);
448 }
449
450
451 //------------------------------------------------------------------
452 // Explicit instantiations
453 template class NloptOptimizationSolver<Number>;
454
455 } // namespace libMesh
456
457
458
459 #endif // #if defined(LIBMESH_HAVE_NLOPT) && !defined(LIBMESH_USE_COMPLEX_NUMBERS)
460