LCOV - code coverage report
Current view: top level - src/systems - implicit_system.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 185 517 35.8 %
Date: 2025-08-19 19:27:09 Functions: 17 28 60.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 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             : // Local includes
      21             : #include "libmesh/dof_map.h"
      22             : #include "libmesh/equation_systems.h"
      23             : #include "libmesh/implicit_system.h"
      24             : #include "libmesh/int_range.h"
      25             : #include "libmesh/libmesh_logging.h"
      26             : #include "libmesh/linear_solver.h"
      27             : #include "libmesh/mesh_base.h"
      28             : #include "libmesh/numeric_vector.h"
      29             : #include "libmesh/parameters.h"
      30             : #include "libmesh/parameter_vector.h"
      31             : #include "libmesh/qoi_set.h"
      32             : #include "libmesh/sensitivity_data.h"
      33             : #include "libmesh/sparse_matrix.h"
      34             : #include "libmesh/diagonal_matrix.h"
      35             : #include "libmesh/utility.h"
      36             : #include "libmesh/static_condensation.h"
      37             : #include "libmesh/static_condensation_preconditioner.h"
      38             : #include "libmesh/nonlinear_solver.h"
      39             : 
      40             : namespace libMesh
      41             : {
      42             : 
      43       18022 : ImplicitSystem::ImplicitSystem (EquationSystems & es,
      44             :                                 const std::string & name_in,
      45       18022 :                                 const unsigned int number_in) :
      46             : 
      47             :   Parent            (es, name_in, number_in),
      48       16974 :   matrix            (nullptr),
      49       18022 :   zero_out_matrix_and_rhs(true)
      50             : {
      51       18022 :   if (this->has_static_condensation())
      52         280 :     this->create_static_condensation_system_matrix();
      53       18022 : }
      54             : 
      55       16974 : ImplicitSystem::~ImplicitSystem () = default;
      56             : 
      57         350 : void ImplicitSystem::create_static_condensation_system_matrix()
      58             : {
      59         350 :   auto sc_system_matrix = std::make_unique<StaticCondensation>(this->get_mesh(), *this, this->get_dof_map(), this->get_dof_map().get_static_condensation());
      60         350 :   _sc_system_matrix = sc_system_matrix.get();
      61         350 :   matrix = &(this->add_matrix ("System Matrix", std::move(sc_system_matrix)));
      62         350 : }
      63             : 
      64          70 : void ImplicitSystem::create_static_condensation ()
      65             : {
      66          70 :   Parent::create_static_condensation();
      67          70 :   this->create_static_condensation_system_matrix();
      68          70 : }
      69             : 
      70             : template <typename T>
      71         350 : void ImplicitSystem::setup_static_condensation_preconditioner(T & solver)
      72             : {
      73          10 :   libmesh_assert(_sc_system_matrix);
      74         360 :   solver.attach_preconditioner(&_sc_system_matrix->get_preconditioner());
      75         350 : }
      76             : 
      77             : 
      78         427 : void ImplicitSystem::clear ()
      79             : {
      80             :   // clear the parent data
      81         427 :   Parent::clear();
      82             : 
      83             :   // Restore us to a "basic" state
      84         427 :   matrix = nullptr;
      85         427 :   _sc_system_matrix = nullptr;
      86         427 : }
      87             : 
      88             : 
      89             : 
      90       38939 : void ImplicitSystem::assemble ()
      91             : {
      92        1344 :   libmesh_assert(matrix);
      93        1344 :   libmesh_assert (matrix->initialized());
      94        1344 :   libmesh_assert(rhs);
      95        1344 :   libmesh_assert (rhs->initialized());
      96             : 
      97       38939 :   if (zero_out_matrix_and_rhs)
      98             :     {
      99       38939 :       matrix->zero ();
     100       38939 :       rhs->zero ();
     101             :     }
     102             : 
     103             :   // Call the base class assemble function
     104       38939 :   Parent::assemble ();
     105       38939 : }
     106             : 
     107             : 
     108             : 
     109       18022 : void ImplicitSystem::add_matrices ()
     110             : {
     111       18022 :   Parent::add_matrices();
     112             : 
     113             :   // Possible that we cleared the _matrices but
     114             :   // forgot to update the matrix pointer?
     115       18022 :   if (this->n_matrices() == 0)
     116       17389 :     matrix = nullptr;
     117             : 
     118             :   // Only need to add the matrix if it isn't there
     119             :   // already!
     120       18022 :   if (matrix == nullptr)
     121       17672 :     matrix = &(this->add_matrix ("System Matrix"));
     122             : 
     123         524 :   libmesh_assert(matrix);
     124       18022 : }
     125             : 
     126             : 
     127             : 
     128      141120 : void ImplicitSystem::disable_cache () {
     129      141120 :   this->assemble_before_solve = true;
     130      141120 :   this->get_linear_solver()->reuse_preconditioner(false);
     131      141120 : }
     132             : 
     133             : 
     134             : 
     135             : std::pair<unsigned int, Real>
     136          71 : ImplicitSystem::sensitivity_solve (const ParameterVector & parameters_vec)
     137             : {
     138             :   // Log how long the linear solve takes.
     139           2 :   LOG_SCOPE("sensitivity_solve()", "ImplicitSystem");
     140             : 
     141             :   // The forward system should now already be solved.
     142             :   // Now assemble the corresponding sensitivity system.
     143             : 
     144          71 :   if (this->assemble_before_solve)
     145             :     {
     146             :       // Build the Jacobian
     147          71 :       this->assembly(false, true);
     148          71 :       this->matrix->close();
     149             : 
     150             :       // Reset and build the RHS from the residual derivatives
     151          71 :       this->assemble_residual_derivatives(parameters_vec);
     152             :     }
     153             : 
     154             :   // The sensitivity problem is linear
     155          71 :   LinearSolver<Number> * solver = this->get_linear_solver();
     156             : 
     157             :   // Our iteration counts and residuals will be sums of the individual
     158             :   // results
     159             :   std::pair<unsigned int, Real> solver_params =
     160          71 :     this->get_linear_solve_parameters();
     161           2 :   std::pair<unsigned int, Real> totalrval = std::make_pair(0,0.0);
     162             : 
     163             :   // Solve the linear system.
     164          71 :   SparseMatrix<Number> * pc = this->request_matrix("Preconditioner");
     165         213 :   for (auto p : make_range(parameters_vec.size()))
     166             :     {
     167             :       std::pair<unsigned int, Real> rval =
     168         142 :         solver->solve (*matrix, pc,
     169             :                        this->add_sensitivity_solution(p),
     170             :                        this->get_sensitivity_rhs(p),
     171         138 :                        double(solver_params.second),
     172           8 :                        solver_params.first);
     173             : 
     174         142 :       totalrval.first  += rval.first;
     175         142 :       totalrval.second += rval.second;
     176             :     }
     177             : 
     178             :   // The linear solver may not have fit our constraints exactly
     179             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     180         213 :   for (auto p : make_range(parameters_vec.size()))
     181           4 :     this->get_dof_map().enforce_constraints_exactly
     182         142 :       (*this, &this->get_sensitivity_solution(p),
     183             :        /* homogeneous = */ true);
     184             : #endif
     185             : 
     186          73 :   return totalrval;
     187             : }
     188             : 
     189             : 
     190             : 
     191             : std::pair<unsigned int, Real>
     192       14583 : ImplicitSystem::adjoint_solve (const QoISet & qoi_indices)
     193             : {
     194             :   // Log how long the linear solve takes.
     195         462 :   LOG_SCOPE("adjoint_solve()", "ImplicitSystem");
     196             : 
     197       14583 :   if (this->assemble_before_solve)
     198             :     // Assemble the linear system
     199       14583 :     this->assembly (/* get_residual = */ false,
     200         924 :                     /* get_jacobian = */ true);
     201             : 
     202             :   // The adjoint problem is linear
     203       14583 :   LinearSolver<Number> * solver = this->get_linear_solver();
     204             : 
     205             :   // Reset and build the RHS from the QOI derivative
     206       15507 :   this->assemble_qoi_derivative(qoi_indices,
     207             :                                 /* include_liftfunc = */ false,
     208         924 :                                 /* apply_constraints = */ true);
     209             : 
     210             :   // Our iteration counts and residuals will be sums of the individual
     211             :   // results
     212             :   std::pair<unsigned int, Real> solver_params =
     213       14583 :     this->get_linear_solve_parameters();
     214         462 :   std::pair<unsigned int, Real> totalrval = std::make_pair(0,0.0);
     215             : 
     216       39329 :   for (auto i : make_range(this->n_qois()))
     217       24746 :     if (qoi_indices.has_index(i))
     218             :       {
     219             :         const std::pair<unsigned int, Real> rval =
     220       24746 :           solver->adjoint_solve (*matrix, this->add_adjoint_solution(i),
     221             :                                  this->get_adjoint_rhs(i),
     222       23948 :                                  double(solver_params.second),
     223        2394 :                                  solver_params.first);
     224             : 
     225       24746 :         totalrval.first  += rval.first;
     226       24746 :         totalrval.second += rval.second;
     227             :       }
     228             : 
     229             :   // The linear solver may not have fit our constraints exactly
     230             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     231       39329 :   for (auto i : make_range(this->n_qois()))
     232       24746 :     if (qoi_indices.has_index(i))
     233         798 :       this->get_dof_map().enforce_adjoint_constraints_exactly
     234       24746 :         (this->get_adjoint_solution(i), i);
     235             : #endif
     236             : 
     237       15045 :   return totalrval;
     238             : }
     239             : 
     240             : 
     241             : 
     242             : std::pair<unsigned int, Real>
     243           0 : ImplicitSystem::weighted_sensitivity_adjoint_solve (const ParameterVector & parameters_in,
     244             :                                                     const ParameterVector & weights,
     245             :                                                     const QoISet & qoi_indices)
     246             : {
     247             :   // Log how long the linear solve takes.
     248           0 :   LOG_SCOPE("weighted_sensitivity_adjoint_solve()", "ImplicitSystem");
     249             : 
     250             :   // We currently get partial derivatives via central differencing
     251           0 :   const Real delta_p = TOLERANCE;
     252             : 
     253           0 :   ParameterVector & parameters_vec =
     254             :     const_cast<ParameterVector &>(parameters_in);
     255             : 
     256             :   // The forward system should now already be solved.
     257             :   // The adjoint system should now already be solved.
     258             :   // Now we're assembling a weighted sum of adjoint-adjoint systems:
     259             :   //
     260             :   // dR/du (u, sum_l(w_l*z^l)) = sum_l(w_l*(Q''_ul - R''_ul (u, z)))
     261             : 
     262             :   // FIXME: The derivation here does not yet take adjoint boundary
     263             :   // conditions into account.
     264             : #ifdef LIBMESH_ENABLE_DIRICHLET
     265           0 :   for (auto i : make_range(this->n_qois()))
     266           0 :     if (qoi_indices.has_index(i))
     267           0 :       libmesh_assert(!this->get_dof_map().has_adjoint_dirichlet_boundaries(i));
     268             : #endif
     269             : 
     270             :   // We'll assemble the rhs first, because the R'' term will require
     271             :   // perturbing the jacobian
     272             : 
     273             :   // We'll use temporary rhs vectors, because we haven't (yet) found
     274             :   // any good reasons why users might want to save these:
     275             : 
     276           0 :   std::vector<std::unique_ptr<NumericVector<Number>>> temprhs(this->n_qois());
     277           0 :   for (auto i : make_range(this->n_qois()))
     278           0 :     if (qoi_indices.has_index(i))
     279           0 :       temprhs[i] = this->rhs->zero_clone();
     280             : 
     281             :   // We approximate the _l partial derivatives via a central
     282             :   // differencing perturbation in the w_l direction:
     283             :   //
     284             :   // sum_l(w_l*v_l) ~= (v(p + dp*w_l*e_l) - v(p - dp*w_l*e_l))/(2*dp)
     285             : 
     286             :   // PETSc doesn't implement SGEMX, so neither does NumericVector,
     287             :   // so we want to avoid calculating f -= R'*z.  We'll thus evaluate
     288             :   // the above equation by first adding -v(p+dp...), then multiplying
     289             :   // the intermediate result vectors by -1, then adding -v(p-dp...),
     290             :   // then finally dividing by 2*dp.
     291             : 
     292           0 :   ParameterVector oldparameters, parameterperturbation;
     293           0 :   parameters_vec.deep_copy(oldparameters);
     294           0 :   weights.deep_copy(parameterperturbation);
     295           0 :   parameterperturbation *= delta_p;
     296           0 :   parameters_vec += parameterperturbation;
     297             : 
     298           0 :   this->assembly(false, true);
     299           0 :   this->matrix->close();
     300             : 
     301             :   // Take the discrete adjoint, so that we can calculate R_u(u,z) with
     302             :   // a matrix-vector product of R_u and z.
     303           0 :   matrix->get_transpose(*matrix);
     304             : 
     305           0 :   this->assemble_qoi_derivative(qoi_indices,
     306             :                                 /* include_liftfunc = */ false,
     307           0 :                                 /* apply_constraints = */ true);
     308           0 :   for (auto i : make_range(this->n_qois()))
     309           0 :     if (qoi_indices.has_index(i))
     310             :       {
     311           0 :         this->get_adjoint_rhs(i).close();
     312           0 :         *(temprhs[i]) -= this->get_adjoint_rhs(i);
     313           0 :         this->matrix->vector_mult_add(*(temprhs[i]), this->get_adjoint_solution(i));
     314           0 :         *(temprhs[i]) *= -1.0;
     315             :       }
     316             : 
     317           0 :   oldparameters.value_copy(parameters_vec);
     318           0 :   parameterperturbation *= -1.0;
     319           0 :   parameters_vec += parameterperturbation;
     320             : 
     321           0 :   this->assembly(false, true);
     322           0 :   this->matrix->close();
     323           0 :   matrix->get_transpose(*matrix);
     324             : 
     325           0 :   this->assemble_qoi_derivative(qoi_indices,
     326             :                                 /* include_liftfunc = */ false,
     327           0 :                                 /* apply_constraints = */ true);
     328           0 :   for (auto i : make_range(this->n_qois()))
     329           0 :     if (qoi_indices.has_index(i))
     330             :       {
     331           0 :         this->get_adjoint_rhs(i).close();
     332           0 :         *(temprhs[i]) -= this->get_adjoint_rhs(i);
     333           0 :         this->matrix->vector_mult_add(*(temprhs[i]), this->get_adjoint_solution(i));
     334           0 :         *(temprhs[i]) /= (2.0*delta_p);
     335             :       }
     336             : 
     337             :   // Finally, assemble the jacobian at the non-perturbed parameter
     338             :   // values.  Ignore assemble_before_solve; if we had a good
     339             :   // non-perturbed matrix before we've already overwritten it.
     340           0 :   oldparameters.value_copy(parameters_vec);
     341             : 
     342             :   // if (this->assemble_before_solve)
     343             :   {
     344             :     // Build the Jacobian
     345           0 :     this->assembly(false, true);
     346           0 :     this->matrix->close();
     347             : 
     348             :     // Take the discrete adjoint
     349           0 :     matrix->get_transpose(*matrix);
     350             :   }
     351             : 
     352             :   // The weighted adjoint-adjoint problem is linear
     353           0 :   LinearSolver<Number> * solver = this->get_linear_solver();
     354             : 
     355             :   // Our iteration counts and residuals will be sums of the individual
     356             :   // results
     357             :   std::pair<unsigned int, Real> solver_params =
     358           0 :     this->get_linear_solve_parameters();
     359           0 :   std::pair<unsigned int, Real> totalrval = std::make_pair(0,0.0);
     360             : 
     361           0 :   for (auto i : make_range(this->n_qois()))
     362           0 :     if (qoi_indices.has_index(i))
     363             :       {
     364             :         const std::pair<unsigned int, Real> rval =
     365           0 :           solver->solve (*matrix, this->add_weighted_sensitivity_adjoint_solution(i),
     366           0 :                          *(temprhs[i]),
     367           0 :                          double(solver_params.second),
     368           0 :                          solver_params.first);
     369             : 
     370           0 :         totalrval.first  += rval.first;
     371           0 :         totalrval.second += rval.second;
     372             :       }
     373             : 
     374             :   // The linear solver may not have fit our constraints exactly
     375             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     376           0 :   for (auto i : make_range(this->n_qois()))
     377           0 :     if (qoi_indices.has_index(i))
     378           0 :       this->get_dof_map().enforce_constraints_exactly
     379           0 :         (*this, &this->get_weighted_sensitivity_adjoint_solution(i),
     380             :          /* homogeneous = */ true);
     381             : #endif
     382             : 
     383           0 :   return totalrval;
     384           0 : }
     385             : 
     386             : 
     387             : 
     388             : std::pair<unsigned int, Real>
     389           0 : ImplicitSystem::weighted_sensitivity_solve (const ParameterVector & parameters_in,
     390             :                                             const ParameterVector & weights)
     391             : {
     392             :   // Log how long the linear solve takes.
     393           0 :   LOG_SCOPE("weighted_sensitivity_solve()", "ImplicitSystem");
     394             : 
     395             :   // We currently get partial derivatives via central differencing
     396           0 :   const Real delta_p = TOLERANCE;
     397             : 
     398           0 :   ParameterVector & parameters_vec =
     399             :     const_cast<ParameterVector &>(parameters_in);
     400             : 
     401             :   // The forward system should now already be solved.
     402             : 
     403             :   // Now we're assembling a weighted sum of sensitivity systems:
     404             :   //
     405             :   // dR/du (u, v)(sum(w_l*u'_l)) = -sum_l(w_l*R'_l (u, v)) forall v
     406             : 
     407             :   // We'll assemble the rhs first, because the R' term will require
     408             :   // perturbing the system, and some applications may not be able to
     409             :   // assemble a perturbed residual without simultaneously constructing
     410             :   // a perturbed jacobian.
     411             : 
     412             :   // We approximate the _l partial derivatives via a central
     413             :   // differencing perturbation in the w_l direction:
     414             :   //
     415             :   // sum_l(w_l*v_l) ~= (v(p + dp*w_l*e_l) - v(p - dp*w_l*e_l))/(2*dp)
     416             : 
     417           0 :   ParameterVector oldparameters, parameterperturbation;
     418           0 :   parameters_vec.deep_copy(oldparameters);
     419           0 :   weights.deep_copy(parameterperturbation);
     420           0 :   parameterperturbation *= delta_p;
     421           0 :   parameters_vec += parameterperturbation;
     422             : 
     423           0 :   this->assembly(true, false, true);
     424           0 :   this->rhs->close();
     425             : 
     426           0 :   std::unique_ptr<NumericVector<Number>> temprhs = this->rhs->clone();
     427             : 
     428           0 :   oldparameters.value_copy(parameters_vec);
     429           0 :   parameterperturbation *= -1.0;
     430           0 :   parameters_vec += parameterperturbation;
     431             : 
     432           0 :   this->assembly(true, false, true);
     433           0 :   this->rhs->close();
     434             : 
     435           0 :   *temprhs -= *(this->rhs);
     436           0 :   *temprhs /= (2.0*delta_p);
     437             : 
     438             :   // Finally, assemble the jacobian at the non-perturbed parameter
     439             :   // values
     440           0 :   oldparameters.value_copy(parameters_vec);
     441             : 
     442             :   // Build the Jacobian
     443           0 :   this->assembly(false, true);
     444           0 :   this->matrix->close();
     445             : 
     446             :   // The weighted sensitivity problem is linear
     447           0 :   LinearSolver<Number> * solver = this->get_linear_solver();
     448             : 
     449             :   std::pair<unsigned int, Real> solver_params =
     450           0 :     this->get_linear_solve_parameters();
     451             : 
     452             :   const std::pair<unsigned int, Real> rval =
     453           0 :     solver->solve (*matrix, this->add_weighted_sensitivity_solution(),
     454           0 :                    *temprhs,
     455           0 :                    double(solver_params.second),
     456           0 :                    solver_params.first);
     457             : 
     458             :   // The linear solver may not have fit our constraints exactly
     459             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     460           0 :   this->get_dof_map().enforce_constraints_exactly
     461           0 :     (*this, &this->get_weighted_sensitivity_solution(),
     462             :      /* homogeneous = */ true);
     463             : #endif
     464             : 
     465           0 :   return rval;
     466           0 : }
     467             : 
     468             : 
     469             : 
     470        7075 : void ImplicitSystem::assemble_residual_derivatives(const ParameterVector & parameters_in)
     471             : {
     472         202 :   ParameterVector & parameters_vec =
     473             :     const_cast<ParameterVector &>(parameters_in);
     474             : 
     475             :   const unsigned int Np = cast_int<unsigned int>
     476         202 :     (parameters_vec.size());
     477             : 
     478       14505 :   for (unsigned int p=0; p != Np; ++p)
     479             :     {
     480        7430 :       NumericVector<Number> & sensitivity_rhs = this->add_sensitivity_rhs(p);
     481             : 
     482             :       // Approximate -(partial R / partial p) by
     483             :       // (R(p-dp) - R(p+dp)) / (2*dp)
     484             : 
     485        7430 :       Number old_parameter = *parameters_vec[p];
     486             : 
     487             :       const Real delta_p =
     488        7642 :         TOLERANCE * std::max(std::abs(old_parameter), 1e-3);
     489             : 
     490        7218 :       *parameters_vec[p] -= delta_p;
     491             : 
     492             :       //      this->assembly(true, false, true);
     493        7430 :       this->assembly(true, false, false);
     494        7430 :       this->rhs->close();
     495        7430 :       sensitivity_rhs = *this->rhs;
     496             : 
     497        7430 :       *parameters_vec[p] = old_parameter + delta_p;
     498             : 
     499             :       //      this->assembly(true, false, true);
     500        7430 :       this->assembly(true, false, false);
     501        7430 :       this->rhs->close();
     502             : 
     503        7430 :       sensitivity_rhs -= *this->rhs;
     504        7430 :       sensitivity_rhs /= (2*delta_p);
     505        7430 :       sensitivity_rhs.close();
     506             : 
     507         212 :       *parameters_vec[p] = old_parameter;
     508             :     }
     509        7075 : }
     510             : 
     511             : 
     512             : 
     513        7004 : void ImplicitSystem::adjoint_qoi_parameter_sensitivity (const QoISet & qoi_indices,
     514             :                                                         const ParameterVector & parameters_in,
     515             :                                                         SensitivityData & sensitivities)
     516             : {
     517         200 :   ParameterVector & parameters_vec =
     518             :     const_cast<ParameterVector &>(parameters_in);
     519             : 
     520             :   const unsigned int Np = cast_int<unsigned int>
     521         200 :     (parameters_vec.size());
     522         200 :   const unsigned int Nq = this->n_qois();
     523             : 
     524             :   // An introduction to the problem:
     525             :   //
     526             :   // Residual R(u(p),p) = 0
     527             :   // partial R / partial u = J = system matrix
     528             :   //
     529             :   // This implies that:
     530             :   // d/dp(R) = 0
     531             :   // (partial R / partial p) +
     532             :   // (partial R / partial u) * (partial u / partial p) = 0
     533             : 
     534             :   // We first do an adjoint solve:
     535             :   // J^T * z = (partial q / partial u)
     536             :   // if we haven't already or dont have an initial condition for the adjoint
     537        7004 :   if (!this->is_adjoint_already_solved())
     538             :     {
     539         284 :       this->adjoint_solve(qoi_indices);
     540             :     }
     541             : 
     542        7004 :   this->assemble_residual_derivatives(parameters_in);
     543             : 
     544             :   // Get ready to fill in sensitivities:
     545        7004 :   sensitivities.allocate_data(qoi_indices, *this, parameters_vec);
     546             : 
     547             :   // We use the identities:
     548             :   // dq/dp = (partial q / partial p) + (partial q / partial u) *
     549             :   //         (partial u / partial p)
     550             :   // dq/dp = (partial q / partial p) + (J^T * z) *
     551             :   //         (partial u / partial p)
     552             :   // dq/dp = (partial q / partial p) + z * J *
     553             :   //         (partial u / partial p)
     554             : 
     555             :   // Leading to our final formula:
     556             :   // dq/dp = (partial q / partial p) - z * (partial R / partial p)
     557             : 
     558             :   // In the case of adjoints with heterogenous Dirichlet boundary
     559             :   // function phi, where
     560             :   // q :=  S(u) - R(u,phi)
     561             :   // the final formula works out to:
     562             :   // dq/dp = (partial S / partial p) - z * (partial R / partial p)
     563             :   // Because we currently have no direct access to
     564             :   // (partial S / partial p), we use the identity
     565             :   // (partial S / partial p) = (partial q / partial p) +
     566             :   //                           phi * (partial R / partial p)
     567             :   // to derive an equivalent equation:
     568             :   // dq/dp = (partial q / partial p) - (z-phi) * (partial R / partial p)
     569             : 
     570             :   // Since z-phi degrees of freedom are zero for constrained indices,
     571             :   // we can use the same constrained -(partial R / partial p) that we
     572             :   // use for forward sensitivity solves, taking into account the
     573             :   // differing sign convention.
     574             :   //
     575             :   // Since that vector is constrained, its constrained indices are
     576             :   // zero, so its product with phi is zero, so we can neglect the
     577             :   // evaluation of phi terms.
     578             : 
     579       14292 :   for (unsigned int j=0; j != Np; ++j)
     580             :     {
     581             :       // We currently get partial derivatives via central differencing
     582             : 
     583             :       // (partial q / partial p) ~= (q(p+dp)-q(p-dp))/(2*dp)
     584             :       // (partial R / partial p) ~= (rhs(p+dp) - rhs(p-dp))/(2*dp)
     585             : 
     586        7288 :       Number old_parameter = *parameters_vec[j];
     587             : 
     588             :       const Real delta_p =
     589        7496 :         TOLERANCE * std::max(std::abs(old_parameter), 1e-3);
     590             : 
     591        7288 :       *parameters_vec[j] = old_parameter - delta_p;
     592        7288 :       this->assemble_qoi(qoi_indices);
     593        7496 :       const std::vector<Number> qoi_minus = this->get_qoi_values();
     594             : 
     595        7288 :       NumericVector<Number> & neg_partialR_partialp = this->get_sensitivity_rhs(j);
     596             : 
     597        7288 :       *parameters_vec[j] = old_parameter + delta_p;
     598        7288 :       this->assemble_qoi(qoi_indices);
     599        7496 :       const std::vector<Number> qoi_plus = this->get_qoi_values();
     600             : 
     601        7496 :       std::vector<Number> partialq_partialp(Nq, 0);
     602       14576 :       for (unsigned int i=0; i != Nq; ++i)
     603        7288 :         if (qoi_indices.has_index(i))
     604        7912 :           partialq_partialp[i] = (qoi_plus[i] - qoi_minus[i]) / (2.*delta_p);
     605             : 
     606             :       // Don't leave the parameter changed
     607         208 :       *parameters_vec[j] = old_parameter;
     608             : 
     609       14576 :       for (unsigned int i=0; i != Nq; ++i)
     610        7288 :         if (qoi_indices.has_index(i))
     611        7912 :           sensitivities[i][j] = partialq_partialp[i] +
     612        7288 :             neg_partialR_partialp.dot(this->get_adjoint_solution(i));
     613             :     }
     614             : 
     615             :   // All parameters_vec have been reset.
     616             :   // Reset the original qoi.
     617             : 
     618        7004 :   this->assemble_qoi(qoi_indices);
     619        7004 : }
     620             : 
     621             : 
     622             : 
     623          71 : void ImplicitSystem::forward_qoi_parameter_sensitivity (const QoISet & qoi_indices,
     624             :                                                         const ParameterVector & parameters_in,
     625             :                                                         SensitivityData & sensitivities)
     626             : {
     627           2 :   ParameterVector & parameters_vec =
     628             :     const_cast<ParameterVector &>(parameters_in);
     629             : 
     630             :   const unsigned int Np = cast_int<unsigned int>
     631           2 :     (parameters_vec.size());
     632           2 :   const unsigned int Nq = this->n_qois();
     633             : 
     634             :   // An introduction to the problem:
     635             :   //
     636             :   // Residual R(u(p),p) = 0
     637             :   // partial R / partial u = J = system matrix
     638             :   //
     639             :   // This implies that:
     640             :   // d/dp(R) = 0
     641             :   // (partial R / partial p) +
     642             :   // (partial R / partial u) * (partial u / partial p) = 0
     643             : 
     644             :   // We first solve for (partial u / partial p) for each parameter:
     645             :   // J * (partial u / partial p) = - (partial R / partial p)
     646             : 
     647          71 :   this->sensitivity_solve(parameters_vec);
     648             : 
     649             :   // Get ready to fill in sensitivities:
     650          71 :   sensitivities.allocate_data(qoi_indices, *this, parameters_vec);
     651             : 
     652             :   // We use the identity:
     653             :   // dq/dp = (partial q / partial p) + (partial q / partial u) *
     654             :   //         (partial u / partial p)
     655             : 
     656             :   // We get (partial q / partial u) from the user
     657          75 :   this->assemble_qoi_derivative(qoi_indices,
     658             :                                 /* include_liftfunc = */ true,
     659           4 :                                 /* apply_constraints = */ false);
     660             : 
     661             :   // We don't need these to be closed() in this function, but libMesh
     662             :   // standard practice is to have them closed() by the time the
     663             :   // function exits
     664         142 :   for (auto i : make_range(this->n_qois()))
     665          71 :     if (qoi_indices.has_index(i))
     666          71 :       this->get_adjoint_rhs(i).close();
     667             : 
     668         213 :   for (unsigned int j=0; j != Np; ++j)
     669             :     {
     670             :       // We currently get partial derivatives via central differencing
     671             : 
     672             :       // (partial q / partial p) ~= (q(p+dp)-q(p-dp))/(2*dp)
     673             : 
     674         142 :       Number old_parameter = *parameters_vec[j];
     675             : 
     676             :       const Real delta_p =
     677         146 :         TOLERANCE * std::max(std::abs(old_parameter), 1e-3);
     678             : 
     679         142 :       *parameters_vec[j] = old_parameter - delta_p;
     680         142 :       this->assemble_qoi(qoi_indices);
     681         146 :       const std::vector<Number> qoi_minus = this->get_qoi_values();
     682             : 
     683         142 :       *parameters_vec[j] = old_parameter + delta_p;
     684         142 :       this->assemble_qoi(qoi_indices);
     685         146 :       const std::vector<Number> qoi_plus = this->get_qoi_values();
     686             : 
     687         146 :       std::vector<Number> partialq_partialp(Nq, 0);
     688         284 :       for (unsigned int i=0; i != Nq; ++i)
     689         142 :         if (qoi_indices.has_index(i))
     690         154 :           partialq_partialp[i] = (qoi_plus[i] - qoi_minus[i]) / (2.*delta_p);
     691             : 
     692             :       // Don't leave the parameter changed
     693           4 :       *parameters_vec[j] = old_parameter;
     694             : 
     695         284 :       for (unsigned int i=0; i != Nq; ++i)
     696         142 :         if (qoi_indices.has_index(i))
     697         154 :           sensitivities[i][j] = partialq_partialp[i] +
     698         142 :             this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(j));
     699             :     }
     700             : 
     701             :   // All parameters_vec have been reset.
     702             :   // We didn't cache the original rhs or matrix for memory reasons,
     703             :   // but we can restore them to a state consistent solution -
     704             :   // principle of least surprise.
     705          71 :   this->assembly(true, true);
     706          71 :   this->rhs->close();
     707          71 :   this->matrix->close();
     708          71 :   this->assemble_qoi(qoi_indices);
     709          71 : }
     710             : 
     711             : 
     712             : 
     713           0 : void ImplicitSystem::qoi_parameter_hessian_vector_product (const QoISet & qoi_indices,
     714             :                                                            const ParameterVector & parameters_in,
     715             :                                                            const ParameterVector & vector,
     716             :                                                            SensitivityData & sensitivities)
     717             : {
     718             :   // We currently get partial derivatives via finite differencing
     719           0 :   const Real delta_p = TOLERANCE;
     720             : 
     721           0 :   ParameterVector & parameters_vec =
     722             :     const_cast<ParameterVector &>(parameters_in);
     723             : 
     724             :   // We'll use a single temporary vector for matrix-vector-vector products
     725           0 :   std::unique_ptr<NumericVector<Number>> tempvec = this->solution->zero_clone();
     726             : 
     727             :   const unsigned int Np = cast_int<unsigned int>
     728           0 :     (parameters_vec.size());
     729           0 :   const unsigned int Nq = this->n_qois();
     730             : 
     731             :   // For each quantity of interest q, the parameter sensitivity
     732             :   // Hessian is defined as q''_{kl} = {d^2 q}/{d p_k d p_l}.
     733             :   // Given a vector of parameter perturbation weights w_l, this
     734             :   // function evaluates the hessian-vector product sum_l(q''_{kl}*w_l)
     735             :   //
     736             :   // We calculate it from values and partial derivatives of the
     737             :   // quantity of interest function Q, solution u, adjoint solution z,
     738             :   // parameter sensitivity adjoint solutions z^l, and residual R, as:
     739             :   //
     740             :   // sum_l(q''_{kl}*w_l) =
     741             :   // sum_l(w_l * Q''_{kl}) + Q''_{uk}(u)*(sum_l(w_l u'_l)) -
     742             :   // R'_k(u, sum_l(w_l*z^l)) - R'_{uk}(u,z)*(sum_l(w_l u'_l) -
     743             :   // sum_l(w_l*R''_{kl}(u,z))
     744             :   //
     745             :   // See the adjoints model document for more details.
     746             : 
     747             :   // We first do an adjoint solve to get z for each quantity of
     748             :   // interest
     749             :   // if we haven't already or dont have an initial condition for the adjoint
     750           0 :   if (!this->is_adjoint_already_solved())
     751             :     {
     752           0 :       this->adjoint_solve(qoi_indices);
     753             :     }
     754             : 
     755             :   // Get ready to fill in sensitivities:
     756           0 :   sensitivities.allocate_data(qoi_indices, *this, parameters_vec);
     757             : 
     758             :   // We can't solve for all the solution sensitivities u'_l or for all
     759             :   // of the parameter sensitivity adjoint solutions z^l without
     760             :   // requiring O(Nq*Np) linear solves.  So we'll solve directly for their
     761             :   // weighted sum - this is just O(Nq) solves.
     762             : 
     763             :   // First solve for sum_l(w_l u'_l).
     764           0 :   this->weighted_sensitivity_solve(parameters_vec, vector);
     765             : 
     766             :   // Then solve for sum_l(w_l z^l).
     767           0 :   this->weighted_sensitivity_adjoint_solve(parameters_vec, vector, qoi_indices);
     768             : 
     769           0 :   for (unsigned int k=0; k != Np; ++k)
     770             :     {
     771             :       // We approximate sum_l(w_l * Q''_{kl}) with a central
     772             :       // differencing perturbation:
     773             :       // sum_l(w_l * Q''_{kl}) ~=
     774             :       // (Q(p + dp*w_l*e_l + dp*e_k) - Q(p - dp*w_l*e_l + dp*e_k) -
     775             :       // Q(p + dp*w_l*e_l - dp*e_k) + Q(p - dp*w_l*e_l - dp*e_k))/(4*dp^2)
     776             : 
     777             :       // The sum(w_l*R''_kl) term requires the same sort of perturbation,
     778             :       // and so we subtract it in at the same time:
     779             :       // sum_l(w_l * R''_{kl}) ~=
     780             :       // (R(p + dp*w_l*e_l + dp*e_k) - R(p - dp*w_l*e_l + dp*e_k) -
     781             :       // R(p + dp*w_l*e_l - dp*e_k) + R(p - dp*w_l*e_l - dp*e_k))/(4*dp^2)
     782             : 
     783           0 :       ParameterVector oldparameters, parameterperturbation;
     784           0 :       parameters_vec.deep_copy(oldparameters);
     785           0 :       vector.deep_copy(parameterperturbation);
     786           0 :       parameterperturbation *= delta_p;
     787           0 :       parameters_vec += parameterperturbation;
     788             : 
     789           0 :       Number old_parameter = *parameters_vec[k];
     790             : 
     791           0 :       *parameters_vec[k] = old_parameter + delta_p;
     792           0 :       this->assemble_qoi(qoi_indices);
     793           0 :       this->assembly(true, false, true);
     794           0 :       this->rhs->close();
     795           0 :       std::vector<Number> partial2q_term = this->get_qoi_values();
     796           0 :       std::vector<Number> partial2R_term(this->n_qois());
     797           0 :       for (unsigned int i=0; i != Nq; ++i)
     798           0 :         if (qoi_indices.has_index(i))
     799           0 :           partial2R_term[i] = this->rhs->dot(this->get_adjoint_solution(i));
     800             : 
     801           0 :       *parameters_vec[k] = old_parameter - delta_p;
     802           0 :       this->assemble_qoi(qoi_indices);
     803           0 :       this->assembly(true, false, true);
     804           0 :       this->rhs->close();
     805           0 :       for (unsigned int i=0; i != Nq; ++i)
     806           0 :         if (qoi_indices.has_index(i))
     807             :           {
     808           0 :             partial2q_term[i] -= this->get_qoi_value(i);
     809           0 :             partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i));
     810             :           }
     811             : 
     812           0 :       oldparameters.value_copy(parameters_vec);
     813           0 :       parameterperturbation *= -1.0;
     814           0 :       parameters_vec += parameterperturbation;
     815             : 
     816             :       // Re-center old_parameter, which may be affected by vector
     817           0 :       old_parameter = *parameters_vec[k];
     818             : 
     819           0 :       *parameters_vec[k] = old_parameter + delta_p;
     820           0 :       this->assemble_qoi(qoi_indices);
     821           0 :       this->assembly(true, false, true);
     822           0 :       this->rhs->close();
     823           0 :       for (unsigned int i=0; i != Nq; ++i)
     824           0 :         if (qoi_indices.has_index(i))
     825             :           {
     826           0 :             partial2q_term[i] -= this->get_qoi_value(i);
     827           0 :             partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i));
     828             :           }
     829             : 
     830           0 :       *parameters_vec[k] = old_parameter - delta_p;
     831           0 :       this->assemble_qoi(qoi_indices);
     832           0 :       this->assembly(true, false, true);
     833           0 :       this->rhs->close();
     834           0 :       for (unsigned int i=0; i != Nq; ++i)
     835           0 :         if (qoi_indices.has_index(i))
     836             :           {
     837           0 :             partial2q_term[i] += this->get_qoi_value(i);
     838           0 :             partial2R_term[i] += this->rhs->dot(this->get_adjoint_solution(i));
     839             :           }
     840             : 
     841           0 :       for (unsigned int i=0; i != Nq; ++i)
     842           0 :         if (qoi_indices.has_index(i))
     843             :           {
     844           0 :             partial2q_term[i] /= (4. * delta_p * delta_p);
     845           0 :             partial2R_term[i] /= (4. * delta_p * delta_p);
     846             :           }
     847             : 
     848           0 :       for (unsigned int i=0; i != Nq; ++i)
     849           0 :         if (qoi_indices.has_index(i))
     850           0 :           sensitivities[i][k] = partial2q_term[i] - partial2R_term[i];
     851             : 
     852             :       // We get (partial q / partial u), R, and
     853             :       // (partial R / partial u) from the user, but centrally
     854             :       // difference to get q_uk, R_k, and R_uk terms:
     855             :       // (partial R / partial k)
     856             :       // R_k*sum(w_l*z^l) = (R(p+dp*e_k)*sum(w_l*z^l) - R(p-dp*e_k)*sum(w_l*z^l))/(2*dp)
     857             :       // (partial^2 q / partial u partial k)
     858             :       // q_uk = (q_u(p+dp*e_k) - q_u(p-dp*e_k))/(2*dp)
     859             :       // (partial^2 R / partial u partial k)
     860             :       // R_uk*z*sum(w_l*u'_l) = (R_u(p+dp*e_k)*z*sum(w_l*u'_l) - R_u(p-dp*e_k)*z*sum(w_l*u'_l))/(2*dp)
     861             : 
     862             :       // To avoid creating Nq temporary vectors for q_uk or R_uk, we add
     863             :       // subterms to the sensitivities output one by one.
     864             :       //
     865             :       // FIXME: this is probably a bad order of operations for
     866             :       // controlling floating point error.
     867             : 
     868           0 :       *parameters_vec[k] = old_parameter + delta_p;
     869           0 :       this->assembly(true, true);
     870           0 :       this->rhs->close();
     871           0 :       this->matrix->close();
     872           0 :       this->assemble_qoi_derivative(qoi_indices,
     873             :                                     /* include_liftfunc = */ true,
     874           0 :                                     /* apply_constraints = */ false);
     875             : 
     876           0 :       this->matrix->vector_mult(*tempvec, this->get_weighted_sensitivity_solution());
     877             : 
     878           0 :       for (unsigned int i=0; i != Nq; ++i)
     879           0 :         if (qoi_indices.has_index(i))
     880             :           {
     881           0 :             this->get_adjoint_rhs(i).close();
     882           0 :             sensitivities[i][k] += (this->get_adjoint_rhs(i).dot(this->get_weighted_sensitivity_solution()) -
     883           0 :                                     this->rhs->dot(this->get_weighted_sensitivity_adjoint_solution(i)) -
     884           0 :                                     this->get_adjoint_solution(i).dot(*tempvec)) / (2.*delta_p);
     885             :           }
     886             : 
     887           0 :       *parameters_vec[k] = old_parameter - delta_p;
     888           0 :       this->assembly(true, true);
     889           0 :       this->rhs->close();
     890           0 :       this->matrix->close();
     891           0 :       this->assemble_qoi_derivative(qoi_indices,
     892             :                                     /* include_liftfunc = */ true,
     893           0 :                                     /* apply_constraints = */ false);
     894             : 
     895           0 :       this->matrix->vector_mult(*tempvec, this->get_weighted_sensitivity_solution());
     896             : 
     897           0 :       for (unsigned int i=0; i != Nq; ++i)
     898           0 :         if (qoi_indices.has_index(i))
     899             :           {
     900           0 :             this->get_adjoint_rhs(i).close();
     901           0 :             sensitivities[i][k] += (-this->get_adjoint_rhs(i).dot(this->get_weighted_sensitivity_solution()) +
     902           0 :                                     this->rhs->dot(this->get_weighted_sensitivity_adjoint_solution(i)) +
     903           0 :                                     this->get_adjoint_solution(i).dot(*tempvec)) / (2.*delta_p);
     904             :           }
     905             :     }
     906             : 
     907             :   // All parameters have been reset.
     908             :   // Don't leave the qoi or system changed - principle of least
     909             :   // surprise.
     910           0 :   this->assembly(true, true);
     911           0 :   this->rhs->close();
     912           0 :   this->matrix->close();
     913           0 :   this->assemble_qoi(qoi_indices);
     914           0 : }
     915             : 
     916             : 
     917             : 
     918           0 : void ImplicitSystem::qoi_parameter_hessian (const QoISet & qoi_indices,
     919             :                                             const ParameterVector & parameters_in,
     920             :                                             SensitivityData & sensitivities)
     921             : {
     922             :   // We currently get partial derivatives via finite differencing
     923           0 :   const Real delta_p = TOLERANCE;
     924             : 
     925           0 :   ParameterVector & parameters_vec =
     926             :     const_cast<ParameterVector &>(parameters_in);
     927             : 
     928             :   // We'll use one temporary vector for matrix-vector-vector products
     929           0 :   std::unique_ptr<NumericVector<Number>> tempvec = this->solution->zero_clone();
     930             : 
     931             :   // And another temporary vector to hold a copy of the true solution
     932             :   // so we can safely perturb this->solution.
     933           0 :   std::unique_ptr<NumericVector<Number>> oldsolution = this->solution->clone();
     934             : 
     935             :   const unsigned int Np = cast_int<unsigned int>
     936           0 :     (parameters_vec.size());
     937           0 :   const unsigned int Nq = this->n_qois();
     938             : 
     939             :   // For each quantity of interest q, the parameter sensitivity
     940             :   // Hessian is defined as q''_{kl} = {d^2 q}/{d p_k d p_l}.
     941             :   //
     942             :   // We calculate it from values and partial derivatives of the
     943             :   // quantity of interest function Q, solution u, adjoint solution z,
     944             :   // and residual R, as:
     945             :   //
     946             :   // q''_{kl} =
     947             :   // Q''_{kl} + Q''_{uk}(u)*u'_l + Q''_{ul}(u) * u'_k +
     948             :   // Q''_{uu}(u)*u'_k*u'_l -
     949             :   // R''_{kl}(u,z) -
     950             :   // R''_{uk}(u,z)*u'_l - R''_{ul}(u,z)*u'_k -
     951             :   // R''_{uu}(u,z)*u'_k*u'_l
     952             :   //
     953             :   // See the adjoints model document for more details.
     954             : 
     955             :   // We first do an adjoint solve to get z for each quantity of
     956             :   // interest
     957             :   // if we haven't already or dont have an initial condition for the adjoint
     958           0 :   if (!this->is_adjoint_already_solved())
     959             :     {
     960           0 :       this->adjoint_solve(qoi_indices);
     961             :     }
     962             : 
     963             :   // And a sensitivity solve to get u_k for each parameter
     964           0 :   this->sensitivity_solve(parameters_vec);
     965             : 
     966             :   // Get ready to fill in second derivatives:
     967           0 :   sensitivities.allocate_hessian_data(qoi_indices, *this, parameters_vec);
     968             : 
     969           0 :   for (unsigned int k=0; k != Np; ++k)
     970             :     {
     971           0 :       Number old_parameterk = *parameters_vec[k];
     972             : 
     973             :       // The Hessian is symmetric, so we just calculate the lower
     974             :       // triangle and the diagonal, and we get the upper triangle from
     975             :       // the transpose of the lower
     976             : 
     977           0 :       for (unsigned int l=0; l != k+1; ++l)
     978             :         {
     979             :           // The second partial derivatives with respect to parameters_vec
     980             :           // are all calculated via a central finite difference
     981             :           // stencil:
     982             :           // F''_{kl} ~= (F(p+dp*e_k+dp*e_l) - F(p+dp*e_k-dp*e_l) -
     983             :           //              F(p-dp*e_k+dp*e_l) + F(p-dp*e_k-dp*e_l))/(4*dp^2)
     984             :           // We will add Q''_{kl}(u) and subtract R''_{kl}(u,z) at the
     985             :           // same time.
     986             :           //
     987             :           // We have to be careful with the perturbations to handle
     988             :           // the k=l case
     989             : 
     990           0 :           Number old_parameterl = *parameters_vec[l];
     991             : 
     992           0 :           *parameters_vec[k] += delta_p;
     993           0 :           *parameters_vec[l] += delta_p;
     994           0 :           this->assemble_qoi(qoi_indices);
     995           0 :           this->assembly(true, false, true);
     996           0 :           this->rhs->close();
     997           0 :           std::vector<Number> partial2q_term = this->get_qoi_values();
     998           0 :           std::vector<Number> partial2R_term(this->n_qois());
     999           0 :           for (unsigned int i=0; i != Nq; ++i)
    1000           0 :             if (qoi_indices.has_index(i))
    1001           0 :               partial2R_term[i] = this->rhs->dot(this->get_adjoint_solution(i));
    1002             : 
    1003           0 :           *parameters_vec[l] -= 2.*delta_p;
    1004           0 :           this->assemble_qoi(qoi_indices);
    1005           0 :           this->assembly(true, false, true);
    1006           0 :           this->rhs->close();
    1007           0 :           for (unsigned int i=0; i != Nq; ++i)
    1008           0 :             if (qoi_indices.has_index(i))
    1009             :               {
    1010           0 :                 partial2q_term[i] -= this->get_qoi_value(i);
    1011           0 :                 partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i));
    1012             :               }
    1013             : 
    1014           0 :           *parameters_vec[k] -= 2.*delta_p;
    1015           0 :           this->assemble_qoi(qoi_indices);
    1016           0 :           this->assembly(true, false, true);
    1017           0 :           this->rhs->close();
    1018           0 :           for (unsigned int i=0; i != Nq; ++i)
    1019           0 :             if (qoi_indices.has_index(i))
    1020             :               {
    1021           0 :                 partial2q_term[i] += this->get_qoi_value(i);
    1022           0 :                 partial2R_term[i] += this->rhs->dot(this->get_adjoint_solution(i));
    1023             :               }
    1024             : 
    1025           0 :           *parameters_vec[l] += 2.*delta_p;
    1026           0 :           this->assemble_qoi(qoi_indices);
    1027           0 :           this->assembly(true, false, true);
    1028           0 :           this->rhs->close();
    1029           0 :           for (unsigned int i=0; i != Nq; ++i)
    1030           0 :             if (qoi_indices.has_index(i))
    1031             :               {
    1032           0 :                 partial2q_term[i] -= this->get_qoi_value(i);
    1033           0 :                 partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i));
    1034           0 :                 partial2q_term[i] /= (4. * delta_p * delta_p);
    1035           0 :                 partial2R_term[i] /= (4. * delta_p * delta_p);
    1036             :               }
    1037             : 
    1038           0 :           for (unsigned int i=0; i != Nq; ++i)
    1039           0 :             if (qoi_indices.has_index(i))
    1040             :               {
    1041           0 :                 Number current_terms = partial2q_term[i] - partial2R_term[i];
    1042           0 :                 sensitivities.second_derivative(i,k,l) += current_terms;
    1043           0 :                 if (k != l)
    1044           0 :                   sensitivities.second_derivative(i,l,k) += current_terms;
    1045             :               }
    1046             : 
    1047             :           // Don't leave the parameters_vec perturbed
    1048           0 :           *parameters_vec[l] = old_parameterl;
    1049           0 :           *parameters_vec[k] = old_parameterk;
    1050             :         }
    1051             : 
    1052             :       // We get (partial q / partial u) and
    1053             :       // (partial R / partial u) from the user, but centrally
    1054             :       // difference to get q_uk and R_uk terms:
    1055             :       // (partial^2 q / partial u partial k)
    1056             :       // q_uk*u'_l = (q_u(p+dp*e_k)*u'_l - q_u(p-dp*e_k)*u'_l)/(2*dp)
    1057             :       // R_uk*z*u'_l = (R_u(p+dp*e_k)*z*u'_l - R_u(p-dp*e_k)*z*u'_l)/(2*dp)
    1058             :       //
    1059             :       // To avoid creating Nq temporary vectors, we add these
    1060             :       // subterms to the sensitivities output one by one.
    1061             :       //
    1062             :       // FIXME: this is probably a bad order of operations for
    1063             :       // controlling floating point error.
    1064             : 
    1065           0 :       *parameters_vec[k] = old_parameterk + delta_p;
    1066           0 :       this->assembly(false, true);
    1067           0 :       this->matrix->close();
    1068           0 :       this->assemble_qoi_derivative(qoi_indices,
    1069             :                                     /* include_liftfunc = */ true,
    1070           0 :                                     /* apply_constraints = */ false);
    1071             : 
    1072           0 :       for (unsigned int l=0; l != Np; ++l)
    1073             :         {
    1074           0 :           this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l));
    1075           0 :           for (unsigned int i=0; i != Nq; ++i)
    1076           0 :             if (qoi_indices.has_index(i))
    1077             :               {
    1078           0 :                 this->get_adjoint_rhs(i).close();
    1079             :                 Number current_terms =
    1080           0 :                   (this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) -
    1081           0 :                    tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p);
    1082           0 :                 sensitivities.second_derivative(i,k,l) += current_terms;
    1083             : 
    1084             :                 // We use the _uk terms twice; symmetry lets us reuse
    1085             :                 // these calculations for the _ul terms.
    1086             : 
    1087           0 :                 sensitivities.second_derivative(i,l,k) += current_terms;
    1088             :               }
    1089             :         }
    1090             : 
    1091           0 :       *parameters_vec[k] = old_parameterk - delta_p;
    1092           0 :       this->assembly(false, true);
    1093           0 :       this->matrix->close();
    1094           0 :       this->assemble_qoi_derivative(qoi_indices,
    1095             :                                     /* include_liftfunc = */ true,
    1096           0 :                                     /* apply_constraints = */ false);
    1097             : 
    1098           0 :       for (unsigned int l=0; l != Np; ++l)
    1099             :         {
    1100           0 :           this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l));
    1101           0 :           for (unsigned int i=0; i != Nq; ++i)
    1102           0 :             if (qoi_indices.has_index(i))
    1103             :               {
    1104           0 :                 this->get_adjoint_rhs(i).close();
    1105             :                 Number current_terms =
    1106           0 :                   (-this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) +
    1107           0 :                    tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p);
    1108           0 :                 sensitivities.second_derivative(i,k,l) += current_terms;
    1109             : 
    1110             :                 // We use the _uk terms twice; symmetry lets us reuse
    1111             :                 // these calculations for the _ul terms.
    1112             : 
    1113           0 :                 sensitivities.second_derivative(i,l,k) += current_terms;
    1114             :               }
    1115             :         }
    1116             : 
    1117             :       // Don't leave the parameter perturbed
    1118           0 :       *parameters_vec[k] = old_parameterk;
    1119             : 
    1120             :       // Our last remaining terms are -R_uu(u,z)*u_k*u_l and
    1121             :       // Q_uu(u)*u_k*u_l
    1122             :       //
    1123             :       // We take directional central finite differences of R_u and Q_u
    1124             :       // to approximate these terms, e.g.:
    1125             :       //
    1126             :       // Q_uu(u)*u_k ~= (Q_u(u+dp*u_k) - Q_u(u-dp*u_k))/(2*dp)
    1127             : 
    1128           0 :       *this->solution = this->get_sensitivity_solution(k);
    1129           0 :       *this->solution *= delta_p;
    1130           0 :       *this->solution += *oldsolution;
    1131             : 
    1132             :       // We've modified solution, so we need to update before calling
    1133             :       // assembly since assembly may only use current_local_solution
    1134           0 :       this->update();
    1135           0 :       this->assembly(false, true);
    1136           0 :       this->matrix->close();
    1137           0 :       this->assemble_qoi_derivative(qoi_indices,
    1138             :                                     /* include_liftfunc = */ true,
    1139           0 :                                     /* apply_constraints = */ false);
    1140             : 
    1141             :       // The Hessian is symmetric, so we just calculate the lower
    1142             :       // triangle and the diagonal, and we get the upper triangle from
    1143             :       // the transpose of the lower
    1144             :       //
    1145             :       // Note that, because we took the directional finite difference
    1146             :       // with respect to k and not l, we've added an O(delta_p^2)
    1147             :       // error to any permutational symmetry in the Hessian...
    1148           0 :       for (unsigned int l=0; l != k+1; ++l)
    1149             :         {
    1150           0 :           this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l));
    1151           0 :           for (unsigned int i=0; i != Nq; ++i)
    1152           0 :             if (qoi_indices.has_index(i))
    1153             :               {
    1154           0 :                 this->get_adjoint_rhs(i).close();
    1155             :                 Number current_terms =
    1156           0 :                   (this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) -
    1157           0 :                    tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p);
    1158           0 :                 sensitivities.second_derivative(i,k,l) += current_terms;
    1159           0 :                 if (k != l)
    1160           0 :                   sensitivities.second_derivative(i,l,k) += current_terms;
    1161             :               }
    1162             :         }
    1163             : 
    1164           0 :       *this->solution = this->get_sensitivity_solution(k);
    1165           0 :       *this->solution *= -delta_p;
    1166           0 :       *this->solution += *oldsolution;
    1167             : 
    1168             :       // We've modified solution, so we need to update before calling
    1169             :       // assembly since assembly may only use current_local_solution
    1170           0 :       this->update();
    1171           0 :       this->assembly(false, true);
    1172           0 :       this->matrix->close();
    1173           0 :       this->assemble_qoi_derivative(qoi_indices,
    1174             :                                     /* include_liftfunc = */ true,
    1175           0 :                                     /* apply_constraints = */ false);
    1176             : 
    1177           0 :       for (unsigned int l=0; l != k+1; ++l)
    1178             :         {
    1179           0 :           this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l));
    1180           0 :           for (unsigned int i=0; i != Nq; ++i)
    1181           0 :             if (qoi_indices.has_index(i))
    1182             :               {
    1183           0 :                 this->get_adjoint_rhs(i).close();
    1184             :                 Number current_terms =
    1185           0 :                   (-this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) +
    1186           0 :                    tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p);
    1187           0 :                 sensitivities.second_derivative(i,k,l) += current_terms;
    1188           0 :                 if (k != l)
    1189           0 :                   sensitivities.second_derivative(i,l,k) += current_terms;
    1190             :               }
    1191             :         }
    1192             : 
    1193             :       // Don't leave the solution perturbed
    1194           0 :       *this->solution = *oldsolution;
    1195             :     }
    1196             : 
    1197             :   // All parameters_vec have been reset.
    1198             :   // Don't leave the qoi or system changed - principle of least
    1199             :   // surprise.
    1200             :   // We've modified solution, so we need to update before calling
    1201             :   // assembly since assembly may only use current_local_solution
    1202           0 :   this->update();
    1203           0 :   this->assembly(true, true);
    1204           0 :   this->rhs->close();
    1205           0 :   this->matrix->close();
    1206           0 :   this->assemble_qoi(qoi_indices);
    1207           0 : }
    1208             : 
    1209             : 
    1210             : 
    1211           0 : LinearSolver<Number> * ImplicitSystem::get_linear_solver() const
    1212             : {
    1213             :   // Note: we always start "from scratch" to mimic the original
    1214             :   // behavior of this function. The goal is not to reuse the
    1215             :   // LinearSolver object, but to manage its lifetime in a more
    1216             :   // consistent manner.
    1217           0 :   linear_solver.reset();
    1218             : 
    1219           0 :   linear_solver = LinearSolver<Number>::build(this->comm());
    1220             : 
    1221           0 :   if (this->prefix_with_name())
    1222           0 :     linear_solver->init(this->prefix().c_str());
    1223             :   else
    1224           0 :     linear_solver->init();
    1225             : 
    1226           0 :   linear_solver->init_names(*this);
    1227             : 
    1228           0 :   return linear_solver.get();
    1229             : }
    1230             : 
    1231             : 
    1232             : 
    1233      231571 : std::pair<unsigned int, Real> ImplicitSystem::get_linear_solve_parameters() const
    1234             : {
    1235      218007 :   return std::make_pair(
    1236      231571 :       parameters.have_parameter<unsigned int>("linear solver maximum iterations")
    1237          71 :           ? parameters.get<unsigned int>("linear solver maximum iterations")
    1238      231500 :           : this->get_equation_systems().parameters.get<unsigned int>(
    1239       13560 :                 "linear solver maximum iterations"),
    1240      231571 :       parameters.have_parameter<Real>("linear solver tolerance")
    1241          71 :           ? parameters.get<Real>("linear solver tolerance")
    1242      476639 :           : this->get_equation_systems().parameters.get<Real>("linear solver tolerance"));
    1243             : }
    1244             : 
    1245             : 
    1246             : 
    1247             : #ifdef LIBMESH_ENABLE_DEPRECATED
    1248           0 : void ImplicitSystem::release_linear_solver(LinearSolver<Number> *) const
    1249             : {
    1250             :   // This function was originally paired with get_linear_solver()
    1251             :   // calls when that returned a dumb pointer which needed to be
    1252             :   // cleaned up. Since get_linear_solver() now just returns a pointer
    1253             :   // to a LinearSolver object managed by this class, this function no
    1254             :   // longer needs to do any cleanup.
    1255             :   libmesh_deprecated();
    1256           0 : }
    1257             : #endif // LIBMESH_ENABLE_DEPRECATED
    1258             : 
    1259             : 
    1260             : 
    1261           0 : const SparseMatrix<Number> & ImplicitSystem::get_system_matrix() const
    1262             : {
    1263           0 :   libmesh_assert(matrix);
    1264           0 :   libmesh_assert_equal_to(&get_matrix("System Matrix"), matrix);
    1265           0 :   return *matrix;
    1266             : }
    1267             : 
    1268             : 
    1269             : 
    1270     6752969 : SparseMatrix<Number> & ImplicitSystem::get_system_matrix()
    1271             : {
    1272      595194 :   libmesh_assert(matrix);
    1273      595194 :   libmesh_assert_equal_to(&get_matrix("System Matrix"), matrix);
    1274     6752969 :   return *matrix;
    1275             : }
    1276             : 
    1277             : template void ImplicitSystem::setup_static_condensation_preconditioner(LinearSolver<Number> & solver);
    1278             : template void ImplicitSystem::setup_static_condensation_preconditioner(NonlinearSolver<Number> & solver);
    1279             : } // namespace libMesh

Generated by: LCOV version 1.14