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