libMesh
nlopt_optimization_solver.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2019 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 
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  if (sys.solution->size() != n)
127  libmesh_error_msg("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 
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  if (sys.solution->size() != n)
207  libmesh_error_msg("Error: Input vector x has different length than sys.solution!");
208 
209  for (auto i : index_range(*sys.solution))
210  sys.solution->set(i, x[i]);
211  sys.solution->close();
212 
213  // Impose constraints on the solution vector
214  sys.get_dof_map().enforce_constraints_exactly(sys);
215 
216  // Update sys.current_local_solution based on the solution vector
217  sys.update();
218 
219  // Call the user's inequality constraints function if there is one.
221  if (ineco)
222  {
223  ineco->inequality_constraints(*sys.current_local_solution,
224  *sys.C_ineq,
225  sys);
226 
227  sys.C_ineq->close();
228 
229  // Copy the values out of ineq_constraints into 'result'.
230  // TODO: Even better would be if we could use 'result' directly
231  // as the storage of ineq_constraints. Perhaps a serial-only
232  // NumericVector variant which supports this option?
233  for (unsigned int i = 0; i < m; ++i)
234  result[i] = (*sys.C_ineq)(i);
235 
236  // If gradient != nullptr, then the Jacobian matrix of the equality
237  // constraints has been requested. The incoming 'gradient'
238  // array is of length m*n and d(c_i)/d(x_j) = gradient[n*i+j].
239  if (gradient)
240  {
243 
244  if (ineco_jac)
245  {
246  ineco_jac->inequality_constraints_jacobian(*sys.current_local_solution,
247  *sys.C_ineq_jac,
248  sys);
249 
250  sys.C_ineq_jac->close();
251 
252  // copy the Jacobian data to the gradient array
253  for (numeric_index_type i=0; i<m; i++)
254  for (const auto & dof_index : sys.ineq_constraint_jac_sparsity[i])
255  gradient[n*i+dof_index] = (*sys.C_ineq_jac)(i,dof_index);
256  }
257  else
258  libmesh_error_msg("Jacobian function not defined in __libmesh_nlopt_inequality_constraints");
259  }
260 
261  }
262  else
263  libmesh_error_msg("Constraints function not defined in __libmesh_nlopt_inequality_constraints");
264 }
265 
266 //---------------------------------------------------------------------
267 
268 
269 
270 //---------------------------------------------------------------------
271 // NloptOptimizationSolver<> methods
272 template <typename T>
274  :
275  OptimizationSolver<T>(system_in),
276  _opt(nullptr),
277  _result(NLOPT_SUCCESS),
278  _iteration_count(0),
279  _constraints_tolerance(1.e-8)
280 {
281  // The nlopt interfaces all use unsigned int as their index type, so
282  // don't risk using the NloptOptimizationSolver with a libmesh that
283  // is configured to use 64-bit indices. We can detect this at
284  // configure time, so it should not be possible to actually reach
285  // this error message... it's here just in case.
286  if (sizeof(dof_id_type) != sizeof(unsigned int))
287  libmesh_error_msg("The NloptOptimizationSolver should not be used with dof_id_type != unsigned int.");
288 }
289 
290 
291 
292 template <typename T>
294 {
295  this->clear ();
296 }
297 
298 
299 
300 template <typename T>
302 {
303  if (this->initialized())
304  {
305  this->_is_initialized = false;
306 
307  nlopt_destroy(_opt);
308  }
309 }
310 
311 
312 
313 template <typename T>
315 {
316  // Initialize the data structures if not done so already.
317  if (!this->initialized())
318  {
319  this->_is_initialized = true;
320 
321  // By default, use the LD_SLSQP solver
322  std::string nlopt_algorithm_name = "LD_SLSQP";
323 
324  if (libMesh::on_command_line("--nlopt-algorithm"))
325  nlopt_algorithm_name = libMesh::command_line_next ("--nlopt-algorithm",
326  nlopt_algorithm_name);
327 
328  // Convert string to an nlopt algorithm type
329  _opt = nlopt_create(libmesh_map_find(_nlopt_algorithms, nlopt_algorithm_name),
330  this->system().solution->size());
331  }
332 }
333 
334 
335 
336 template <typename T>
338 {
339  LOG_SCOPE("solve()", "NloptOptimizationSolver");
340 
341  this->init ();
342 
343  unsigned int nlopt_size = this->system().solution->size();
344 
345  // We have to have an objective function
346  libmesh_assert( this->objective_object );
347 
348  // Set routine for objective and (optionally) gradient evaluation
349  {
350  nlopt_result ierr =
351  nlopt_set_min_objective(_opt,
353  this);
354  if (ierr < 0)
355  libmesh_error_msg("NLopt failed to set min objective: " << ierr);
356  }
357 
358  if (this->lower_and_upper_bounds_object)
359  {
360  // Need to actually compute the bounds vectors first
361  this->lower_and_upper_bounds_object->lower_and_upper_bounds(this->system());
362 
363  std::vector<Real> nlopt_lb(nlopt_size);
364  std::vector<Real> nlopt_ub(nlopt_size);
365  for (unsigned int i = 0; i < nlopt_size; ++i)
366  {
367  nlopt_lb[i] = this->system().get_vector("lower_bounds")(i);
368  nlopt_ub[i] = this->system().get_vector("upper_bounds")(i);
369  }
370 
371  nlopt_set_lower_bounds(_opt, nlopt_lb.data());
372  nlopt_set_upper_bounds(_opt, nlopt_ub.data());
373  }
374 
375  // If we have an equality constraints object, tell NLopt about it.
376  if (this->equality_constraints_object)
377  {
378  // NLopt requires a vector to specify the tolerance for each constraint.
379  // NLopt makes a copy of this vector internally, so it's safe for us to
380  // let it go out of scope.
381  std::vector<double> equality_constraints_tolerances(this->system().C_eq->size(),
382  _constraints_tolerance);
383 
384  // It would be nice to call the C interface directly, at least it should return an error
385  // code we could parse... unfortunately, there does not seem to be a way to extract
386  // the underlying nlopt_opt object from the nlopt::opt class!
387  nlopt_result ierr =
388  nlopt_add_equality_mconstraint(_opt,
389  equality_constraints_tolerances.size(),
391  this,
392  equality_constraints_tolerances.data());
393 
394  if (ierr < 0)
395  libmesh_error_msg("NLopt failed to add equality constraint: " << ierr);
396  }
397 
398  // If we have an inequality constraints object, tell NLopt about it.
399  if (this->inequality_constraints_object)
400  {
401  // NLopt requires a vector to specify the tolerance for each constraint
402  std::vector<double> inequality_constraints_tolerances(this->system().C_ineq->size(),
403  _constraints_tolerance);
404 
405  nlopt_add_inequality_mconstraint(_opt,
406  inequality_constraints_tolerances.size(),
408  this,
409  inequality_constraints_tolerances.data());
410  }
411 
412  // Set a relative tolerance on the optimization parameters
413  nlopt_set_ftol_rel(_opt, this->objective_function_relative_tolerance);
414 
415  // Set the maximum number of allowed objective function evaluations
416  nlopt_set_maxeval(_opt, this->max_objective_function_evaluations);
417 
418  // Reset internal iteration counter
419  this->_iteration_count = 0;
420 
421  // Perform the optimization
422  std::vector<Real> x(nlopt_size);
423  Real min_val = 0.;
424  _result = nlopt_optimize(_opt, x.data(), &min_val);
425 
426  if (_result < 0)
427  libMesh::out << "NLopt failed!" << std::endl;
428  else
429  libMesh::out << "NLopt obtained optimal value: "
430  << min_val
431  << " in "
432  << this->get_iteration_count()
433  << " iterations."
434  << std::endl;
435 }
436 
437 
438 template <typename T>
440 {
441  libMesh::out << "NLopt optimization solver convergence/divergence reason: "
442  << this->get_converged_reason() << std::endl;
443 }
444 
445 
446 
447 template <typename T>
449 {
450  return static_cast<int>(_result);
451 }
452 
453 
454 //------------------------------------------------------------------
455 // Explicit instantiations
456 template class NloptOptimizationSolver<Number>;
457 
458 } // namespace libMesh
459 
460 
461 
462 #endif // #if defined(LIBMESH_HAVE_NLOPT) && !defined(LIBMESH_USE_COMPLEX_NUMBERS)
libMesh::OptimizationSystem
This System subclass enables us to assemble an objective function, gradient, Hessian and bounds for o...
Definition: optimization_system.h:43
libMesh::OptimizationSolver::equality_constraints_jacobian_object
OptimizationSystem::ComputeEqualityConstraintsJacobian * equality_constraints_jacobian_object
Object that computes the Jacobian of C_eq(X).
Definition: optimization_solver.h:160
libMesh::NloptOptimizationSolver::print_converged_reason
virtual void print_converged_reason() override
Prints a useful message about why the latest optimization solve con(di)verged.
Definition: nlopt_optimization_solver.C:439
libMesh::dof_id_type
uint8_t dof_id_type
Definition: id_types.h:67
libMesh::NloptOptimizationSolver::solve
virtual void solve() override
Call the NLopt solver.
Definition: nlopt_optimization_solver.C:337
libMesh::OptimizationSystem::ComputeEqualityConstraintsJacobian::equality_constraints_jacobian
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).
libMesh::command_line_next
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:963
libMesh::OptimizationSystem::ComputeInequalityConstraintsJacobian
Abstract base class to be used to calculate the Jacobian of the inequality constraints.
Definition: optimization_system.h:187
libMesh::__libmesh_nlopt_equality_constraints
void __libmesh_nlopt_equality_constraints(unsigned m, double *result, unsigned n, const double *x, double *gradient, void *data)
Definition: nlopt_optimization_solver.C:107
libMesh::index_range
IntRange< std::size_t > index_range(const std::vector< T > &vec)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:106
libMesh
The libMesh namespace provides an interface to certain functionality in the library.
Definition: factoryfunction.C:55
libMesh::OptimizationSystem::ComputeInequalityConstraints
Abstract base class to be used to calculate the inequality constraints.
Definition: optimization_system.h:169
libMesh::OptimizationSolver::objective_object
OptimizationSystem::ComputeObjective * objective_object
Object that computes the objective function f(X) at the input iterate X.
Definition: optimization_solver.h:137
libMesh::NloptOptimizationSolver::get_iteration_count
unsigned & get_iteration_count()
Definition: nlopt_optimization_solver.h:127
libMesh::ierr
ierr
Definition: petsc_dm_wrapper.C:72
libMesh::TriangleWrapper::init
void init(triangulateio &t)
Initializes the fields of t to nullptr/0 as necessary.
libMesh::libmesh_assert
libmesh_assert(ctx)
libMesh::OptimizationSystem::ComputeEqualityConstraints
Abstract base class to be used to calculate the equality constraints.
Definition: optimization_system.h:135
libMesh::OptimizationSolver::inequality_constraints_jacobian_object
OptimizationSystem::ComputeInequalityConstraintsJacobian * inequality_constraints_jacobian_object
Object that computes the Jacobian of C_ineq(X).
Definition: optimization_solver.h:171
libMesh::OptimizationSolver
This base class can be inherited from to provide interfaces to optimization solvers from different pa...
Definition: optimization_solver.h:60
libMesh::NloptOptimizationSolver::clear
virtual void clear() override
Release all memory and clear data structures.
Definition: nlopt_optimization_solver.C:301
libMesh::NloptOptimizationSolver::get_converged_reason
virtual int get_converged_reason() override
Definition: nlopt_optimization_solver.C:448
libMesh::OptimizationSystem::ComputeGradient::gradient
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...
libMesh::OptimizationSystem::ComputeEqualityConstraintsJacobian
Abstract base class to be used to calculate the Jacobian of the equality constraints.
Definition: optimization_system.h:153
libMesh::NloptOptimizationSolver::init
virtual void init() override
Initialize data structures if not done so already.
Definition: nlopt_optimization_solver.C:314
libMesh::OptimizationSystem::ComputeObjective::objective
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...
libMesh::OptimizationSolver::system
const sys_type & system() const
Definition: optimization_solver.h:182
libMesh::__libmesh_nlopt_inequality_constraints
void __libmesh_nlopt_inequality_constraints(unsigned m, double *result, unsigned n, const double *x, double *gradient, void *data)
Definition: nlopt_optimization_solver.C:187
libMesh::OptimizationSystem::ComputeInequalityConstraintsJacobian::inequality_constraints_jacobian
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).
libMesh::numeric_index_type
dof_id_type numeric_index_type
Definition: id_types.h:99
libMesh::NloptOptimizationSolver::~NloptOptimizationSolver
~NloptOptimizationSolver()
Destructor.
Definition: nlopt_optimization_solver.C:293
libMesh::initialized
bool initialized()
Checks that library initialization has been done.
Definition: libmesh.C:265
libMesh::OptimizationSystem::ComputeEqualityConstraints::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).
libMesh::libMeshPrivateData::_is_initialized
bool _is_initialized
Flag that tells if init() has been called.
Definition: libmesh.C:246
libMesh::OptimizationSolver::verbose
bool verbose
Control how much is output from the OptimizationSolver as it's running.
Definition: optimization_solver.h:203
libMesh::__libmesh_nlopt_objective
double __libmesh_nlopt_objective(unsigned n, const double *x, double *gradient, void *data)
Definition: nlopt_optimization_solver.C:37
libMesh::OptimizationSystem::ComputeInequalityConstraints::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).
libMesh::on_command_line
bool on_command_line(std::string arg)
Definition: libmesh.C:898
data
IterBase * data
Ideally this private member data should have protected access.
Definition: variant_filter_iterator.h:337
libMesh::NloptOptimizationSolver
This class provides an interface to the NLopt optimization solvers.
Definition: nlopt_optimization_solver.h:70
libMesh::Real
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Definition: libmesh_common.h:121
libMesh::OptimizationSolver::gradient_object
OptimizationSystem::ComputeGradient * gradient_object
Object that computes the gradient grad_f(X) of the objective function at the input iterate X.
Definition: optimization_solver.h:143
libMesh::OptimizationSolver::equality_constraints_object
OptimizationSystem::ComputeEqualityConstraints * equality_constraints_object
Object that computes the equality constraints vector C_eq(X).
Definition: optimization_solver.h:155
libMesh::OptimizationSolver::inequality_constraints_object
OptimizationSystem::ComputeInequalityConstraints * inequality_constraints_object
Object that computes the inequality constraints vector C_ineq(X).
Definition: optimization_solver.h:166
libMesh::out
OStreamProxy out
libMesh::NloptOptimizationSolver::NloptOptimizationSolver
NloptOptimizationSolver(sys_type &system)
Constructor.
Definition: nlopt_optimization_solver.C:273