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 // <h1>Optimization Example 2 - Optimization with constraints</h1>
21 // \author David Knezevic
22 // \date 2015
23 //
24 // In this example we extend example 1 to demonstrate how to use
25 // OptimizationSystem's interface for imposing equality and inequality
26 // constraints.
27 
28 // C++ include files that we need
29 #include <iostream>
30 
31 // libMesh includes
32 #include "libmesh/libmesh.h"
33 #include "libmesh/mesh.h"
34 #include "libmesh/mesh_generation.h"
35 #include "libmesh/exodusII_io.h"
36 #include "libmesh/equation_systems.h"
37 #include "libmesh/fe.h"
38 #include "libmesh/quadrature_gauss.h"
39 #include "libmesh/dof_map.h"
40 #include "libmesh/sparse_matrix.h"
41 #include "libmesh/numeric_vector.h"
42 #include "libmesh/dense_matrix.h"
43 #include "libmesh/dense_vector.h"
44 #include "libmesh/elem.h"
45 #include "libmesh/boundary_info.h"
46 #include "libmesh/string_to_enum.h"
47 #include "libmesh/getpot.h"
48 #include "libmesh/zero_function.h"
49 #include "libmesh/dirichlet_boundaries.h"
50 #include "libmesh/optimization_system.h"
51 #include "libmesh/optimization_solver.h"
52 #include "libmesh/parallel.h"
53 
54 // Bring in everything from the libMesh namespace
55 using namespace libMesh;
56 
57 /**
58  * This class encapsulate all functionality required for assembling
59  * the objective function, gradient, hessian, and constraints.
60  */
61 class AssembleOptimization :
62   public OptimizationSystem::ComputeObjective,
63   public OptimizationSystem::ComputeGradient,
64   public OptimizationSystem::ComputeHessian,
65   public OptimizationSystem::ComputeEqualityConstraints,
66   public OptimizationSystem::ComputeEqualityConstraintsJacobian,
67   public OptimizationSystem::ComputeInequalityConstraints,
68   public OptimizationSystem::ComputeInequalityConstraintsJacobian,
69   public OptimizationSystem::ComputeLowerAndUpperBounds
70 {
71 private:
72 
73   /**
74    * Keep a reference to the OptimizationSystem.
75    */
76   OptimizationSystem & _sys;
77 
78 public:
79 
80   /**
81    * Constructor.
82    */
83   AssembleOptimization(OptimizationSystem & sys_in);
84 
85   /**
86    * The optimization problem we consider here is:
87    *   min_U 0.5*U^T A U - U^T F.
88    * In this method, we assemble A and F.
89    */
90   void assemble_A_and_F();
91 
92   /**
93    * Evaluate the objective function.
94    */
95   virtual Number objective (const NumericVector<Number> & soln,
96                             OptimizationSystem & /*sys*/);
97 
98   /**
99    * Evaluate the gradient.
100    */
101   virtual void gradient (const NumericVector<Number> & soln,
102                          NumericVector<Number> & grad_f,
103                          OptimizationSystem & /*sys*/);
104 
105   /**
106    * Evaluate the Hessian.
107    */
108   virtual void hessian (const NumericVector<Number> & soln,
109                         SparseMatrix<Number> & H_f,
110                         OptimizationSystem & /*sys*/);
111 
112   /**
113    * Evaluate the equality constraints.
114    */
115   virtual void equality_constraints (const NumericVector<Number> & X,
116                                      NumericVector<Number> & C_eq,
117                                      OptimizationSystem & /*sys*/);
118 
119   /**
120    * Evaluate the equality constraints Jacobian.
121    */
122   virtual void equality_constraints_jacobian (const NumericVector<Number> & X,
123                                               SparseMatrix<Number> & C_eq_jac,
124                                               OptimizationSystem & /*sys*/);
125 
126   /**
127    * Evaluate the inequality constraints.
128    */
129   virtual void inequality_constraints (const NumericVector<Number> & X,
130                                        NumericVector<Number> & C_ineq,
131                                        OptimizationSystem & /*sys*/);
132 
133   /**
134    * Evaluate the inequality constraints Jacobian.
135    */
136   virtual void inequality_constraints_jacobian (const NumericVector<Number> & X,
137                                                 SparseMatrix<Number> & C_ineq_jac,
138                                                 OptimizationSystem & /*sys*/);
139 
140   /**
141    * Evaluate the lower and upper bounds vectors.
142    */
143   virtual void lower_and_upper_bounds (OptimizationSystem & sys);
144 
145   /**
146    * Sparse matrix for storing the matrix A. We use
147    * this to facilitate computation of objective, gradient
148    * and hessian.
149    */
150   SparseMatrix<Number> * A_matrix;
151 
152   /**
153    * Vector for storing F. We use this to facilitate
154    * computation of objective, gradient and hessian.
155    */
156   NumericVector<Number> * F_vector;
157 };
158 
AssembleOptimization(OptimizationSystem & sys_in)159 AssembleOptimization::AssembleOptimization(OptimizationSystem & sys_in) :
160   _sys(sys_in)
161 {}
162 
assemble_A_and_F()163 void AssembleOptimization::assemble_A_and_F()
164 {
165   A_matrix->zero();
166   F_vector->zero();
167 
168   const MeshBase & mesh = _sys.get_mesh();
169 
170   const unsigned int dim = mesh.mesh_dimension();
171   const unsigned int u_var = _sys.variable_number ("u");
172 
173   const DofMap & dof_map = _sys.get_dof_map();
174   FEType fe_type = dof_map.variable_type(u_var);
175   std::unique_ptr<FEBase> fe (FEBase::build(dim, fe_type));
176   QGauss qrule (dim, fe_type.default_quadrature_order());
177   fe->attach_quadrature_rule (&qrule);
178 
179   const std::vector<Real> & JxW = fe->get_JxW();
180   const std::vector<std::vector<Real>> & phi = fe->get_phi();
181   const std::vector<std::vector<RealGradient>> & dphi = fe->get_dphi();
182 
183   std::vector<dof_id_type> dof_indices;
184 
185   DenseMatrix<Number> Ke;
186   DenseVector<Number> Fe;
187 
188   for (const auto & elem : mesh.active_local_element_ptr_range())
189     {
190       dof_map.dof_indices (elem, dof_indices);
191 
192       const unsigned int n_dofs = dof_indices.size();
193 
194       fe->reinit (elem);
195 
196       Ke.resize (n_dofs, n_dofs);
197       Fe.resize (n_dofs);
198 
199       for (unsigned int qp=0; qp<qrule.n_points(); qp++)
200         {
201           for (unsigned int dof_i=0; dof_i<n_dofs; dof_i++)
202             {
203               for (unsigned int dof_j=0; dof_j<n_dofs; dof_j++)
204                 {
205                   Ke(dof_i, dof_j) += JxW[qp] * (dphi[dof_j][qp]* dphi[dof_i][qp]);
206                 }
207               Fe(dof_i) += JxW[qp] * phi[dof_i][qp];
208             }
209         }
210 
211       A_matrix->add_matrix (Ke, dof_indices);
212       F_vector->add_vector (Fe, dof_indices);
213     }
214 
215   A_matrix->close();
216   F_vector->close();
217 }
218 
objective(const NumericVector<Number> & soln,OptimizationSystem &)219 Number AssembleOptimization::objective (const NumericVector<Number> & soln,
220                                         OptimizationSystem & /*sys*/)
221 {
222   std::unique_ptr<NumericVector<Number>> AxU = soln.zero_clone();
223 
224   A_matrix->vector_mult(*AxU, soln);
225   Number UTxAxU = AxU->dot(soln);
226 
227   Number UTxF = F_vector->dot(soln);
228 
229   return 0.5 * UTxAxU - UTxF;
230 }
231 
gradient(const NumericVector<Number> & soln,NumericVector<Number> & grad_f,OptimizationSystem &)232 void AssembleOptimization::gradient (const NumericVector<Number> & soln,
233                                      NumericVector<Number> & grad_f,
234                                      OptimizationSystem & /*sys*/)
235 {
236   grad_f.zero();
237 
238   A_matrix->vector_mult(grad_f, soln);
239   grad_f.add(-1, *F_vector);
240 }
241 
242 
hessian(const NumericVector<Number> &,SparseMatrix<Number> & H_f,OptimizationSystem & sys)243 void AssembleOptimization::hessian (const NumericVector<Number> & /*soln*/,
244                                     SparseMatrix<Number> & H_f,
245                                     OptimizationSystem & sys)
246 {
247   H_f.zero();
248   H_f.add(1., *A_matrix);
249 
250   // We also need to add the Hessian of the inequality and equality constraints,
251   //  \sum_i^n_eq lambda_eq_i H_eq_i + \sum_i^n_ineq lambda_ineq_i H_ineq_i
252   // where lambda_eq and lambda_ineq denote Lagrange multipliers associated
253   // with the equality and inequality constraints, respectively.
254   // In this example, our equality constraints are linear, hence H_eq_i = 0.
255   // However, our inequality constraint is nonlinear, so it will contribute
256   // to the Hessian matrix.
257   sys.optimization_solver->get_dual_variables();
258   dof_id_type ineq_index = 0;
259   Number lambda_ineq_0 = 0.;
260   unsigned int lambda_rank = 0;
261   if ((sys.lambda_ineq->first_local_index() <= ineq_index) &&
262       (ineq_index < sys.lambda_ineq->last_local_index()))
263     {
264       lambda_ineq_0 = (*sys.lambda_ineq)(0);
265       lambda_rank = sys.comm().rank();
266     }
267 
268   // Sync lambda_rank across all processors.
269   sys.comm().sum(lambda_rank);
270   sys.comm().broadcast(lambda_rank, lambda_rank);
271 
272   if ((sys.get_dof_map().first_dof() <= 200) && (200 < sys.get_dof_map().end_dof()))
273     H_f.add(200, 200, 2. * lambda_ineq_0);
274 }
275 
equality_constraints(const NumericVector<Number> & X,NumericVector<Number> & C_eq,OptimizationSystem &)276 void AssembleOptimization::equality_constraints (const NumericVector<Number> & X,
277                                                  NumericVector<Number> & C_eq,
278                                                  OptimizationSystem & /*sys*/)
279 {
280   C_eq.zero();
281 
282   std::unique_ptr<NumericVector<Number>> X_localized =
283     NumericVector<Number>::build(X.comm());
284   X_localized->init(X.size(), false, SERIAL);
285   X.localize(*X_localized);
286 
287   std::vector<Number> constraint_values(3);
288   constraint_values[0] = (*X_localized)(17);
289   constraint_values[1] = (*X_localized)(23);
290   constraint_values[2] = (*X_localized)(98) + (*X_localized)(185);
291 
292   for (std::size_t i=0; i<constraint_values.size(); i++)
293     if ((C_eq.first_local_index() <= i) &&
294         (i < C_eq.last_local_index()))
295       C_eq.set(i, constraint_values[i]);
296 }
297 
equality_constraints_jacobian(const NumericVector<Number> &,SparseMatrix<Number> & C_eq_jac,OptimizationSystem & sys)298 void AssembleOptimization::equality_constraints_jacobian (const NumericVector<Number> & /*X*/,
299                                                           SparseMatrix<Number> & C_eq_jac,
300                                                           OptimizationSystem & sys)
301 {
302   C_eq_jac.zero();
303 
304   std::vector<std::vector<Number>> constraint_jac_values(3);
305   std::vector<std::vector<dof_id_type>> constraint_jac_indices(3);
306 
307   constraint_jac_values[0].resize(1);
308   constraint_jac_indices[0].resize(1);
309   constraint_jac_values[0][0] = 1.;
310   constraint_jac_indices[0][0] = 17;
311 
312   constraint_jac_values[1].resize(1);
313   constraint_jac_indices[1].resize(1);
314   constraint_jac_values[1][0] = 1.;
315   constraint_jac_indices[1][0] = 23;
316 
317   constraint_jac_values[2].resize(2);
318   constraint_jac_indices[2].resize(2);
319   constraint_jac_values[2][0] = 1.;
320   constraint_jac_values[2][1] = 1.;
321   constraint_jac_indices[2][0] = 98;
322   constraint_jac_indices[2][1] = 185;
323 
324   for (std::size_t i=0; i<constraint_jac_values.size(); i++)
325     for (std::size_t j=0; j<constraint_jac_values[i].size(); j++)
326       if ((sys.C_eq->first_local_index() <= i) &&
327           (i < sys.C_eq->last_local_index()))
328         {
329           dof_id_type col_index = constraint_jac_indices[i][j];
330           Number value = constraint_jac_values[i][j];
331           C_eq_jac.set(i, col_index, value);
332         }
333 }
334 
inequality_constraints(const NumericVector<Number> & X,NumericVector<Number> & C_ineq,OptimizationSystem &)335 void AssembleOptimization::inequality_constraints (const NumericVector<Number> & X,
336                                                    NumericVector<Number> & C_ineq,
337                                                    OptimizationSystem & /*sys*/)
338 {
339   C_ineq.zero();
340 
341   std::unique_ptr<NumericVector<Number>> X_localized =
342     NumericVector<Number>::build(X.comm());
343   X_localized->init(X.size(), false, SERIAL);
344   X.localize(*X_localized);
345 
346   std::vector<Number> constraint_values(1);
347   constraint_values[0] = (*X_localized)(200)*(*X_localized)(200) + (*X_localized)(201) - 5.;
348 
349   for (std::size_t i=0; i<constraint_values.size(); i++)
350     if ((C_ineq.first_local_index() <= i) && (i < C_ineq.last_local_index()))
351       C_ineq.set(i, constraint_values[i]);
352 }
353 
inequality_constraints_jacobian(const NumericVector<Number> & X,SparseMatrix<Number> & C_ineq_jac,OptimizationSystem & sys)354 void AssembleOptimization::inequality_constraints_jacobian (const NumericVector<Number> & X,
355                                                             SparseMatrix<Number> & C_ineq_jac,
356                                                             OptimizationSystem & sys)
357 {
358   C_ineq_jac.zero();
359 
360   std::unique_ptr<NumericVector<Number>> X_localized =
361     NumericVector<Number>::build(X.comm());
362   X_localized->init(X.size(), false, SERIAL);
363   X.localize(*X_localized);
364 
365   std::vector<std::vector<Number>> constraint_jac_values(1);
366   std::vector<std::vector<dof_id_type>> constraint_jac_indices(1);
367 
368   constraint_jac_values[0].resize(2);
369   constraint_jac_indices[0].resize(2);
370   constraint_jac_values[0][0] = 2.* (*X_localized)(200);
371   constraint_jac_values[0][1] = 1.;
372   constraint_jac_indices[0][0] = 200;
373   constraint_jac_indices[0][1] = 201;
374 
375   for (std::size_t i=0; i<constraint_jac_values.size(); i++)
376     for (std::size_t j=0; j<constraint_jac_values[i].size(); j++)
377       if ((sys.C_ineq->first_local_index() <= i) &&
378           (i < sys.C_ineq->last_local_index()))
379         {
380           dof_id_type col_index = constraint_jac_indices[i][j];
381           Number value = constraint_jac_values[i][j];
382           C_ineq_jac.set(i, col_index, value);
383         }
384 
385 }
386 
lower_and_upper_bounds(OptimizationSystem & sys)387 void AssembleOptimization::lower_and_upper_bounds (OptimizationSystem & sys)
388 {
389   for (unsigned int i=sys.get_dof_map().first_dof(); i<sys.get_dof_map().end_dof(); i++)
390     {
391       sys.get_vector("lower_bounds").set(i, -2.);
392       sys.get_vector("upper_bounds").set(i, 2.);
393     }
394 }
395 
main(int argc,char ** argv)396 int main (int argc, char ** argv)
397 {
398   LibMeshInit init (argc, argv);
399 
400 #ifndef LIBMESH_HAVE_PETSC_TAO
401 
402   libmesh_example_requires(false, "PETSc >= 3.5.0 with built-in TAO support");
403 
404 #elif LIBMESH_USE_COMPLEX_NUMBERS
405 
406   // According to
407   // http://www.mcs.anl.gov/research/projects/tao/documentation/installation.html
408   // TAO & PETSc-complex are currently mutually exclusive
409   libmesh_example_requires(false, "PETSc >= 3.5.0 with built-in TAO support & real-numbers only");
410 
411 #endif
412 
413   // We use a 2D domain.
414   libmesh_example_requires(LIBMESH_DIM > 1, "--disable-1D-only");
415 
416   // TAO is giving us problems in parallel?
417   if (init.comm().size() != 1)
418     {
419       libMesh::out << "This example can currently only be run in serial." << std::endl;
420       return 77;
421     }
422 
423   GetPot infile("optimization_ex2.in");
424   const std::string approx_order = infile("approx_order", "FIRST");
425   const std::string fe_family = infile("fe_family", "LAGRANGE");
426   const unsigned int n_elem = infile("n_elem", 10);
427 
428   Mesh mesh(init.comm());
429   MeshTools::Generation::build_square (mesh,
430                                        n_elem,
431                                        n_elem,
432                                        -1., 1.,
433                                        -1., 1.,
434                                        QUAD9);
435 
436   mesh.print_info();
437 
438   EquationSystems equation_systems (mesh);
439 
440   OptimizationSystem & system =
441     equation_systems.add_system<OptimizationSystem> ("Optimization");
442 
443   // The default is to use PETSc/Tao solvers, but let the user change
444   // the optimization solver package on the fly.
445   {
446     const std::string optimization_solver_type = infile("optimization_solver_type",
447                                                         "PETSC_SOLVERS");
448     SolverPackage sp = Utility::string_to_enum<SolverPackage>(optimization_solver_type);
449     std::unique_ptr<OptimizationSolver<Number>> new_solver =
450       OptimizationSolver<Number>::build(system, sp);
451     system.optimization_solver.reset(new_solver.release());
452   }
453 
454   // Set tolerances and maximum iteration counts directly on the optimization solver.
455   system.optimization_solver->max_objective_function_evaluations = 128;
456   system.optimization_solver->objective_function_relative_tolerance = 1.e-4;
457   system.optimization_solver->verbose = true;
458 
459   AssembleOptimization assemble_opt(system);
460 
461   system.add_variable("u",
462                       Utility::string_to_enum<Order>   (approx_order),
463                       Utility::string_to_enum<FEFamily>(fe_family));
464 
465   system.optimization_solver->objective_object = &assemble_opt;
466   system.optimization_solver->gradient_object = &assemble_opt;
467   system.optimization_solver->hessian_object = &assemble_opt;
468   system.optimization_solver->equality_constraints_object = &assemble_opt;
469   system.optimization_solver->equality_constraints_jacobian_object = &assemble_opt;
470   system.optimization_solver->inequality_constraints_object = &assemble_opt;
471   system.optimization_solver->inequality_constraints_jacobian_object = &assemble_opt;
472   system.optimization_solver->lower_and_upper_bounds_object = &assemble_opt;
473 
474   // system.matrix and system.rhs are used for the gradient and Hessian,
475   // so in this case we add an extra matrix and vector to store A and F.
476   // This makes it easy to write the code for evaluating the objective,
477   // gradient, and hessian.
478   system.add_matrix("A_matrix");
479   system.add_vector("F_vector");
480   assemble_opt.A_matrix = &system.get_matrix("A_matrix");
481   assemble_opt.F_vector = &system.get_vector("F_vector");
482 
483 
484   equation_systems.init();
485   equation_systems.print_info();
486 
487   assemble_opt.assemble_A_and_F();
488 
489   {
490     std::vector<std::set<numeric_index_type>> constraint_jac_sparsity;
491     std::set<numeric_index_type> sparsity_row;
492     sparsity_row.insert(17);
493     constraint_jac_sparsity.push_back(sparsity_row);
494     sparsity_row.clear();
495 
496     sparsity_row.insert(23);
497     constraint_jac_sparsity.push_back(sparsity_row);
498     sparsity_row.clear();
499 
500     sparsity_row.insert(98);
501     sparsity_row.insert(185);
502     constraint_jac_sparsity.push_back(sparsity_row);
503 
504     system.initialize_equality_constraints_storage(constraint_jac_sparsity);
505   }
506 
507   {
508     std::vector<std::set<numeric_index_type>> constraint_jac_sparsity;
509     std::set<numeric_index_type> sparsity_row;
510     sparsity_row.insert(200);
511     sparsity_row.insert(201);
512     constraint_jac_sparsity.push_back(sparsity_row);
513 
514     system.initialize_inequality_constraints_storage(constraint_jac_sparsity);
515   }
516 
517   // We need to close the matrix so that we can use it to store the
518   // Hessian during the solve.
519   system.matrix->close();
520   system.solve();
521 
522   // Print convergence information
523   system.optimization_solver->print_converged_reason();
524 
525   // Write the solution to file if the optimization solver converged
526   if (system.optimization_solver->get_converged_reason() > 0)
527     {
528       std::stringstream filename;
529       ExodusII_IO (mesh).write_equation_systems("optimization_soln.exo",
530                                                 equation_systems);
531     }
532 
533   return 0;
534 }
535