libMesh
nlopt_optimization_solver.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2025 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 
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 *)
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
271 template <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 
291 template <typename T>
293 {
294  this->clear ();
295 }
296 
297 
298 
299 template <typename T>
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>
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>
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 
435 template <typename T>
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>
446 {
447  return static_cast<int>(_result);
448 }
449 
450 
451 //------------------------------------------------------------------
452 // Explicit instantiations
453 template class LIBMESH_EXPORT NloptOptimizationSolver<Number>;
454 
455 } // namespace libMesh
456 
457 
458 
459 #endif // #if defined(LIBMESH_HAVE_NLOPT) && !defined(LIBMESH_USE_COMPLEX_NUMBERS)
Abstract base class to be used to calculate the inequality constraints.
OptimizationSystem::ComputeObjective * objective_object
Object that computes the objective function f(X) at the input iterate X.
T command_line_next(std::string name, T default_value)
Use GetPot&#39;s search()/next() functions to get following arguments from the command line...
Definition: libmesh.C:1078
virtual void init() override
Initialize data structures if not done so already.
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...
This class provides an interface to the NLopt optimization solvers.
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).
OptimizationSystem::ComputeGradient * gradient_object
Object that computes the gradient grad_f(X) of the objective function at the input iterate X...
The libMesh namespace provides an interface to certain functionality in the library.
OptimizationSystem::ComputeInequalityConstraints * inequality_constraints_object
Object that computes the inequality constraints vector C_ineq(X).
virtual void solve() override
Call the NLopt solver.
OptimizationSystem::ComputeEqualityConstraintsJacobian * equality_constraints_jacobian_object
Object that computes the Jacobian of C_eq(X).
Abstract base class to be used to calculate the equality constraints.
dof_id_type numeric_index_type
Definition: id_types.h:99
bool _is_initialized
Flag that tells if init() has been called.
Definition: libmesh.C:257
This System subclass enables us to assemble an objective function, gradient, Hessian and bounds for o...
NloptOptimizationSolver(sys_type &system)
Constructor.
void init(triangulateio &t)
Initializes the fields of t to nullptr/0 as necessary.
libmesh_assert(ctx)
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 clear() override
Release all memory and clear data structures.
void __libmesh_nlopt_inequality_constraints(unsigned m, double *result, unsigned n, const double *x, double *gradient, void *data)
const sys_type & system() const
Abstract base class to be used to calculate the Jacobian of the equality constraints.
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...
virtual void print_converged_reason() override
Prints a useful message about why the latest optimization solve con(di)verged.
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).
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
bool verbose
Control how much is output from the OptimizationSolver as it&#39;s running.
This base class can be inherited from to provide interfaces to optimization solvers from different pa...
OStreamProxy out
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:276
bool on_command_line(std::string arg)
Definition: libmesh.C:987
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:117
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).
OptimizationSystem::ComputeInequalityConstraintsJacobian * inequality_constraints_jacobian_object
Object that computes the Jacobian of C_ineq(X).
uint8_t dof_id_type
Definition: id_types.h:67
OptimizationSystem::ComputeEqualityConstraints * equality_constraints_object
Object that computes the equality constraints vector C_eq(X).
Abstract base class to be used to calculate the Jacobian of the inequality constraints.
void __libmesh_nlopt_equality_constraints(unsigned m, double *result, unsigned n, const double *x, double *gradient, void *data)