LCOV - code coverage report
Current view: top level - src/systems - fem_system.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 424 663 64.0 %
Date: 2025-08-19 19:27:09 Functions: 27 31 87.1 %
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             : // libMesh includes
      20             : #include "libmesh/dof_map.h"
      21             : #include "libmesh/elem.h"
      22             : #include "libmesh/equation_systems.h"
      23             : #include "libmesh/fe_base.h"
      24             : #include "libmesh/fem_context.h"
      25             : #include "libmesh/fem_system.h"
      26             : #include "libmesh/libmesh_logging.h"
      27             : #include "libmesh/mesh_base.h"
      28             : #include "libmesh/numeric_vector.h"
      29             : #include "libmesh/parallel_algebra.h"
      30             : #include "libmesh/parallel_ghost_sync.h"
      31             : #include "libmesh/quadrature.h"
      32             : #include "libmesh/sparse_matrix.h"
      33             : #include "libmesh/time_solver.h"
      34             : #include "libmesh/unsteady_solver.h" // For eulerian_residual
      35             : #include "libmesh/fe_interface.h"
      36             : 
      37             : namespace {
      38             : using namespace libMesh;
      39             : 
      40             : // give this guy some scope since there
      41             : // is underlying vector allocation upon
      42             : // creation/deletion
      43             : ConstElemRange elem_range;
      44             : 
      45             : typedef Threads::spin_mutex femsystem_mutex;
      46             : femsystem_mutex assembly_mutex;
      47             : 
      48    21041685 : void assemble_unconstrained_element_system(const FEMSystem & _sys,
      49             :                                            const bool _get_jacobian,
      50             :                                            const bool _constrain_heterogeneously,
      51             :                                            FEMContext & _femcontext)
      52             : {
      53    21041685 :   if (_sys.print_element_solutions)
      54             :     {
      55           0 :       std::streamsize old_precision = libMesh::out.precision();
      56           0 :       libMesh::out.precision(16);
      57           0 :       if (_femcontext.has_elem())
      58           0 :         libMesh::out << "U_elem " << _femcontext.get_elem().id();
      59             :       else
      60           0 :         libMesh::out << "U_scalar ";
      61           0 :       libMesh::out << " = " << _femcontext.get_elem_solution() << std::endl;
      62             : 
      63           0 :       if (_sys.use_fixed_solution)
      64             :         {
      65           0 :           if (_femcontext.has_elem())
      66           0 :             libMesh::out << "Ufixed_elem " << _femcontext.get_elem().id();
      67             :           else
      68           0 :             libMesh::out << "Ufixed_scalar ";
      69           0 :           libMesh::out << " = " << _femcontext.get_elem_fixed_solution() << std::endl;
      70           0 :           libMesh::out.precision(old_precision);
      71             :         }
      72             :     }
      73             : 
      74             :   // We need jacobians to do heterogeneous residual constraints
      75    21041685 :   const bool need_jacobian =
      76     1885846 :     (_get_jacobian || _constrain_heterogeneously);
      77             : 
      78             :   bool jacobian_computed =
      79    21041685 :     _sys.time_solver->element_residual(need_jacobian, _femcontext);
      80             : 
      81             :   // Compute a numeric jacobian if we have to
      82    21041685 :   if (need_jacobian && !jacobian_computed)
      83             :     {
      84             :       // Make sure we didn't compute a jacobian and lie about it
      85       21556 :       libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
      86             :       // Logging of numerical jacobians is done separately
      87      227292 :       _sys.numerical_elem_jacobian(_femcontext);
      88             :     }
      89             : 
      90             :   // Compute a numeric jacobian if we're asked to verify the
      91             :   // analytic jacobian we got
      92    21041685 :   if (need_jacobian && jacobian_computed &&
      93     6144148 :       _sys.verify_analytic_jacobians != 0.0)
      94             :     {
      95           0 :       DenseMatrix<Number> analytic_jacobian(_femcontext.get_elem_jacobian());
      96             : 
      97           0 :       _femcontext.get_elem_jacobian().zero();
      98             :       // Logging of numerical jacobians is done separately
      99           0 :       _sys.numerical_elem_jacobian(_femcontext);
     100             : 
     101           0 :       Real analytic_norm = analytic_jacobian.l1_norm();
     102           0 :       Real numerical_norm = _femcontext.get_elem_jacobian().l1_norm();
     103             : 
     104             :       // If we can continue, we'll probably prefer the analytic jacobian
     105           0 :       analytic_jacobian.swap(_femcontext.get_elem_jacobian());
     106             : 
     107             :       // The matrix "analytic_jacobian" will now hold the error matrix
     108           0 :       analytic_jacobian.add(-1.0, _femcontext.get_elem_jacobian());
     109           0 :       Real error_norm = analytic_jacobian.l1_norm();
     110             : 
     111           0 :       Real relative_error = error_norm /
     112           0 :         std::max(analytic_norm, numerical_norm);
     113             : 
     114           0 :       if (relative_error > _sys.verify_analytic_jacobians)
     115             :         {
     116           0 :           libMesh::err << "Relative error " << relative_error
     117           0 :                        << " detected in analytic jacobian on element "
     118           0 :                        << _femcontext.get_elem().id() << '!' << std::endl;
     119             : 
     120           0 :           std::streamsize old_precision = libMesh::out.precision();
     121           0 :           libMesh::out.precision(16);
     122           0 :           libMesh::out << "J_analytic " << _femcontext.get_elem().id() << " = "
     123           0 :                        << _femcontext.get_elem_jacobian() << std::endl;
     124           0 :           analytic_jacobian.add(1.0, _femcontext.get_elem_jacobian());
     125           0 :           libMesh::out << "J_numeric " << _femcontext.get_elem().id() << " = "
     126           0 :                        << analytic_jacobian << std::endl;
     127             : 
     128           0 :           libMesh::out.precision(old_precision);
     129             : 
     130           0 :           libmesh_error_msg("Relative error too large, exiting!");
     131             :         }
     132           0 :     }
     133             : 
     134    21041685 :   const unsigned char n_sides = _femcontext.get_elem().n_sides();
     135   105634793 :   for (_femcontext.side = 0; _femcontext.side != n_sides;
     136    84593108 :        ++_femcontext.side)
     137             :     {
     138             :       // Don't compute on non-boundary sides unless requested
     139   169186216 :       if (!_sys.get_physics()->compute_internal_sides &&
     140    84593108 :           _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr)
     141    82111062 :         continue;
     142             : 
     143             :       // Any mesh movement has already been done (and restored,
     144             :       // if the TimeSolver isn't broken), but
     145             :       // reinitializing the side FE objects is still necessary
     146     2482046 :       _femcontext.side_fe_reinit();
     147             : 
     148     2917658 :       DenseMatrix<Number> old_jacobian;
     149             :       // If we're in DEBUG mode, we should always verify that the
     150             :       // user's side_residual function doesn't alter our existing
     151             :       // jacobian and then lie about it
     152             : #ifndef DEBUG
     153             :       // Even if we're not in DEBUG mode, when we're verifying
     154             :       // analytic jacobians we'll want to verify each side's
     155             :       // jacobian contribution separately.
     156     2264240 :       if (_sys.verify_analytic_jacobians != 0.0 && need_jacobian)
     157             : #endif // ifndef DEBUG
     158             :         {
     159      217806 :           old_jacobian = _femcontext.get_elem_jacobian();
     160      217806 :           _femcontext.get_elem_jacobian().zero();
     161             :         }
     162             : 
     163             :       jacobian_computed =
     164     2482046 :         _sys.time_solver->side_residual(need_jacobian, _femcontext);
     165             : 
     166             :       // Compute a numeric jacobian if we have to
     167     2482046 :       if (need_jacobian && !jacobian_computed)
     168             :         {
     169             :           // If we have already backed up old_jacobian,
     170             :           // we can make sure side_residual didn't compute a
     171             :           // jacobian and lie about it.
     172             :           //
     173             :           // If we haven't, then we need to, to let
     174             :           // numerical_side_jacobian work.
     175       49166 :           if (old_jacobian.m())
     176        5620 :             libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
     177             :           else
     178             :             {
     179       43546 :               old_jacobian = _femcontext.get_elem_jacobian();
     180           0 :               _femcontext.get_elem_jacobian().zero();
     181             :             }
     182             : 
     183             :           // Logging of numerical jacobians is done separately
     184       49166 :           _sys.numerical_side_jacobian(_femcontext);
     185             : 
     186             :           // Add back in element interior numerical Jacobian
     187       43546 :           _femcontext.get_elem_jacobian() += old_jacobian;
     188             :         }
     189             : 
     190             :       // Compute a numeric jacobian if we're asked to verify the
     191             :       // analytic jacobian we got
     192     2432880 :       else if (need_jacobian && jacobian_computed &&
     193      797997 :                _sys.verify_analytic_jacobians != 0.0)
     194             :         {
     195           0 :           DenseMatrix<Number> analytic_jacobian(_femcontext.get_elem_jacobian());
     196             : 
     197           0 :           _femcontext.get_elem_jacobian().zero();
     198             :           // Logging of numerical jacobians is done separately
     199           0 :           _sys.numerical_side_jacobian(_femcontext);
     200             : 
     201           0 :           Real analytic_norm = analytic_jacobian.l1_norm();
     202           0 :           Real numerical_norm = _femcontext.get_elem_jacobian().l1_norm();
     203             : 
     204             :           // If we can continue, we'll probably prefer the analytic jacobian
     205           0 :           analytic_jacobian.swap(_femcontext.get_elem_jacobian());
     206             : 
     207             :           // The matrix "analytic_jacobian" will now hold the error matrix
     208           0 :           analytic_jacobian.add(-1.0, _femcontext.get_elem_jacobian());
     209           0 :           Real error_norm = analytic_jacobian.l1_norm();
     210             : 
     211           0 :           Real relative_error = error_norm /
     212           0 :             std::max(analytic_norm, numerical_norm);
     213             : 
     214           0 :           if (relative_error > _sys.verify_analytic_jacobians)
     215             :             {
     216           0 :               libMesh::err << "Relative error " << relative_error
     217           0 :                            << " detected in analytic jacobian on element "
     218           0 :                            << _femcontext.get_elem().id()
     219           0 :                            << ", side "
     220           0 :                            << static_cast<unsigned int>(_femcontext.side) << '!' << std::endl;
     221             : 
     222           0 :               std::streamsize old_precision = libMesh::out.precision();
     223           0 :               libMesh::out.precision(16);
     224           0 :               libMesh::out << "J_analytic " << _femcontext.get_elem().id() << " = "
     225           0 :                            << _femcontext.get_elem_jacobian() << std::endl;
     226           0 :               analytic_jacobian.add(1.0, _femcontext.get_elem_jacobian());
     227           0 :               libMesh::out << "J_numeric " << _femcontext.get_elem().id() << " = "
     228           0 :                            << analytic_jacobian << std::endl;
     229           0 :               libMesh::out.precision(old_precision);
     230             : 
     231           0 :               libmesh_error_msg("Relative error too large, exiting!");
     232             :             }
     233             :           // Once we've verified a side, we'll want to add back the
     234             :           // rest of the accumulated jacobian
     235           0 :           _femcontext.get_elem_jacobian() += old_jacobian;
     236           0 :         }
     237             : 
     238             :       // In DEBUG mode, we've set elem_jacobian == 0, and we
     239             :       // may have yet to add the old jacobian back
     240             : #ifdef DEBUG
     241             :       else
     242             :         {
     243      212186 :           _femcontext.get_elem_jacobian() += old_jacobian;
     244             :         }
     245             : #endif // ifdef DEBUG
     246     2046434 :     }
     247    21041685 : }
     248             : 
     249    21041685 : void add_element_system(FEMSystem & _sys,
     250             :                         const bool _get_residual,
     251             :                         const bool _get_jacobian,
     252             :                         const bool _constrain_heterogeneously,
     253             :                         const bool _no_constraints,
     254             :                         FEMContext & _femcontext)
     255             : {
     256             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     257    21041685 :   if (_get_residual && _sys.print_element_residuals)
     258             :     {
     259           0 :       std::streamsize old_precision = libMesh::out.precision();
     260           0 :       libMesh::out.precision(16);
     261           0 :       if (_femcontext.has_elem())
     262           0 :         libMesh::out << "Rraw_elem " << _femcontext.get_elem().id();
     263             :       else
     264           0 :         libMesh::out << "Rraw_scalar ";
     265           0 :       libMesh::out << " = " << _femcontext.get_elem_residual() << std::endl;
     266           0 :       libMesh::out.precision(old_precision);
     267             :     }
     268             : 
     269    21041685 :   if (_get_jacobian && _sys.print_element_jacobians)
     270             :     {
     271           0 :       std::streamsize old_precision = libMesh::out.precision();
     272           0 :       libMesh::out.precision(16);
     273           0 :       if (_femcontext.has_elem())
     274           0 :         libMesh::out << "Jraw_elem " << _femcontext.get_elem().id();
     275             :       else
     276           0 :         libMesh::out << "Jraw_scalar ";
     277           0 :       libMesh::out << " = " << _femcontext.get_elem_jacobian() << std::endl;
     278           0 :       libMesh::out.precision(old_precision);
     279             :     }
     280             : 
     281             :   // We turn off the asymmetric constraint application iff we expect
     282             :   // enforce_constraints_exactly() to be called in the solver
     283    21041685 :   const bool constrain_in_solver = _sys.get_constrain_in_solver();
     284             : 
     285    21041685 :   if (_get_residual && _get_jacobian)
     286             :     {
     287     4193579 :       if (constrain_in_solver)
     288             :         {
     289     3846417 :           if (_constrain_heterogeneously)
     290           0 :             _sys.get_dof_map().heterogenously_constrain_element_matrix_and_vector
     291           0 :               (_femcontext.get_elem_jacobian(),
     292             :                _femcontext.get_elem_residual(),
     293             :                _femcontext.get_dof_indices(), false);
     294     3846417 :           else if (!_no_constraints)
     295      345242 :             _sys.get_dof_map().constrain_element_matrix_and_vector
     296     3846417 :               (_femcontext.get_elem_jacobian(),
     297             :                _femcontext.get_elem_residual(),
     298             :                _femcontext.get_dof_indices(), false);
     299             :         }
     300        1760 :       else if (!_no_constraints)
     301         160 :         _sys.get_dof_map().heterogeneously_constrain_element_jacobian_and_residual
     302        1760 :           (_femcontext.get_elem_jacobian(),
     303             :            _femcontext.get_elem_residual(),
     304             :            _femcontext.get_dof_indices(),
     305         160 :            *_sys.current_local_solution);
     306             :       // Do nothing if (_no_constraints)
     307             :     }
     308    17193508 :   else if (_get_residual)
     309             :     {
     310    14672005 :       if (constrain_in_solver)
     311             :         {
     312    14662589 :           if (_constrain_heterogeneously)
     313           0 :             _sys.get_dof_map().heterogenously_constrain_element_vector
     314           0 :               (_femcontext.get_elem_jacobian(),
     315             :                _femcontext.get_elem_residual(),
     316             :                _femcontext.get_dof_indices(), false);
     317    14662589 :           else if (!_no_constraints)
     318     1299222 :             _sys.get_dof_map().constrain_element_vector
     319    14522693 :               (_femcontext.get_elem_residual(),
     320             :                _femcontext.get_dof_indices(), false);
     321             :         }
     322        9416 :       else if (!_no_constraints)
     323         160 :         _sys.get_dof_map().heterogeneously_constrain_element_residual
     324        1760 :           (_femcontext.get_elem_residual(),
     325             :            _femcontext.get_dof_indices(),
     326         160 :            *_sys.current_local_solution);
     327             :       // Do nothing if (_no_constraints)
     328             :     }
     329     2521503 :   else if (_get_jacobian)
     330             :     {
     331             :       // Heterogeneous and homogeneous constraints are the same on the
     332             :       // matrix
     333             :       // Only get these contribs if we are applying some constraints
     334     2521503 :       if (!_no_constraints)
     335     2750037 :         _sys.get_dof_map().constrain_element_matrix (_femcontext.get_elem_jacobian(),
     336             :                                                      _femcontext.get_dof_indices(),
     337     2521503 :                                                      !constrain_in_solver);
     338             :     }
     339             : #else
     340             :   libmesh_ignore(_constrain_heterogeneously, _no_constraints);
     341             : #endif // #ifdef LIBMESH_ENABLE_CONSTRAINTS
     342             : 
     343    21041685 :   if (_get_residual && _sys.print_element_residuals)
     344             :     {
     345           0 :       std::streamsize old_precision = libMesh::out.precision();
     346           0 :       libMesh::out.precision(16);
     347           0 :       if (_femcontext.has_elem())
     348           0 :         libMesh::out << "R_elem " << _femcontext.get_elem().id();
     349             :       else
     350           0 :         libMesh::out << "R_scalar ";
     351           0 :       libMesh::out << " = " << _femcontext.get_elem_residual() << std::endl;
     352           0 :       libMesh::out.precision(old_precision);
     353             :     }
     354             : 
     355    21041685 :   if (_get_jacobian && _sys.print_element_jacobians)
     356             :     {
     357           0 :       std::streamsize old_precision = libMesh::out.precision();
     358           0 :       libMesh::out.precision(16);
     359           0 :       if (_femcontext.has_elem())
     360           0 :         libMesh::out << "J_elem " << _femcontext.get_elem().id();
     361             :       else
     362           0 :         libMesh::out << "J_scalar ";
     363           0 :       libMesh::out << " = " << _femcontext.get_elem_jacobian() << std::endl;
     364           0 :       libMesh::out.precision(old_precision);
     365             :     }
     366             : 
     367             :   { // A lock is necessary around access to the global system
     368     3771692 :     femsystem_mutex::scoped_lock lock(assembly_mutex);
     369             : 
     370    21041685 :     if (_get_jacobian)
     371     6943616 :       _sys.get_system_matrix().add_matrix (_femcontext.get_elem_jacobian(),
     372     1147872 :                                            _femcontext.get_dof_indices());
     373    21041685 :     if (_get_residual)
     374    18520182 :       _sys.rhs->add_vector (_femcontext.get_elem_residual(),
     375     1657312 :                             _femcontext.get_dof_indices());
     376             :   } // Scope for assembly mutex
     377    21041685 : }
     378             : 
     379             : 
     380             : 
     381             : class AssemblyContributions
     382             : {
     383             : public:
     384             :   /**
     385             :    * constructor to set context
     386             :    */
     387        5900 :   AssemblyContributions(FEMSystem & sys,
     388             :                         bool get_residual,
     389             :                         bool get_jacobian,
     390             :                         bool constrain_heterogeneously,
     391      204784 :                         bool no_constraints) :
     392      192984 :     _sys(sys),
     393      192984 :     _get_residual(get_residual),
     394      192984 :     _get_jacobian(get_jacobian),
     395      192984 :     _constrain_heterogeneously(constrain_heterogeneously),
     396      204784 :     _no_constraints(no_constraints) {}
     397             : 
     398             :   /**
     399             :    * operator() for use with Threads::parallel_for().
     400             :    */
     401      220030 :   void operator()(const ConstElemRange & range) const
     402             :   {
     403      231042 :     std::unique_ptr<DiffContext> con = _sys.build_context();
     404       11012 :     FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
     405      220030 :     _sys.init_context(_femcontext);
     406             : 
     407    21261715 :     for (const auto & elem : range)
     408             :       {
     409    21041685 :         _femcontext.pre_fe_reinit(_sys, elem);
     410    21041685 :         _femcontext.elem_fe_reinit();
     411             : 
     412             :         assemble_unconstrained_element_system
     413    21041685 :           (_sys, _get_jacobian, _constrain_heterogeneously, _femcontext);
     414             : 
     415             :         add_element_system
     416    24813377 :           (_sys, _get_residual, _get_jacobian,
     417    21041685 :            _constrain_heterogeneously, _no_constraints, _femcontext);
     418             :       }
     419      220030 :   }
     420             : 
     421             : private:
     422             : 
     423             :   FEMSystem & _sys;
     424             : 
     425             :   const bool _get_residual, _get_jacobian, _constrain_heterogeneously, _no_constraints;
     426             : };
     427             : 
     428             : class PostprocessContributions
     429             : {
     430             : public:
     431             :   /**
     432             :    * constructor to set context
     433             :    */
     434             :   explicit
     435        1976 :   PostprocessContributions(FEMSystem & sys) : _sys(sys) {}
     436             : 
     437             :   /**
     438             :    * operator() for use with Threads::parallel_for().
     439             :    */
     440        2048 :   void operator()(const ConstElemRange & range) const
     441             :   {
     442        2252 :     std::unique_ptr<DiffContext> con = _sys.build_context();
     443         204 :     FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
     444        2048 :     _sys.init_context(_femcontext);
     445             : 
     446       64788 :     for (const auto & elem : range)
     447             :       {
     448       62740 :         _femcontext.pre_fe_reinit(_sys, elem);
     449             : 
     450             :         // Optionally initialize all the interior FE objects on elem.
     451       62740 :         if (_sys.fe_reinit_during_postprocess)
     452       62740 :           _femcontext.elem_fe_reinit();
     453             : 
     454       62740 :         _sys.element_postprocess(_femcontext);
     455             : 
     456       62740 :         const unsigned char n_sides = _femcontext.get_elem().n_sides();
     457      310884 :         for (_femcontext.side = 0; _femcontext.side != n_sides;
     458      248144 :              ++_femcontext.side)
     459             :           {
     460             :             // Don't compute on non-boundary sides unless requested
     461      269216 :             if (!_sys.postprocess_sides ||
     462      316992 :                 (!_sys.get_physics()->compute_internal_sides &&
     463      169032 :                  _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr))
     464      232531 :               continue;
     465             : 
     466             :             // Optionally initialize all the FE objects on this side.
     467       15613 :             if (_sys.fe_reinit_during_postprocess)
     468       15613 :               _femcontext.side_fe_reinit();
     469             : 
     470       15613 :             _sys.side_postprocess(_femcontext);
     471             :           }
     472             :       }
     473        2048 :   }
     474             : 
     475             : private:
     476             : 
     477             :   FEMSystem & _sys;
     478             : };
     479             : 
     480             : class QoIContributions
     481             : {
     482             : public:
     483             :   /**
     484             :    * constructor to set context
     485             :    */
     486             :   explicit
     487       39155 :   QoIContributions(FEMSystem & sys,
     488             :                    DifferentiableQoI & diff_qoi,
     489       39155 :                    const QoISet & qoi_indices) :
     490       40273 :     qoi(sys.n_qois(), 0.), _sys(sys), _diff_qoi(diff_qoi),_qoi_indices(qoi_indices) {}
     491             : 
     492             :   /**
     493             :    * splitting constructor
     494             :    */
     495        2236 :   QoIContributions(const QoIContributions & other,
     496        2236 :                    Threads::split) :
     497        2236 :     qoi(other._sys.n_qois(), 0.), _sys(other._sys), _diff_qoi(other._diff_qoi) {}
     498             : 
     499             :   /**
     500             :    * operator() for use with Threads::parallel_reduce().
     501             :    */
     502       42509 :   void operator()(const ConstElemRange & range)
     503             :   {
     504       44745 :     std::unique_ptr<DiffContext> con = _sys.build_context();
     505        2236 :     FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
     506       42509 :     _diff_qoi.init_context(_femcontext);
     507             : 
     508        2236 :     bool have_some_heterogenous_qoi_bc = false;
     509             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     510       46981 :     std::vector<bool> have_heterogenous_qoi_bc(_sys.n_qois(), false);
     511      103258 :     for (auto q : make_range(_sys.n_qois()))
     512       63945 :       if (_qoi_indices.has_index(q) &&
     513        6392 :           _sys.get_dof_map().has_heterogenous_adjoint_constraints(q))
     514             :         {
     515           0 :           have_heterogenous_qoi_bc[q] = true;
     516           0 :           have_some_heterogenous_qoi_bc = true;
     517             :         }
     518             : #endif
     519             : 
     520       42509 :     if (have_some_heterogenous_qoi_bc)
     521           0 :       _sys.init_context(_femcontext);
     522             : 
     523    13461113 :     for (const auto & elem : range)
     524             :       {
     525    13418604 :         _femcontext.pre_fe_reinit(_sys, elem);
     526             : 
     527             :         // We might have some heterogenous dofs here; let's see for
     528             :         // certain
     529     1216614 :         bool elem_has_some_heterogenous_qoi_bc = false;
     530             : 
     531             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     532             :         const unsigned int n_dofs =
     533     2433228 :           cast_int<unsigned int>(_femcontext.get_dof_indices().size());
     534             : 
     535    15851832 :         std::vector<bool> elem_has_heterogenous_qoi_bc(_sys.n_qois(), false);
     536    13418604 :         if (have_some_heterogenous_qoi_bc)
     537             :           {
     538           0 :             for (auto q : make_range(_sys.n_qois()))
     539             :               {
     540           0 :                 if (have_heterogenous_qoi_bc[q])
     541             :                   {
     542           0 :                     for (auto d : make_range(n_dofs))
     543           0 :                       if (_sys.get_dof_map().has_heterogenous_adjoint_constraint
     544           0 :                           (q, _femcontext.get_dof_indices()[d]) != Number(0))
     545             :                         {
     546           0 :                           elem_has_some_heterogenous_qoi_bc = true;
     547           0 :                           elem_has_heterogenous_qoi_bc[q] = true;
     548           0 :                           break;
     549             :                         }
     550             :                   }
     551             :               }
     552             :           }
     553             : #endif
     554             : 
     555    13418604 :         if (_diff_qoi.assemble_qoi_elements ||
     556             :             elem_has_some_heterogenous_qoi_bc)
     557    13418604 :           _femcontext.elem_fe_reinit();
     558             : 
     559    13418604 :         if (_diff_qoi.assemble_qoi_elements)
     560    13418604 :           _diff_qoi.element_qoi(_femcontext, _qoi_indices);
     561             : 
     562             :         // If we have some heterogenous dofs here, those are
     563             :         // themselves part of a regularized flux QoI which the library
     564             :         // handles integrating
     565             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     566    13418604 :         if (elem_has_some_heterogenous_qoi_bc)
     567             :           {
     568           0 :             _sys.time_solver->element_residual(false, _femcontext);
     569             : 
     570           0 :             for (auto q : make_range(_sys.n_qois()))
     571             :               {
     572           0 :                 if (elem_has_heterogenous_qoi_bc[q])
     573             :                   {
     574           0 :                     for (auto d : make_range(n_dofs))
     575           0 :                       this->qoi[q] -= _femcontext.get_elem_residual()(d) *
     576           0 :                         _sys.get_dof_map().has_heterogenous_adjoint_constraint(q, _femcontext.get_dof_indices()[d]);
     577             : 
     578             :                   }
     579             :               }
     580             :           }
     581             : #endif
     582             : 
     583    13418604 :         const unsigned char n_sides = _femcontext.get_elem().n_sides();
     584    67093020 :         for (_femcontext.side = 0; _femcontext.side != n_sides;
     585    53674416 :              ++_femcontext.side)
     586             :           {
     587             :             // Don't compute on non-boundary sides unless requested
     588    53675400 :             if (!_diff_qoi.assemble_qoi_sides ||
     589       11184 :                 (!_diff_qoi.assemble_qoi_internal_sides &&
     590       11184 :                  _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr))
     591    48805234 :               continue;
     592             : 
     593        2999 :             _femcontext.side_fe_reinit();
     594             : 
     595        2999 :             _diff_qoi.side_qoi(_femcontext, _qoi_indices);
     596             :           }
     597             :       }
     598             : 
     599       44745 :     this->_diff_qoi.thread_join( this->qoi, _femcontext.get_qois(), _qoi_indices );
     600       42509 :   }
     601             : 
     602        2236 :   void join (const QoIContributions & other)
     603             :   {
     604        1118 :     libmesh_assert_equal_to (this->qoi.size(), other.qoi.size());
     605        3354 :     this->_diff_qoi.thread_join( this->qoi, other.qoi, _qoi_indices );
     606        2236 :   }
     607             : 
     608             :   std::vector<Number> qoi;
     609             : 
     610             : private:
     611             : 
     612             :   FEMSystem & _sys;
     613             :   DifferentiableQoI & _diff_qoi;
     614             : 
     615             :   const QoISet _qoi_indices;
     616             : };
     617             : 
     618             : class QoIDerivativeContributions
     619             : {
     620             : public:
     621             :   /**
     622             :    * constructor to set context
     623             :    */
     624         464 :   QoIDerivativeContributions(FEMSystem & sys,
     625             :                              const QoISet & qoi_indices,
     626             :                              DifferentiableQoI & qoi,
     627             :                              bool include_liftfunc,
     628       14654 :                              bool apply_constraints) :
     629       13726 :     _sys(sys),
     630       13726 :     _qoi_indices(qoi_indices),
     631       13726 :     _qoi(qoi),
     632       13726 :     _include_liftfunc(include_liftfunc),
     633       14654 :     _apply_constraints(apply_constraints) {}
     634             : 
     635             :   /**
     636             :    * operator() for use with Threads::parallel_for().
     637             :    */
     638       16016 :   void operator()(const ConstElemRange & range) const
     639             :   {
     640       16944 :     std::unique_ptr<DiffContext> con = _sys.build_context();
     641         928 :     FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
     642       16016 :     _qoi.init_context(_femcontext);
     643             : 
     644         928 :     bool have_some_heterogenous_qoi_bc = false;
     645             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     646       17872 :     std::vector<bool> have_heterogenous_qoi_bc(_sys.n_qois(), false);
     647       16016 :     if (_include_liftfunc || _apply_constraints)
     648       43173 :       for (auto q : make_range(_sys.n_qois()))
     649       28757 :         if (_qoi_indices.has_index(q) &&
     650        3200 :             _sys.get_dof_map().has_heterogenous_adjoint_constraints(q))
     651             :           {
     652          48 :             have_heterogenous_qoi_bc[q] = true;
     653          24 :             have_some_heterogenous_qoi_bc = true;
     654             :           }
     655             : #endif
     656             : 
     657       16016 :     if (have_some_heterogenous_qoi_bc)
     658         212 :       _sys.init_context(_femcontext);
     659             : 
     660     2432151 :     for (const auto & elem : range)
     661             :       {
     662     2416135 :         _femcontext.pre_fe_reinit(_sys, elem);
     663             : 
     664             :         // We might have some heterogenous dofs here; let's see for
     665             :         // certain
     666      219086 :         bool elem_has_some_heterogenous_qoi_bc = false;
     667             : 
     668             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     669             :         const unsigned int n_dofs =
     670      438172 :           cast_int<unsigned int>(_femcontext.get_dof_indices().size());
     671             : 
     672     2854307 :         std::vector<bool> elem_has_heterogenous_qoi_bc(_sys.n_qois(), false);
     673     2416135 :         if (have_some_heterogenous_qoi_bc)
     674             :           {
     675       85476 :             for (auto q : make_range(_sys.n_qois()))
     676             :               {
     677       48450 :                 if (have_heterogenous_qoi_bc[q])
     678             :                   {
     679      412332 :                     for (auto d : make_range(n_dofs))
     680      320980 :                       if (_sys.get_dof_map().has_heterogenous_adjoint_constraint
     681      421568 :                           (q, _femcontext.get_dof_indices()[d]) != Number(0))
     682             :                         {
     683         140 :                           elem_has_some_heterogenous_qoi_bc = true;
     684         280 :                           elem_has_heterogenous_qoi_bc[q] = true;
     685        1680 :                           break;
     686             :                         }
     687             :                   }
     688             :               }
     689             :           }
     690             : #endif
     691             : 
     692             :         // If we're going to call a user integral, then we need FE
     693             :         // information to call element_qoi.
     694             :         // If we're going to evaluate lift-function-based components
     695             :         // of a QoI, then we need FE information to assemble the
     696             :         // element residual.
     697     2416135 :         if (_qoi.assemble_qoi_elements ||
     698           0 :             ((_include_liftfunc || _apply_constraints) &&
     699             :              elem_has_some_heterogenous_qoi_bc))
     700     2416135 :           _femcontext.elem_fe_reinit();
     701             : 
     702     2416135 :         if (_qoi.assemble_qoi_elements)
     703     2416135 :           _qoi.element_qoi_derivative(_femcontext, _qoi_indices);
     704             : 
     705             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     706             :         // If we need to use heterogenous dofs here, we need the
     707             :         // Jacobian either for the regularized flux QoI integration
     708             :         // and/or for constraint application.
     709     2416135 :         if ((_include_liftfunc || _apply_constraints) &&
     710             :             elem_has_some_heterogenous_qoi_bc)
     711             :           {
     712        1820 :             bool jacobian_computed = _sys.time_solver->element_residual(true, _femcontext);
     713             : 
     714             :             // If we're using numerical jacobians, above wont compute them
     715        1680 :             if (!jacobian_computed)
     716             :               {
     717             :                 // Make sure we didn't compute a jacobian and lie about it
     718         140 :                 libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
     719             :                 // Logging of numerical jacobians is done separately
     720        1680 :                 _sys.numerical_elem_jacobian(_femcontext);
     721             :               }
     722             :           }
     723             : 
     724             :         // If we have some heterogenous dofs here, those are
     725             :         // themselves part of a regularized flux QoI which the library
     726             :         // may handle integrating
     727     2416135 :         if (_include_liftfunc && elem_has_some_heterogenous_qoi_bc)
     728             :           {
     729           0 :             for (auto q : make_range(_sys.n_qois()))
     730             :               {
     731           0 :                 if (elem_has_heterogenous_qoi_bc[q])
     732             :                   {
     733           0 :                     for (auto i : make_range(n_dofs))
     734             :                       {
     735             :                         Number liftfunc_val =
     736           0 :                           _sys.get_dof_map().has_heterogenous_adjoint_constraint(q, _femcontext.get_dof_indices()[i]);
     737             : 
     738           0 :                         if (liftfunc_val != Number(0))
     739             :                           {
     740           0 :                             for (auto j : make_range(n_dofs))
     741           0 :                               _femcontext.get_qoi_derivatives()[q](j) -=
     742           0 :                                 _femcontext.get_elem_jacobian()(i,j) *
     743             :                                 liftfunc_val;
     744             :                           }
     745             :                       }
     746             :                   }
     747             :               }
     748             :           }
     749             : #endif
     750             : 
     751             : 
     752     2416135 :         const unsigned char n_sides = _femcontext.get_elem().n_sides();
     753    12080675 :         for (_femcontext.side = 0; _femcontext.side != n_sides;
     754     9664540 :              ++_femcontext.side)
     755             :           {
     756             :             // Don't compute on non-boundary sides unless requested
     757     9723924 :             if (!_qoi.assemble_qoi_sides ||
     758      649372 :                 (!_qoi.assemble_qoi_internal_sides &&
     759      649372 :                  _femcontext.get_elem().neighbor_ptr(_femcontext.side) != nullptr))
     760     8760594 :               continue;
     761             : 
     762       30960 :             _femcontext.side_fe_reinit();
     763             : 
     764       30960 :             _qoi.side_qoi_derivative(_femcontext, _qoi_indices);
     765             :           }
     766             : 
     767             :         // We need some unmodified indices to use for constraining
     768             :         // multiple vector
     769             :         // FIXME - there should be a DofMap::constrain_element_vectors
     770             :         // to do this more efficiently
     771             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     772     2635221 :         std::vector<dof_id_type> original_dofs = _femcontext.get_dof_indices();
     773             : #endif
     774             : 
     775             :         { // A lock is necessary around access to the global system
     776      438172 :           femsystem_mutex::scoped_lock lock(assembly_mutex);
     777             : 
     778             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     779             :           // We'll need to see if any heterogenous constraints apply
     780             :           // to the QoI dofs on this element *or* to any of the dofs
     781             :           // they depend on, so let's get those dependencies
     782     2416135 :           if (_apply_constraints)
     783     2595285 :             _sys.get_dof_map().constrain_nothing(_femcontext.get_dof_indices());
     784             : #endif
     785             : 
     786     4929428 :           for (auto i : make_range(_sys.n_qois()))
     787     2513293 :             if (_qoi_indices.has_index(i))
     788             :               {
     789             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     790     2513293 :                 if (_apply_constraints)
     791             :                   {
     792             : #ifndef NDEBUG
     793      225458 :                     bool has_heterogenous_constraint = false;
     794     1269226 :                     for (auto d : make_range(n_dofs))
     795     1043908 :                       if (_sys.get_dof_map().has_heterogenous_adjoint_constraint
     796     1043908 :                           (i, _femcontext.get_dof_indices()[d]) != Number(0))
     797             :                         {
     798         140 :                           has_heterogenous_constraint = true;
     799         140 :                           libmesh_assert(elem_has_heterogenous_qoi_bc[i]);
     800         140 :                           libmesh_assert(elem_has_some_heterogenous_qoi_bc);
     801         140 :                           break;
     802             :                         }
     803             : #else
     804             :                     bool has_heterogenous_constraint =
     805      225458 :                       elem_has_heterogenous_qoi_bc[i];
     806             : #endif
     807             : 
     808     2476429 :                     _femcontext.get_dof_indices() = original_dofs;
     809             : 
     810     2476429 :                     if (has_heterogenous_constraint)
     811             :                       {
     812             :                         // Q_u gets used for *adjoint* solves, so we
     813             :                         // need K^T here.
     814        1960 :                         DenseMatrix<Number> elem_jacobian_transpose;
     815         140 :                         _femcontext.get_elem_jacobian().get_transpose
     816        1680 :                           (elem_jacobian_transpose);
     817             : 
     818        1680 :                         _sys.get_dof_map().heterogenously_constrain_element_vector
     819        1820 :                           (elem_jacobian_transpose,
     820         280 :                            _femcontext.get_qoi_derivatives()[i],
     821             :                            _femcontext.get_dof_indices(), false, i);
     822        1400 :                       }
     823             :                     else
     824             :                       {
     825     2474749 :                         _sys.get_dof_map().constrain_element_vector
     826     2700067 :                           (_femcontext.get_qoi_derivatives()[i],
     827             :                            _femcontext.get_dof_indices(), false);
     828             :                       }
     829             :                   }
     830             : #endif
     831             : 
     832     2513293 :                 _sys.get_adjoint_rhs(i).add_vector
     833     2513293 :                   (_femcontext.get_qoi_derivatives()[i], _femcontext.get_dof_indices());
     834             :               }
     835             :         }
     836             :       }
     837       16016 :   }
     838             : 
     839             : private:
     840             : 
     841             :   FEMSystem & _sys;
     842             :   const QoISet & _qoi_indices;
     843             :   DifferentiableQoI & _qoi;
     844             :   bool _include_liftfunc, _apply_constraints;
     845             : };
     846             : 
     847             : 
     848             : }
     849             : 
     850             : 
     851             : namespace libMesh
     852             : {
     853             : 
     854             : 
     855             : 
     856             : 
     857             : 
     858        5044 : FEMSystem::FEMSystem (EquationSystems & es,
     859             :                       const std::string & name_in,
     860           0 :                       const unsigned int number_in)
     861             :   : Parent(es, name_in, number_in),
     862        4744 :     fe_reinit_during_postprocess(true),
     863        4744 :     numerical_jacobian_h(TOLERANCE),
     864        5044 :     verify_analytic_jacobians(0.0)
     865             : {
     866        5044 : }
     867             : 
     868             : 
     869        4744 : FEMSystem::~FEMSystem () = default;
     870             : 
     871             : 
     872             : 
     873        5044 : void FEMSystem::init_data ()
     874             : {
     875             :   // First initialize LinearImplicitSystem data
     876        5044 :   Parent::init_data();
     877        5044 : }
     878             : 
     879             : 
     880      204784 : void FEMSystem::assembly (bool get_residual, bool get_jacobian,
     881             :                           bool apply_heterogeneous_constraints,
     882             :                           bool apply_no_constraints)
     883             : {
     884        5900 :   libmesh_assert(get_residual || get_jacobian);
     885             : 
     886             :   // Log residual and jacobian and combined performance separately
     887             : #ifdef LIBMESH_ENABLE_PERFORMANCE_LOGGING
     888             :   const char * log_name;
     889       11800 :   if (get_residual && get_jacobian)
     890        1218 :     log_name = "assembly()";
     891        9364 :   else if (get_residual)
     892        4030 :     log_name = "assembly(get_residual)";
     893             :   else
     894         652 :     log_name = "assembly(get_jacobian)";
     895             : 
     896       11800 :   LOG_SCOPE(log_name, "FEMSystem");
     897             : #endif
     898             : 
     899       11800 :   const MeshBase & mesh = this->get_mesh();
     900             : 
     901      204784 :   if (print_solution_norms)
     902             :     {
     903           0 :       this->solution->close();
     904             : 
     905           0 :       std::streamsize old_precision = libMesh::out.precision();
     906           0 :       libMesh::out.precision(16);
     907           0 :       libMesh::out << "|U| = "
     908           0 :                    << this->solution->l1_norm()
     909           0 :                    << std::endl;
     910           0 :       libMesh::out.precision(old_precision);
     911             :     }
     912      204784 :   if (print_solutions)
     913             :     {
     914           0 :       std::streamsize old_precision = libMesh::out.precision();
     915           0 :       libMesh::out.precision(16);
     916           0 :       libMesh::out << "U = [" << *(this->solution)
     917           0 :                    << "];" << std::endl;
     918           0 :       libMesh::out.precision(old_precision);
     919             :     }
     920             : 
     921             :   // Is this definitely necessary? [RHS]
     922             :   // Yes. [RHS 2012]
     923      204784 :   if (get_jacobian)
     924       63332 :     matrix->zero();
     925      204784 :   if (get_residual)
     926      183460 :     rhs->zero();
     927             : 
     928             :   // Stupid C++ lets you set *Real* verify_analytic_jacobians = true!
     929      204784 :   if (verify_analytic_jacobians > 0.5)
     930             :     {
     931           0 :       libMesh::err << "WARNING!  verify_analytic_jacobians was set "
     932           0 :                    << "to absurdly large value of "
     933           0 :                    << verify_analytic_jacobians << std::endl;
     934           0 :       libMesh::err << "Resetting to 1e-6!" << std::endl;
     935           0 :       verify_analytic_jacobians = 1e-6;
     936             :     }
     937             : 
     938             :   // In time-dependent problems, the nonlinear function we're trying
     939             :   // to solve at each timestep may depend on the particular solver
     940             :   // we're using
     941        5900 :   libmesh_assert(time_solver.get());
     942             : 
     943             :   // Build the residual and jacobian contributions on every active
     944             :   // mesh element on this processor
     945             :   Threads::parallel_for
     946      415468 :     (elem_range.reset(mesh.active_local_elements_begin(),
     947      409568 :                       mesh.active_local_elements_end()),
     948      403668 :      AssemblyContributions(*this, get_residual, get_jacobian,
     949             :                            apply_heterogeneous_constraints,
     950             :                            apply_no_constraints));
     951             : 
     952             :   // Check and see if we have SCALAR variables
     953        5900 :   bool have_scalar = false;
     954      453098 :   for (auto i : make_range(this->n_variable_groups()))
     955             :     {
     956      248314 :       if (this->variable_group(i).type().family == SCALAR)
     957             :         {
     958           0 :           have_scalar = true;
     959           0 :           break;
     960             :         }
     961             :     }
     962             : 
     963             :   // SCALAR dofs are stored on the last processor, so we'll evaluate
     964             :   // their equation terms there and only if we have a SCALAR variable
     965      210684 :   if (this->processor_id() == (this->n_processors()-1) && have_scalar)
     966             :     {
     967           0 :       std::unique_ptr<DiffContext> con = this->build_context();
     968           0 :       FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
     969           0 :       this->init_context(_femcontext);
     970           0 :       _femcontext.pre_fe_reinit(*this, nullptr);
     971             : 
     972             :       bool jacobian_computed =
     973           0 :         this->time_solver->nonlocal_residual(get_jacobian, _femcontext);
     974             : 
     975             :       // Nonlocal residuals are likely to be length 0, in which case we
     976             :       // don't need to do any more.  And we shouldn't try to do any
     977             :       // more; lots of DenseVector/DenseMatrix code assumes rank>0.
     978           0 :       if (_femcontext.get_elem_residual().size())
     979             :         {
     980             :           // Compute a numeric jacobian if we have to
     981           0 :           if (get_jacobian && !jacobian_computed)
     982             :             {
     983             :               // Make sure we didn't compute a jacobian and lie about it
     984           0 :               libmesh_assert_equal_to (_femcontext.get_elem_jacobian().l1_norm(), 0.0);
     985             :               // Logging of numerical jacobians is done separately
     986           0 :               this->numerical_nonlocal_jacobian(_femcontext);
     987             :             }
     988             : 
     989             :           // Compute a numeric jacobian if we're asked to verify the
     990             :           // analytic jacobian we got
     991           0 :           if (get_jacobian && jacobian_computed &&
     992           0 :               this->verify_analytic_jacobians != 0.0)
     993             :             {
     994           0 :               DenseMatrix<Number> analytic_jacobian(_femcontext.get_elem_jacobian());
     995             : 
     996           0 :               _femcontext.get_elem_jacobian().zero();
     997             :               // Logging of numerical jacobians is done separately
     998           0 :               this->numerical_nonlocal_jacobian(_femcontext);
     999             : 
    1000           0 :               Real analytic_norm = analytic_jacobian.l1_norm();
    1001           0 :               Real numerical_norm = _femcontext.get_elem_jacobian().l1_norm();
    1002             : 
    1003             :               // If we can continue, we'll probably prefer the analytic jacobian
    1004           0 :               analytic_jacobian.swap(_femcontext.get_elem_jacobian());
    1005             : 
    1006             :               // The matrix "analytic_jacobian" will now hold the error matrix
    1007           0 :               analytic_jacobian.add(-1.0, _femcontext.get_elem_jacobian());
    1008           0 :               Real error_norm = analytic_jacobian.l1_norm();
    1009             : 
    1010           0 :               Real relative_error = error_norm /
    1011           0 :                 std::max(analytic_norm, numerical_norm);
    1012             : 
    1013           0 :               if (relative_error > this->verify_analytic_jacobians)
    1014             :                 {
    1015           0 :                   libMesh::err << "Relative error " << relative_error
    1016           0 :                                << " detected in analytic jacobian on nonlocal dofs!"
    1017           0 :                                << std::endl;
    1018             : 
    1019           0 :                   std::streamsize old_precision = libMesh::out.precision();
    1020           0 :                   libMesh::out.precision(16);
    1021           0 :                   libMesh::out << "J_analytic nonlocal = "
    1022           0 :                                << _femcontext.get_elem_jacobian() << std::endl;
    1023           0 :                   analytic_jacobian.add(1.0, _femcontext.get_elem_jacobian());
    1024           0 :                   libMesh::out << "J_numeric nonlocal = "
    1025           0 :                                << analytic_jacobian << std::endl;
    1026             : 
    1027           0 :                   libMesh::out.precision(old_precision);
    1028             : 
    1029           0 :                   libmesh_error_msg("Relative error too large, exiting!");
    1030             :                 }
    1031           0 :             }
    1032             : 
    1033             :           add_element_system
    1034           0 :             (*this, get_residual, get_jacobian,
    1035             :              apply_heterogeneous_constraints, apply_no_constraints, _femcontext);
    1036             :         }
    1037           0 :     }
    1038             : 
    1039      204784 :   if (get_residual && (print_residual_norms || print_residuals))
    1040           0 :     this->rhs->close();
    1041      184764 :   if (get_residual && print_residual_norms)
    1042             :     {
    1043           0 :       std::streamsize old_precision = libMesh::out.precision();
    1044           0 :       libMesh::out.precision(16);
    1045           0 :       libMesh::out << "|F| = " << this->rhs->l1_norm() << std::endl;
    1046           0 :       libMesh::out.precision(old_precision);
    1047             :     }
    1048      184764 :   if (get_residual && print_residuals)
    1049             :     {
    1050           0 :       std::streamsize old_precision = libMesh::out.precision();
    1051           0 :       libMesh::out.precision(16);
    1052           0 :       libMesh::out << "F = [" << *(this->rhs) << "];" << std::endl;
    1053           0 :       libMesh::out.precision(old_precision);
    1054             :     }
    1055             : 
    1056      204784 :   if (get_jacobian && (print_jacobian_norms || print_jacobians))
    1057           0 :     this->matrix->close();
    1058       71392 :   if (get_jacobian && print_jacobian_norms)
    1059             :     {
    1060           0 :       std::streamsize old_precision = libMesh::out.precision();
    1061           0 :       libMesh::out.precision(16);
    1062           0 :       libMesh::out << "|J| = " << this->matrix->l1_norm() << std::endl;
    1063           0 :       libMesh::out.precision(old_precision);
    1064             :     }
    1065       71392 :   if (get_jacobian && print_jacobians)
    1066             :     {
    1067           0 :       std::streamsize old_precision = libMesh::out.precision();
    1068           0 :       libMesh::out.precision(16);
    1069           0 :       libMesh::out << "J = [" << *(this->matrix) << "];" << std::endl;
    1070           0 :       libMesh::out.precision(old_precision);
    1071             :     }
    1072      204784 : }
    1073             : 
    1074             : 
    1075             : 
    1076       25037 : void FEMSystem::solve()
    1077             : {
    1078             :   // We are solving the primal problem
    1079       25037 :   Parent::solve();
    1080             : 
    1081             :   // On a moving mesh we want the mesh to reflect the new solution
    1082       25037 :   this->mesh_position_set();
    1083       25037 : }
    1084             : 
    1085             : 
    1086             : 
    1087       27312 : void FEMSystem::mesh_position_set()
    1088             : {
    1089             :   // If we don't need to move the mesh, we're done
    1090       27312 :   if (_mesh_sys != this)
    1091       24712 :     return;
    1092             : 
    1093           0 :   MeshBase & mesh = this->get_mesh();
    1094             : 
    1095        2600 :   std::unique_ptr<DiffContext> con = this->build_context();
    1096           0 :   FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
    1097        2600 :   this->init_context(_femcontext);
    1098             : 
    1099             :   // Move every mesh element we can
    1100       51280 :   for (const auto & elem : mesh.active_local_element_ptr_range())
    1101             :     {
    1102             :       // We need the algebraic data
    1103       23040 :       _femcontext.pre_fe_reinit(*this, elem);
    1104             :       // And when asserts are on, we also need the FE so
    1105             :       // we can assert that the mesh data is of the right type.
    1106             : #ifndef NDEBUG
    1107           0 :       _femcontext.elem_fe_reinit();
    1108             : #endif
    1109             : 
    1110             :       // This code won't handle moving subactive elements
    1111           0 :       libmesh_assert(!_femcontext.get_elem().has_children());
    1112             : 
    1113           0 :       _femcontext.elem_position_set(1.);
    1114        2600 :     }
    1115             : 
    1116             :   // We've now got positions set on all local nodes (and some
    1117             :   // semilocal nodes); let's request positions for non-local nodes
    1118             :   // from their processors.
    1119             : 
    1120        2600 :   SyncNodalPositions sync_object(mesh);
    1121             :   Parallel::sync_dofobject_data_by_id
    1122        5200 :     (this->comm(), mesh.nodes_begin(), mesh.nodes_end(), sync_object);
    1123        2600 : }
    1124             : 
    1125             : 
    1126             : 
    1127        1976 : void FEMSystem::postprocess ()
    1128             : {
    1129         102 :   LOG_SCOPE("postprocess()", "FEMSystem");
    1130             : 
    1131         204 :   const MeshBase & mesh = this->get_mesh();
    1132             : 
    1133        1976 :   this->update();
    1134             : 
    1135             :   // Get the time solver object associated with the system, and tell it that
    1136             :   // we are not solving the adjoint problem
    1137         102 :   this->get_time_solver().set_is_adjoint(false);
    1138             : 
    1139             :   // Loop over every active mesh element on this processor
    1140        4054 :   Threads::parallel_for (elem_range.reset(mesh.active_local_elements_begin(),
    1141        2282 :                                           mesh.active_local_elements_end()),
    1142        2078 :                          PostprocessContributions(*this));
    1143        1976 : }
    1144             : 
    1145             : 
    1146             : 
    1147       39155 : void FEMSystem::assemble_qoi (const QoISet & qoi_indices)
    1148             : {
    1149        2236 :   LOG_SCOPE("assemble_qoi()", "FEMSystem");
    1150             : 
    1151        2236 :   const MeshBase & mesh = this->get_mesh();
    1152             : 
    1153       39155 :   this->update();
    1154             : 
    1155        1118 :   const unsigned int Nq = this->n_qois();
    1156             : 
    1157             :   // the quantity of interest is assumed to be a sum of element and
    1158             :   // side terms
    1159       95110 :   for (unsigned int i=0; i != Nq; ++i)
    1160       55955 :     if (qoi_indices.has_index(i))
    1161       55955 :       this->set_qoi(i, 0);
    1162             : 
    1163             :   // Create a non-temporary qoi_contributions object, so we can query
    1164             :   // its results after the reduction
    1165       79428 :   QoIContributions qoi_contributions(*this, *(this->get_qoi()), qoi_indices);
    1166             : 
    1167             :   // Loop over every active mesh element on this processor
    1168       79428 :   Threads::parallel_reduce(elem_range.reset(mesh.active_local_elements_begin(),
    1169       42509 :                                             mesh.active_local_elements_end()),
    1170             :                            qoi_contributions);
    1171             : 
    1172       39155 :   std::vector<Number> global_qoi = this->get_qoi_values();
    1173       39155 :   this->get_qoi()->parallel_op( this->comm(), global_qoi, qoi_contributions.qoi, qoi_indices );
    1174       77192 :   this->set_qoi(std::move(global_qoi));
    1175       39155 : }
    1176             : 
    1177             : 
    1178             : 
    1179       14654 : void FEMSystem::assemble_qoi_derivative (const QoISet & qoi_indices,
    1180             :                                          bool include_liftfunc,
    1181             :                                          bool apply_constraints)
    1182             : {
    1183         928 :   LOG_SCOPE("assemble_qoi_derivative()", "FEMSystem");
    1184             : 
    1185         928 :   const MeshBase & mesh = this->get_mesh();
    1186             : 
    1187       14654 :   this->update();
    1188             : 
    1189             :   // The quantity of interest derivative assembly accumulates on
    1190             :   // initially zero vectors
    1191       39471 :   for (auto i : make_range(this->n_qois()))
    1192       24817 :     if (qoi_indices.has_index(i))
    1193       24817 :       this->add_adjoint_rhs(i).zero();
    1194             : 
    1195             :   // Loop over every active mesh element on this processor
    1196       29772 :   Threads::parallel_for (elem_range.reset(mesh.active_local_elements_begin(),
    1197       16046 :                                           mesh.active_local_elements_end()),
    1198       14190 :                          QoIDerivativeContributions(*this, qoi_indices,
    1199       14654 :                                                     *(this->get_qoi()),
    1200             :                                                     include_liftfunc,
    1201             :                                                     apply_constraints));
    1202             : 
    1203       39471 :   for (auto i : make_range(this->n_qois()))
    1204       24817 :     if (qoi_indices.has_index(i))
    1205       24817 :       this->get_qoi()->finalize_derivative(this->get_adjoint_rhs(i),i);
    1206       14654 : }
    1207             : 
    1208             : 
    1209             : 
    1210      278138 : void FEMSystem::numerical_jacobian (TimeSolverResPtr res,
    1211             :                                     FEMContext & context) const
    1212             : {
    1213             :   // Logging is done by numerical_elem_jacobian
    1214             :   // or numerical_side_jacobian
    1215             : 
    1216       54632 :   DenseVector<Number> original_residual(context.get_elem_residual());
    1217       54632 :   DenseVector<Number> backwards_residual(context.get_elem_residual());
    1218      332770 :   DenseMatrix<Number> numeric_jacobian(context.get_elem_jacobian());
    1219             : #ifdef DEBUG
    1220       54632 :   DenseMatrix<Number> old_jacobian(context.get_elem_jacobian());
    1221             : #endif
    1222             : 
    1223       27316 :   Real numerical_point_h = 0.;
    1224      278138 :   if (_mesh_sys == this)
    1225           0 :     numerical_point_h = numerical_jacobian_h * context.get_elem().hmin();
    1226             : 
    1227             :   const unsigned int n_dofs =
    1228       54632 :     cast_int<unsigned int>(context.get_dof_indices().size());
    1229             : 
    1230      608548 :   for (auto v : make_range(context.n_vars()))
    1231             :     {
    1232      298738 :       const Real my_h = this->numerical_jacobian_h_for_var(v);
    1233             : 
    1234       31672 :       unsigned int j_offset = libMesh::invalid_uint;
    1235             : 
    1236      330410 :       if (!context.get_dof_indices(v).empty())
    1237             :         {
    1238     4320260 :           for (auto i : make_range(n_dofs))
    1239     4729306 :             if (context.get_dof_indices()[i] ==
    1240      369728 :                 context.get_dof_indices(v)[0])
    1241       31672 :               j_offset = i;
    1242             : 
    1243       31672 :           libmesh_assert_not_equal_to(j_offset, libMesh::invalid_uint);
    1244             :         }
    1245             : 
    1246     3174308 :       for (auto j : make_range(context.get_dof_indices(v).size()))
    1247             :         {
    1248     2843898 :           const unsigned int total_j = j + j_offset;
    1249             : 
    1250             :           // Take the "minus" side of a central differenced first derivative
    1251     2843898 :           Number original_solution = context.get_elem_solution(v)(j);
    1252     2597836 :           context.get_elem_solution(v)(j) -= my_h;
    1253             : 
    1254             :           // Make sure to catch any moving mesh terms
    1255      274232 :           Real * coord = nullptr;
    1256     2843898 :           if (_mesh_sys == this)
    1257             :             {
    1258           0 :               if (_mesh_x_var == v)
    1259           0 :                 coord = &(context.get_elem().point(j)(0));
    1260           0 :               else if (_mesh_y_var == v)
    1261           0 :                 coord = &(context.get_elem().point(j)(1));
    1262           0 :               else if (_mesh_z_var == v)
    1263           0 :                 coord = &(context.get_elem().point(j)(2));
    1264             :             }
    1265      274232 :           if (coord)
    1266             :             {
    1267             :               // We have enough information to scale the perturbations
    1268             :               // here appropriately
    1269           0 :               context.get_elem_solution(v)(j) = original_solution - numerical_point_h;
    1270           0 :               *coord = libmesh_real(context.get_elem_solution(v)(j));
    1271             :             }
    1272             : 
    1273      274232 :           context.get_elem_residual().zero();
    1274     2843898 :           ((*time_solver).*(res))(false, context);
    1275             : #ifdef DEBUG
    1276      274232 :           libmesh_assert_equal_to (old_jacobian, context.get_elem_jacobian());
    1277             : #endif
    1278      274232 :           backwards_residual = context.get_elem_residual();
    1279             : 
    1280             :           // Take the "plus" side of a central differenced first derivative
    1281     2843898 :           context.get_elem_solution(v)(j) = original_solution + my_h;
    1282     2843898 :           if (coord)
    1283             :             {
    1284           0 :               context.get_elem_solution()(j) = original_solution + numerical_point_h;
    1285           0 :               *coord = libmesh_real(context.get_elem_solution(v)(j));
    1286             :             }
    1287      274232 :           context.get_elem_residual().zero();
    1288     2843898 :           ((*time_solver).*(res))(false, context);
    1289             : #ifdef DEBUG
    1290      274232 :           libmesh_assert_equal_to (old_jacobian, context.get_elem_jacobian());
    1291             : #endif
    1292             : 
    1293     2843898 :           context.get_elem_solution(v)(j) = original_solution;
    1294     2843898 :           if (coord)
    1295             :             {
    1296           0 :               *coord = libmesh_real(context.get_elem_solution(v)(j));
    1297           0 :               for (auto i : make_range(n_dofs))
    1298             :                 {
    1299           0 :                   numeric_jacobian(i,total_j) =
    1300           0 :                     (context.get_elem_residual()(i) - backwards_residual(i)) /
    1301           0 :                     2. / numerical_point_h;
    1302             :                 }
    1303             :             }
    1304             :           else
    1305             :             {
    1306    37023780 :               for (auto i : make_range(n_dofs))
    1307             :                 {
    1308    34179882 :                   numeric_jacobian(i,total_j) =
    1309    31249924 :                     (context.get_elem_residual()(i) - backwards_residual(i)) /
    1310    31249924 :                     2. / my_h;
    1311             :                 }
    1312             :             }
    1313             :         }
    1314             :     }
    1315             : 
    1316       27316 :   context.get_elem_residual() = original_residual;
    1317      278138 :   context.get_elem_jacobian() = numeric_jacobian;
    1318      501644 : }
    1319             : 
    1320             : 
    1321             : 
    1322      228972 : void FEMSystem::numerical_elem_jacobian (FEMContext & context) const
    1323             : {
    1324       43392 :   LOG_SCOPE("numerical_elem_jacobian()", "FEMSystem");
    1325      228972 :   this->numerical_jacobian(&TimeSolver::element_residual, context);
    1326      228972 : }
    1327             : 
    1328             : 
    1329             : 
    1330       49166 : void FEMSystem::numerical_side_jacobian (FEMContext & context) const
    1331             : {
    1332       11240 :   LOG_SCOPE("numerical_side_jacobian()", "FEMSystem");
    1333       49166 :   this->numerical_jacobian(&TimeSolver::side_residual, context);
    1334       49166 : }
    1335             : 
    1336             : 
    1337             : 
    1338           0 : void FEMSystem::numerical_nonlocal_jacobian (FEMContext & context) const
    1339             : {
    1340           0 :   LOG_SCOPE("numerical_nonlocal_jacobian()", "FEMSystem");
    1341           0 :   this->numerical_jacobian(&TimeSolver::nonlocal_residual, context);
    1342           0 : }
    1343             : 
    1344             : 
    1345             : 
    1346      284324 : std::unique_ptr<DiffContext> FEMSystem::build_context ()
    1347             : {
    1348      298728 :   auto fc = std::make_unique<FEMContext>(*this);
    1349             : 
    1350      284324 :   DifferentiablePhysics * phys = this->get_physics();
    1351             : 
    1352       14404 :   libmesh_assert (phys);
    1353             : 
    1354             :   // If we are solving a moving mesh problem, tell that to the Context
    1355      284324 :   fc->set_mesh_system(phys->get_mesh_system());
    1356       28808 :   fc->set_mesh_x_var(phys->get_mesh_x_var());
    1357       28808 :   fc->set_mesh_y_var(phys->get_mesh_y_var());
    1358       28808 :   fc->set_mesh_z_var(phys->get_mesh_z_var());
    1359             : 
    1360      284324 :   fc->set_deltat_pointer( &deltat );
    1361             : 
    1362             :   // If we are solving the adjoint problem, tell that to the Context
    1363      298728 :   fc->is_adjoint() = this->get_time_solver().is_adjoint();
    1364             : 
    1365      298728 :   return fc;
    1366      255516 : }
    1367             : 
    1368             : 
    1369             : 
    1370      208445 : void FEMSystem::init_context(DiffContext & c)
    1371             : {
    1372             :   // Parent::init_context(c);  // may be a good idea in derived classes
    1373             : 
    1374             :   // Although we do this in DiffSystem::build_context() and
    1375             :   // FEMSystem::build_context() as well, we do it here just to be
    1376             :   // extra sure that the deltat pointer gets set.  Since the
    1377             :   // intended behavior is for classes derived from FEMSystem to
    1378             :   // call Parent::init_context() in their own init_context()
    1379             :   // overloads, we can ensure that those classes get the correct
    1380             :   // deltat pointers even if they have different build_context()
    1381             :   // overloads.
    1382      208445 :   c.set_deltat_pointer ( &deltat );
    1383             : 
    1384       10492 :   FEMContext & context = cast_ref<FEMContext &>(c);
    1385             : 
    1386             :   // Make sure we're prepared to do mass integration
    1387      440211 :   for (auto var : make_range(this->n_vars()))
    1388      451918 :     if (this->get_physics()->is_time_evolving(var))
    1389             :       {
    1390             :         // Request shape functions based on FEType
    1391      192107 :         switch( FEInterface::field_type( this->variable_type(var) ) )
    1392             :           {
    1393      192107 :           case( TYPE_SCALAR ):
    1394             :             {
    1395        9596 :               FEBase * elem_fe = nullptr;
    1396      385369 :               for (auto dim : context.elem_dimensions())
    1397             :                 {
    1398        9656 :                   context.get_element_fe(var, elem_fe, dim);
    1399       19638 :                   elem_fe->get_JxW();
    1400        9656 :                   elem_fe->get_phi();
    1401           0 :                 }
    1402             :             }
    1403        9596 :             break;
    1404           0 :           case( TYPE_VECTOR ):
    1405             :             {
    1406           0 :               FEGenericBase<RealGradient> * elem_fe = nullptr;
    1407           0 :               for (auto dim : context.elem_dimensions())
    1408             :                 {
    1409           0 :                   context.get_element_fe(var, elem_fe, dim);
    1410           0 :                   elem_fe->get_JxW();
    1411           0 :                   elem_fe->get_phi();
    1412           0 :                 }
    1413             :             }
    1414           0 :             break;
    1415           0 :           default:
    1416           0 :             libmesh_error_msg("Unrecognized field type!");
    1417             :           }
    1418             :       }
    1419      208445 : }
    1420             : 
    1421             : 
    1422             : 
    1423          65 : void FEMSystem::mesh_position_get()
    1424             : {
    1425             :   // This function makes no sense unless we've already picked out some
    1426             :   // variable(s) to reflect mesh position coordinates
    1427          65 :   libmesh_error_msg_if(!_mesh_sys, "_mesh_sys was nullptr!");
    1428             : 
    1429             :   // We currently assume mesh variables are in our own system
    1430          65 :   if (_mesh_sys != this)
    1431           0 :     libmesh_not_implemented();
    1432             : 
    1433             :   // Loop over every active mesh element on this processor
    1434           0 :   const MeshBase & mesh = this->get_mesh();
    1435             : 
    1436          65 :   std::unique_ptr<DiffContext> con = this->build_context();
    1437           0 :   FEMContext & _femcontext = cast_ref<FEMContext &>(*con);
    1438          65 :   this->init_context(_femcontext);
    1439             : 
    1440             :   // Get the solution's mesh variables from every element
    1441        1282 :   for (const auto & elem : mesh.active_local_element_ptr_range())
    1442             :     {
    1443         576 :       _femcontext.pre_fe_reinit(*this, elem);
    1444             : 
    1445         576 :       _femcontext.elem_position_get();
    1446             : 
    1447         576 :       if (_mesh_x_var != libMesh::invalid_uint)
    1448         576 :         this->solution->insert(_femcontext.get_elem_solution(_mesh_x_var),
    1449           0 :                                _femcontext.get_dof_indices(_mesh_x_var) );
    1450         576 :       if (_mesh_y_var != libMesh::invalid_uint)
    1451         576 :         this->solution->insert(_femcontext.get_elem_solution(_mesh_y_var),
    1452           0 :                                _femcontext.get_dof_indices(_mesh_y_var));
    1453         576 :       if (_mesh_z_var != libMesh::invalid_uint)
    1454         576 :         this->solution->insert(_femcontext.get_elem_solution(_mesh_z_var),
    1455           0 :                                _femcontext.get_dof_indices(_mesh_z_var));
    1456          65 :     }
    1457             : 
    1458          65 :   this->solution->close();
    1459             : 
    1460             :   // And make sure the current_local_solution is up to date too
    1461          65 :   this->System::update();
    1462          65 : }
    1463             : 
    1464             : } // namespace libMesh

Generated by: LCOV version 1.14