MAST
Multidisciplinary-design Adaptation and Sensitivity Toolkit (MAST)
continuation_solver_base.cpp
Go to the documentation of this file.
1 /*
2  * MAST: Multidisciplinary-design Adaptation and Sensitivity Toolkit
3  * Copyright (C) 2013-2020 Manav Bhatia and MAST authors
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 // MAST includes
22 #include "base/nonlinear_system.h"
23 #include "base/assembly_base.h"
25 #include "base/parameter.h"
26 
27 // libMesh includes
28 #include "libmesh/linear_solver.h"
29 #include "libmesh/dof_map.h"
30 #include "libmesh/petsc_matrix.h"
31 #include "libmesh/petsc_vector.h"
32 
33 // PETSc includes
34 #include <petscmat.h>
35 
36 
38 max_it (10),
39 abs_tol (1.e-8),
40 rel_tol (1.e-8),
41 arc_length (0.),
42 min_step (2.),
43 max_step (20.),
44 step_size_change_exponent (0.5),
45 step_desired_iters (5),
46 schur_factorization (true),
47 _initialized (false),
48 _elem_ops (nullptr),
49 _assembly (nullptr),
50 _p (nullptr),
51 _p0 (0.),
52 _X_scale (0.),
53 _p_scale (0.) {
54 
55 }
56 
57 
58 
60 
61 }
62 
63 
64 
65 void
68  MAST::AssemblyBase& assembly,
69  MAST::Parameter& p) {
70 
71  libmesh_assert(!_elem_ops);
72 
73  _elem_ops = &elem_ops;
74  _assembly = &assembly;
75  _p = &p;
76 
77 
78 }
79 
80 
81 void
83 
84  _elem_ops = nullptr;
85  _assembly = nullptr;
86  _p = nullptr;
87 
88 }
89 
90 
91 
92 void
94 
95  libmesh_assert(_initialized);
96 
97  unsigned int
98  iter = 0;
99 
100  libMesh::NumericVector<Real>
101  &X = *_assembly->system().solution;
102 
103  _p0 = (*_p)();
104  _X0.reset(X.clone().release());
105 
106 
107  // save data for possible reuse if the iterations are restarted.
109 
110  Real
111  norm0 = _res_norm(X, *_p),
112  norm = norm0;
113 
114  bool
115  cont = true;
116 
117  while (cont) {
118 
119  libMesh::out
120  << std::setw(10) << "iter: "
121  << std::setw(5) << iter
122  << std::setw(10) << "res-l2: "
123  << std::setw(15) << norm
124  << std::setw(20) << "relative res-l2: "
125  << std::setw(15) << norm/norm0 << std::endl;
126 
127  _solve_NR_iterate(X, *_p);
128  norm = _res_norm(X, *_p);
129  iter++;
130 
131  if (norm < abs_tol) cont = false;
132  if (norm/norm0 < rel_tol) cont = false;
133  if (iter >= max_it) {
134  if (arc_length > min_step) {
135 
136  // reduce step-size if possible, otherwise terminate
137  libMesh::out
138  << " Retrying with smaller step-size" << std::endl;
139 
140  // reanalyze with a smaller step-size
141  iter = 0;
142  arc_length = std::max(min_step, .5*arc_length);
143  X.zero();
144  X.add(1., *_X0);
145  X.close();
146  *_p = _p0;
148  cont = true;
149  }
150  else
151  cont = false;
152  }
153  }
154 
155  libMesh::out
156  << std::setw(10) << "iter: "
157  << std::setw(5) << iter
158  << std::setw(10) << "res-l2: "
159  << std::setw(15) << norm
160  << std::setw(20) << "relative res-l2: "
161  << std::setw(15) << norm/norm0
162  << std::setw(20) << "Terminated" << std::endl;
163 
164  if (iter) {
165  Real
166  factor = std::pow((1.*step_desired_iters)/(1.*iter+1.), step_size_change_exponent);
167  if (factor > 1.) {
168  arc_length = std::min(max_step, factor*arc_length);
169  libMesh::out
170  << std::setw(30) << "increased step-size: "
171  << std::setw(15) << arc_length << std::endl;
172  }
173  else if (factor < 1.) {
174  arc_length = std::max(min_step, factor*arc_length);
175  libMesh::out
176  << std::setw(30) << "reduced step-size: "
177  << std::setw(15) << arc_length << std::endl;
178  }
179  }
180 
181 }
182 
183 
184 void
185 MAST::ContinuationSolverBase::_solve(const libMesh::NumericVector<Real> &X,
186  const MAST::Parameter &p,
187  libMesh::NumericVector<Real> &f,
188  bool update_f,
189  libMesh::NumericVector<Real> &dfdp,
190  bool update_dfdp,
191  const libMesh::NumericVector<Real> &dgdX,
192  const Real dgdp,
193  const Real g,
194  libMesh::NumericVector<Real> &dX,
195  Real &dp) {
196 
197  libmesh_assert(_elem_ops);
198 
200  &system = _assembly->system();
201 
202  libmesh_assert(system.operation() == MAST::NonlinearSystem::NONE);
203 
204  //
205  // { f(X, p) } = { 0 }
206  // { g(X, p) } = { 0 }
207  //
208  // [df/dX df/dp] { dX } = { -f}
209  // [dg/dX dg/dp] { dp } = { -g}
210  //
211 
212  // create matrix, vector and solver objects for the enlarged system
213  // first, the sparsity pattern.
214  // We will append one row and column to the sparsity pattern of the
215  // system. The highest rank cpu will own this new unknown so that
216  // the dof ids do not change
217 
219  // create the new matrix and vector quantities for the
220  // combined system
222  libMesh::DofMap& dof_map = system.get_dof_map();
223  const libMesh::Parallel::Communicator
224  &comm = system.comm();
225 
226  PetscInt
227  rank = comm.rank(),
228  my_m = dof_map.n_dofs(),
229  my_n = my_m,
230  total_m = my_m + 1, // size of the system with the constraint equation
231  total_n = total_m,
232  n_l = dof_map.n_dofs_on_processor(system.processor_id()),
233  m_l = n_l,
234  total_n_l = n_l,
235  total_m_l = n_l;
236 
237  std::vector<libMesh::dof_id_type>
238  n_nz = dof_map.get_n_nz(), // number of non-zeros per row in the diagonal block on this rank
239  n_oz = dof_map.get_n_oz(); // number of non-zeros per row in the off-diagonal blocks on other ranks
240 
241 
242  if (rank == comm.size()-1) {
243 
244  // the last rank will own the new dof, so add one more non-zero to
245  // the rows on this rank
246  total_n_l++;
247  total_m_l++;
248 
249  // add one more non-zero to the block matrix
250  for (unsigned int i=0; i<n_nz.size(); i++)
251  n_nz[i]++;
252  // the final row on this rank will be a full row, that is all
253  // entries of this row will be non-zero
254  n_nz.push_back(total_n_l);
255  // likewise, all entries in the off-diagonal block will be nonzero
256  n_oz.push_back(total_n - total_n_l);
257  }
258  else {
259 
260  // add an extra non-zero in the off-diagonal block
261  for (unsigned int i=0; i<n_oz.size(); i++)
262  n_oz[i]++;
263  }
264 
265  // create the matrix
266  PetscErrorCode ierr;
267  Mat mat;
268 
269  ierr = MatCreate(comm.get(), &mat); CHKERRABORT(comm.get(), ierr);
270  ierr = MatSetSizes(mat,
271  total_m_l, total_n_l,
272  total_m, total_n); CHKERRABORT(comm.get(), ierr);
273 
274  if (libMesh::on_command_line("--solver_system_names")) {
275 
276  std::string nm = _assembly->system().name() + "_continuation_";
277  MatSetOptionsPrefix(mat, nm.c_str());
278  }
279  ierr = MatSetFromOptions(mat); CHKERRABORT(comm.get(), ierr);
280 
281  ierr = MatSeqAIJSetPreallocation(mat,
282  my_m,
283  (PetscInt*)&n_nz[0]); CHKERRABORT(comm.get(), ierr);
284  ierr = MatMPIAIJSetPreallocation(mat,
285  0,
286  (PetscInt*)&n_nz[0],
287  0,
288  (PetscInt*)&n_oz[0]); CHKERRABORT(comm.get(), ierr);
289  ierr = MatSeqBAIJSetPreallocation (mat, 1,
290  0, (PetscInt*)&n_nz[0]); CHKERRABORT(comm.get(), ierr);
291  ierr = MatMPIBAIJSetPreallocation (mat, 1,
292  0, (PetscInt*)&n_nz[0],
293  0, (PetscInt*)&n_oz[0]); CHKERRABORT(comm.get(), ierr);
294 
295  // now add the block entries
296  ierr = MatSetOption(mat,
297  MAT_NEW_NONZERO_ALLOCATION_ERR,
298  PETSC_TRUE); CHKERRABORT(comm.get(), ierr);
299 
300  // now create the vectors
301  Vec res_vec, sol_vec;
302 
303  ierr = MatCreateVecs(mat, &res_vec, PETSC_NULL); CHKERRABORT(comm.get(), ierr);
304  ierr = MatCreateVecs(mat, &sol_vec, PETSC_NULL); CHKERRABORT(comm.get(), ierr);
305 
306 
307  std::unique_ptr<libMesh::SparseMatrix<Real> >
308  jac_mat(new libMesh::PetscMatrix<Real>(mat, comm));
309 
310  std::unique_ptr<libMesh::NumericVector<Real> >
311  res(new libMesh::PetscVector<Real>(res_vec, comm)),
312  sol(new libMesh::PetscVector<Real>(sol_vec, comm));
313 
315  // now, assemble the information into the new matrix/vector
317  // compute the dfdp term if asked for, since that goes into the matrix.
320  if (update_dfdp)
321  _assembly->sensitivity_assemble(*system.solution, true, p, dfdp);
322 
323  // now compute the jacobian
325  _assembly->close_matrix = false;
327  update_f?res.get():nullptr,
328  jac_mat.get(),
329  system);
330  _assembly->close_matrix = true;
331 
334 
335  // first set the data for the last column
336  if (!update_f) {
337  for (unsigned int i=dof_map.first_dof(rank); i<dof_map.end_dof(rank); i++)
338  res->set(i, f.el(i));
339  }
340 
341  // diagonal entry on the last rank
342  if (rank == comm.size()-1)
343  res->set(total_m-1, g);
344 
345  // finish assembling the residual vector if it was not updated by
346  // the residual and jacobian routine
347  res->close();
348  res->scale(-1.);
349  res->close();
350 
351 
352  // first set the data for the last column
353  for (unsigned int i=dof_map.first_dof(rank);
354  i<dof_map.end_dof(rank); i++) {
355 
356  jac_mat->set( i, total_m-1, dfdp.el(i));
357  jac_mat->set(total_m-1, i, dgdX.el(i));
358  }
359 
360  // diagonal entry on the last rank
361  if (rank == comm.size()-1)
362  jac_mat->set(total_m-1, total_m-1, dgdp);
363 
364  jac_mat->close();
365 
366 
368  // now, copy the information to the new matrix/vector
370  KSP ksp;
371  PC pc;
372 
373  // setup the KSP
374  ierr = KSPCreate(comm.get(), &ksp); CHKERRABORT(comm.get(), ierr);
375 
376  if (libMesh::on_command_line("--solver_system_names")) {
377 
378  std::string nm = _assembly->system().name() + "_continuation_";
379  KSPSetOptionsPrefix(ksp, nm.c_str());
380  }
381 
382  ierr = KSPSetOperators(ksp, mat, mat); CHKERRABORT(comm.get(), ierr);
383  ierr = KSPSetFromOptions(ksp); CHKERRABORT(comm.get(), ierr);
384 
385  // setup the PC
386  ierr = KSPGetPC(ksp, &pc); CHKERRABORT(comm.get(), ierr);
387  ierr = PCSetFromOptions(pc); CHKERRABORT(comm.get(), ierr);
388 
389  // now solve
390  ierr = KSPSolve(ksp, res_vec, sol_vec);
391 
392  // copy the solution back to the system
393  dX.zero();
394  for (unsigned int i=dof_map.first_dof(rank);
395  i<dof_map.end_dof(rank); i++)
396  dX.set(i, sol->el(i));
397  dX.close();
398 
399 #ifdef LIBMESH_ENABLE_CONSTRAINTS
400  system.get_dof_map().enforce_constraints_exactly (system,
401  &dX,
402  /* homogeneous = */ true);
403 #endif
404 
405  std::vector<Real> val(1, 0.);
406  std::vector<libMesh::numeric_index_type> dofs(1, total_m-1);
407  sol->localize(val, dofs);
408  dp = val[0];
409 
410  // destroy the objects
411  ierr = KSPDestroy(&ksp);
412  ierr = MatDestroy(&mat);
413  ierr = VecDestroy(&res_vec);
414  ierr = VecDestroy(&sol_vec);
415 }
416 
417 
418 
419 
420 void
422 _solve_schur_factorization(const libMesh::NumericVector<Real> &X,
423  const MAST::Parameter &p,
424  libMesh::SparseMatrix<Real> &jac,
425  bool update_jac,
426  libMesh::NumericVector<Real> &f,
427  bool update_f,
428  libMesh::NumericVector<Real> &dfdp,
429  bool update_dfdp,
430  libMesh::NumericVector<Real> &dXdp,
431  bool update_dXdp,
432  const libMesh::NumericVector<Real> &dgdX,
433  const Real dgdp,
434  const Real g,
435  libMesh::NumericVector<Real> &dX,
436  Real &dp) {
437 
438  libmesh_assert(_elem_ops);
439 
441  &system = _assembly->system();
442 
443  libmesh_assert(system.operation() == MAST::NonlinearSystem::NONE);
444 
445  //
446  // { f(X, p) } = { 0 }
447  // { g(X, p) } = { 0 }
448  //
449  // [df/dX df/dp] { dX } = { -f}
450  // [dg/dX dg/dp] { dp } = { -g}
451  //
452  // Schur factorization:
453  // df/dX dX = -f - df/dp dp
454  //
455  // Substitute in second equation
456  // dg/dp dp - dg/dX inv(df/dX) ( f + df/dp dp) = -g
457  // => [dg/dp - dg/dX inv(df/dX) df/dp ] dp = -g + dg/dX inv(df/dX) f
458  // => [dg/dp + dg/dX dX/dp ] dp = -g + dg/dX r1
459  //
460  // 1. solve r1 = inv(df/dX) f
461  // 2. solve dXdp = -inv(df/dX) df/dp
462  // 3. solve a = dg/dp + dg/dX dXdp
463  // 4. solve a dp = -g + dg/dX r1
464  // 5. solve df/dX dX = -f - df/dp dp
465  //
466 
467 
468  std::pair<unsigned int, Real>
469  solver_params = system.get_linear_solve_parameters(),
470  rval;
471 
472 
473  std::unique_ptr<libMesh::NumericVector<Real>>
474  r1(X.zero_clone().release());
475 
476  libMesh::SparseMatrix<Real>
477  *pc = system.request_matrix("Preconditioner");
478 
479  Real
480  a = 0.;
481 
483  // STEP 1: r1 = inv(df/dX) f
487 
488  if (update_f || update_jac)
490  update_f? &f:nullptr,
491  update_jac? &jac:nullptr,
492  system);
493  rval = system.linear_solver->solve (jac, pc,
494  *r1,
495  f,
496  solver_params.second,
497  solver_params.first);
498 
499 #ifdef LIBMESH_ENABLE_CONSTRAINTS
500  system.get_dof_map().enforce_constraints_exactly (system,
501  r1.get(),
502  /* homogeneous = */ true);
503 #endif
504 
505 
507  // STEP 2: dXdp = - inv(df/dX) df/dp
510  if (update_dfdp)
511  _assembly->sensitivity_assemble(*system.solution, true, p, dfdp);
512 
513  if (update_dfdp || update_dXdp) {
514 
515  rval = system.linear_solver->solve (jac, pc,
516  dXdp,
517  dfdp,
518  solver_params.second,
519  solver_params.first);
520 
521  dXdp.scale(-1.);
522  dXdp.close();
523 
524  // The linear solver may not have fit our constraints exactly
525 #ifdef LIBMESH_ENABLE_CONSTRAINTS
526  system.get_dof_map().enforce_constraints_exactly (system,
527  &dXdp,
528  /* homogeneous = */ true);
529 #endif
530  }
531 
533  // STEP 3: a = dg/dp + dg/dX dXdp
535  a = dgdp + dgdX.dot(dXdp);
536 
538  // STEP 4: a dp = -g + dg/dX r1
540  dp = 1./a * (- g + dgdX.dot(*r1));
541 
543  // STEP 5: df/dX dX = -f - df/dp dp
545  r1->zero();
546  r1->add(1., f);
547  r1->add(dp, dfdp);
548  r1->scale(-1.);
549  r1->close();
550  rval = system.linear_solver->solve (jac, pc,
551  dX,
552  *r1,
553  solver_params.second,
554  solver_params.first);
555 
556 
557  // The linear solver may not have fit our constraints exactly
558 #ifdef LIBMESH_ENABLE_CONSTRAINTS
559  system.get_dof_map().enforce_constraints_exactly (system,
560  &dX,
561  /* homogeneous = */ true);
562 #endif
563 
566 }
567 
568 
569 Real
571 _res_norm(const libMesh::NumericVector<Real> &X,
572  const MAST::Parameter &p) {
573 
574  libmesh_assert(_elem_ops);
575 
577  &system = _assembly->system();
578 
579  libmesh_assert(system.operation() == MAST::NonlinearSystem::NONE);
580 
581  Real
582  val = 0.;
583 
584  std::unique_ptr<libMesh::NumericVector<Real>>
585  f(X.zero_clone().release());
586 
589 
590  _assembly->residual_and_jacobian(X, f.get(), nullptr, system);
591 
594 
595  val = std::pow(std::pow(_g(X, p), 2) + std::pow(f->l2_norm(), 2), 0.5);
596 
597  return val;
598 }
599 
Real rel_tol
Relative tolerance for the solver.
Real max_step
maximum step size allowed with adaptivity
const MAST::NonlinearSystem & system() const
This class implements a system for solution of nonlinear systems.
virtual void clear_elem_operation_object()
clears the association of this object with the assembly element operation object. ...
virtual void solve()
solves for the next load step
This is a scalar function whose value can be changed and one that can be used as a design variable in...
Definition: parameter.h:35
bool close_matrix
flag to control the closing fo the Jacobian after assembly
virtual void set_elem_operation_object(MAST::AssemblyElemOperations &elem_ops)
attaches a element operation to this object, and associated this with the element operation object...
libMesh::Real Real
void _solve_schur_factorization(const libMesh::NumericVector< Real > &X, const MAST::Parameter &p, libMesh::SparseMatrix< Real > &jac, bool update_jac, libMesh::NumericVector< Real > &f, bool update_f, libMesh::NumericVector< Real > &dfdp, bool update_dfdp, libMesh::NumericVector< Real > &dXdp, bool update_dXdp, const libMesh::NumericVector< Real > &dgdX, const Real dgdp, const Real g, libMesh::NumericVector< Real > &dX, Real &dp)
solves for the linear system of equation using Schur factorization.
virtual void _solve_NR_iterate(libMesh::NumericVector< Real > &X, MAST::Parameter &p)=0
Real min_step
minimum step size allowed with adaptivity
virtual void _save_iteration_data()=0
method saves any data for possible resuse if the solution step is restarted
Real _res_norm(const libMesh::NumericVector< Real > &X, const MAST::Parameter &p)
void _solve(const libMesh::NumericVector< Real > &X, const MAST::Parameter &p, libMesh::NumericVector< Real > &f, bool update_f, libMesh::NumericVector< Real > &dfdp, bool update_dfdp, const libMesh::NumericVector< Real > &dgdX, const Real dgdp, const Real g, libMesh::NumericVector< Real > &dX, Real &dp)
solves for the linear system of equation as a monolithic system dX and dp are returned from the solu...
virtual std::pair< unsigned int, Real > get_linear_solve_parameters()
calls NonlinearImplicitSystem::set_solver_parameters() before accessing the values.
virtual Real _g(const libMesh::NumericVector< Real > &X, const MAST::Parameter &p)=0
Real arc_length
arc length that the solver is required to satisfy for the update.
Real abs_tol
Absolute tolerance for the solver.
std::unique_ptr< libMesh::NumericVector< Real > > _X0
void set_operation(MAST::NonlinearSystem::Operation op)
sets the current operation of the system
void set_assembly_and_load_parameter(MAST::AssemblyElemOperations &elem_ops, MAST::AssemblyBase &assembly, MAST::Parameter &p)
sets the assembly object for this solver
std::unique_ptr< libMesh::LinearSolver< Real > > linear_solver
The LinearSolver for solution of the linear equations.
virtual void residual_and_jacobian(const libMesh::NumericVector< Real > &X, libMesh::NumericVector< Real > *R, libMesh::SparseMatrix< Real > *J, libMesh::NonlinearImplicitSystem &S)
function that assembles the matrices and vectors quantities for nonlinear solution ...
MAST::NonlinearSystem::Operation operation()
virtual void _reset_iterations()=0
method resets any data if a soltion step is restarted
unsigned int max_it
Maximum number of Newton-Raphson iterations for the solver.
Real step_size_change_exponent
exponent used in step size update.
MAST::AssemblyElemOperations * _elem_ops
void clear_assembly_and_load_parameters()
clears the assembly object from this solver
unsigned int step_desired_iters
desired N-R iterations per load-step.
virtual bool sensitivity_assemble(const libMesh::NumericVector< Real > &X, bool if_localize_sol, const MAST::FunctionBase &f, libMesh::NumericVector< Real > &sensitivity_rhs, bool close_vector=true)
Assembly function.