libMesh
Loading...
Searching...
No Matches
nlopt_optimization_solver.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 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
34namespace libMesh
35{
36
37double __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 *)
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
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 *)
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.
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 {
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
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 *)
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.
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 {
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
271template <typename T>
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
291template <typename T>
296
297
298
299template <typename T>
301{
302 if (this->initialized())
303 {
304 this->_is_initialized = false;
305
306 nlopt_destroy(_opt);
307 }
308}
309
310
311
312template <typename T>
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
335template <typename T>
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,
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(),
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(),
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
435template <typename T>
437{
438 libMesh::out << "NLopt optimization solver convergence/divergence reason: "
439 << this->get_converged_reason() << std::endl;
440}
441
442
443
444template <typename T>
446{
447 return static_cast<int>(_result);
448}
449
450
451//------------------------------------------------------------------
452// Explicit instantiations
453template class LIBMESH_EXPORT NloptOptimizationSolver<Number>;
454
455} // namespace libMesh
456
457
458
459#endif // #if defined(LIBMESH_HAVE_NLOPT) && !defined(LIBMESH_USE_COMPLEX_NUMBERS)
This class provides an interface to the NLopt optimization solvers.
virtual void clear() override
Release all memory and clear data structures.
virtual void init() override
Initialize data structures if not done so already.
virtual void print_converged_reason() override
Prints a useful message about why the latest optimization solve con(di)verged.
virtual void solve() override
Call the NLopt solver.
NloptOptimizationSolver(sys_type &system)
Constructor.
This base class can be inherited from to provide interfaces to optimization solvers from different pa...
OptimizationSystem::ComputeInequalityConstraints * inequality_constraints_object
Object that computes the inequality constraints vector C_ineq(X).
OptimizationSystem::ComputeGradient * gradient_object
Object that computes the gradient grad_f(X) of the objective function at the input iterate X.
OptimizationSystem::ComputeEqualityConstraintsJacobian * equality_constraints_jacobian_object
Object that computes the Jacobian of C_eq(X).
const sys_type & system() const
OptimizationSystem::ComputeInequalityConstraintsJacobian * inequality_constraints_jacobian_object
Object that computes the Jacobian of C_ineq(X).
OptimizationSystem::ComputeEqualityConstraints * equality_constraints_object
Object that computes the equality constraints vector C_eq(X).
OptimizationSystem::ComputeObjective * objective_object
Object that computes the objective function f(X) at the input iterate X.
bool verbose
Control how much is output from the OptimizationSolver as it's running.
Abstract base class to be used to calculate the Jacobian of the equality constraints.
virtual void equality_constraints_jacobian(const NumericVector< Number > &X, SparseMatrix< Number > &C_eq_jac, sys_type &S)=0
This function will be called to evaluate the Jacobian of C_eq(X).
Abstract base class to be used to calculate the equality constraints.
virtual void equality_constraints(const NumericVector< Number > &X, NumericVector< Number > &C_eq, sys_type &S)=0
This function will be called to evaluate the equality constraints vector C_eq(X).
virtual void gradient(const NumericVector< Number > &X, NumericVector< Number > &grad_f, sys_type &S)=0
This function will be called to compute the gradient of the objective function, and must be implement...
Abstract base class to be used to calculate the Jacobian of the inequality constraints.
virtual void inequality_constraints_jacobian(const NumericVector< Number > &X, SparseMatrix< Number > &C_ineq_jac, sys_type &S)=0
This function will be called to evaluate the Jacobian of C_ineq(X).
Abstract base class to be used to calculate the inequality constraints.
virtual void inequality_constraints(const NumericVector< Number > &X, NumericVector< Number > &C_ineq, sys_type &S)=0
This function will be called to evaluate the equality constraints vector C_ineq(X).
virtual Number objective(const NumericVector< Number > &X, sys_type &S)=0
This function will be called to compute the objective function to be minimized, and must be implement...
This System subclass enables us to assemble an objective function, gradient, Hessian and bounds for o...
The libMesh namespace provides an interface to certain functionality in the library.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
void __libmesh_nlopt_equality_constraints(unsigned m, double *result, unsigned n, const double *x, double *gradient, void *data)
libmesh_assert(ctx)
OStreamProxy out
void __libmesh_nlopt_inequality_constraints(unsigned m, double *result, unsigned n, const double *x, double *gradient, void *data)
dof_id_type numeric_index_type
Definition id_types.h:99
T command_line_next(std::string name, T default_value)
Use GetPot's search()/next() functions to get following arguments from the command line.
Definition libmesh.C:1025
double __libmesh_nlopt_objective(unsigned n, const double *x, double *gradient, void *data)
bool initialized()
Checks that library initialization has been done.
Definition libmesh.C:324
uint8_t dof_id_type
Definition id_types.h:67
bool on_command_line(std::string arg)
Definition libmesh.C:934
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real