MAST
Multidisciplinary-design Adaptation and Sensitivity Toolkit (MAST)
stabilized_first_order_transient_sensitivity_solver.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 
21 // MAST includes
25 #include "base/elem_base.h"
26 #include "base/nonlinear_system.h"
27 #include "base/assembly_base.h"
30 
31 // libMesh includes
32 #include "libmesh/libmesh_logging.h"
33 #include "libmesh/sparse_matrix.h"
34 #include "libmesh/numeric_vector.h"
35 #include "libmesh/linear_solver.h"
36 #include "libmesh/petsc_matrix.h"
37 #include "libmesh/enum_norm_type.h"
38 #include "libmesh/dof_map.h"
39 
40 
44 max_amp (1.),
45 beta (1.),
46 max_index (0),
47 _use_eigenvalue_stabilization (true),
48 _assemble_mass (false),
49 _t0 (0.),
50 _index0 (0),
51 _index1 (0) {
52 
53 }
54 
55 
58 
59 }
60 
61 
62 void
65 
67 }
68 
69 
70 void
72 set_nolinear_solution_location(std::string& file_root,
73  std::string& dir) {
74 
75  _sol_name_root = file_root;
76  _sol_dir = dir;
77 }
78 
79 
80 void
83  const MAST::FunctionBase& f) {
84 
85  libmesh_assert_greater( max_amp, 0.);
86  libmesh_assert_greater(max_index, 0);
87  libmesh_assert(_sol_name_root.size());
88  libmesh_assert(_sol_dir.size());
89 
90  // Log how long the linear solve takes.
91  LOG_SCOPE("sensitivity_solve()", "StabilizedSensitivity");
92 
93  MAST::NonlinearSystem& sys = assembly.system();
94 
95  assembly.set_elem_operation_object(*this);
96 
97  libMesh::SparseMatrix<Real>
98  &M1 = *sys.matrix,
99  &J_int = *sys.matrix_A,
100  &M_avg = *sys.matrix_B;
101 
102  // build the system matrix
103  std::unique_ptr<libMesh::SparseMatrix<Real>>
104  M0(libMesh::SparseMatrix<Real>::build(sys.comm()).release());
105  sys.get_dof_map().attach_matrix(*M0);
106  M0->init();
107  M0->zero();
108  M0->close();
109 
110 
111 
112  libMesh::NumericVector<Real>
113  &sol = this->solution(),
114  &dsol0 = this->solution_sensitivity(1), // previous solution
115  &dsol1 = this->solution_sensitivity(), // current solution
116  &rhs = sys.add_sensitivity_rhs();
117 
118  std::unique_ptr<libMesh::NumericVector<Real>>
119  f_int(rhs.zero_clone().release()),
120  vec1(rhs.zero_clone().release());
121 
122  J_int.zero();
123  M_avg.zero();
124  rhs.zero();
125  J_int.close();
126  M_avg.close();
127  rhs.close();
128 
129  _index0 = _index1;
130 
131  Real
132  amp = 0.;
133 
134  _t0 = sys.time;
135  sys.time += this->dt;
136  _index1++;
137 
138  bool
139  continue_it = true;
140 
141  // now iterate till a suitable time-step has been found
142  while (continue_it) {
143 
144  // update the solution vector
145  std::ostringstream oss;
146  oss << _sol_name_root << _index1;
147  sys.read_in_vector(sol, _sol_dir, oss.str(), true);
148  sys.update();
149 
150  // assemble the Jacobian matrix
151  _assemble_mass = false;
152  assembly.residual_and_jacobian(sol, nullptr, &M1, sys);
153  J_int.add(this->dt, M1);
154 
155  // assemble the mass matrix
156  _assemble_mass = true;
157  assembly.residual_and_jacobian(sol, nullptr, &M1, sys);
158  Mat M_avg_mat = dynamic_cast<libMesh::PetscMatrix<Real>&>(M_avg).mat();
159  PetscErrorCode ierr = MatScale(M_avg_mat, (sys.time - _t0 - this->dt)/(sys.time - _t0));
160  CHKERRABORT(sys.comm().get(), ierr);
161  M_avg.add(this->dt/(sys.time - _t0), M1);
162 
163  // close the quantities
164  J_int.close();
165  M_avg.close();
166 
167  // assemble the forcing vector
168  assembly.sensitivity_assemble(*sys.solution, true, f, rhs);
169  rhs.scale(-1.);
170  f_int->add(this->dt, rhs); // int_0^t F dt
171  f_int->close();
172  rhs.zero();
173  M_avg.vector_mult(rhs, dsol0); // M_avg x0
174  rhs.add(1., *f_int); // M_avg x0 + int_0^t F dt
175  rhs.close();
176 
177  // now compute the matrix for the linear solution
178  // Note that the stabilized solver for system definde as
179  // M x_dot = f(x) will define the Jacobian as (M_avg - Jint).
180  // However, since MAST defines the system as M x_dot - f(x) = 0
181  // and returns the Jacobian as J = -df(x)/dx, we will not multiply
182  // the Jacobian with -1.
183  M1.zero();
184  M1.add(1., M_avg);
185  M1.add(this->beta, J_int); // see explanation above for positive sign.
186  M1.close();
187 
188  M0->zero();
189  M0->add(1., M_avg);
190  M0->add(-(1.-this->beta), J_int); // see explanation above for negative sign.
191  M0->close();
192 
193 
194  // The sensitivity problem is linear
195  // Our iteration counts and residuals will be sums of the individual
196  // results
197  std::pair<unsigned int, Real>
198  solver_params = sys.get_linear_solve_parameters();
199 
200  // Solve the linear system.
201  libMesh::SparseMatrix<Real> * pc = sys.request_matrix("Preconditioner");
202 
203  std::pair<unsigned int, Real> rval =
204  sys.linear_solver->solve (M1, pc,
205  dsol1,
206  rhs,
207  solver_params.second,
208  solver_params.first);
209 
210  // The linear solver may not have fit our constraints exactly
211 #ifdef LIBMESH_ENABLE_CONSTRAINTS
212  sys.get_dof_map().enforce_constraints_exactly (sys, &dsol1, /* homogeneous = */ true);
213 #endif
214 
215  M_avg.vector_mult(*vec1, dsol1);
216 
217  // estimate the amplification factor. The norm based method only
218  // works for beta = 1 (backward Euler).
219  if (!_use_eigenvalue_stabilization && this->beta == 1.)
220  amp = _compute_norm_amplification_factor(rhs, *vec1);
221  else
222  amp = _compute_eig_amplification_factor(*M0, M1);
223 
224  if (amp <= this->max_amp || _index1 > max_index) {
225 
226  continue_it = false;
227 
228  // we may have reached here due to end of time integration.
229  // In this case, the final Dmag may be unstable. If this is the
230  // case, we should throw that solution out.
231  if (amp <= this->max_amp) {
232 
233  libMesh::out << "accepting sol" << std::endl;
234  dsol0.zero();
235  dsol0.add(1., dsol1);
236  dsol0.close();
237  }
238  else {
239  // present sol is same as previous sol
240  dsol1.zero();
241  dsol1.add(1., dsol0);
242  dsol1.zero();
243 
244  libMesh::out << "reached final step. Rjecting solution" << std::endl;
245  }
246  }
247  else {
248 
249  _index1++;
250  sys.time += this->dt;
251  }
252  }
253 
254 
255  assembly.clear_elem_operation_object();
256 }
257 
258 
259 Real
262  const MAST::FunctionBase& p,
264 
265 
266  MAST::NonlinearSystem& sys = assembly.system();
267 
268  Real
269  eta = 0.,
270  t1 = sys.time,
271  q = 0.;
272 
273  libMesh::NumericVector<Real>
274  &sol = this->solution(),
275  &dx0 = this->solution_sensitivity(1),
276  &dx1 = this->solution_sensitivity();
277 
278  std::unique_ptr<libMesh::NumericVector<Real>>
279  dx(dx0.zero_clone().release());
280 
281  libmesh_assert_greater(_index1, _index0);
282 
283  for (unsigned int i=_index0; i<_index1; i++) {
284 
285  // update the nonlinear solution
286  std::ostringstream oss;
287  oss << _sol_name_root << _index1;
288  sys.read_in_vector(sol, _sol_dir, oss.str(), true);
289  sys.update();
290 
291  sys.time = _t0 + this->dt * ( i - _index0);
292 
293  eta = (sys.time-_t0)/(t1-_t0);
294 
295  dx->zero();
296  dx->add(1.-eta, dx0);
297  dx->add( eta, dx1);
298  dx->close();
299 
300  output.zero_for_analysis();
301  assembly.calculate_output_direct_sensitivity(sol, true, dx.get(), true, p, output);
302 
303  q += output.output_sensitivity_total(p) * this->dt;
304  }
305 
306  return q;
307 }
308 
309 
310 
311 void
313 set_element_data(const std::vector<libMesh::dof_id_type>& dof_indices,
314  const std::vector<libMesh::NumericVector<Real>*>& sols) {
315 
316  libmesh_assert_equal_to(sols.size(), 2);
317 
318  const unsigned int n_dofs = (unsigned int)dof_indices.size();
319 
320  // get the current state and velocity estimates
321  // also get the current discrete velocity replacement
323  sol = RealVectorX::Zero(n_dofs),
324  vel = RealVectorX::Zero(n_dofs);
325 
326 
327  const libMesh::NumericVector<Real>
328  &sol_global = *sols[0],
329  &vel_global = *sols[1];
330 
331  // get the references to current and previous sol and velocity
332 
333  for (unsigned int i=0; i<n_dofs; i++) {
334 
335  sol(i) = sol_global(dof_indices[i]);
336  vel(i) = vel_global(dof_indices[i]);
337  }
338 
339  _assembly_ops->set_elem_solution(sol);
340  _assembly_ops->set_elem_velocity(vel);
341 }
342 
343 
344 
345 void
347 extract_element_sensitivity_data(const std::vector<libMesh::dof_id_type>& dof_indices,
348  const std::vector<libMesh::NumericVector<Real>*>& sols,
349  std::vector<RealVectorX>& local_sols) {
350 
351  libmesh_assert_equal_to(sols.size(), 2);
352 
353  const unsigned int n_dofs = (unsigned int)dof_indices.size();
354 
355  local_sols.resize(2);
356 
358  &sol = local_sols[0],
359  &vel = local_sols[1];
360 
361  sol = RealVectorX::Zero(n_dofs);
362  vel = RealVectorX::Zero(n_dofs);
363 
364  const libMesh::NumericVector<Real>
365  &sol_global = *sols[0],
366  &vel_global = *sols[1];
367 
368  // get the references to current and previous sol and velocity
369 
370  for (unsigned int i=0; i<n_dofs; i++) {
371 
372  sol(i) = sol_global(dof_indices[i]);
373  vel(i) = vel_global(dof_indices[i]);
374  }
375 
376 }
377 
378 
379 void
381 update_velocity(libMesh::NumericVector<Real>& vec,
382  const libMesh::NumericVector<Real>& sol) {
383 
384  const libMesh::NumericVector<Real>
385  &prev_sol = this->solution(1),
386  &prev_vel = this->velocity(1);
387 
388  vec.zero();
389  vec.add( 1., sol);
390  vec.add(-1., prev_sol);
391  vec.scale(1./beta/dt);
392  vec.close();
393  vec.add(-(1.-beta)/beta, prev_vel);
394 
395  vec.close();
396 }
397 
398 
399 void
401 update_sensitivity_velocity(libMesh::NumericVector<Real>& vec,
402  const libMesh::NumericVector<Real>& sol) {
403 
404  const libMesh::NumericVector<Real>
405  &prev_sol = this->solution_sensitivity(1),
406  &prev_vel = this->velocity_sensitivity(1);
407 
408  vec.zero();
409  vec.add( 1., sol);
410  vec.add(-1., prev_sol);
411  vec.scale(1./beta/dt);
412  vec.close();
413  vec.add(-(1.-beta)/beta, prev_vel);
414 
415  vec.close();
416 }
417 
418 
419 
420 void
422 elem_calculations(bool if_jac,
423  RealVectorX& vec,
424  RealMatrixX& mat) {
425  // make sure that the assembly object is provided
426  libmesh_assert(_assembly_ops);
427  unsigned int n_dofs = (unsigned int)vec.size();
428 
430  f_x = RealVectorX::Zero(n_dofs),
431  f_m = RealVectorX::Zero(n_dofs);
432 
434  f_m_jac_xdot = RealMatrixX::Zero(n_dofs, n_dofs),
435  f_m_jac = RealMatrixX::Zero(n_dofs, n_dofs),
436  f_x_jac = RealMatrixX::Zero(n_dofs, n_dofs);
437 
438  // perform the element assembly
439  _assembly_ops->elem_calculations(if_jac,
440  f_m, // mass vector
441  f_x, // forcing vector
442  f_m_jac_xdot, // Jac of mass wrt x_dot
443  f_m_jac, // Jac of mass wrt x
444  f_x_jac); // Jac of forcing vector wrt x
445 
446  if (_assemble_mass)
447  mat = f_m_jac_xdot;
448  else
449  mat = f_m_jac + f_x_jac;
450 }
451 
452 
453 
454 void
457  RealVectorX& vec) {
458 
459  // make sure that the assembly object is provided
460  libmesh_assert(_assembly_ops);
461  unsigned int n_dofs = (unsigned int)vec.size();
462 
464  f_x = RealVectorX::Zero(n_dofs),
465  f_m = RealVectorX::Zero(n_dofs);
466 
467  // perform the element assembly
468  _assembly_ops->elem_sensitivity_calculations(f,
469  f_m, // mass vector
470  f_x); // forcing vector
471 
472  // sensitivity of system residual involving mass and internal force term
473  vec = (f_m + f_x);
474 }
475 
476 
477 
478 
479 void
481 elem_sensitivity_contribution_previous_timestep(const std::vector<RealVectorX>& prev_sols,
482  RealVectorX& vec) {
483 
484  vec.setZero();
485 }
486 
487 
488 Real
490 _compute_norm_amplification_factor(const libMesh::NumericVector<Real>& sol0,
491  const libMesh::NumericVector<Real>& sol1) {
492 
493  libmesh_assert(_system);
494 
496  sys = _system->system();
497 
498  Real
499  norm0 = 0.,
500  norm1 = 0.;
501 
502  unsigned int
503  n_vars = _system->n_vars();
504 
506  var_norm = RealVectorX::Zero(n_vars);
507 
508  for (unsigned int i=0; i<n_vars; i++) {
509 
510  norm0 = sys.calculate_norm(sol0, i, libMesh::L2);
511  norm1 = sys.calculate_norm(sol1, i, libMesh::L2);
512 
513  if (norm0 > 0.)
514  var_norm(i) = norm1/norm0;
515  }
516 
517  libMesh::out << "amp coeffs: " << var_norm.transpose() << std::endl;
518  return var_norm.maxCoeff();
519 }
520 
521 
522 Real
524 _compute_eig_amplification_factor(libMesh::SparseMatrix<Real>& A,
525  libMesh::SparseMatrix<Real>& B) {
526 
527  libmesh_assert(_system);
528 
530  sys = _system->system();
531 
532  std::unique_ptr<MAST::SlepcEigenSolver>
533  eigen_solver(new MAST::SlepcEigenSolver(sys.comm()));
534 
535  if (libMesh::on_command_line("--solver_system_names")) {
536 
537  EPS eps = eigen_solver.get()->eps();
538  std::string nm = sys.name() + "_";
539  EPSSetOptionsPrefix(eps, nm.c_str());
540  }
541  eigen_solver->set_eigenproblem_type(libMesh::GNHEP);
542  eigen_solver->set_position_of_spectrum(libMesh::LARGEST_MAGNITUDE);
543  eigen_solver->init();
544 
545  // solve the eigenvalue problem
546  // Ax = lambda Bx
547  std::pair<unsigned int, unsigned int>
548  solve_data = eigen_solver->solve_generalized (A, B,
549  1,
550  20,
551  1.e-6,
552  50);
553 
554  // make sure that atleast one eigenvalue was computed
555  libmesh_assert(solve_data.first);
556 
557  std::pair<Real, Real> pair = eigen_solver->get_eigenvalue(0);
558  std::complex<Real> eig(pair.first, pair.second);
559  Real amp = std::abs(eig);
560 
561  libMesh::out << "amp coeffs: " << amp << std::endl;
562  return amp;
563 }
const MAST::NonlinearSystem & system() const
MAST::NonlinearSystem & system()
void set_nolinear_solution_location(std::string &file_root, std::string &dir)
sets the directory where the nonlinear solutions are stored.
void read_in_vector(libMesh::NumericVector< Real > &vec, const std::string &directory_name, const std::string &data_name, const bool read_binary_vectors)
reads the specified vector with the specified name in a directory.
This class implements a system for solution of nonlinear systems.
virtual void elem_sensitivity_calculations(const MAST::FunctionBase &f, RealVectorX &vec)
performs the element sensitivity calculations over elem, and returns the element residual sensitivity...
virtual void clear_elem_operation_object()
clears the association of this object with the assembly element operation object. ...
void set_eigenvalue_stabilization(bool f)
sets if the eigenvalue-based stabilization will be used.
This provides the base class for definitin of element level contribution of output quantity in an ana...
#define eps
libMesh::NumericVector< Real > & solution(unsigned int prev_iter=0) const
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...
This class inherits from libMesh::SlepcEigenSolver<Real> and implements a method for retriving the re...
libMesh::Real Real
void set_eigenproblem_type(libMesh::EigenProblemType ept)
Sets the type of the current eigen problem.
libMesh::NumericVector< Real > & solution_sensitivity(unsigned int prev_iter=0) const
virtual void sensitivity_solve(MAST::AssemblyBase &assembly, const MAST::FunctionBase &f)
solvers the current time step for sensitivity wrt f
virtual void update_sensitivity_velocity(libMesh::NumericVector< Real > &vec, const libMesh::NumericVector< Real > &sol)
update the transient sensitivity velocity based on the current sensitivity solution ...
Real _compute_eig_amplification_factor(libMesh::SparseMatrix< Real > &A, libMesh::SparseMatrix< Real > &B)
libMesh::SparseMatrix< Real > * matrix_A
The system matrix for standard eigenvalue problems.
Matrix< Real, Dynamic, Dynamic > RealMatrixX
libMesh::NumericVector< Real > & velocity(unsigned int prev_iter=0) const
virtual std::pair< unsigned int, Real > get_linear_solve_parameters()
calls NonlinearImplicitSystem::set_solver_parameters() before accessing the values.
virtual void set_element_data(const std::vector< libMesh::dof_id_type > &dof_indices, const std::vector< libMesh::NumericVector< Real > *> &sols)
Matrix< Real, Dynamic, 1 > RealVectorX
virtual Real output_sensitivity_total(const MAST::FunctionBase &p)=0
virtual void elem_sensitivity_contribution_previous_timestep(const std::vector< RealVectorX > &prev_sols, RealVectorX &vec)
virtual void calculate_output_direct_sensitivity(const libMesh::NumericVector< Real > &X, bool if_localize_sol, const libMesh::NumericVector< Real > *dXdp, bool if_localize_sol_sens, const MAST::FunctionBase &p, MAST::OutputAssemblyElemOperations &output)
evaluates the sensitivity of the outputs in the attached discipline with respect to the parametrs in ...
libMesh::SparseMatrix< Real > * matrix_B
A second system matrix for generalized eigenvalue problems.
Real _compute_norm_amplification_factor(const libMesh::NumericVector< Real > &sol0, const libMesh::NumericVector< Real > &sol1)
virtual void zero_for_analysis()=0
zeroes the output quantity values stored inside this object so that assembly process can begin...
virtual void extract_element_sensitivity_data(const std::vector< libMesh::dof_id_type > &dof_indices, const std::vector< libMesh::NumericVector< Real > *> &sols, std::vector< RealVectorX > &local_sols)
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 ...
unsigned int max_index
index of solution that is used for current linearization
virtual void elem_calculations(bool if_jac, RealVectorX &vec, RealMatrixX &mat)
performs the element calculations over elem, and returns the element vector and matrix quantities in ...
libMesh::NumericVector< Real > & velocity_sensitivity(unsigned int prev_iter=0) const
virtual Real evaluate_q_sens_for_previous_interval(MAST::AssemblyBase &assembly, const MAST::FunctionBase &p, MAST::OutputAssemblyElemOperations &output)
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.
virtual void update_velocity(libMesh::NumericVector< Real > &vel, const libMesh::NumericVector< Real > &sol)
update the transient velocity based on the current solution
MAST::SystemInitialization * _system