LCOV - code coverage report
Current view: top level - src/base - dof_map_constraints.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4546 (ebe2b5) with base a20bc7 Lines: 1857 2410 77.1 %
Date: 2026-09-11 19:50:22 Functions: 122 156 78.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2026 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             : // Local includes
      19             : #include "libmesh/dof_map.h"
      20             : 
      21             : // libMesh includes
      22             : #include "libmesh/boundary_info.h" // needed for dirichlet constraints
      23             : #include "libmesh/dense_matrix.h"
      24             : #include "libmesh/dense_vector.h"
      25             : #include "libmesh/dirichlet_boundaries.h"
      26             : #include "libmesh/elem.h"
      27             : #include "libmesh/elem_range.h"
      28             : #include "libmesh/fe_base.h"
      29             : #include "libmesh/fe_interface.h"
      30             : #include "libmesh/fe_type.h"
      31             : #include "libmesh/function_base.h"
      32             : #include "libmesh/int_range.h"
      33             : #include "libmesh/libmesh_logging.h"
      34             : #include "libmesh/linear_solver.h" // for spline Dirichlet projection solves
      35             : #include "libmesh/mesh_base.h"
      36             : #include "libmesh/null_output_iterator.h"
      37             : #include "libmesh/mesh_tools.h" // for libmesh_assert_valid_boundary_ids()
      38             : #include "libmesh/nonlinear_implicit_system.h"
      39             : #include "libmesh/numeric_vector.h" // for enforce_constraints_exactly()
      40             : #include "libmesh/parallel_algebra.h"
      41             : #include "libmesh/parallel_elem.h"
      42             : #include "libmesh/parallel_node.h"
      43             : #include "libmesh/periodic_boundaries.h"
      44             : #include "libmesh/periodic_boundary.h"
      45             : #include "libmesh/periodic_boundary_base.h"
      46             : #include "libmesh/point_locator_base.h"
      47             : #include "libmesh/quadrature.h" // for dirichlet constraints
      48             : #include "libmesh/raw_accessor.h"
      49             : #include "libmesh/sparse_matrix.h" // needed to constrain adjoint rhs
      50             : #include "libmesh/static_condensation_dof_map.h"
      51             : #include "libmesh/system.h" // needed by enforce_constraints_exactly()
      52             : #include "libmesh/tensor_tools.h"
      53             : #include "libmesh/threads.h"
      54             : #include "libmesh/enum_to_string.h"
      55             : #include "libmesh/coupling_matrix.h"
      56             : 
      57             : // TIMPI includes
      58             : #include "timpi/parallel_implementation.h"
      59             : #include "timpi/parallel_sync.h"
      60             : 
      61             : // C++ Includes
      62             : #include <set>
      63             : #include <algorithm> // for std::count, std::fill
      64             : #include <sstream>
      65             : #include <cstdlib> // *must* precede <cmath> for proper std:abs() on PGI, Sun Studio CC
      66             : #include <cmath>
      67             : #include <memory>
      68             : #include <numeric>
      69             : #include <unordered_set>
      70             : 
      71             : // Anonymous namespace to hold helper classes
      72             : namespace {
      73             : 
      74             : using namespace libMesh;
      75             : 
      76             : class ComputeConstraints
      77             : {
      78             : public:
      79        8426 :   ComputeConstraints (DofConstraints & constraints,
      80             :                       DofMap & dof_map,
      81             : #ifdef LIBMESH_ENABLE_PERIODIC
      82             :                       PeriodicBoundaries & periodic_boundaries,
      83             : #endif
      84             :                       const MeshBase & mesh,
      85      294738 :                       const unsigned int variable_number) :
      86      277886 :     _constraints(constraints),
      87      277886 :     _dof_map(dof_map),
      88             : #ifdef LIBMESH_ENABLE_PERIODIC
      89      277886 :     _periodic_boundaries(periodic_boundaries),
      90             : #endif
      91      277886 :     _mesh(mesh),
      92      294738 :     _variable_number(variable_number)
      93        8426 :   {}
      94             : 
      95      294941 :   void operator()(const ConstElemRange & range) const
      96             :   {
      97      294941 :     const Variable & var_description = _dof_map.variable(_variable_number);
      98             : 
      99             : #ifdef LIBMESH_ENABLE_PERIODIC
     100      286448 :     std::unique_ptr<PointLocatorBase> point_locator;
     101             :     const bool have_periodic_boundaries =
     102      294941 :       !_periodic_boundaries.empty();
     103      294941 :     if (have_periodic_boundaries && !range.empty())
     104        1414 :       point_locator = _mesh.sub_point_locator();
     105             : #endif
     106             : 
     107     8140148 :     for (const auto & elem : range)
     108     7845207 :       if (var_description.active_on_subdomain(elem->subdomain_id()))
     109             :         {
     110             : #ifdef LIBMESH_ENABLE_AMR
     111     8524775 :           FEInterface::compute_constraints (_constraints,
     112             :                                             _dof_map,
     113     7830679 :                                             _variable_number,
     114             :                                             elem);
     115             : #endif
     116             : #ifdef LIBMESH_ENABLE_PERIODIC
     117             :           // FIXME: periodic constraints won't work on a non-serial
     118             :           // mesh unless it's kept ghost elements from opposing
     119             :           // boundaries!
     120     7830679 :           if (have_periodic_boundaries)
     121      532492 :             FEInterface::compute_periodic_constraints (_constraints,
     122             :                                                        _dof_map,
     123      264164 :                                                        _periodic_boundaries,
     124             :                                                        _mesh,
     125       67082 :                                                        point_locator.get(),
     126      264164 :                                                        _variable_number,
     127             :                                                        elem);
     128             : #endif
     129             :         }
     130      294941 :   }
     131             : 
     132             : private:
     133             :   DofConstraints & _constraints;
     134             :   DofMap & _dof_map;
     135             : #ifdef LIBMESH_ENABLE_PERIODIC
     136             :   PeriodicBoundaries & _periodic_boundaries;
     137             : #endif
     138             :   const MeshBase & _mesh;
     139             :   const unsigned int _variable_number;
     140             : };
     141             : 
     142             : 
     143             : 
     144             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
     145             : class ComputeNodeConstraints
     146             : {
     147             : public:
     148        7468 :   ComputeNodeConstraints (NodeConstraints & node_constraints,
     149             : #ifdef LIBMESH_ENABLE_PERIODIC
     150             :                           PeriodicBoundaries & periodic_boundaries,
     151             : #endif
     152       14936 :                           const MeshBase & mesh) :
     153             :     _node_constraints(node_constraints),
     154             : #ifdef LIBMESH_ENABLE_PERIODIC
     155             :     _periodic_boundaries(periodic_boundaries),
     156             : #endif
     157       14936 :     _mesh(mesh)
     158        7468 :   {}
     159             : 
     160       15026 :   void operator()(const ConstElemRange & range) const
     161             :   {
     162             : #ifdef LIBMESH_ENABLE_PERIODIC
     163        7513 :     std::unique_ptr<PointLocatorBase> point_locator;
     164       15026 :     bool have_periodic_boundaries = !_periodic_boundaries.empty();
     165       15026 :     if (have_periodic_boundaries && !range.empty())
     166         244 :       point_locator = _mesh.sub_point_locator();
     167             : #endif
     168             : 
     169     1045106 :     for (const auto & elem : range)
     170             :       {
     171             : #ifdef LIBMESH_ENABLE_AMR
     172     1030080 :         FEBase::compute_node_constraints (_node_constraints, elem);
     173             : #endif
     174             : #ifdef LIBMESH_ENABLE_PERIODIC
     175             :         // FIXME: periodic constraints won't work on a non-serial
     176             :         // mesh unless it's kept ghost elements from opposing
     177             :         // boundaries!
     178     1030080 :         if (have_periodic_boundaries)
     179      324310 :           FEBase::compute_periodic_node_constraints (_node_constraints,
     180      129724 :                                                      _periodic_boundaries,
     181             :                                                      _mesh,
     182       64862 :                                                      point_locator.get(),
     183             :                                                      elem);
     184             : #endif
     185             :       }
     186       15026 :   }
     187             : 
     188             : private:
     189             :   NodeConstraints & _node_constraints;
     190             : #ifdef LIBMESH_ENABLE_PERIODIC
     191             :   PeriodicBoundaries & _periodic_boundaries;
     192             : #endif
     193             :   const MeshBase & _mesh;
     194             : };
     195             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
     196             : 
     197             : 
     198             : #ifdef LIBMESH_ENABLE_DIRICHLET
     199             : 
     200             : /**
     201             :  * This functor class hierarchy disposes of a computed constraint.  Writing it
     202             :  * into a DofMap is one disposition; collecting the prescribed values without
     203             :  * constraining anything is another, so the base holds no DofMap of its own.
     204             :  */
     205             : class AddConstraint
     206             : {
     207             : public:
     208         570 :   virtual ~AddConstraint() = default;
     209             : 
     210             :   virtual void operator()(dof_id_type dof_number,
     211             :                           const DofConstraintRow & constraint_row,
     212             :                           const Number constraint_rhs) const = 0;
     213             : };
     214             : 
     215       16971 : class AddPrimalConstraint : public AddConstraint
     216             : {
     217             : private:
     218             :   DofMap                  & dof_map;
     219             : 
     220             : public:
     221       17987 :   AddPrimalConstraint(DofMap & dof_map_in) : dof_map(dof_map_in) {}
     222             : 
     223      646730 :   virtual void operator()(dof_id_type dof_number,
     224             :                           const DofConstraintRow & constraint_row,
     225             :                           const Number constraint_rhs) const
     226             :   {
     227      646730 :     if (!dof_map.is_constrained_dof(dof_number))
     228      318122 :       dof_map.add_constraint_row (dof_number, constraint_row,
     229             :                                   constraint_rhs, true);
     230      646730 :   }
     231             : };
     232             : 
     233             : class AddAdjointConstraint : public AddConstraint
     234             : {
     235             : private:
     236             :   DofMap                  & dof_map;
     237             :   const unsigned int qoi_index;
     238             : 
     239             : public:
     240          62 :   AddAdjointConstraint(DofMap & dof_map_in, unsigned int qoi_index_in)
     241        2185 :     : dof_map(dof_map_in), qoi_index(qoi_index_in) {}
     242             : 
     243       14304 :   virtual void operator()(dof_id_type dof_number,
     244             :                           const DofConstraintRow & constraint_row,
     245             :                           const Number constraint_rhs) const
     246             :   {
     247       14304 :     dof_map.add_adjoint_constraint_row
     248       14304 :       (qoi_index, dof_number, constraint_row, constraint_rhs,
     249             :        true);
     250       14304 :   }
     251             : };
     252             : 
     253             : /**
     254             :  * Records the prescribed value of each degree of freedom a Dirichlet projection
     255             :  * determines, leaving the DofMap untouched.  A degree of freedom the DofMap
     256             :  * already constrains is skipped, as AddPrimalConstraint skips it, so that the
     257             :  * values collected are those the constraint path would have added.
     258             :  */
     259           0 : class CollectDirichletValues : public AddConstraint
     260             : {
     261             : private:
     262             :   const DofMap            & dof_map;
     263             :   DofConstraintValueMap   & values;
     264             : 
     265             : public:
     266           0 :   CollectDirichletValues(const DofMap & dof_map_in,
     267             :                          DofConstraintValueMap & values_in)
     268           0 :     : dof_map(dof_map_in), values(values_in) {}
     269             : 
     270           0 :   virtual void operator()(dof_id_type dof_number,
     271             :                           const DofConstraintRow & /*constraint_row*/,
     272             :                           const Number constraint_rhs) const
     273             :   {
     274           0 :     if (!dof_map.is_constrained_dof(dof_number))
     275           0 :       values[dof_number] = constraint_rhs;
     276           0 :   }
     277             : };
     278             : 
     279             : 
     280             : 
     281             : /**
     282             :  * This class implements turning an arbitrary
     283             :  * boundary function into Dirichlet constraints.  It
     284             :  * may be executed in parallel on multiple threads.
     285             :  */
     286             : class ConstrainDirichlet
     287             : {
     288             : private:
     289             :   const DofMap            & dof_map;
     290             :   const MeshBase          & mesh;
     291             :   const Real               time;
     292             :   const DirichletBoundaries & dirichlets;
     293             : 
     294             :   const AddConstraint     & add_fn;
     295             : 
     296      661886 :   static Number f_component (FunctionBase<Number> * f,
     297             :                              FEMFunctionBase<Number> * f_fem,
     298             :                              const FEMContext * c,
     299             :                              unsigned int i,
     300             :                              const Point & p,
     301             :                              Real time)
     302             :   {
     303      661886 :     if (f_fem)
     304             :       {
     305           0 :         if (c)
     306           0 :           return f_fem->component(*c, i, p, time);
     307             :         else
     308           0 :           return std::numeric_limits<Real>::quiet_NaN();
     309             :       }
     310      661886 :     return f->component(i, p, time);
     311             :   }
     312             : 
     313       30960 :   static Gradient g_component (FunctionBase<Gradient> * g,
     314             :                                FEMFunctionBase<Gradient> * g_fem,
     315             :                                const FEMContext * c,
     316             :                                unsigned int i,
     317             :                                const Point & p,
     318             :                                Real time)
     319             :   {
     320       30960 :     if (g_fem)
     321             :       {
     322           0 :         if (c)
     323           0 :           return g_fem->component(*c, i, p, time);
     324             :         else
     325           0 :           return std::numeric_limits<Number>::quiet_NaN();
     326             :       }
     327       30960 :     return g->component(i, p, time);
     328             :   }
     329             : 
     330             : 
     331             : 
     332             :   /**
     333             :    * Handy struct to pass around BoundaryInfo for a single Elem.  Must
     334             :    * be created with a reference to a BoundaryInfo object and a map
     335             :    * from boundary_id -> set<DirichletBoundary *> objects involving
     336             :    * that id.
     337             :    */
     338             :   struct SingleElemBoundaryInfo
     339             :   {
     340       19646 :     SingleElemBoundaryInfo(const BoundaryInfo & bi,
     341       20238 :                            const std::map<boundary_id_type, std::set<std::pair<unsigned int, DirichletBoundary *>>> & ordered_map_in) :
     342       19054 :       boundary_info(bi),
     343       19054 :       boundary_id_to_ordered_dirichlet_boundaries(ordered_map_in),
     344       19054 :       elem(nullptr),
     345       19054 :       n_sides(0),
     346       19054 :       n_edges(0),
     347       20238 :       n_nodes(0)
     348       19646 :     {}
     349             : 
     350             :     const BoundaryInfo & boundary_info;
     351             :     const std::map<boundary_id_type, std::set<std::pair<unsigned int, DirichletBoundary *>>> & boundary_id_to_ordered_dirichlet_boundaries;
     352             :     const Elem * elem;
     353             : 
     354             :     unsigned short n_sides;
     355             :     unsigned short n_edges;
     356             :     unsigned short n_nodes;
     357             : 
     358             :     // Mapping from DirichletBoundary objects which are active on this
     359             :     // element to sides/nodes/edges/shellfaces of this element which
     360             :     // they are active on.
     361             :     std::map<const DirichletBoundary *, std::vector<bool>> is_boundary_node_map;
     362             :     std::map<const DirichletBoundary *, std::vector<bool>> is_boundary_side_map;
     363             :     std::map<const DirichletBoundary *, std::vector<bool>> is_boundary_edge_map;
     364             :     std::map<const DirichletBoundary *, std::vector<bool>> is_boundary_shellface_map;
     365             : 
     366             :     std::map<const DirichletBoundary *, std::vector<bool>> is_boundary_nodeset_map;
     367             : 
     368             :     // The set of (dirichlet_id, DirichletBoundary) pairs which have at least one boundary
     369             :     // id related to this Elem.
     370             :     std::set<std::pair<unsigned int, DirichletBoundary *>> ordered_dbs;
     371             : 
     372             :     /**
     373             :      * Given a single Elem, fills the SingleElemBoundaryInfo struct with
     374             :      * required data.
     375             :      *
     376             :      * @return true if this Elem has _any_ boundary ids associated with
     377             :      * it, false otherwise.
     378             :      */
     379     1167979 :     bool reinit(const Elem * elem_in)
     380             :     {
     381     1167979 :       elem = elem_in;
     382             : 
     383     1167979 :       n_sides = elem->n_sides();
     384     1167979 :       n_edges = elem->n_edges();
     385     1167979 :       n_nodes = elem->n_nodes();
     386             : 
     387             :       // objects and node/side/edge/shellface ids.
     388      100215 :       is_boundary_node_map.clear();
     389      100215 :       is_boundary_side_map.clear();
     390      100215 :       is_boundary_edge_map.clear();
     391      100215 :       is_boundary_shellface_map.clear();
     392      100215 :       is_boundary_nodeset_map.clear();
     393             : 
     394             :       // Clear any DirichletBoundaries from the previous Elem
     395      100215 :       ordered_dbs.clear();
     396             : 
     397             :       // Update has_dirichlet_constraint below, and if it remains false then
     398             :       // we can skip this element since there are not constraints to impose.
     399      100215 :       bool has_dirichlet_constraint = false;
     400             : 
     401             :       // Container to catch boundary ids handed back for sides,
     402             :       // nodes, and edges in the loops below.
     403      100215 :       std::vector<boundary_id_type> ids_vec;
     404             : 
     405     6203134 :       for (unsigned char s = 0; s != n_sides; ++s)
     406             :         {
     407             :           // First see if this side has been requested
     408     5035155 :           boundary_info.boundary_ids (elem, s, ids_vec);
     409             : 
     410      431027 :           bool do_this_side = false;
     411     5286830 :           for (const auto & bc_id : ids_vec)
     412             :             {
     413      272947 :               if (auto it = boundary_id_to_ordered_dirichlet_boundaries.find(bc_id);
     414       21272 :                   it != boundary_id_to_ordered_dirichlet_boundaries.end())
     415             :                 {
     416       13074 :                   do_this_side = true;
     417             : 
     418             :                   // Associate every DirichletBoundary object that has this bc_id with the current Elem
     419      153377 :                   ordered_dbs.insert(it->second.begin(), it->second.end());
     420             : 
     421             :                   // Turn on the flag for the current side for each DirichletBoundary
     422      315047 :                   for (const auto & db_pair : it->second)
     423             :                     {
     424             :                       // Attempt to emplace an empty vector. If there
     425             :                       // is already an entry, the insertion will fail
     426             :                       // and we'll get an iterator back to the
     427             :                       // existing entry. Either way, we'll then set
     428             :                       // index s of that vector to "true".
     429      295738 :                       auto pr = is_boundary_side_map.emplace(db_pair.second, std::vector<bool>(n_sides, false));
     430       27602 :                       pr.first->second[s] = true;
     431             :                     }
     432             :                 }
     433             :             }
     434             : 
     435     5035155 :           if (!do_this_side)
     436     4463825 :             continue;
     437             : 
     438       13074 :           has_dirichlet_constraint = true;
     439             : 
     440             :           // Then determine what nodes are on this side
     441     1370416 :           for (unsigned int n = 0; n != n_nodes; ++n)
     442     1217039 :             if (elem->is_node_on_side(n,s))
     443             :               {
     444             :                 // Attempt to emplace an empty vector. If there is
     445             :                 // already an entry, the insertion will fail and we'll
     446             :                 // get an iterator back to the existing entry. Either
     447             :                 // way, we'll then set index n of that vector to
     448             :                 // "true".
     449     1086125 :                 for (const auto & db_pair : ordered_dbs)
     450             :                   {
     451             :                     // Only add this as a boundary node for this db if
     452             :                     // it is also a boundary side for this db.
     453      556804 :                     if (auto side_it = is_boundary_side_map.find(db_pair.second);
     454      556804 :                         side_it != is_boundary_side_map.end() && side_it->second[s])
     455             :                       {
     456     1014830 :                         auto pr = is_boundary_node_map.emplace(db_pair.second, std::vector<bool>(n_nodes, false));
     457       93954 :                         pr.first->second[n] = true;
     458             :                       }
     459             :                   }
     460             :               }
     461             : 
     462             :           // Finally determine what edges are on this side
     463     1283945 :           for (unsigned int e = 0; e != n_edges; ++e)
     464     1130568 :             if (elem->is_edge_on_side(e,s))
     465             :               {
     466             :                 // Attempt to emplace an empty vector. If there is
     467             :                 // already an entry, the insertion will fail and we'll
     468             :                 // get an iterator back to the existing entry. Either
     469             :                 // way, we'll then set index e of that vector to
     470             :                 // "true".
     471      735919 :                 for (const auto & db_pair : ordered_dbs)
     472             :                   {
     473             :                     // Only add this as a boundary edge for this db if
     474             :                     // it is also a boundary side for this db.
     475      376706 :                     if (auto side_it = is_boundary_side_map.find(db_pair.second);
     476      376706 :                         side_it != is_boundary_side_map.end() && side_it->second[s])
     477             :                       {
     478      688370 :                         auto pr = is_boundary_edge_map.emplace(db_pair.second, std::vector<bool>(n_edges, false));
     479       63370 :                         pr.first->second[e] = true;
     480             :                       }
     481             :                   }
     482             :               }
     483             :         } // for (s = 0..n_sides)
     484             : 
     485             :       // We can also impose Dirichlet boundary conditions on nodes, so we should
     486             :       // also independently check whether the nodes have been requested
     487     8931090 :       for (unsigned int n=0; n != n_nodes; ++n)
     488             :         {
     489     8423094 :           boundary_info.boundary_ids (elem->node_ptr(n), ids_vec);
     490             : 
     491     8324035 :           for (const auto & bc_id : ids_vec)
     492             :             {
     493      607891 :               if (auto it = boundary_id_to_ordered_dirichlet_boundaries.find(bc_id);
     494       46967 :                   it != boundary_id_to_ordered_dirichlet_boundaries.end())
     495             :                 {
     496             :                   // Associate every DirichletBoundary object that has this bc_id with the current Elem
     497      212732 :                   ordered_dbs.insert(it->second.begin(), it->second.end());
     498             : 
     499             :                   // Turn on the flag for the current node for each DirichletBoundary
     500      443182 :                   for (const auto & db_pair : it->second)
     501             :                     {
     502      422026 :                       auto pr = is_boundary_node_map.emplace(db_pair.second, std::vector<bool>(n_nodes, false));
     503       38874 :                       pr.first->second[n] = true;
     504             : 
     505      422026 :                       auto pr2 = is_boundary_nodeset_map.emplace(db_pair.second, std::vector<bool>(n_nodes, false));
     506       38874 :                       pr2.first->second[n] = true;
     507             :                     }
     508             : 
     509       17913 :                   has_dirichlet_constraint = true;
     510             :                 }
     511             :             }
     512             :         } // for (n = 0..n_nodes)
     513             : 
     514             :       // We can also impose Dirichlet boundary conditions on edges, so we should
     515             :       // also independently check whether the edges have been requested
     516     8035714 :       for (unsigned short e=0; e != n_edges; ++e)
     517             :         {
     518     6867735 :           boundary_info.edge_boundary_ids (elem, e, ids_vec);
     519             : 
     520      585147 :           bool do_this_side = false;
     521     6867975 :           for (const auto & bc_id : ids_vec)
     522             :             {
     523         260 :               if (auto it = boundary_id_to_ordered_dirichlet_boundaries.find(bc_id);
     524          20 :                   it != boundary_id_to_ordered_dirichlet_boundaries.end())
     525             :                 {
     526          20 :                   do_this_side = true;
     527             : 
     528             :                   // We need to loop over all DirichletBoundary objects associated with bc_id
     529         240 :                   ordered_dbs.insert(it->second.begin(), it->second.end());
     530             : 
     531             :                   // Turn on the flag for the current edge for each DirichletBoundary
     532         768 :                   for (const auto & db_pair : it->second)
     533             :                     {
     534         968 :                       auto pr = is_boundary_edge_map.emplace(db_pair.second, std::vector<bool>(n_edges, false));
     535          88 :                       pr.first->second[e] = true;
     536             :                     }
     537             :                 }
     538             :             }
     539             : 
     540     6867735 :           if (!do_this_side)
     541     6282368 :             continue;
     542             : 
     543          20 :           has_dirichlet_constraint = true;
     544             : 
     545             :           // Then determine what nodes are on this edge
     546        2160 :           for (unsigned int n = 0; n != n_nodes; ++n)
     547        1920 :             if (elem->is_node_on_edge(n,e))
     548             :               {
     549             :                 // Attempt to emplace an empty vector. If there is
     550             :                 // already an entry, the insertion will fail and we'll
     551             :                 // get an iterator back to the existing entry. Either
     552             :                 // way, we'll then set index n of that vector to
     553             :                 // "true".
     554        1536 :                 for (const auto & db_pair : ordered_dbs)
     555             :                   {
     556             :                     // Only add this as a boundary node for this db if
     557             :                     // it is also a boundary edge for this db.
     558        1056 :                     if (auto edge_it = is_boundary_edge_map.find(db_pair.second);
     559        1056 :                         edge_it != is_boundary_edge_map.end() && edge_it->second[e])
     560             :                       {
     561        1936 :                         auto pr = is_boundary_node_map.emplace(db_pair.second, std::vector<bool>(n_nodes, false));
     562         176 :                         pr.first->second[n] = true;
     563             :                       }
     564             :                   }
     565             :               }
     566             :         }
     567             : 
     568             :       // We can also impose Dirichlet boundary conditions on shellfaces, so we should
     569             :       // also independently check whether the shellfaces have been requested
     570     3503937 :       for (unsigned short shellface=0; shellface != 2; ++shellface)
     571             :         {
     572     2335958 :           boundary_info.shellface_boundary_ids (elem, shellface, ids_vec);
     573      200430 :           bool do_this_shellface = false;
     574             : 
     575     2375918 :           for (const auto & bc_id : ids_vec)
     576             :             {
     577       43290 :               if (auto it = boundary_id_to_ordered_dirichlet_boundaries.find(bc_id);
     578        3330 :                   it != boundary_id_to_ordered_dirichlet_boundaries.end())
     579             :                 {
     580           1 :                   has_dirichlet_constraint = true;
     581           1 :                   do_this_shellface = true;
     582             : 
     583             :                   // We need to loop over all DirichletBoundary objects associated with bc_id
     584          12 :                   ordered_dbs.insert(it->second.begin(), it->second.end());
     585             : 
     586             :                   // Turn on the flag for the current shellface for each DirichletBoundary
     587          24 :                   for (const auto & db_pair : it->second)
     588             :                     {
     589          22 :                       auto pr = is_boundary_shellface_map.emplace(db_pair.second, std::vector<bool>(/*n_shellfaces=*/2, false));
     590           2 :                       pr.first->second[shellface] = true;
     591             :                     }
     592             :                 }
     593             :             }
     594             : 
     595     2335958 :           if (do_this_shellface)
     596             :             {
     597             :               // Shellface BCs induce BCs on all the nodes of a shell Elem
     598          60 :               for (unsigned int n = 0; n != n_nodes; ++n)
     599          96 :                 for (const auto & db_pair : ordered_dbs)
     600             :                   {
     601             :                     // Only add this as a boundary node for this db if
     602             :                     // it is also a boundary shellface for this db.
     603          48 :                     if (auto side_it = is_boundary_shellface_map.find(db_pair.second);
     604          48 :                         side_it != is_boundary_shellface_map.end() && side_it->second[shellface])
     605             :                       {
     606          88 :                         auto pr = is_boundary_node_map.emplace(db_pair.second, std::vector<bool>(n_nodes, false));
     607           8 :                         pr.first->second[n] = true;
     608             :                       }
     609             :                   }
     610             :             }
     611             :         } // for (shellface = 0..2)
     612             : 
     613     1268194 :       return has_dirichlet_constraint;
     614             :     } // SingleElemBoundaryInfo::reinit()
     615             : 
     616             :   }; // struct SingleElemBoundaryInfo
     617             : 
     618             : 
     619             : 
     620             :   template<typename OutputType>
     621      165370 :   void apply_lagrange_dirichlet_impl(const SingleElemBoundaryInfo & sebi,
     622             :                             const Variable & variable,
     623             :                             const DirichletBoundary & dirichlet,
     624             :                             FEMContext & fem_context) const
     625             :   {
     626             :     // Get pointer to the Elem we are currently working on
     627      165370 :     const Elem * elem = sebi.elem;
     628             : 
     629             :     // Per-subdomain variables don't need to be projected on
     630             :     // elements where they're not active
     631      165370 :     if (!variable.active_on_subdomain(elem->subdomain_id()))
     632           0 :       return;
     633             : 
     634       14192 :     FunctionBase<Number> * f = dirichlet.f.get();
     635       14192 :     FEMFunctionBase<Number> * f_fem = dirichlet.f_fem.get();
     636             : 
     637      165370 :     const System * f_system = dirichlet.f_system;
     638             : 
     639             :     // We need data to project
     640       14192 :     libmesh_assert(f || f_fem);
     641       14192 :     libmesh_assert(!(f && f_fem));
     642             : 
     643             :     // Iff our data depends on a system, we should have it.
     644       14192 :     libmesh_assert(!(f && f_system));
     645       14192 :     libmesh_assert(!(f_fem && !f_system));
     646             : 
     647             :     // The new element coefficients. For Lagrange FEs, these are the
     648             :     // nodal values.
     649      165370 :     DenseVector<Number> Ue;
     650             : 
     651             :     // Get a reference to the fe_type associated with this variable
     652       14192 :     const FEType & fe_type = variable.type();
     653             : 
     654             :     // Dimension of the vector-valued FE (1 for scalar-valued FEs)
     655      165370 :     unsigned int n_vec_dim = FEInterface::n_vec_dim(mesh, fe_type);
     656             : 
     657             :     const unsigned int var_component =
     658       28384 :       variable.first_scalar_number();
     659             : 
     660             :     // Get this Variable's number, as determined by the System.
     661       28384 :     const unsigned int var = variable.number();
     662             : 
     663             :     // If our supplied functions require a FEMContext, and if we have
     664             :     // an initialized solution to use with that FEMContext, then
     665             :     // create one.  We're not going to use elem_jacobian or
     666             :     // subjacobians here so don't allocate them.
     667      151178 :     std::unique_ptr<FEMContext> context;
     668      165370 :     if (f_fem)
     669             :       {
     670           0 :         libmesh_assert(f_system);
     671           0 :         if (f_system->current_local_solution->initialized())
     672             :           {
     673           0 :             context = std::make_unique<FEMContext>
     674           0 :               (*f_system, nullptr,
     675           0 :                /* allocate local_matrices = */ false);
     676           0 :             f_fem->init_context(*context);
     677             :           }
     678             :       }
     679             : 
     680      165370 :     if (f_system && context.get())
     681           0 :       context->pre_fe_reinit(*f_system, elem);
     682             : 
     683             :     // Also pre-init the fem_context() we were passed on the current Elem.
     684      165370 :     fem_context.pre_fe_reinit(fem_context.get_system(), elem);
     685             : 
     686             :     // Get a reference to the DOF indices for the current element
     687             :     const std::vector<dof_id_type> & dof_indices =
     688       14192 :       fem_context.get_dof_indices(var);
     689             : 
     690             :     // The number of DOFs on the element
     691             :     const unsigned int n_dofs =
     692       28384 :       cast_int<unsigned int>(dof_indices.size());
     693             : 
     694             :     // Fixed vs. free DoFs on edge/face projections
     695      179562 :     std::vector<char> dof_is_fixed(n_dofs, false); // bools
     696             : 
     697             :     // Zero the interpolated values
     698      151178 :     Ue.resize (n_dofs); Ue.zero();
     699             : 
     700             :     // For Lagrange elements, side, edge, and shellface BCs all
     701             :     // "induce" boundary conditions on the nodes of those entities.
     702             :     // In SingleElemBoundaryInfo::reinit(), we therefore set entries
     703             :     // in the "is_boundary_node_map" container based on side and
     704             :     // shellface BCs, Then, when we actually apply constraints, we
     705             :     // only have to check whether any Nodes are in this container, and
     706             :     // compute values as necessary.
     707       14192 :     unsigned int current_dof = 0;
     708     1475035 :     for (unsigned int n=0; n!= sebi.n_nodes; ++n)
     709             :       {
     710             :         // For Lagrange this can return 0 (in case of a lower-order FE
     711             :         // on a higher-order Elem) or 1. This function accounts for the
     712             :         // Elem::p_level() internally.
     713      111719 :         const unsigned int nc =
     714     1309665 :           FEInterface::n_dofs_at_node (fe_type, elem, n);
     715             : 
     716             :         // If there are no DOFs at this node, then it doesn't matter
     717             :         // if it's technically a boundary node or not, there's nothing
     718             :         // to constrain.
     719     1309665 :         if (!nc)
     720      100892 :           continue;
     721             : 
     722             :         // Check whether the current node is a boundary node
     723     1263261 :         auto is_boundary_node_it = sebi.is_boundary_node_map.find(&dirichlet);
     724      107833 :         const bool is_boundary_node =
     725     1371094 :           (is_boundary_node_it != sebi.is_boundary_node_map.end() &&
     726      107833 :            is_boundary_node_it->second[n]);
     727             : 
     728             :         // Check whether the current node is in a boundary nodeset
     729     1263261 :         auto is_boundary_nodeset_it = sebi.is_boundary_nodeset_map.find(&dirichlet);
     730      107833 :         const bool is_boundary_nodeset =
     731     1329165 :           (is_boundary_nodeset_it != sebi.is_boundary_nodeset_map.end() &&
     732       65904 :            is_boundary_nodeset_it->second[n]);
     733             : 
     734             :         // If node is neither a boundary node or from a boundary nodeset, go to the next one.
     735     1263261 :         if ( !(is_boundary_node || is_boundary_nodeset) )
     736             :           {
     737      682975 :             current_dof += nc;
     738      682975 :             continue;
     739             :           }
     740             : 
     741             :         // Compute function values, storing them in Ue
     742       49459 :         libmesh_assert_equal_to (nc, n_vec_dim);
     743     1160572 :         for (unsigned int c = 0; c < n_vec_dim; c++)
     744             :           {
     745      629745 :             Ue(current_dof+c) =
     746      580286 :               f_component(f, f_fem, context.get(), var_component+c,
     747      580286 :                           elem->point(n), time);
     748      629745 :             dof_is_fixed[current_dof+c] = true;
     749             :           }
     750      580286 :         current_dof += n_vec_dim;
     751             :       } // end for (n=0..n_nodes)
     752             : 
     753             :     // Lock the DofConstraints since it is shared among threads.
     754             :     {
     755       28384 :       Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
     756             : 
     757     1428631 :       for (unsigned int i = 0; i < n_dofs; i++)
     758             :         {
     759      215666 :           DofConstraintRow empty_row;
     760     1371094 :           if (dof_is_fixed[i] && !libmesh_isnan(Ue(i)))
     761      629745 :             add_fn (dof_indices[i], empty_row, Ue(i));
     762             :         }
     763             :     }
     764             : 
     765      136986 :   } // apply_lagrange_dirichlet_impl
     766             : 
     767             : 
     768             : 
     769             :   template<typename OutputType>
     770       13500 :   void apply_dirichlet_impl(const SingleElemBoundaryInfo & sebi,
     771             :                             const Variable & variable,
     772             :                             const DirichletBoundary & dirichlet,
     773             :                             FEMContext & fem_context) const
     774             :   {
     775             :     // Get pointer to the Elem we are currently working on
     776       13500 :     const Elem * elem = sebi.elem;
     777             : 
     778             :     // Per-subdomain variables don't need to be projected on
     779             :     // elements where they're not active
     780       13500 :     if (!variable.active_on_subdomain(elem->subdomain_id()))
     781           0 :       return;
     782             : 
     783             :     typedef OutputType                                                      OutputShape;
     784             :     typedef typename TensorTools::IncrementRank<OutputShape>::type          OutputGradient;
     785             :     //typedef typename TensorTools::IncrementRank<OutputGradient>::type       OutputTensor;
     786             :     typedef typename TensorTools::MakeNumber<OutputShape>::type             OutputNumber;
     787             :     typedef typename TensorTools::IncrementRank<OutputNumber>::type         OutputNumberGradient;
     788             :     //typedef typename TensorTools::IncrementRank<OutputNumberGradient>::type OutputNumberTensor;
     789             : 
     790        1127 :     FunctionBase<Number> * f = dirichlet.f.get();
     791        1127 :     FunctionBase<Gradient> * g = dirichlet.g.get();
     792             : 
     793        1127 :     FEMFunctionBase<Number> * f_fem = dirichlet.f_fem.get();
     794        1127 :     FEMFunctionBase<Gradient> * g_fem = dirichlet.g_fem.get();
     795             : 
     796       13500 :     const System * f_system = dirichlet.f_system;
     797             : 
     798             :     // We need data to project
     799        1127 :     libmesh_assert(f || f_fem);
     800        1127 :     libmesh_assert(!(f && f_fem));
     801             : 
     802             :     // Iff our data depends on a system, we should have it.
     803        1127 :     libmesh_assert(!(f && f_system));
     804        1127 :     libmesh_assert(!(f_fem && !f_system));
     805             : 
     806             :     // The element matrix and RHS for projections.
     807             :     // Note that Ke is always real-valued, whereas
     808             :     // Fe may be complex valued if complex number
     809             :     // support is enabled
     810       15754 :     DenseMatrix<Real> Ke;
     811       13500 :     DenseVector<Number> Fe;
     812             :     // The new element coefficients
     813       13500 :     DenseVector<Number> Ue;
     814             : 
     815             :     // The dimensionality of the current mesh
     816       13500 :     const unsigned int dim = mesh.mesh_dimension();
     817             : 
     818             :     // Get a reference to the fe_type associated with this variable
     819        1127 :     const FEType & fe_type = variable.type();
     820             : 
     821             :     // Dimension of the vector-valued FE (1 for scalar-valued FEs)
     822       13500 :     unsigned int n_vec_dim = FEInterface::n_vec_dim(mesh, fe_type);
     823             : 
     824             :     const unsigned int var_component =
     825        2254 :       variable.first_scalar_number();
     826             : 
     827             :     // Get this Variable's number, as determined by the System.
     828        2254 :     const unsigned int var = variable.number();
     829             : 
     830             :     // The type of projections done depend on the FE's continuity.
     831       13500 :     FEContinuity cont = FEInterface::get_continuity(fe_type);
     832             : 
     833             :     // Make sure we have the right data available for C1 projections
     834        1127 :     if ((cont == C_ONE) && (fe_type.family != SUBDIVISION))
     835             :       {
     836             :         // We'll need gradient data for a C1 projection
     837         459 :         libmesh_assert(g || g_fem);
     838             : 
     839             :         // We currently demand that either neither nor both function
     840             :         // object depend on current FEM data.
     841         459 :         libmesh_assert(!(g && g_fem));
     842         459 :         libmesh_assert(!(f && g_fem));
     843         459 :         libmesh_assert(!(f_fem && g));
     844             :       }
     845             : 
     846             :     // If our supplied functions require a FEMContext, and if we have
     847             :     // an initialized solution to use with that FEMContext, then
     848             :     // create one.  We're not going to use elem_jacobian or
     849             :     // subjacobians here so don't allocate them.
     850       12373 :     std::unique_ptr<FEMContext> context;
     851       13500 :     if (f_fem)
     852             :       {
     853           0 :         libmesh_assert(f_system);
     854           0 :         if (f_system->current_local_solution->initialized())
     855             :           {
     856           0 :             context = std::make_unique<FEMContext>
     857           0 :               (*f_system, nullptr,
     858           0 :                /* allocate local_matrices = */ false);
     859           0 :             f_fem->init_context(*context);
     860           0 :             if (g_fem)
     861           0 :               g_fem->init_context(*context);
     862             :           }
     863             :       }
     864             : 
     865             :     // There's a chicken-and-egg problem with FEMFunction-based
     866             :     // Dirichlet constraints: we can't evaluate the FEMFunction
     867             :     // until we have an initialized local solution vector, we
     868             :     // can't initialize local solution vectors until we have a
     869             :     // send list, and we can't generate a send list until we know
     870             :     // all our constraints
     871             :     //
     872             :     // We don't generate constraints on uninitialized systems;
     873             :     // currently user code will have to reinit() before any
     874             :     // FEMFunction-based constraints will be correct.  This should
     875             :     // be fine, since user code would want to reinit() after
     876             :     // setting initial conditions anyway.
     877       13500 :     if (f_system && context.get())
     878           0 :       context->pre_fe_reinit(*f_system, elem);
     879             : 
     880             :     // Also pre-init the fem_context() we were passed on the current Elem.
     881       13500 :     fem_context.pre_fe_reinit(fem_context.get_system(), elem);
     882             : 
     883             :     // Get a reference to the DOF indices for the current element
     884             :     const std::vector<dof_id_type> & dof_indices =
     885        1127 :       fem_context.get_dof_indices(var);
     886             : 
     887             :     // The number of DOFs on the element
     888             :     const unsigned int n_dofs =
     889        2254 :       cast_int<unsigned int>(dof_indices.size());
     890             : 
     891             :     // Fixed vs. free DoFs on edge/face projections
     892       14627 :     std::vector<char> dof_is_fixed(n_dofs, false); // bools
     893       14627 :     std::vector<int> free_dof(n_dofs, 0);
     894             : 
     895             :     // Zero the interpolated values
     896       12373 :     Ue.resize (n_dofs); Ue.zero();
     897             : 
     898             :     // In general, we need a series of
     899             :     // projections to ensure a unique and continuous
     900             :     // solution.  We start by interpolating boundary nodes, then
     901             :     // hold those fixed and project boundary edges, then hold
     902             :     // those fixed and project boundary faces,
     903             : 
     904             :     // Interpolate node values first.
     905             :     //
     906             :     // If we have non-vertex nodes that have a boundary nodeset, we
     907             :     // need to interpolate them directly, but that had better be
     908             :     // happening in the apply_lagrange_dirichlet_impl code path,
     909             :     // because you *can't* interpolate to evaluate non-nodal FE
     910             :     // coefficients.
     911        1127 :     unsigned int current_dof = 0;
     912      117804 :     for (unsigned int n=0; n!= sebi.n_nodes; ++n)
     913             :       {
     914             :         // FIXME: this should go through the DofMap,
     915             :         // not duplicate dof_indices code badly!
     916             : 
     917             :         // Get the number of DOFs at this node, accounting for
     918             :         // Elem::p_level() internally.
     919        8704 :         const unsigned int nc =
     920      104304 :           FEInterface::n_dofs_at_node (fe_type, elem, n);
     921             : 
     922             :         // Get a reference to the "is_boundary_node" flags for the
     923             :         // current DirichletBoundary object.  In case the map does not
     924             :         // contain an entry for this DirichletBoundary object, it
     925             :         // means there are no boundary nodes active.
     926      104304 :         auto is_boundary_node_it = sebi.is_boundary_node_map.find(&dirichlet);
     927             : 
     928             :         // The current n is not a boundary node if either there is no
     929             :         // boundary_node_map for this DirichletBoundary object, or if
     930             :         // there is but the entry in the corresponding vector is
     931             :         // false.
     932        8704 :         const bool not_boundary_node =
     933      113008 :           (is_boundary_node_it == sebi.is_boundary_node_map.end() ||
     934       17408 :            !is_boundary_node_it->second[n]);
     935             : 
     936      104304 :         if (!elem->is_vertex(n) || not_boundary_node)
     937             :           {
     938       72840 :             current_dof += nc;
     939        6078 :             continue;
     940             :           }
     941       31464 :         if (cont == DISCONTINUOUS)
     942             :           {
     943           0 :             libmesh_assert_equal_to (nc, 0);
     944             :           }
     945             :         // Assume that C_ZERO elements have a single nodal
     946             :         // value shape function
     947       31464 :         else if ((cont == C_ZERO) || (fe_type.family == SUBDIVISION))
     948             :           {
     949        1726 :             libmesh_assert_equal_to (nc, n_vec_dim);
     950       53136 :             for (unsigned int c = 0; c < n_vec_dim; c++)
     951             :               {
     952       35126 :                 Ue(current_dof+c) =
     953       32424 :                   f_component(f, f_fem, context.get(), var_component+c,
     954       32424 :                               elem->point(n), time);
     955       35126 :                 dof_is_fixed[current_dof+c] = true;
     956             :               }
     957       20712 :             current_dof += n_vec_dim;
     958       17260 :           }
     959             :         // The hermite element vertex shape functions are weird
     960       10752 :         else if (fe_type.family == HERMITE)
     961             :           {
     962        4628 :             Ue(current_dof) =
     963        4272 :               f_component(f, f_fem, context.get(), var_component,
     964        4272 :                           elem->point(n), time);
     965        4272 :             dof_is_fixed[current_dof] = true;
     966        4272 :             current_dof++;
     967             :             Gradient grad =
     968        4272 :               g_component(g, g_fem, context.get(), var_component,
     969        4272 :                           elem->point(n), time);
     970             :             // x derivative
     971        4272 :             Ue(current_dof) = grad(0);
     972        4272 :             dof_is_fixed[current_dof] = true;
     973        4272 :             current_dof++;
     974        4272 :             if (dim > 1)
     975             :               {
     976             :                 // We'll finite difference mixed derivatives
     977        3744 :                 Point nxminus = elem->point(n),
     978        3744 :                   nxplus = elem->point(n);
     979        3744 :                 nxminus(0) -= TOLERANCE;
     980        3744 :                 nxplus(0) += TOLERANCE;
     981             :                 Gradient gxminus =
     982        3744 :                   g_component(g, g_fem, context.get(), var_component,
     983        3744 :                               nxminus, time);
     984             :                 Gradient gxplus =
     985        3744 :                   g_component(g, g_fem, context.get(), var_component,
     986        3744 :                               nxplus, time);
     987             :                 // y derivative
     988        3744 :                 Ue(current_dof) = grad(1);
     989        3744 :                 dof_is_fixed[current_dof] = true;
     990        3744 :                 current_dof++;
     991             :                 // xy derivative
     992        4056 :                 Ue(current_dof) = (gxplus(1) - gxminus(1))
     993        3432 :                   / 2. / TOLERANCE;
     994        3744 :                 dof_is_fixed[current_dof] = true;
     995        3744 :                 current_dof++;
     996             : 
     997        3744 :                 if (dim > 2)
     998             :                   {
     999             :                     // z derivative
    1000           0 :                     Ue(current_dof) = grad(2);
    1001           0 :                     dof_is_fixed[current_dof] = true;
    1002           0 :                     current_dof++;
    1003             :                     // xz derivative
    1004           0 :                     Ue(current_dof) = (gxplus(2) - gxminus(2))
    1005           0 :                       / 2. / TOLERANCE;
    1006           0 :                     dof_is_fixed[current_dof] = true;
    1007           0 :                     current_dof++;
    1008             :                     // We need new points for yz
    1009           0 :                     Point nyminus = elem->point(n),
    1010           0 :                       nyplus = elem->point(n);
    1011           0 :                     nyminus(1) -= TOLERANCE;
    1012           0 :                     nyplus(1) += TOLERANCE;
    1013             :                     Gradient gyminus =
    1014           0 :                       g_component(g, g_fem, context.get(), var_component,
    1015           0 :                                   nyminus, time);
    1016             :                     Gradient gyplus =
    1017           0 :                       g_component(g, g_fem, context.get(), var_component,
    1018           0 :                                   nyplus, time);
    1019             :                     // xz derivative
    1020           0 :                     Ue(current_dof) = (gyplus(2) - gyminus(2))
    1021           0 :                       / 2. / TOLERANCE;
    1022           0 :                     dof_is_fixed[current_dof] = true;
    1023           0 :                     current_dof++;
    1024             :                     // Getting a 2nd order xyz is more tedious
    1025           0 :                     Point nxmym = elem->point(n),
    1026           0 :                       nxmyp = elem->point(n),
    1027           0 :                       nxpym = elem->point(n),
    1028           0 :                       nxpyp = elem->point(n);
    1029           0 :                     nxmym(0) -= TOLERANCE;
    1030           0 :                     nxmym(1) -= TOLERANCE;
    1031           0 :                     nxmyp(0) -= TOLERANCE;
    1032           0 :                     nxmyp(1) += TOLERANCE;
    1033           0 :                     nxpym(0) += TOLERANCE;
    1034           0 :                     nxpym(1) -= TOLERANCE;
    1035           0 :                     nxpyp(0) += TOLERANCE;
    1036           0 :                     nxpyp(1) += TOLERANCE;
    1037             :                     Gradient gxmym =
    1038           0 :                       g_component(g, g_fem, context.get(), var_component,
    1039           0 :                                   nxmym, time);
    1040             :                     Gradient gxmyp =
    1041           0 :                       g_component(g, g_fem, context.get(), var_component,
    1042           0 :                                   nxmyp, time);
    1043             :                     Gradient gxpym =
    1044           0 :                       g_component(g, g_fem, context.get(), var_component,
    1045           0 :                                   nxpym, time);
    1046             :                     Gradient gxpyp =
    1047           0 :                       g_component(g, g_fem, context.get(), var_component,
    1048           0 :                                   nxpyp, time);
    1049           0 :                     Number gxzplus = (gxpyp(2) - gxmyp(2))
    1050           0 :                       / 2. / TOLERANCE;
    1051           0 :                     Number gxzminus = (gxpym(2) - gxmym(2))
    1052           0 :                       / 2. / TOLERANCE;
    1053             :                     // xyz derivative
    1054           0 :                     Ue(current_dof) = (gxzplus - gxzminus)
    1055           0 :                       / 2. / TOLERANCE;
    1056           0 :                     dof_is_fixed[current_dof] = true;
    1057           0 :                     current_dof++;
    1058             :                   }
    1059             :               }
    1060             :           }
    1061             :         // Assume that other C_ONE elements have a single nodal
    1062             :         // value shape function and nodal gradient component
    1063             :         // shape functions
    1064        6480 :         else if (cont == C_ONE)
    1065             :           {
    1066         544 :             libmesh_assert_equal_to (nc, 1 + dim);
    1067        7024 :             Ue(current_dof) =
    1068        6480 :               f_component(f, f_fem, context.get(), var_component,
    1069        6480 :                           elem->point(n), time);
    1070        6480 :             dof_is_fixed[current_dof] = true;
    1071        6480 :             current_dof++;
    1072             :             Gradient grad =
    1073        6480 :               g_component(g, g_fem, context.get(), var_component,
    1074        6480 :                           elem->point(n), time);
    1075       19440 :             for (unsigned int i=0; i!= dim; ++i)
    1076             :               {
    1077       12960 :                 Ue(current_dof) = grad(i);
    1078       12960 :                 dof_is_fixed[current_dof] = true;
    1079       12960 :                 current_dof++;
    1080             :               }
    1081             :           }
    1082             :         else
    1083           0 :           libmesh_error_msg("Unknown continuity cont = " << cont);
    1084             :       } // end for (n=0..n_nodes)
    1085             : 
    1086             :         // In 3D, project any edge values next
    1087       13500 :     if (dim > 2 && cont != DISCONTINUOUS)
    1088             :       {
    1089             :         // Get a pointer to the 1 dimensional (edge) FE for the current
    1090             :         // var which is stored in the fem_context. This will only be
    1091             :         // different from side_fe in 3D.
    1092          98 :         FEGenericBase<OutputType> * edge_fe = nullptr;
    1093          98 :         fem_context.get_edge_fe(var, edge_fe);
    1094             : 
    1095             :         // Set tolerance on underlying FEMap object. This will allow us to
    1096             :         // avoid spurious negative Jacobian errors while imposing BCs by
    1097             :         // simply ignoring them. This should only be required in certain
    1098             :         // special cases, see the DirichletBoundaries comments on this
    1099             :         // parameter for more information.
    1100        1176 :         edge_fe->get_fe_map().set_jacobian_tolerance(dirichlet.jacobian_tolerance);
    1101             : 
    1102             :         // Pre-request FE data
    1103          98 :         const std::vector<std::vector<OutputShape>> & phi = edge_fe->get_phi();
    1104        1176 :         const std::vector<Point> & xyz_values = edge_fe->get_xyz();
    1105         294 :         const std::vector<Real> & JxW = edge_fe->get_JxW();
    1106             : 
    1107             :         // Only pre-request gradients for C1 projections
    1108          98 :         const std::vector<std::vector<OutputGradient>> * dphi = nullptr;
    1109        1176 :         if ((cont == C_ONE) && (fe_type.family != SUBDIVISION))
    1110             :           {
    1111           0 :             const std::vector<std::vector<OutputGradient>> & ref_dphi = edge_fe->get_dphi();
    1112           0 :             dphi = &ref_dphi;
    1113             :           }
    1114             : 
    1115             :         // Vector to hold edge local DOF indices
    1116         196 :         std::vector<unsigned int> edge_dofs;
    1117             : 
    1118             :         // Get a reference to the "is_boundary_edge" flags for the
    1119             :         // current DirichletBoundary object.  In case the map does not
    1120             :         // contain an entry for this DirichletBoundary object, it
    1121             :         // means there are no boundary edges active.
    1122        1176 :         if (const auto is_boundary_edge_it = sebi.is_boundary_edge_map.find(&dirichlet);
    1123          98 :             is_boundary_edge_it != sebi.is_boundary_edge_map.end())
    1124             :         {
    1125       15288 :         for (unsigned int e=0; e != sebi.n_edges; ++e)
    1126             :           {
    1127       15288 :             if (!is_boundary_edge_it->second[e])
    1128       14112 :               continue;
    1129             : 
    1130        6480 :             FEInterface::dofs_on_edge(elem, dim, fe_type, e,
    1131             :                                       edge_dofs);
    1132             : 
    1133             :             const unsigned int n_edge_dofs =
    1134        1080 :               cast_int<unsigned int>(edge_dofs.size());
    1135             : 
    1136             :             // Some edge dofs are on nodes and already
    1137             :             // fixed, others are free to calculate
    1138         540 :             unsigned int free_dofs = 0;
    1139       45360 :             for (unsigned int i=0; i != n_edge_dofs; ++i)
    1140       45360 :               if (!dof_is_fixed[edge_dofs[i]])
    1141           0 :                 free_dof[free_dofs++] = i;
    1142             : 
    1143             :             // There may be nothing to project
    1144        6480 :             if (!free_dofs)
    1145        5940 :               continue;
    1146             : 
    1147           0 :             Ke.resize (free_dofs, free_dofs); Ke.zero();
    1148           0 :             Fe.resize (free_dofs); Fe.zero();
    1149             :             // The new edge coefficients
    1150           0 :             DenseVector<Number> Uedge(free_dofs);
    1151             : 
    1152             :             // Initialize FE data on the edge
    1153           0 :             edge_fe->edge_reinit(elem, e);
    1154           0 :             const unsigned int n_qp = fem_context.get_edge_qrule().n_points();
    1155             : 
    1156             :             // Loop over the quadrature points
    1157           0 :             for (unsigned int qp=0; qp<n_qp; qp++)
    1158             :               {
    1159             :                 // solution at the quadrature point
    1160           0 :                 OutputNumber fineval(0);
    1161           0 :                 libMesh::RawAccessor<OutputNumber> f_accessor( fineval, dim );
    1162             : 
    1163           0 :                 for (unsigned int c = 0; c < n_vec_dim; c++)
    1164           0 :                   f_accessor(c) =
    1165           0 :                     f_component(f, f_fem, context.get(), var_component+c,
    1166           0 :                                 xyz_values[qp], time);
    1167             : 
    1168             :                 // solution grad at the quadrature point
    1169           0 :                 OutputNumberGradient finegrad;
    1170           0 :                 libMesh::RawAccessor<OutputNumberGradient> g_accessor( finegrad, dim );
    1171             : 
    1172             :                 unsigned int g_rank;
    1173           0 :                 switch( FEInterface::field_type( fe_type ) )
    1174             :                   {
    1175           0 :                   case TYPE_SCALAR:
    1176             :                     {
    1177           0 :                       g_rank = 1;
    1178           0 :                       break;
    1179             :                     }
    1180           0 :                   case TYPE_VECTOR:
    1181             :                     {
    1182           0 :                       g_rank = 2;
    1183           0 :                       break;
    1184             :                     }
    1185           0 :                   default:
    1186           0 :                     libmesh_error_msg("Unknown field type!");
    1187             :                   }
    1188             : 
    1189           0 :                 if (cont == C_ONE)
    1190           0 :                   for (unsigned int c = 0; c < n_vec_dim; c++)
    1191           0 :                     for (unsigned int d = 0; d < g_rank; d++)
    1192           0 :                       g_accessor(c + d*dim ) =
    1193           0 :                         g_component(g, g_fem, context.get(), var_component,
    1194           0 :                                     xyz_values[qp], time)(c);
    1195             : 
    1196             :                 // Form edge projection matrix
    1197           0 :                 for (unsigned int sidei=0, freei=0; sidei != n_edge_dofs; ++sidei)
    1198             :                   {
    1199           0 :                     unsigned int i = edge_dofs[sidei];
    1200             :                     // fixed DoFs aren't test functions
    1201           0 :                     if (dof_is_fixed[i])
    1202           0 :                       continue;
    1203           0 :                     for (unsigned int sidej=0, freej=0; sidej != n_edge_dofs; ++sidej)
    1204             :                       {
    1205           0 :                         unsigned int j = edge_dofs[sidej];
    1206           0 :                         if (dof_is_fixed[j])
    1207           0 :                           Fe(freei) -= phi[i][qp] * phi[j][qp] *
    1208           0 :                             JxW[qp] * Ue(j);
    1209             :                         else
    1210           0 :                           Ke(freei,freej) += phi[i][qp] *
    1211           0 :                             phi[j][qp] * JxW[qp];
    1212           0 :                         if (cont == C_ONE)
    1213             :                           {
    1214           0 :                             if (dof_is_fixed[j])
    1215           0 :                               Fe(freei) -= ((*dphi)[i][qp].contract((*dphi)[j][qp]) ) *
    1216           0 :                                 JxW[qp] * Ue(j);
    1217             :                             else
    1218           0 :                               Ke(freei,freej) += ((*dphi)[i][qp].contract((*dphi)[j][qp]))
    1219           0 :                                 * JxW[qp];
    1220             :                           }
    1221           0 :                         if (!dof_is_fixed[j])
    1222           0 :                           freej++;
    1223             :                       }
    1224           0 :                     Fe(freei) += phi[i][qp] * fineval * JxW[qp];
    1225           0 :                     if (cont == C_ONE)
    1226           0 :                       Fe(freei) += (finegrad.contract( (*dphi)[i][qp]) ) *
    1227             :                         JxW[qp];
    1228           0 :                     freei++;
    1229             :                   }
    1230             :               }
    1231             : 
    1232           0 :             Ke.cholesky_solve(Fe, Uedge);
    1233             : 
    1234             :             // Transfer new edge solutions to element
    1235           0 :             for (unsigned int i=0; i != free_dofs; ++i)
    1236             :               {
    1237           0 :                 Number & ui = Ue(edge_dofs[free_dof[i]]);
    1238           0 :                 libmesh_assert(std::abs(ui) < TOLERANCE ||
    1239             :                                std::abs(ui - Uedge(i)) < TOLERANCE);
    1240           0 :                 ui = Uedge(i);
    1241           0 :                 dof_is_fixed[edge_dofs[free_dof[i]]] = true;
    1242             :               }
    1243             :           } // end for (e = 0..n_edges)
    1244             :         } // end if (is_boundary_edge_it != sebi.is_boundary_edge_map.end())
    1245             :       } // end if (dim > 2 && cont != DISCONTINUOUS)
    1246             : 
    1247             :         // Project any side values (edges in 2D, faces in 3D)
    1248       13500 :     if (dim > 1 && cont != DISCONTINUOUS)
    1249             :       {
    1250       11581 :         FEGenericBase<OutputType> * side_fe = nullptr;
    1251       11581 :         fem_context.get_side_fe(var, side_fe);
    1252             : 
    1253             :         // Set tolerance on underlying FEMap object. This will allow us to
    1254             :         // avoid spurious negative Jacobian errors while imposing BCs by
    1255             :         // simply ignoring them. This should only be required in certain
    1256             :         // special cases, see the DirichletBoundaries comments on this
    1257             :         // parameter for more information.
    1258       12636 :         side_fe->get_fe_map().set_jacobian_tolerance(dirichlet.jacobian_tolerance);
    1259             : 
    1260             :         // Pre-request FE data
    1261        1055 :         const std::vector<std::vector<OutputShape>> & phi = side_fe->get_phi();
    1262       12636 :         const std::vector<Point> & xyz_values = side_fe->get_xyz();
    1263        3165 :         const std::vector<Real> & JxW = side_fe->get_JxW();
    1264             : 
    1265             :         // Only pre-request gradients for C1 projections
    1266        1055 :         const std::vector<std::vector<OutputGradient>> * dphi = nullptr;
    1267       12636 :         if ((cont == C_ONE) && (fe_type.family != SUBDIVISION))
    1268             :           {
    1269         415 :             const std::vector<std::vector<OutputGradient>> & ref_dphi = side_fe->get_dphi();
    1270         415 :             dphi = &ref_dphi;
    1271             :           }
    1272             : 
    1273             :         // Vector to hold side local DOF indices
    1274        2110 :         std::vector<unsigned int> side_dofs;
    1275             : 
    1276             :         // Get a reference to the "is_boundary_side" flags for the
    1277             :         // current DirichletBoundary object.  In case the map does not
    1278             :         // contain an entry for this DirichletBoundary object, it
    1279             :         // means there are no boundary sides active.
    1280       12636 :         if (const auto is_boundary_side_it = sebi.is_boundary_side_map.find(&dirichlet);
    1281        1055 :             is_boundary_side_it != sebi.is_boundary_side_map.end())
    1282             :         {
    1283       60480 :         for (unsigned int s=0; s != sebi.n_sides; ++s)
    1284             :           {
    1285       52227 :             if (!is_boundary_side_it->second[s])
    1286       37008 :               continue;
    1287             : 
    1288       15060 :             FEInterface::dofs_on_side(elem, dim, fe_type, s,
    1289             :                                       side_dofs);
    1290             : 
    1291             :             const unsigned int n_side_dofs =
    1292        2514 :               cast_int<unsigned int>(side_dofs.size());
    1293             : 
    1294             :             // Some side dofs are on nodes/edges and already
    1295             :             // fixed, others are free to calculate
    1296        1257 :             unsigned int free_dofs = 0;
    1297      101136 :             for (unsigned int i=0; i != n_side_dofs; ++i)
    1298      100450 :               if (!dof_is_fixed[side_dofs[i]])
    1299       13925 :                 free_dof[free_dofs++] = i;
    1300             : 
    1301             :             // There may be nothing to project
    1302       15060 :             if (!free_dofs)
    1303        3542 :               continue;
    1304             : 
    1305       10261 :             Ke.resize (free_dofs, free_dofs); Ke.zero();
    1306       10261 :             Fe.resize (free_dofs); Fe.zero();
    1307             :             // The new side coefficients
    1308       11196 :             DenseVector<Number> Uside(free_dofs);
    1309             : 
    1310             :             // Initialize FE data on the side
    1311       11196 :             side_fe->reinit(elem, s);
    1312         935 :             const unsigned int n_qp = fem_context.get_side_qrule().n_points();
    1313             : 
    1314             :             // Loop over the quadrature points
    1315       49620 :             for (unsigned int qp=0; qp<n_qp; qp++)
    1316             :               {
    1317             :                 // solution at the quadrature point
    1318       38424 :                 OutputNumber fineval(0);
    1319        3210 :                 libMesh::RawAccessor<OutputNumber> f_accessor( fineval, dim );
    1320             : 
    1321       76848 :                 for (unsigned int c = 0; c < n_vec_dim; c++)
    1322       38424 :                   f_accessor(c) =
    1323       38424 :                     f_component(f, f_fem, context.get(), var_component+c,
    1324       38424 :                                 xyz_values[qp], time);
    1325             : 
    1326             :                 // solution grad at the quadrature point
    1327        3210 :                 OutputNumberGradient finegrad;
    1328        3210 :                 libMesh::RawAccessor<OutputNumberGradient> g_accessor( finegrad, dim );
    1329             : 
    1330             :                 unsigned int g_rank;
    1331       38424 :                 switch( FEInterface::field_type( fe_type ) )
    1332             :                   {
    1333        3210 :                   case TYPE_SCALAR:
    1334             :                     {
    1335        3210 :                       g_rank = 1;
    1336        3210 :                       break;
    1337             :                     }
    1338           0 :                   case TYPE_VECTOR:
    1339             :                     {
    1340           0 :                       g_rank = 2;
    1341           0 :                       break;
    1342             :                     }
    1343           0 :                   default:
    1344           0 :                     libmesh_error_msg("Unknown field type!");
    1345             :                   }
    1346             : 
    1347       38424 :                 if (cont == C_ONE)
    1348       25440 :                   for (unsigned int c = 0; c < n_vec_dim; c++)
    1349       25440 :                     for (unsigned int d = 0; d < g_rank; d++)
    1350       13788 :                       g_accessor(c + d*dim ) =
    1351       13788 :                         g_component(g, g_fem, context.get(), var_component,
    1352       13788 :                                     xyz_values[qp], time)(c);
    1353             : 
    1354             :                 // Form side projection matrix
    1355      212400 :                 for (unsigned int sidei=0, freei=0; sidei != n_side_dofs; ++sidei)
    1356             :                   {
    1357      173976 :                     unsigned int i = side_dofs[sidei];
    1358             :                     // fixed DoFs aren't test functions
    1359      188530 :                     if (dof_is_fixed[i])
    1360      117036 :                       continue;
    1361      258384 :                     for (unsigned int sidej=0, freej=0; sidej != n_side_dofs; ++sidej)
    1362             :                       {
    1363      212136 :                         unsigned int j = side_dofs[sidej];
    1364      229870 :                         if (dof_is_fixed[j])
    1365      227348 :                           Fe(freei) -= phi[i][qp] * phi[j][qp] *
    1366      131380 :                             JxW[qp] * Ue(j);
    1367             :                         else
    1368       91712 :                           Ke(freei,freej) += phi[i][qp] *
    1369       80236 :                             phi[j][qp] * JxW[qp];
    1370      212136 :                         if (cont == C_ONE)
    1371             :                           {
    1372       96516 :                             if (dof_is_fixed[j])
    1373      114768 :                               Fe(freei) -= ((*dphi)[i][qp].contract((*dphi)[j][qp])) *
    1374       69912 :                                 JxW[qp] * Ue(j);
    1375             :                             else
    1376       18060 :                               Ke(freei,freej) += ((*dphi)[i][qp].contract((*dphi)[j][qp]))
    1377       14856 :                                 * JxW[qp];
    1378             :                           }
    1379      229870 :                         if (!dof_is_fixed[j])
    1380       68760 :                           freej++;
    1381             :                       }
    1382       57834 :                     Fe(freei) += (fineval * phi[i][qp]) * JxW[qp];
    1383       46248 :                     if (cont == C_ONE)
    1384       14856 :                       Fe(freei) += (finegrad.contract((*dphi)[i][qp])) *
    1385             :                         JxW[qp];
    1386       46248 :                     freei++;
    1387             :                   }
    1388             :               }
    1389             : 
    1390       11196 :             Ke.cholesky_solve(Fe, Uside);
    1391             : 
    1392             :             // Transfer new side solutions to element
    1393       24048 :             for (unsigned int i=0; i != free_dofs; ++i)
    1394             :               {
    1395       14998 :                 Number & ui = Ue(side_dofs[free_dof[i]]);
    1396             : 
    1397        1073 :                 libmesh_assert(std::abs(ui) < TOLERANCE ||
    1398             :                                std::abs(ui - Uside(i)) < TOLERANCE);
    1399       12852 :                 ui = Uside(i);
    1400             : 
    1401       13925 :                 dof_is_fixed[side_dofs[free_dof[i]]] = true;
    1402             :               }
    1403             :           } // end for (s = 0..n_sides)
    1404             :         } // end if (is_boundary_side_it != sebi.is_boundary_side_map.end())
    1405             :       } // end if (dim > 1 && cont != DISCONTINUOUS)
    1406             : 
    1407             :         // Project any shellface values
    1408       13500 :     if (dim == 2 && cont != DISCONTINUOUS)
    1409             :       {
    1410         957 :         FEGenericBase<OutputType> * fe = nullptr;
    1411         957 :         fem_context.get_element_fe(var, fe, dim);
    1412             : 
    1413             :         // Set tolerance on underlying FEMap object. This will allow us to
    1414             :         // avoid spurious negative Jacobian errors while imposing BCs by
    1415             :         // simply ignoring them. This should only be required in certain
    1416             :         // special cases, see the DirichletBoundaries comments on this
    1417             :         // parameter for more information.
    1418       11460 :         fe->get_fe_map().set_jacobian_tolerance(dirichlet.jacobian_tolerance);
    1419             : 
    1420             :         // Pre-request FE data
    1421         957 :         const std::vector<std::vector<OutputShape>> & phi = fe->get_phi();
    1422       11460 :         const std::vector<Point> & xyz_values = fe->get_xyz();
    1423        2871 :         const std::vector<Real> & JxW = fe->get_JxW();
    1424             : 
    1425             :         // Only pre-request gradients for C1 projections
    1426         957 :         const std::vector<std::vector<OutputGradient>> * dphi = nullptr;
    1427       11460 :         if ((cont == C_ONE) && (fe_type.family != SUBDIVISION))
    1428             :           {
    1429         415 :             const std::vector<std::vector<OutputGradient>> & ref_dphi = fe->get_dphi();
    1430         415 :             dphi = &ref_dphi;
    1431             :           }
    1432             : 
    1433             :         // Get a reference to the "is_boundary_shellface" flags for the
    1434             :         // current DirichletBoundary object.  In case the map does not
    1435             :         // contain an entry for this DirichletBoundary object, it
    1436             :         // means there are no boundary shellfaces active.
    1437       11460 :         if (const auto is_boundary_shellface_it = sebi.is_boundary_shellface_map.find(&dirichlet);
    1438         957 :             is_boundary_shellface_it != sebi.is_boundary_shellface_map.end())
    1439             :         {
    1440           0 :         for (unsigned int shellface=0; shellface != 2; ++shellface)
    1441             :           {
    1442           0 :             if (!is_boundary_shellface_it->second[shellface])
    1443           0 :               continue;
    1444             : 
    1445             :             // A shellface has the same dof indices as the element itself
    1446           0 :             std::vector<unsigned int> shellface_dofs(n_dofs);
    1447           0 :             std::iota(shellface_dofs.begin(), shellface_dofs.end(), 0);
    1448             : 
    1449             :             // Some shellface dofs are on nodes/edges and already
    1450             :             // fixed, others are free to calculate
    1451           0 :             unsigned int free_dofs = 0;
    1452           0 :             for (unsigned int i=0; i != n_dofs; ++i)
    1453           0 :               if (!dof_is_fixed[shellface_dofs[i]])
    1454           0 :                 free_dof[free_dofs++] = i;
    1455             : 
    1456             :             // There may be nothing to project
    1457           0 :             if (!free_dofs)
    1458           0 :               continue;
    1459             : 
    1460           0 :             Ke.resize (free_dofs, free_dofs); Ke.zero();
    1461           0 :             Fe.resize (free_dofs); Fe.zero();
    1462             :             // The new shellface coefficients
    1463           0 :             DenseVector<Number> Ushellface(free_dofs);
    1464             : 
    1465             :             // Initialize FE data on the element
    1466           0 :             fe->reinit (elem);
    1467           0 :             const unsigned int n_qp = fem_context.get_element_qrule().n_points();
    1468             : 
    1469             :             // Loop over the quadrature points
    1470           0 :             for (unsigned int qp=0; qp<n_qp; qp++)
    1471             :               {
    1472             :                 // solution at the quadrature point
    1473           0 :                 OutputNumber fineval(0);
    1474           0 :                 libMesh::RawAccessor<OutputNumber> f_accessor( fineval, dim );
    1475             : 
    1476           0 :                 for (unsigned int c = 0; c < n_vec_dim; c++)
    1477           0 :                   f_accessor(c) =
    1478           0 :                     f_component(f, f_fem, context.get(), var_component+c,
    1479           0 :                                 xyz_values[qp], time);
    1480             : 
    1481             :                 // solution grad at the quadrature point
    1482           0 :                 OutputNumberGradient finegrad;
    1483           0 :                 libMesh::RawAccessor<OutputNumberGradient> g_accessor( finegrad, dim );
    1484             : 
    1485             :                 unsigned int g_rank;
    1486           0 :                 switch( FEInterface::field_type( fe_type ) )
    1487             :                   {
    1488           0 :                   case TYPE_SCALAR:
    1489             :                     {
    1490           0 :                       g_rank = 1;
    1491           0 :                       break;
    1492             :                     }
    1493           0 :                   case TYPE_VECTOR:
    1494             :                     {
    1495           0 :                       g_rank = 2;
    1496           0 :                       break;
    1497             :                     }
    1498           0 :                   default:
    1499           0 :                     libmesh_error_msg("Unknown field type!");
    1500             :                   }
    1501             : 
    1502           0 :                 if (cont == C_ONE)
    1503           0 :                   for (unsigned int c = 0; c < n_vec_dim; c++)
    1504           0 :                     for (unsigned int d = 0; d < g_rank; d++)
    1505           0 :                       g_accessor(c + d*dim ) =
    1506           0 :                         g_component(g, g_fem, context.get(), var_component,
    1507           0 :                                     xyz_values[qp], time)(c);
    1508             : 
    1509             :                 // Form shellface projection matrix
    1510           0 :                 for (unsigned int shellfacei=0, freei=0;
    1511           0 :                      shellfacei != n_dofs; ++shellfacei)
    1512             :                   {
    1513           0 :                     unsigned int i = shellface_dofs[shellfacei];
    1514             :                     // fixed DoFs aren't test functions
    1515           0 :                     if (dof_is_fixed[i])
    1516           0 :                       continue;
    1517           0 :                     for (unsigned int shellfacej=0, freej=0;
    1518           0 :                          shellfacej != n_dofs; ++shellfacej)
    1519             :                       {
    1520           0 :                         unsigned int j = shellface_dofs[shellfacej];
    1521           0 :                         if (dof_is_fixed[j])
    1522           0 :                           Fe(freei) -= phi[i][qp] * phi[j][qp] *
    1523           0 :                             JxW[qp] * Ue(j);
    1524             :                         else
    1525           0 :                           Ke(freei,freej) += phi[i][qp] *
    1526           0 :                             phi[j][qp] * JxW[qp];
    1527           0 :                         if (cont == C_ONE)
    1528             :                           {
    1529           0 :                             if (dof_is_fixed[j])
    1530           0 :                               Fe(freei) -= ((*dphi)[i][qp].contract((*dphi)[j][qp])) *
    1531           0 :                                 JxW[qp] * Ue(j);
    1532             :                             else
    1533           0 :                               Ke(freei,freej) += ((*dphi)[i][qp].contract((*dphi)[j][qp]))
    1534           0 :                                 * JxW[qp];
    1535             :                           }
    1536           0 :                         if (!dof_is_fixed[j])
    1537           0 :                           freej++;
    1538             :                       }
    1539           0 :                     Fe(freei) += (fineval * phi[i][qp]) * JxW[qp];
    1540           0 :                     if (cont == C_ONE)
    1541           0 :                       Fe(freei) += (finegrad.contract((*dphi)[i][qp])) *
    1542             :                         JxW[qp];
    1543           0 :                     freei++;
    1544             :                   }
    1545             :               }
    1546             : 
    1547           0 :             Ke.cholesky_solve(Fe, Ushellface);
    1548             : 
    1549             :             // Transfer new shellface solutions to element
    1550           0 :             for (unsigned int i=0; i != free_dofs; ++i)
    1551             :               {
    1552           0 :                 Number & ui = Ue(shellface_dofs[free_dof[i]]);
    1553           0 :                 libmesh_assert(std::abs(ui) < TOLERANCE ||
    1554             :                                std::abs(ui - Ushellface(i)) < TOLERANCE);
    1555           0 :                 ui = Ushellface(i);
    1556           0 :                 dof_is_fixed[shellface_dofs[free_dof[i]]] = true;
    1557             :               }
    1558             :           } // end for (shellface = 0..2)
    1559             :         } // end if (is_boundary_shellface_it != sebi.is_boundary_shellface_map.end())
    1560             :       } // end if (dim == 2 && cont != DISCONTINUOUS)
    1561             : 
    1562             :     // Lock the DofConstraints since it is shared among threads.
    1563             :     {
    1564        2254 :       Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
    1565             : 
    1566      177208 :       for (unsigned int i = 0; i < n_dofs; i++)
    1567             :         {
    1568       27332 :           DofConstraintRow empty_row;
    1569      177374 :           if (dof_is_fixed[i] && !libmesh_isnan(Ue(i)))
    1570       87491 :             add_fn (dof_indices[i], empty_row, Ue(i));
    1571             :         }
    1572             :     }
    1573       22492 :   } // apply_dirichlet_impl
    1574             : 
    1575             : public:
    1576         570 :   ConstrainDirichlet (const DofMap & dof_map_in,
    1577             :                       const MeshBase & mesh_in,
    1578             :                       const Real time_in,
    1579             :                       const DirichletBoundaries & dirichlets_in,
    1580       20172 :                       const AddConstraint & add_in) :
    1581       19032 :     dof_map(dof_map_in),
    1582       19032 :     mesh(mesh_in),
    1583       19032 :     time(time_in),
    1584       19032 :     dirichlets(dirichlets_in),
    1585       20172 :     add_fn(add_in) { }
    1586             : 
    1587             :   // This class can be default copy/move constructed.
    1588             :   ConstrainDirichlet (ConstrainDirichlet &&) = default;
    1589             :   ConstrainDirichlet (const ConstrainDirichlet &) = default;
    1590             : 
    1591             :   // This class cannot be default copy/move assigned because it
    1592             :   // contains reference members.
    1593             :   ConstrainDirichlet & operator= (const ConstrainDirichlet &) = delete;
    1594             :   ConstrainDirichlet & operator= (ConstrainDirichlet &&) = delete;
    1595             : 
    1596       20238 :   void operator()(const ConstElemRange & range) const
    1597             :   {
    1598             :     /**
    1599             :      * This method examines an arbitrary boundary solution to calculate
    1600             :      * corresponding Dirichlet constraints on the current mesh.  The
    1601             :      * input function \p f gives the arbitrary solution.
    1602             :      */
    1603             : 
    1604             :     // Figure out which System the DirichletBoundary objects are
    1605             :     // defined for. We break out of the loop as soon as we encounter a
    1606             :     // valid System pointer, the assumption is thus that all Variables
    1607             :     // are defined on the same System.
    1608         592 :     System * system = nullptr;
    1609             : 
    1610             :     // Map from boundary_id -> set<pair<id,DirichletBoundary*>> objects which
    1611             :     // are active on that boundary_id. Later we will use this to determine
    1612             :     // which DirichletBoundary objects to loop over for each Elem.
    1613             :     std::map<boundary_id_type, std::set<std::pair<unsigned int, DirichletBoundary *>>>
    1614        1184 :       boundary_id_to_ordered_dirichlet_boundaries;
    1615             : 
    1616       48090 :     for (auto dirichlet_id : index_range(dirichlets))
    1617             :       {
    1618             :         // Pointer to the current DirichletBoundary object
    1619       27852 :         const auto & dirichlet = dirichlets[dirichlet_id];
    1620             : 
    1621             :         // Construct mapping from boundary_id -> (dirichlet_id, DirichletBoundary)
    1622       84865 :         for (const auto & b_id : dirichlet->b)
    1623       57013 :           boundary_id_to_ordered_dirichlet_boundaries[b_id].emplace(dirichlet_id, dirichlet.get());
    1624             : 
    1625       64763 :         for (const auto & var : dirichlet->variables)
    1626             :           {
    1627       36911 :             const Variable & variable = dof_map.variable(var);
    1628        2148 :             auto current_system = variable.system();
    1629             : 
    1630       36911 :             if (!system)
    1631         592 :               system = current_system;
    1632             :             else
    1633       16673 :               libmesh_error_msg_if(current_system != system,
    1634             :                                    "All variables should be defined on the same System");
    1635             :           }
    1636             :       }
    1637             : 
    1638             :     // If we found no System, it could be because none of the
    1639             :     // Variables have one defined, or because there are
    1640             :     // DirichletBoundary objects with no Variables defined on
    1641             :     // them. These situations both indicate a likely error in the
    1642             :     // setup of a problem, so let's throw an error in this case.
    1643       20238 :     libmesh_error_msg_if(!system, "Valid System not found for any Variables.");
    1644             : 
    1645             :     // Construct a FEMContext object for the System on which the
    1646             :     // Variables in our DirichletBoundary objects are defined. This
    1647             :     // will be used in the apply_dirichlet_impl() function.
    1648             :     // We're not going to use elem_jacobian or subjacobians here so
    1649             :     // don't allocate them.
    1650             :     auto fem_context = std::make_unique<FEMContext>
    1651       20830 :       (*system, nullptr, /* allocate local_matrices = */ false);
    1652             : 
    1653             :     // At the time we are using this FEMContext, the current_local_solution
    1654             :     // vector is not initialized, but also we don't need it, so set
    1655             :     // the algebraic_type flag to DOFS_ONLY.
    1656         592 :     fem_context->set_algebraic_type(FEMContext::DOFS_ONLY);
    1657             : 
    1658             :     // Boundary info for the current mesh
    1659       20238 :     const BoundaryInfo & boundary_info = mesh.get_boundary_info();
    1660             : 
    1661             :     // This object keeps track of the BoundaryInfo for a single Elem
    1662       20830 :     SingleElemBoundaryInfo sebi(boundary_info, boundary_id_to_ordered_dirichlet_boundaries);
    1663             : 
    1664             :     // Iterate over all the elements in the range
    1665     1564909 :     for (const auto & elem : range)
    1666             :       {
    1667             :         // We only calculate Dirichlet constraints on active
    1668             :         // elements
    1669     1544671 :         if (!elem->active())
    1670      344533 :           continue;
    1671             : 
    1672             :         // Reinitialize BoundaryInfo data structures for the current elem
    1673     1167979 :         bool has_dirichlet_constraint = sebi.reinit(elem);
    1674             : 
    1675             :         // If this Elem has no boundary ids, go to the next one.
    1676     1167979 :         if (!has_dirichlet_constraint)
    1677      941514 :           continue;
    1678             : 
    1679      284220 :         for (const auto & db_pair : sebi.ordered_dbs)
    1680             :           {
    1681             :             // Get pointer to the DirichletBoundary object
    1682       12483 :             const auto & dirichlet = db_pair.second;
    1683             : 
    1684             :             // Loop over all the variables which this DirichletBoundary object is responsible for
    1685      325071 :             for (const auto & var : dirichlet->variables)
    1686             :               {
    1687      178870 :                 const Variable & variable = dof_map.variable(var);
    1688             : 
    1689             :                 // Make sure that the Variable and the DofMap agree on
    1690             :                 // what number this variable is.
    1691       15319 :                 libmesh_assert_equal_to(variable.number(), var);
    1692             : 
    1693       15319 :                 const FEType & fe_type = variable.type();
    1694             : 
    1695      178870 :                 if (fe_type.family == SCALAR)
    1696           0 :                   continue;
    1697             : 
    1698      178870 :                 switch( FEInterface::field_type( fe_type ) )
    1699             :                   {
    1700      177694 :                   case TYPE_SCALAR:
    1701             :                     {
    1702             :                       // For Lagrange FEs we don't need to do a full
    1703             :                       // blown projection, we can just interpolate
    1704             :                       // values directly.
    1705      177694 :                       if (fe_type.family == LAGRANGE)
    1706      165370 :                         this->apply_lagrange_dirichlet_impl<Real>( sebi, variable, *dirichlet, *fem_context );
    1707             :                       else
    1708       12324 :                         this->apply_dirichlet_impl<Real>( sebi, variable, *dirichlet, *fem_context );
    1709       15221 :                       break;
    1710             :                     }
    1711          98 :                   case TYPE_VECTOR:
    1712             :                     {
    1713        1176 :                       this->apply_dirichlet_impl<RealGradient>( sebi, variable, *dirichlet, *fem_context );
    1714          98 :                       break;
    1715             :                     }
    1716           0 :                   default:
    1717           0 :                     libmesh_error_msg("Unknown field type!");
    1718             :                   }
    1719             :               } // for (var : variables)
    1720             :           } // for (db_pair : ordered_dbs)
    1721             :       } // for (elem : range)
    1722       39292 :   } // operator()
    1723             : 
    1724             : }; // class ConstrainDirichlet
    1725             : 
    1726             : 
    1727             : #endif // LIBMESH_ENABLE_DIRICHLET
    1728             : 
    1729             : 
    1730             : } // anonymous namespace
    1731             : 
    1732             : 
    1733             : 
    1734             : namespace libMesh
    1735             : {
    1736             : 
    1737             : // ------------------------------------------------------------
    1738             : // DofMap member functions
    1739             : 
    1740             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
    1741             : 
    1742             : 
    1743     1412640 : dof_id_type DofMap::n_constrained_dofs() const
    1744             : {
    1745       34312 :   parallel_object_only();
    1746             : 
    1747     1412640 :   dof_id_type nc_dofs = this->n_local_constrained_dofs();
    1748     1412640 :   this->comm().sum(nc_dofs);
    1749     1412640 :   return nc_dofs;
    1750             : }
    1751             : 
    1752             : 
    1753     1432874 : dof_id_type DofMap::n_local_constrained_dofs() const
    1754             : {
    1755             :   const DofConstraints::const_iterator lower =
    1756       34884 :     _dof_constraints.lower_bound(this->first_dof()),
    1757             :     upper =
    1758       34884 :     _dof_constraints.lower_bound(this->end_dof());
    1759             : 
    1760     1467758 :   return cast_int<dof_id_type>(std::distance(lower, upper));
    1761             : }
    1762             : 
    1763             : 
    1764             : 
    1765      295431 : void DofMap::create_dof_constraints(const MeshBase & mesh, Real time)
    1766             : {
    1767        8488 :   parallel_object_only();
    1768             : 
    1769        8488 :   LOG_SCOPE("create_dof_constraints()", "DofMap");
    1770             : 
    1771        8488 :   libmesh_assert (mesh.is_prepared());
    1772             : 
    1773             :   // The user might have set boundary conditions after the mesh was
    1774             :   // prepared; we should double-check that those boundary conditions
    1775             :   // are still consistent.
    1776             : #ifdef DEBUG
    1777        8488 :   MeshTools::libmesh_assert_valid_boundary_ids(mesh);
    1778             : #endif
    1779             : 
    1780             :   // In a distributed mesh we might have constraint rows on some
    1781             :   // processors but not all; if we have constraint rows on *any*
    1782             :   // processor then we need to process them.
    1783      295431 :   bool constraint_rows_empty = mesh.get_constraint_rows().empty();
    1784      295431 :   this->comm().min(constraint_rows_empty);
    1785             : 
    1786             :   // We might get constraint equations from AMR hanging nodes in
    1787             :   // 2D/3D, or from spline constraint rows or boundary conditions in
    1788             :   // any dimension
    1789             :   const bool possible_local_constraints = false
    1790      295431 :     || !mesh.n_elem()
    1791      295431 :     || !constraint_rows_empty
    1792             : #ifdef LIBMESH_ENABLE_AMR
    1793      294211 :     || mesh.mesh_dimension() > 1
    1794             : #endif
    1795             : #ifdef LIBMESH_ENABLE_PERIODIC
    1796       41094 :     || !_periodic_boundaries->empty()
    1797             : #endif
    1798             : #ifdef LIBMESH_ENABLE_DIRICHLET
    1799      343583 :     || !_dirichlet_boundaries->empty()
    1800             : #endif
    1801             :     ;
    1802             : 
    1803             :   // Even if we don't have constraints, another processor might.
    1804      295431 :   bool possible_global_constraints = possible_local_constraints;
    1805             : #if defined(LIBMESH_ENABLE_PERIODIC) || defined(LIBMESH_ENABLE_DIRICHLET) || defined(LIBMESH_ENABLE_AMR)
    1806        8488 :   libmesh_assert(this->comm().verify(mesh.is_serial()));
    1807             : 
    1808      295431 :   this->comm().max(possible_global_constraints);
    1809             : #endif
    1810             : 
    1811             :   // Recalculate dof constraints from scratch.  (Or just clear them,
    1812             :   // if the user has just deleted their last dirichlet/periodic/user
    1813             :   // constraint)
    1814             :   // Note: any _stashed_dof_constraints are not cleared as it
    1815             :   // may be the user's intention to restore them later.
    1816             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
    1817        8488 :   _dof_constraints.clear();
    1818        8488 :   _primal_constraint_values.clear();
    1819        8488 :   _adjoint_constraint_values.clear();
    1820             : #endif
    1821             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    1822        8488 :   _node_constraints.clear();
    1823             : #endif
    1824             : 
    1825      295431 :   if (!possible_global_constraints)
    1826       35179 :     return;
    1827             : 
    1828             :   // Here we build the hanging node constraints.  This is done
    1829             :   // by enforcing the condition u_a = u_b along hanging sides.
    1830             :   // u_a = u_b is collocated at the nodes of side a, which gives
    1831             :   // one row of the constraint matrix.
    1832             : 
    1833             :   // Processors only compute their local constraints
    1834      518464 :   ConstElemRange range (mesh.local_elements_begin(),
    1835      777696 :                         mesh.local_elements_end());
    1836             : 
    1837             :   // Global computation fails if we're using a FEMFunctionBase BC on a
    1838             :   // ReplicatedMesh in parallel
    1839             :   // ConstElemRange range (mesh.elements_begin(),
    1840             :   //                       mesh.elements_end());
    1841             : 
    1842             :   // compute_periodic_constraints requires a point_locator() from our
    1843             :   // Mesh, but point_locator() construction is parallel and threaded.
    1844             :   // Rather than nest threads within threads we'll make sure it's
    1845             :   // preconstructed.
    1846             : #ifdef LIBMESH_ENABLE_PERIODIC
    1847      503081 :   bool need_point_locator = !_periodic_boundaries->empty() && !range.empty();
    1848             : 
    1849      259232 :   this->comm().max(need_point_locator);
    1850             : 
    1851      259232 :   if (need_point_locator)
    1852         805 :     mesh.sub_point_locator();
    1853             : #endif
    1854             : 
    1855             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    1856       22404 :   Threads::parallel_for (range,
    1857       14936 :                          ComputeNodeConstraints (_node_constraints,
    1858             : #ifdef LIBMESH_ENABLE_PERIODIC
    1859        7468 :                                                  *_periodic_boundaries,
    1860             : #endif
    1861             :                                                  mesh));
    1862             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    1863             : 
    1864             : 
    1865             :   // Look at all the variables in the system.  Reset the element
    1866             :   // range at each iteration -- there is no need to reconstruct it.
    1867      259232 :   const auto n_vars = this->n_variables();
    1868      553970 :   for (unsigned int variable_number=0; variable_number<n_vars;
    1869      286312 :        ++variable_number, range.reset())
    1870      303164 :     Threads::parallel_for (range,
    1871      303164 :                            ComputeConstraints (_dof_constraints,
    1872             :                                                *this,
    1873             : #ifdef LIBMESH_ENABLE_PERIODIC
    1874        8426 :                                                *_periodic_boundaries,
    1875             : #endif
    1876             :                                                mesh,
    1877             :                                                variable_number));
    1878             : 
    1879             : #ifdef LIBMESH_ENABLE_DIRICHLET
    1880             : 
    1881      259232 :   if (!_dirichlet_boundaries->empty())
    1882             :     {
    1883             :       // Sanity check that the boundary ids associated with the
    1884             :       // DirichletBoundary objects are actually present in the
    1885             :       // mesh. We do this check by default, but in cases where you
    1886             :       // intentionally add "inconsistent but valid" DirichletBoundary
    1887             :       // objects in parallel, this check can deadlock since it does a
    1888             :       // collective communication internally. In that case it is
    1889             :       // possible to disable this check by setting the flag to false.
    1890       17987 :       if (_verify_dirichlet_bc_consistency)
    1891       42456 :         for (const auto & dirichlet : *_dirichlet_boundaries)
    1892       24469 :           this->check_dirichlet_bcid_consistency(mesh, *dirichlet);
    1893             : 
    1894             :       // Threaded loop over local over elems applying all Dirichlet BCs
    1895             :       Threads::parallel_for
    1896       18495 :         (range,
    1897       17987 :          ConstrainDirichlet(*this, mesh, time, *_dirichlet_boundaries,
    1898       18495 :                             AddPrimalConstraint(*this)));
    1899             : 
    1900             :       // Threaded loop over local over elems per QOI applying all adjoint
    1901             :       // Dirichlet BCs.  Note that the ConstElemRange is reset before each
    1902             :       // execution of Threads::parallel_for().
    1903             : 
    1904       20172 :       for (auto qoi_index : index_range(_adjoint_dirichlet_boundaries))
    1905             :         {
    1906             :           const DirichletBoundaries & adb_q =
    1907         124 :             *(_adjoint_dirichlet_boundaries[qoi_index]);
    1908             : 
    1909        2185 :           if (!adb_q.empty())
    1910             :             Threads::parallel_for
    1911        2247 :               (range.reset(),
    1912        2185 :                ConstrainDirichlet(*this, mesh, time, adb_q,
    1913        2247 :                                   AddAdjointConstraint(*this, qoi_index)));
    1914             :         }
    1915             :     }
    1916             : 
    1917             : #endif // LIBMESH_ENABLE_DIRICHLET
    1918             : 
    1919             :   // Handle spline node constraints last, so we can try to move
    1920             :   // existing constraints onto the spline basis if necessary.
    1921      259232 :   if (!constraint_rows_empty)
    1922        1220 :     this->process_mesh_constraint_rows(mesh);
    1923             : }
    1924             : 
    1925             : 
    1926             : 
    1927             : #ifdef LIBMESH_ENABLE_DIRICHLET
    1928           0 : void DofMap::compute_dirichlet_values(const DirichletBoundaries & dirichlets,
    1929             :                                       const MeshBase & mesh,
    1930             :                                       const Real time,
    1931             :                                       DofConstraintValueMap & values) const
    1932             : {
    1933           0 :   parallel_object_only();
    1934             : 
    1935           0 :   values.clear();
    1936             : 
    1937           0 :   if (dirichlets.empty())
    1938           0 :     return;
    1939             : 
    1940           0 :   if (_verify_dirichlet_bc_consistency)
    1941           0 :     for (const auto & dirichlet : dirichlets)
    1942           0 :       this->check_dirichlet_bcid_consistency(mesh, *dirichlet);
    1943             : 
    1944             :   // Processors only project their local elements, as the constraint path does
    1945           0 :   ConstElemRange range (mesh.local_elements_begin(),
    1946           0 :                         mesh.local_elements_end());
    1947             : 
    1948             :   Threads::parallel_for
    1949           0 :     (range, ConstrainDirichlet(*this, mesh, time, dirichlets,
    1950           0 :                                CollectDirichletValues(*this, values)));
    1951             : }
    1952             : #endif // LIBMESH_ENABLE_DIRICHLET
    1953             : 
    1954             : 
    1955             : 
    1956        1220 : void DofMap::process_mesh_constraint_rows(const MeshBase & mesh)
    1957             : {
    1958             :   // If we already have simple Dirichlet constraints (with right hand
    1959             :   // sides but with no coupling between DoFs) on spline-constrained FE
    1960             :   // nodes, then we'll need a solve to compute the corresponding
    1961             :   // constraints on the relevant spline nodes.  (If we already have
    1962             :   // constraints with coupling between DoFs on spline-constrained FE
    1963             :   // nodes, then we'll need to go sit down and cry until we figure out
    1964             :   // how to handle that.)
    1965             : 
    1966          34 :   const auto & constraint_rows = mesh.get_constraint_rows();
    1967             : 
    1968             :   // This routine is too expensive to use unless we really might
    1969             :   // need it
    1970             : #ifdef DEBUG
    1971          34 :   bool constraint_rows_empty = constraint_rows.empty();
    1972          34 :   this->comm().min(constraint_rows_empty);
    1973          34 :   libmesh_assert(!constraint_rows_empty);
    1974             : #endif
    1975             : 
    1976             :   // We can't handle periodic boundary conditions on spline meshes
    1977             :   // yet.
    1978             : #ifdef LIBMESH_ENABLE_PERIODIC
    1979        1220 :   libmesh_error_msg_if (!_periodic_boundaries->empty(),
    1980             :                         "Periodic boundary conditions are not yet implemented for spline meshes");
    1981             : #endif
    1982             : 
    1983             :   // We can handle existing Dirichlet constraints, but we'll need
    1984             :   // to do solves to project them down onto the spline basis.
    1985        1186 :   std::unique_ptr<SparsityPattern::Build> sp;
    1986        1186 :   std::unique_ptr<SparseMatrix<Number>> mat;
    1987             : 
    1988             :   const unsigned int n_adjoint_rhs =
    1989        1220 :     _adjoint_constraint_values.size();
    1990             : 
    1991             :   // [0] for primal rhs, [q+1] for adjoint qoi q
    1992             :   std::vector<std::unique_ptr<NumericVector<Number>>>
    1993        1288 :     solve_rhs(n_adjoint_rhs+1);
    1994             : 
    1995             :   // Keep track of which spline DoFs will be Dirichlet.
    1996             :   // We use a set here to make it easier to find what processors
    1997             :   // to send the DoFs to later.
    1998          68 :   std::set<dof_id_type> my_dirichlet_spline_dofs;
    1999             : 
    2000             :   // And keep track of which non-spline Dofs were Dirichlet
    2001          68 :   std::unordered_set<dof_id_type> was_previously_constrained;
    2002             : 
    2003          68 :   const unsigned int sys_num = this->sys_number();
    2004      192810 :   for (auto & node_row : constraint_rows)
    2005             :     {
    2006      191590 :       const Node * node = node_row.first;
    2007       16528 :       libmesh_assert(node == mesh.node_ptr(node->id()));
    2008             : 
    2009             :       // Each processor only computes its own (and in distributed
    2010             :       // cases, is only guaranteed to have the dependency data to
    2011             :       // compute its own) constraints here.
    2012      208118 :       if (node->processor_id() != mesh.processor_id())
    2013       83862 :         continue;
    2014             : 
    2015      329608 :       for (auto var_num : IntRange<unsigned int>(0, this->n_variables()))
    2016             :         {
    2017       19154 :           const FEFamily & fe_family = this->variable_type(var_num).family;
    2018             : 
    2019             :           // constraint_rows only applies to nodal variables
    2020      230144 :           if (fe_family != LAGRANGE &&
    2021       18788 :               fe_family != RATIONAL_BERNSTEIN)
    2022           0 :             continue;
    2023             : 
    2024       38308 :           DofConstraintRow dc_row;
    2025             : 
    2026             :           const dof_id_type constrained_id =
    2027      230144 :             node->dof_number(sys_num, var_num, 0);
    2028      729015 :           for (const auto & [pr, val] : node_row.second)
    2029             :             {
    2030      498871 :               const Elem * spline_elem = pr.first;
    2031       41623 :               libmesh_assert(spline_elem == mesh.elem_ptr(spline_elem->id()));
    2032             : 
    2033             :               const Node & spline_node =
    2034      498871 :                 spline_elem->node_ref(pr.second);
    2035             : 
    2036             :               const dof_id_type spline_dof_id =
    2037      498871 :                 spline_node.dof_number(sys_num, var_num, 0);
    2038      498871 :               dc_row[spline_dof_id] = val;
    2039             :             }
    2040             : 
    2041             :           // See if we already have a constraint here.
    2042      230144 :           if (this->is_constrained_dof(constrained_id))
    2043             :             {
    2044         396 :               was_previously_constrained.insert(constrained_id);
    2045             : 
    2046             :               // Keep track of which spline DoFs will be
    2047             :               // inheriting this non-spline DoF's constraints
    2048       11664 :               for (auto & row_entry : dc_row)
    2049        6912 :                 my_dirichlet_spline_dofs.insert(row_entry.first);
    2050             : 
    2051             :               // If it wasn't a simple Dirichlet-type constraint
    2052             :               // then I don't know what to do with it.  We'll make
    2053             :               // this an assertion only because this should only
    2054             :               // crop up with periodic boundary conditions, which
    2055             :               // we've already made sure we don't have.
    2056         396 :               libmesh_assert(_dof_constraints[constrained_id].empty());
    2057             :             }
    2058             : 
    2059             :           // Add the constraint, replacing any previous, so we can
    2060             :           // use the new constraint in setting up the solve below
    2061       38308 :           this->add_constraint_row(constrained_id, dc_row, false);
    2062             :         }
    2063             :     }
    2064             : 
    2065             :   // my_dirichlet_spline_dofs may now include DoFs whose owners
    2066             :   // don't know they need to become spline DoFs!  We need to push
    2067             :   // this data to them.
    2068        1220 :   if (this->comm().size() > 1)
    2069             :     {
    2070             :       std::unordered_map
    2071             :         <processor_id_type, std::vector<dof_id_type>>
    2072          68 :         their_dirichlet_spline_dofs;
    2073             : 
    2074             :       // If we ever change the underlying container here then we'd
    2075             :       // better do some kind of sort before using it; we'll rely
    2076             :       // on sorting to make the processor id lookup efficient.
    2077          34 :       libmesh_assert(std::is_sorted(my_dirichlet_spline_dofs.begin(),
    2078             :                                     my_dirichlet_spline_dofs.end()));
    2079        1189 :       processor_id_type destination_pid = 0;
    2080        3475 :       for (auto d : my_dirichlet_spline_dofs)
    2081             :         {
    2082         216 :           libmesh_assert_less(d, this->end_dof(this->comm().size()-1));
    2083        3378 :           while (d >= this->end_dof(destination_pid))
    2084         872 :             destination_pid++;
    2085             : 
    2086        2502 :           if (destination_pid != this->processor_id())
    2087         612 :             their_dirichlet_spline_dofs[destination_pid].push_back(d);
    2088             :         }
    2089             : 
    2090             :       auto receive_dof_functor =
    2091          88 :         [& my_dirichlet_spline_dofs]
    2092             :         (processor_id_type,
    2093          92 :          const std::vector<dof_id_type> & dofs)
    2094             :         {
    2095          96 :           my_dirichlet_spline_dofs.insert(dofs.begin(), dofs.end());
    2096        1193 :         };
    2097             : 
    2098             :       Parallel::push_parallel_vector_data
    2099        1189 :         (this->comm(), their_dirichlet_spline_dofs, receive_dof_functor);
    2100             :     }
    2101             : 
    2102             : 
    2103             :   // If anyone had any prior constraints in effect, then we need
    2104             :   // to convert them to constraints on the spline nodes.
    2105             :   //
    2106             :   // NOT simply testing prior_constraints here; maybe it turned
    2107             :   // out that all our constraints were on non-spline-constrained
    2108             :   // parts of a hybrid mesh?
    2109             :   bool important_prior_constraints =
    2110        1220 :     !was_previously_constrained.empty();
    2111        1220 :   this->comm().max(important_prior_constraints);
    2112             : 
    2113        1220 :   if (important_prior_constraints)
    2114             :     {
    2115             :       // Now that we have the spline constraints added, we can
    2116             :       // finally construct a sparsity pattern that correctly
    2117             :       // accounts for those constraints!
    2118         552 :       mat = SparseMatrix<Number>::build(this->comm());
    2119         568 :       for (auto q : IntRange<unsigned int>(0, n_adjoint_rhs+1))
    2120             :         {
    2121         284 :           solve_rhs[q] = NumericVector<Number>::build(this->comm());
    2122         300 :           solve_rhs[q]->init(this->n_dofs(), this->n_local_dofs(),
    2123          16 :                              false, PARALLEL);
    2124             :         }
    2125             : 
    2126             :       // We need to compute our own sparsity pattern, to take into
    2127             :       // account the particularly non-sparse rows that can be
    2128             :       // created by the spline constraints we just added.
    2129         284 :       mat->attach_dof_map(*this);
    2130         552 :       sp = this->build_sparsity(mesh);
    2131         284 :       mat->attach_sparsity_pattern(*sp);
    2132         284 :       mat->init();
    2133             : 
    2134       92892 :       for (auto & node_row : constraint_rows)
    2135             :         {
    2136       92608 :           const Node * node = node_row.first;
    2137        8712 :           libmesh_assert(node == mesh.node_ptr(node->id()));
    2138             : 
    2139      370432 :           for (auto var_num : IntRange<unsigned int>(0, this->n_variables()))
    2140             :             {
    2141       26136 :               const FEFamily & fe_family = this->variable_type(var_num).family;
    2142             : 
    2143             :               // constraint_rows only applies to nodal variables
    2144      277824 :               if (fe_family != LAGRANGE &&
    2145       26136 :                   fe_family != RATIONAL_BERNSTEIN)
    2146           0 :                 continue;
    2147             : 
    2148             :               const dof_id_type constrained_id =
    2149      277824 :                 node->dof_number(sys_num, var_num, 0);
    2150             : 
    2151       52272 :               if (was_previously_constrained.count(constrained_id))
    2152             :                 {
    2153        9504 :                   for (auto q : IntRange<int>(0, n_adjoint_rhs+1))
    2154             :                     {
    2155        5544 :                       DenseMatrix<Number> K(1,1);
    2156        4752 :                       DenseVector<Number> F(1);
    2157        5148 :                       std::vector<dof_id_type> dof_indices(1, constrained_id);
    2158             : 
    2159        4356 :                       K(0,0) = 1;
    2160             : 
    2161        4752 :                       DofConstraintValueMap & vals = q ?
    2162        4356 :                         _adjoint_constraint_values[q-1] :
    2163         396 :                         _primal_constraint_values;
    2164             : 
    2165             :                       DofConstraintValueMap::const_iterator rhsit =
    2166         396 :                         vals.find(constrained_id);
    2167        4752 :                       F(0) = (rhsit == vals.end()) ?  0 : rhsit->second;
    2168             : 
    2169             :                       // We no longer need any rhs values here directly.
    2170        4752 :                       if (rhsit != vals.end())
    2171        4356 :                         vals.erase(rhsit);
    2172             : 
    2173             :                       this->heterogeneously_constrain_element_matrix_and_vector
    2174        4752 :                         (K, F, dof_indices, false, q ? (q-1) : -1);
    2175        4752 :                       if (!q)
    2176        4752 :                         mat->add_matrix(K, dof_indices);
    2177        4752 :                       solve_rhs[q]->add_vector(F, dof_indices);
    2178        3960 :                     }
    2179             :                 }
    2180             :             }
    2181             :         }
    2182             : 
    2183             :       // Any DoFs that aren't part of any constraint, directly or
    2184             :       // indirectly, need a diagonal term to make the matrix
    2185             :       // here invertible.
    2186       17224 :       for (dof_id_type d : IntRange<dof_id_type>(this->first_dof(),
    2187      220712 :                                                  this->end_dof()))
    2188       50472 :         if (!was_previously_constrained.count(d) &&
    2189       16560 :             !my_dirichlet_spline_dofs.count(d))
    2190      196128 :           mat->add(d,d,1);
    2191             : 
    2192             :       // At this point, we're finally ready to solve for Dirichlet
    2193             :       // constraint values on spline nodes.
    2194             :       std::unique_ptr<LinearSolver<Number>> linear_solver =
    2195         292 :         LinearSolver<Number>::build(this->comm());
    2196             : 
    2197             :       std::unique_ptr<NumericVector<Number>> projected_vals =
    2198         292 :         NumericVector<Number>::build(this->comm());
    2199             : 
    2200         292 :       projected_vals->init(this->n_dofs(), this->n_local_dofs(),
    2201          16 :                            false, PARALLEL);
    2202             : 
    2203          16 :       DofConstraintRow empty_row;
    2204        3488 :       for (auto sd : my_dirichlet_spline_dofs)
    2205        2832 :         if (this->local_index(sd))
    2206         216 :           this->add_constraint_row(sd, empty_row);
    2207             : 
    2208         568 :       for (auto q : IntRange<unsigned int>(0, n_adjoint_rhs+1))
    2209             :         {
    2210             :           // FIXME: we don't have an EquationSystems here, but I'd
    2211             :           // rather not hardcode these...
    2212           8 :           const double tol = double(TOLERANCE * TOLERANCE);
    2213           8 :           const unsigned int max_its = 5000;
    2214             : 
    2215         284 :           linear_solver->solve(*mat, *projected_vals,
    2216         308 :                                *(solve_rhs[q]), tol, max_its);
    2217             : 
    2218         284 :           DofConstraintValueMap & vals = q ?
    2219         276 :             _adjoint_constraint_values[q-1] :
    2220           8 :             _primal_constraint_values;
    2221             : 
    2222        3488 :           for (auto sd : my_dirichlet_spline_dofs)
    2223        2832 :             if (this->local_index(sd))
    2224             :               {
    2225        2592 :                 Number constraint_rhs = (*projected_vals)(sd);
    2226             : 
    2227             :                 std::pair<DofConstraintValueMap::iterator, bool> rhs_it =
    2228         216 :                   vals.emplace(sd, constraint_rhs);
    2229        2592 :                 if (!rhs_it.second)
    2230        2592 :                   rhs_it.first->second = constraint_rhs;
    2231             :               }
    2232             :         }
    2233         268 :     }
    2234        1220 : }
    2235             : 
    2236             : 
    2237             : 
    2238      591878 : void DofMap::add_constraint_row (const dof_id_type dof_number,
    2239             :                                  const DofConstraintRow & constraint_row,
    2240             :                                  const Number constraint_rhs,
    2241             :                                  const bool forbid_constraint_overwrite)
    2242             : {
    2243             :   // Optionally allow the user to overwrite constraints.  Defaults to false.
    2244      591878 :   libmesh_error_msg_if(forbid_constraint_overwrite && this->is_constrained_dof(dof_number),
    2245             :                        "ERROR: DOF " << dof_number << " was already constrained!");
    2246             : 
    2247       48011 :   libmesh_assert_less(dof_number, this->n_dofs());
    2248             : 
    2249             :   // There is an implied "1" on the diagonal of the constraint row, and the user
    2250             :   // should not try to manually set _any_ value on the diagonal.
    2251       48011 :   libmesh_assert_msg(!constraint_row.count(dof_number),
    2252             :                      "Error: constraint_row for dof " << dof_number <<
    2253             :                      " should not contain an entry for dof " << dof_number);
    2254             : 
    2255             : #ifndef NDEBUG
    2256       93136 :   for (const auto & pr : constraint_row)
    2257       45125 :     libmesh_assert_less(pr.first, this->n_dofs());
    2258             : #endif
    2259             : 
    2260             :   // Store the constraint_row in the map
    2261      591878 :   _dof_constraints.insert_or_assign(dof_number, constraint_row);
    2262             : 
    2263             :   std::pair<DofConstraintValueMap::iterator, bool> rhs_it =
    2264       48011 :     _primal_constraint_values.emplace(dof_number, constraint_rhs);
    2265      591878 :   if (!rhs_it.second)
    2266        4752 :     rhs_it.first->second = constraint_rhs;
    2267      591878 : }
    2268             : 
    2269             : 
    2270       14304 : void DofMap::add_adjoint_constraint_row (const unsigned int qoi_index,
    2271             :                                          const dof_id_type dof_number,
    2272             :                                          const DofConstraintRow & /*constraint_row*/,
    2273             :                                          const Number constraint_rhs,
    2274             :                                          const bool forbid_constraint_overwrite)
    2275             : {
    2276             :   // Optionally allow the user to overwrite constraints.  Defaults to false.
    2277       14304 :   if (forbid_constraint_overwrite)
    2278             :     {
    2279       14304 :       libmesh_error_msg_if(!this->is_constrained_dof(dof_number),
    2280             :                            "ERROR: DOF " << dof_number << " has no corresponding primal constraint!");
    2281             : #ifndef NDEBUG
    2282             :       // No way to do this without a non-normalized tolerance?
    2283             : 
    2284             :       // // If the user passed in more than just the rhs, let's check the
    2285             :       // // coefficients for consistency
    2286             :       // if (!constraint_row.empty())
    2287             :       //   {
    2288             :       //     DofConstraintRow row = _dof_constraints[dof_number];
    2289             :       //     for (const auto & [dof, val] : row)
    2290             :       //       libmesh_assert(constraint_row.find(dof)->second == val);
    2291             :       //   }
    2292             :       //
    2293             :       // if (_adjoint_constraint_values[qoi_index].find(dof_number) !=
    2294             :       //     _adjoint_constraint_values[qoi_index].end())
    2295             :       //   libmesh_assert_equal_to(_adjoint_constraint_values[qoi_index][dof_number],
    2296             :       //                           constraint_rhs);
    2297             : 
    2298             : #endif
    2299             :     }
    2300             : 
    2301             :   // Creates the map of rhs values if it doesn't already exist; then
    2302             :   // adds the current value to that map
    2303             : 
    2304             :   // Store the rhs value in the map
    2305       14304 :   _adjoint_constraint_values[qoi_index].insert_or_assign(dof_number, constraint_rhs);
    2306       14304 : }
    2307             : 
    2308             : 
    2309             : 
    2310             : 
    2311           0 : void DofMap::print_dof_constraints(std::ostream & os,
    2312             :                                    bool print_nonlocal) const
    2313             : {
    2314           0 :   parallel_object_only();
    2315             : 
    2316             :   std::string local_constraints =
    2317           0 :     this->get_local_constraints(print_nonlocal);
    2318             : 
    2319           0 :   if (this->processor_id())
    2320             :     {
    2321           0 :       this->comm().send(0, local_constraints);
    2322             :     }
    2323             :   else
    2324             :     {
    2325           0 :       os << "Processor 0:\n";
    2326           0 :       os << local_constraints;
    2327             : 
    2328           0 :       for (auto p : IntRange<processor_id_type>(1, this->n_processors()))
    2329             :         {
    2330           0 :           this->comm().receive(p, local_constraints);
    2331           0 :           os << "Processor " << p << ":\n";
    2332           0 :           os << local_constraints;
    2333             :         }
    2334             :     }
    2335           0 : }
    2336             : 
    2337           0 : std::string DofMap::get_local_constraints(bool print_nonlocal) const
    2338             : {
    2339           0 :   std::ostringstream os;
    2340             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    2341           0 :   if (print_nonlocal)
    2342           0 :     os << "All ";
    2343             :   else
    2344           0 :     os << "Local ";
    2345             : 
    2346           0 :   os << "Node Constraints:"
    2347           0 :      << std::endl;
    2348             : 
    2349           0 :   for (const auto & [node, pr] : _node_constraints)
    2350             :     {
    2351             :       // Skip non-local nodes if requested
    2352           0 :       if (!print_nonlocal &&
    2353           0 :           node->processor_id() != this->processor_id())
    2354           0 :         continue;
    2355             : 
    2356           0 :       const NodeConstraintRow & row = pr.first;
    2357           0 :       const Point & offset = pr.second;
    2358             : 
    2359           0 :       os << "Constraints for Node id " << node->id()
    2360           0 :          << ": \t";
    2361             : 
    2362           0 :       for (const auto & [cnode, val] : row)
    2363           0 :         os << " (" << cnode->id() << "," << val << ")\t";
    2364             : 
    2365           0 :       os << "rhs: " << offset;
    2366             : 
    2367           0 :       os << std::endl;
    2368             :     }
    2369             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    2370             : 
    2371           0 :   if (print_nonlocal)
    2372           0 :     os << "All ";
    2373             :   else
    2374           0 :     os << "Local ";
    2375             : 
    2376           0 :   os << "DoF Constraints:"
    2377           0 :      << std::endl;
    2378             : 
    2379           0 :   for (const auto & [i, row] : _dof_constraints)
    2380             :     {
    2381             :       // Skip non-local dofs if requested
    2382           0 :       if (!print_nonlocal && !this->local_index(i))
    2383           0 :         continue;
    2384             : 
    2385             :       DofConstraintValueMap::const_iterator rhsit =
    2386           0 :         _primal_constraint_values.find(i);
    2387           0 :       const Number rhs = (rhsit == _primal_constraint_values.end()) ?
    2388           0 :         0 : rhsit->second;
    2389             : 
    2390           0 :       os << "Constraints for DoF " << i
    2391           0 :          << ": \t";
    2392             : 
    2393           0 :       for (const auto & item : row)
    2394           0 :         os << " (" << item.first << "," << item.second << ")\t";
    2395             : 
    2396           0 :       os << "rhs: " << rhs;
    2397           0 :       os << std::endl;
    2398             :     }
    2399             : 
    2400           0 :   for (unsigned int qoi_index = 0,
    2401           0 :        n_qois = cast_int<unsigned int>(_adjoint_dirichlet_boundaries.size());
    2402           0 :        qoi_index != n_qois; ++qoi_index)
    2403             :     {
    2404           0 :       os << "Adjoint " << qoi_index << " DoF rhs values:"
    2405           0 :          << std::endl;
    2406             : 
    2407           0 :       if (auto adjoint_map_it = _adjoint_constraint_values.find(qoi_index);
    2408           0 :           adjoint_map_it != _adjoint_constraint_values.end())
    2409           0 :         for (const auto & [i, rhs] : adjoint_map_it->second)
    2410             :           {
    2411             :             // Skip non-local dofs if requested
    2412           0 :             if (!print_nonlocal && !this->local_index(i))
    2413           0 :               continue;
    2414             : 
    2415           0 :             os << "RHS for DoF " << i
    2416           0 :                << ": " << rhs;
    2417             : 
    2418           0 :             os << std::endl;
    2419             :           }
    2420             :     }
    2421             : 
    2422           0 :   return os.str();
    2423           0 : }
    2424             : 
    2425             : 
    2426             : 
    2427    27029005 : void DofMap::constrain_element_matrix (DenseMatrix<Number> & matrix,
    2428             :                                        std::vector<dof_id_type> & elem_dofs,
    2429             :                                        bool asymmetric_constraint_rows) const
    2430             : {
    2431     1838984 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.m());
    2432     1838984 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.n());
    2433             : 
    2434             :   // check for easy return
    2435    27029005 :   if (this->_dof_constraints.empty())
    2436    11593632 :     return;
    2437             : 
    2438             :   // The constrained matrix is built up as C^T K C.
    2439    19080109 :   DenseMatrix<Number> C;
    2440             : 
    2441             : 
    2442    15435373 :   this->build_constraint_matrix (C, elem_dofs);
    2443             : 
    2444     3644736 :   LOG_SCOPE("constrain_elem_matrix()", "DofMap");
    2445             : 
    2446             :   // It is possible that the matrix is not constrained at all.
    2447    15454905 :   if ((C.m() == matrix.m()) &&
    2448      221603 :       (C.n() == elem_dofs.size())) // It the matrix is constrained
    2449             :     {
    2450             :       // Compute the matrix-matrix-matrix product C^T K C
    2451      221603 :       matrix.left_multiply_transpose  (C);
    2452      221603 :       matrix.right_multiply (C);
    2453             : 
    2454             : 
    2455       19532 :       libmesh_assert_equal_to (matrix.m(), matrix.n());
    2456       19532 :       libmesh_assert_equal_to (matrix.m(), elem_dofs.size());
    2457       19532 :       libmesh_assert_equal_to (matrix.n(), elem_dofs.size());
    2458             : 
    2459             : 
    2460     1805615 :       for (unsigned int i=0,
    2461       39064 :            n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    2462     1844679 :            i != n_elem_dofs; i++)
    2463             :         // If the DOF is constrained
    2464     1767178 :         if (this->is_constrained_dof(elem_dofs[i]))
    2465             :           {
    2466     6978208 :             for (auto j : make_range(matrix.n()))
    2467     6361975 :               matrix(i,j) = 0.;
    2468             : 
    2469      583009 :             matrix(i,i) = 1.;
    2470             : 
    2471      586121 :             if (asymmetric_constraint_rows)
    2472             :               {
    2473             :                 DofConstraints::const_iterator
    2474        3684 :                   pos = _dof_constraints.find(elem_dofs[i]);
    2475             : 
    2476        3684 :                 libmesh_assert (pos != _dof_constraints.end());
    2477             : 
    2478        3684 :                 const DofConstraintRow & constraint_row = pos->second;
    2479             : 
    2480             :                 // This is an overzealous assertion in the presence of
    2481             :                 // heterogeneous constraints: we now can constrain "u_i = c"
    2482             :                 // with no other u_j terms involved.
    2483             :                 //
    2484             :                 // libmesh_assert (!constraint_row.empty());
    2485             : 
    2486      104748 :                 for (const auto & item : constraint_row)
    2487      350886 :                   for (unsigned int j=0; j != n_elem_dofs; j++)
    2488      319140 :                     if (elem_dofs[j] == item.first)
    2489       53670 :                       matrix(i,j) = -item.second;
    2490             :               }
    2491             :           }
    2492             :     } // end if is constrained...
    2493    11790637 : }
    2494             : 
    2495             : 
    2496             : 
    2497    14710852 : void DofMap::constrain_element_matrix_and_vector (DenseMatrix<Number> & matrix,
    2498             :                                                   DenseVector<Number> & rhs,
    2499             :                                                   std::vector<dof_id_type> & elem_dofs,
    2500             :                                                   bool asymmetric_constraint_rows) const
    2501             : {
    2502     1300170 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.m());
    2503     1300170 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.n());
    2504     1300170 :   libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
    2505             : 
    2506             :   // check for easy return
    2507    14710852 :   if (this->_dof_constraints.empty())
    2508     7941830 :     return;
    2509             : 
    2510             :   // The constrained matrix is built up as C^T K C.
    2511             :   // The constrained RHS is built up as C^T F
    2512     8185792 :   DenseMatrix<Number> C;
    2513             : 
    2514     6769022 :   this->build_constraint_matrix (C, elem_dofs);
    2515             : 
    2516     1416770 :   LOG_SCOPE("cnstrn_elem_mat_vec()", "DofMap");
    2517             : 
    2518             :   // It is possible that the matrix is not constrained at all.
    2519     6890092 :   if ((C.m() == matrix.m()) &&
    2520     1242677 :       (C.n() == elem_dofs.size())) // It the matrix is constrained
    2521             :     {
    2522             :       // Compute the matrix-matrix-matrix product C^T K C
    2523     1242677 :       matrix.left_multiply_transpose  (C);
    2524     1242677 :       matrix.right_multiply (C);
    2525             : 
    2526             : 
    2527      121070 :       libmesh_assert_equal_to (matrix.m(), matrix.n());
    2528      121070 :       libmesh_assert_equal_to (matrix.m(), elem_dofs.size());
    2529      121070 :       libmesh_assert_equal_to (matrix.n(), elem_dofs.size());
    2530             : 
    2531             : 
    2532    17946304 :       for (unsigned int i=0,
    2533      242140 :            n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    2534    18188444 :            i != n_elem_dofs; i++)
    2535    18522246 :         if (this->is_constrained_dof(elem_dofs[i]))
    2536             :           {
    2537   220027669 :             for (auto j : make_range(matrix.n()))
    2538   202480594 :               matrix(i,j) = 0.;
    2539             : 
    2540             :             // If the DOF is constrained
    2541     5620288 :             matrix(i,i) = 1.;
    2542             : 
    2543             :             // This will put a nonsymmetric entry in the constraint
    2544             :             // row to ensure that the linear system produces the
    2545             :             // correct value for the constrained DOF.
    2546     5938685 :             if (asymmetric_constraint_rows)
    2547             :               {
    2548             :                 DofConstraints::const_iterator
    2549      179993 :                   pos = _dof_constraints.find(elem_dofs[i]);
    2550             : 
    2551      179993 :                 libmesh_assert (pos != _dof_constraints.end());
    2552             : 
    2553      179993 :                 const DofConstraintRow & constraint_row = pos->second;
    2554             : 
    2555             :                 // p refinement creates empty constraint rows
    2556             :                 //    libmesh_assert (!constraint_row.empty());
    2557             : 
    2558     5017306 :                 for (const auto & item : constraint_row)
    2559    51672175 :                   for (unsigned int j=0; j != n_elem_dofs; j++)
    2560    53050158 :                     if (elem_dofs[j] == item.first)
    2561     3448773 :                       matrix(i,j) = -item.second;
    2562             :               }
    2563             :           }
    2564             : 
    2565             : 
    2566             :       // Compute the matrix-vector product C^T F
    2567      242140 :       DenseVector<Number> old_rhs(rhs);
    2568             : 
    2569             :       // compute matrix/vector product
    2570     1242677 :       C.vector_mult_transpose(rhs, old_rhs);
    2571             :     } // end if is constrained...
    2572     5352252 : }
    2573             : 
    2574             : 
    2575             : 
    2576      684225 : void DofMap::heterogeneously_constrain_element_matrix_and_vector (DenseMatrix<Number> & matrix,
    2577             :                                                                   DenseVector<Number> & rhs,
    2578             :                                                                   std::vector<dof_id_type> & elem_dofs,
    2579             :                                                                   bool asymmetric_constraint_rows,
    2580             :                                                                   int qoi_index) const
    2581             : {
    2582       67251 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.m());
    2583       67251 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.n());
    2584       67251 :   libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
    2585             : 
    2586             :   // check for easy return
    2587      684225 :   if (this->_dof_constraints.empty())
    2588       30122 :     return;
    2589             : 
    2590             :   // The constrained matrix is built up as C^T K C.
    2591             :   // The constrained RHS is built up as C^T (F - K H)
    2592      787351 :   DenseMatrix<Number> C;
    2593      654103 :   DenseVector<Number> H;
    2594             : 
    2595      654103 :   this->build_constraint_matrix_and_vector (C, H, elem_dofs, qoi_index);
    2596             : 
    2597      133248 :   LOG_SCOPE("hetero_cnstrn_elem_mat_vec()", "DofMap");
    2598             : 
    2599             :   // It is possible that the matrix is not constrained at all.
    2600      670393 :   if ((C.m() == matrix.m()) &&
    2601      172597 :       (C.n() == elem_dofs.size())) // It the matrix is constrained
    2602             :     {
    2603             :       // We may have rhs values to use later
    2604       16290 :       const DofConstraintValueMap * rhs_values = nullptr;
    2605      172597 :       if (qoi_index < 0)
    2606      172597 :         rhs_values = &_primal_constraint_values;
    2607           0 :       else if (auto it = _adjoint_constraint_values.find(qoi_index);
    2608           0 :                it != _adjoint_constraint_values.end())
    2609           0 :         rhs_values = &it->second;
    2610             : 
    2611             :       // Compute matrix/vector product K H
    2612      172597 :       DenseVector<Number> KH;
    2613      172597 :       matrix.vector_mult(KH, H);
    2614             : 
    2615             :       // Compute the matrix-vector product C^T (F - KH)
    2616       32580 :       DenseVector<Number> F_minus_KH(rhs);
    2617      156307 :       F_minus_KH -= KH;
    2618      172597 :       C.vector_mult_transpose(rhs, F_minus_KH);
    2619             : 
    2620             :       // Compute the matrix-matrix-matrix product C^T K C
    2621      172597 :       matrix.left_multiply_transpose  (C);
    2622      172597 :       matrix.right_multiply (C);
    2623             : 
    2624       16290 :       libmesh_assert_equal_to (matrix.m(), matrix.n());
    2625       16290 :       libmesh_assert_equal_to (matrix.m(), elem_dofs.size());
    2626       16290 :       libmesh_assert_equal_to (matrix.n(), elem_dofs.size());
    2627             : 
    2628     3154352 :       for (unsigned int i=0,
    2629       32580 :            n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    2630     3186932 :            i != n_elem_dofs; i++)
    2631             :         {
    2632     3309045 :           const dof_id_type dof_id = elem_dofs[i];
    2633             : 
    2634     2719625 :           if (this->is_constrained_dof(dof_id))
    2635             :             {
    2636    19117355 :               for (auto j : make_range(matrix.n()))
    2637    16936862 :                 matrix(i,j) = 0.;
    2638             : 
    2639             :               // If the DOF is constrained
    2640      838246 :               matrix(i,i) = 1.;
    2641             : 
    2642             :               // This will put a nonsymmetric entry in the constraint
    2643             :               // row to ensure that the linear system produces the
    2644             :               // correct value for the constrained DOF.
    2645      902897 :               if (asymmetric_constraint_rows)
    2646             :                 {
    2647             :                   DofConstraints::const_iterator
    2648       85547 :                     pos = _dof_constraints.find(dof_id);
    2649             : 
    2650       85547 :                   libmesh_assert (pos != _dof_constraints.end());
    2651             : 
    2652       85547 :                   const DofConstraintRow & constraint_row = pos->second;
    2653             : 
    2654      969308 :                   for (const auto & item : constraint_row)
    2655     1677124 :                     for (unsigned int j=0; j != n_elem_dofs; j++)
    2656     1720581 :                       if (elem_dofs[j] == item.first)
    2657       95333 :                         matrix(i,j) = -item.second;
    2658             : 
    2659      881254 :                   if (rhs_values)
    2660             :                     {
    2661             :                       const DofConstraintValueMap::const_iterator valpos =
    2662       85547 :                         rhs_values->find(dof_id);
    2663             : 
    2664      904191 :                       rhs(i) = (valpos == rhs_values->end()) ?
    2665       22937 :                         0 : valpos->second;
    2666             :                     }
    2667             :                 }
    2668             :               else
    2669       19883 :                 rhs(i) = 0.;
    2670             :             }
    2671             :         }
    2672             : 
    2673             :     } // end if is constrained...
    2674      520855 : }
    2675             : 
    2676             : 
    2677        1760 : void DofMap::heterogeneously_constrain_element_jacobian_and_residual
    2678             :   (DenseMatrix<Number> & matrix,
    2679             :    DenseVector<Number> & rhs,
    2680             :    std::vector<dof_id_type> & elem_dofs,
    2681             :    NumericVector<Number> & solution_local) const
    2682             : {
    2683         160 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.m());
    2684         160 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.n());
    2685         160 :   libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
    2686             : 
    2687         160 :   libmesh_assert (solution_local.type() == SERIAL ||
    2688             :                   solution_local.type() == GHOSTED);
    2689             : 
    2690             :   // check for easy return
    2691        1760 :   if (this->_dof_constraints.empty())
    2692           0 :     return;
    2693             : 
    2694             :   // The constrained matrix is built up as C^T K C.
    2695             :   // The constrained RHS is built up as C^T F
    2696             :   // Asymmetric residual terms are added if we do not have x = Cx+h
    2697        1920 :   DenseMatrix<Number> C;
    2698        1600 :   DenseVector<Number> H;
    2699             : 
    2700        1760 :   this->build_constraint_matrix_and_vector (C, H, elem_dofs);
    2701             : 
    2702         160 :   LOG_SCOPE("hetero_cnstrn_elem_jac_res()", "DofMap");
    2703             : 
    2704             :   // It is possible that the matrix is not constrained at all.
    2705        1920 :   if ((C.m() != matrix.m()) ||
    2706        1760 :       (C.n() != elem_dofs.size()))
    2707           0 :     return;
    2708             : 
    2709             :   // Compute the matrix-vector product C^T F
    2710         320 :   DenseVector<Number> old_rhs(rhs);
    2711        1760 :   C.vector_mult_transpose(rhs, old_rhs);
    2712             : 
    2713             :   // Compute the matrix-matrix-matrix product C^T K C
    2714        1760 :   matrix.left_multiply_transpose  (C);
    2715        1760 :   matrix.right_multiply (C);
    2716             : 
    2717         160 :   libmesh_assert_equal_to (matrix.m(), matrix.n());
    2718         160 :   libmesh_assert_equal_to (matrix.m(), elem_dofs.size());
    2719         160 :   libmesh_assert_equal_to (matrix.n(), elem_dofs.size());
    2720             : 
    2721        8480 :   for (unsigned int i=0,
    2722         320 :        n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    2723        8800 :        i != n_elem_dofs; i++)
    2724             :     {
    2725        7680 :       const dof_id_type dof_id = elem_dofs[i];
    2726             : 
    2727        7040 :       if (auto pos = _dof_constraints.find(dof_id);
    2728         640 :           pos != _dof_constraints.end())
    2729             :         {
    2730       26400 :           for (auto j : make_range(matrix.n()))
    2731       21120 :             matrix(i,j) = 0.;
    2732             : 
    2733             :           // If the DOF is constrained
    2734        5280 :           matrix(i,i) = 1.;
    2735             : 
    2736             :           // This will put a nonsymmetric entry in the constraint
    2737             :           // row to ensure that the linear system produces the
    2738             :           // correct value for the constrained DOF.
    2739         480 :           const DofConstraintRow & constraint_row = pos->second;
    2740             : 
    2741        5280 :           for (const auto & item : constraint_row)
    2742           0 :             for (unsigned int j=0; j != n_elem_dofs; j++)
    2743           0 :               if (elem_dofs[j] == item.first)
    2744           0 :                 matrix(i,j) = -item.second;
    2745             : 
    2746             :           const DofConstraintValueMap::const_iterator valpos =
    2747         480 :             _primal_constraint_values.find(dof_id);
    2748             : 
    2749         480 :           Number & rhs_val = rhs(i);
    2750        5760 :           rhs_val = (valpos == _primal_constraint_values.end()) ?
    2751        5280 :             0 : -valpos->second;
    2752        5280 :           for (const auto & [constraining_dof, coef] : constraint_row)
    2753           0 :             rhs_val -= coef * solution_local(constraining_dof);
    2754        5280 :           rhs_val += solution_local(dof_id);
    2755             :         }
    2756             :     }
    2757        1440 : }
    2758             : 
    2759             : 
    2760        1760 : void DofMap::heterogeneously_constrain_element_residual
    2761             :   (DenseVector<Number> & rhs,
    2762             :    std::vector<dof_id_type> & elem_dofs,
    2763             :    NumericVector<Number> & solution_local) const
    2764             : {
    2765         160 :   libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
    2766             : 
    2767         160 :   libmesh_assert (solution_local.type() == SERIAL ||
    2768             :                   solution_local.type() == GHOSTED);
    2769             : 
    2770             :   // check for easy return
    2771        1760 :   if (this->_dof_constraints.empty())
    2772           0 :     return;
    2773             : 
    2774             :   // The constrained RHS is built up as C^T F
    2775             :   // Asymmetric residual terms are added if we do not have x = Cx+h
    2776        1920 :   DenseMatrix<Number> C;
    2777        1600 :   DenseVector<Number> H;
    2778             : 
    2779        1760 :   this->build_constraint_matrix_and_vector (C, H, elem_dofs);
    2780             : 
    2781         160 :   LOG_SCOPE("hetero_cnstrn_elem_res()", "DofMap");
    2782             : 
    2783             :   // It is possible that the element is not constrained at all.
    2784        2080 :   if ((C.m() != rhs.size()) ||
    2785        1760 :       (C.n() != elem_dofs.size()))
    2786           0 :     return;
    2787             : 
    2788             :   // Compute the matrix-vector product C^T F
    2789         320 :   DenseVector<Number> old_rhs(rhs);
    2790        1760 :   C.vector_mult_transpose(rhs, old_rhs);
    2791             : 
    2792        8480 :   for (unsigned int i=0,
    2793         320 :        n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    2794        8800 :        i != n_elem_dofs; i++)
    2795             :     {
    2796        7680 :       const dof_id_type dof_id = elem_dofs[i];
    2797             : 
    2798        7040 :       if (auto pos = _dof_constraints.find(dof_id);
    2799         640 :           pos != _dof_constraints.end())
    2800             :         {
    2801             :           // This will put a nonsymmetric entry in the constraint
    2802             :           // row to ensure that the linear system produces the
    2803             :           // correct value for the constrained DOF.
    2804         480 :           const DofConstraintRow & constraint_row = pos->second;
    2805             : 
    2806             :           const DofConstraintValueMap::const_iterator valpos =
    2807         480 :             _primal_constraint_values.find(dof_id);
    2808             : 
    2809         480 :           Number & rhs_val = rhs(i);
    2810       10080 :           rhs_val = (valpos == _primal_constraint_values.end()) ?
    2811        5280 :             0 : -valpos->second;
    2812        5280 :           for (const auto & [constraining_dof, coef] : constraint_row)
    2813           0 :             rhs_val -= coef * solution_local(constraining_dof);
    2814        5280 :           rhs_val += solution_local(dof_id);
    2815             :         }
    2816             :     }
    2817        1440 : }
    2818             : 
    2819             : 
    2820           0 : void DofMap::constrain_element_residual
    2821             :   (DenseVector<Number> & rhs,
    2822             :    std::vector<dof_id_type> & elem_dofs,
    2823             :    NumericVector<Number> & solution_local) const
    2824             : {
    2825           0 :   libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
    2826             : 
    2827           0 :   libmesh_assert (solution_local.type() == SERIAL ||
    2828             :                   solution_local.type() == GHOSTED);
    2829             : 
    2830             :   // check for easy return
    2831           0 :   if (this->_dof_constraints.empty())
    2832           0 :     return;
    2833             : 
    2834             :   // The constrained RHS is built up as C^T F
    2835           0 :   DenseMatrix<Number> C;
    2836             : 
    2837           0 :   this->build_constraint_matrix (C, elem_dofs);
    2838             : 
    2839           0 :   LOG_SCOPE("cnstrn_elem_residual()", "DofMap");
    2840             : 
    2841             :   // It is possible that the matrix is not constrained at all.
    2842           0 :   if (C.n() != elem_dofs.size())
    2843           0 :     return;
    2844             : 
    2845             :   // Compute the matrix-vector product C^T F
    2846           0 :   DenseVector<Number> old_rhs(rhs);
    2847           0 :   C.vector_mult_transpose(rhs, old_rhs);
    2848             : 
    2849           0 :   for (unsigned int i=0,
    2850           0 :        n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    2851           0 :        i != n_elem_dofs; i++)
    2852             :     {
    2853           0 :       const dof_id_type dof_id = elem_dofs[i];
    2854             : 
    2855           0 :       if (auto pos = _dof_constraints.find(dof_id);
    2856           0 :           pos != _dof_constraints.end())
    2857             :         {
    2858             :           // This will put a nonsymmetric entry in the constraint
    2859             :           // row to ensure that the linear system produces the
    2860             :           // correct value for the constrained DOF.
    2861           0 :           const DofConstraintRow & constraint_row = pos->second;
    2862             : 
    2863           0 :           Number & rhs_val = rhs(i);
    2864           0 :           rhs_val = 0;
    2865           0 :           for (const auto & [constraining_dof, coef] : constraint_row)
    2866           0 :             rhs_val -= coef * solution_local(constraining_dof);
    2867           0 :           rhs_val += solution_local(dof_id);
    2868             :         }
    2869             :     }
    2870           0 : }
    2871             : 
    2872             : 
    2873        1680 : void DofMap::heterogeneously_constrain_element_vector (const DenseMatrix<Number> & matrix,
    2874             :                                                        DenseVector<Number> & rhs,
    2875             :                                                        std::vector<dof_id_type> & elem_dofs,
    2876             :                                                        bool asymmetric_constraint_rows,
    2877             :                                                        int qoi_index) const
    2878             : {
    2879         140 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.m());
    2880         140 :   libmesh_assert_equal_to (elem_dofs.size(), matrix.n());
    2881         140 :   libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
    2882             : 
    2883             :   // check for easy return
    2884        1680 :   if (this->_dof_constraints.empty())
    2885           0 :     return;
    2886             : 
    2887             :   // The constrained matrix is built up as C^T K C.
    2888             :   // The constrained RHS is built up as C^T (F - K H)
    2889        1960 :   DenseMatrix<Number> C;
    2890        1680 :   DenseVector<Number> H;
    2891             : 
    2892        1680 :   this->build_constraint_matrix_and_vector (C, H, elem_dofs, qoi_index);
    2893             : 
    2894         280 :   LOG_SCOPE("hetero_cnstrn_elem_vec()", "DofMap");
    2895             : 
    2896             :   // It is possible that the matrix is not constrained at all.
    2897        1820 :   if ((C.m() == matrix.m()) &&
    2898        1680 :       (C.n() == elem_dofs.size())) // It the matrix is constrained
    2899             :     {
    2900             :       // We may have rhs values to use later
    2901         140 :       const DofConstraintValueMap * rhs_values = nullptr;
    2902        1680 :       if (qoi_index < 0)
    2903           0 :         rhs_values = &_primal_constraint_values;
    2904        1820 :       else if (auto it = _adjoint_constraint_values.find(qoi_index);
    2905         140 :                it != _adjoint_constraint_values.end())
    2906        1680 :         rhs_values = &it->second;
    2907             : 
    2908             :       // Compute matrix/vector product K H
    2909        1680 :       DenseVector<Number> KH;
    2910        1680 :       matrix.vector_mult(KH, H);
    2911             : 
    2912             :       // Compute the matrix-vector product C^T (F - KH)
    2913         280 :       DenseVector<Number> F_minus_KH(rhs);
    2914        1540 :       F_minus_KH -= KH;
    2915        1680 :       C.vector_mult_transpose(rhs, F_minus_KH);
    2916             : 
    2917       16520 :       for (unsigned int i=0,
    2918         280 :            n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    2919       16800 :            i != n_elem_dofs; i++)
    2920             :         {
    2921       16380 :           const dof_id_type dof_id = elem_dofs[i];
    2922             : 
    2923        9420 :           if (this->is_constrained_dof(dof_id))
    2924             :             {
    2925             :               // This will put a nonsymmetric entry in the constraint
    2926             :               // row to ensure that the linear system produces the
    2927             :               // correct value for the constrained DOF.
    2928        5328 :               if (asymmetric_constraint_rows && rhs_values)
    2929             :                 {
    2930             :                   const DofConstraintValueMap::const_iterator valpos =
    2931           0 :                     rhs_values->find(dof_id);
    2932             : 
    2933           0 :                   rhs(i) = (valpos == rhs_values->end()) ?
    2934           0 :                     0 : valpos->second;
    2935             :                 }
    2936             :               else
    2937        4884 :                 rhs(i) = 0.;
    2938             :             }
    2939             :         }
    2940             : 
    2941             :     } // end if is constrained...
    2942        1400 : }
    2943             : 
    2944             : 
    2945             : 
    2946             : 
    2947           0 : void DofMap::constrain_element_matrix (DenseMatrix<Number> & matrix,
    2948             :                                        std::vector<dof_id_type> & row_dofs,
    2949             :                                        std::vector<dof_id_type> & col_dofs,
    2950             :                                        bool asymmetric_constraint_rows) const
    2951             : {
    2952           0 :   libmesh_assert_equal_to (row_dofs.size(), matrix.m());
    2953           0 :   libmesh_assert_equal_to (col_dofs.size(), matrix.n());
    2954             : 
    2955             :   // check for easy return
    2956           0 :   if (this->_dof_constraints.empty())
    2957           0 :     return;
    2958             : 
    2959             :   // The constrained matrix is built up as R^T K C.
    2960           0 :   DenseMatrix<Number> R;
    2961           0 :   DenseMatrix<Number> C;
    2962             : 
    2963             :   // Safeguard against the user passing us the same
    2964             :   // object for row_dofs and col_dofs.  If that is done
    2965             :   // the calls to build_matrix would fail
    2966           0 :   std::vector<dof_id_type> orig_row_dofs(row_dofs);
    2967           0 :   std::vector<dof_id_type> orig_col_dofs(col_dofs);
    2968             : 
    2969           0 :   this->build_constraint_matrix (R, orig_row_dofs);
    2970           0 :   this->build_constraint_matrix (C, orig_col_dofs);
    2971             : 
    2972           0 :   LOG_SCOPE("constrain_elem_matrix()", "DofMap");
    2973             : 
    2974           0 :   row_dofs = orig_row_dofs;
    2975           0 :   col_dofs = orig_col_dofs;
    2976             : 
    2977           0 :   bool constraint_found = false;
    2978             : 
    2979             :   // K_constrained = R^T K C
    2980             : 
    2981           0 :   if ((R.m() == matrix.m()) &&
    2982           0 :       (R.n() == row_dofs.size()))
    2983             :     {
    2984           0 :       matrix.left_multiply_transpose  (R);
    2985           0 :       constraint_found = true;
    2986             :     }
    2987             : 
    2988           0 :   if ((C.m() == matrix.n()) &&
    2989           0 :       (C.n() == col_dofs.size()))
    2990             :     {
    2991           0 :       matrix.right_multiply (C);
    2992           0 :       constraint_found = true;
    2993             :     }
    2994             : 
    2995             :   // It is possible that the matrix is not constrained at all.
    2996           0 :   if (constraint_found)
    2997             :     {
    2998           0 :       libmesh_assert_equal_to (matrix.m(), row_dofs.size());
    2999           0 :       libmesh_assert_equal_to (matrix.n(), col_dofs.size());
    3000             : 
    3001             : 
    3002           0 :       for (unsigned int i=0,
    3003           0 :            n_row_dofs = cast_int<unsigned int>(row_dofs.size());
    3004           0 :            i != n_row_dofs; i++)
    3005           0 :         if (this->is_constrained_dof(row_dofs[i]))
    3006             :           {
    3007           0 :             for (auto j : make_range(matrix.n()))
    3008             :               {
    3009           0 :                 if (row_dofs[i] != col_dofs[j])
    3010           0 :                   matrix(i,j) = 0.;
    3011             :                 else // If the DOF is constrained
    3012           0 :                   matrix(i,j) = 1.;
    3013             :               }
    3014             : 
    3015           0 :             if (asymmetric_constraint_rows)
    3016             :               {
    3017             :                 DofConstraints::const_iterator
    3018           0 :                   pos = _dof_constraints.find(row_dofs[i]);
    3019             : 
    3020           0 :                 libmesh_assert (pos != _dof_constraints.end());
    3021             : 
    3022           0 :                 const DofConstraintRow & constraint_row = pos->second;
    3023             : 
    3024           0 :                 libmesh_assert (!constraint_row.empty());
    3025             : 
    3026           0 :                 for (const auto & item : constraint_row)
    3027           0 :                   for (unsigned int j=0,
    3028           0 :                        n_col_dofs = cast_int<unsigned int>(col_dofs.size());
    3029           0 :                        j != n_col_dofs; j++)
    3030           0 :                     if (col_dofs[j] == item.first)
    3031           0 :                       matrix(i,j) = -item.second;
    3032             :               }
    3033             :           }
    3034             :     } // end if is constrained...
    3035           0 : }
    3036             : 
    3037             : 
    3038             : 
    3039    60067192 : void DofMap::constrain_element_vector (DenseVector<Number> & rhs,
    3040             :                                        std::vector<dof_id_type> & row_dofs,
    3041             :                                        bool) const
    3042             : {
    3043     4363684 :   libmesh_assert_equal_to (rhs.size(), row_dofs.size());
    3044             : 
    3045             :   // check for easy return
    3046    60067192 :   if (this->_dof_constraints.empty())
    3047    21977259 :     return;
    3048             : 
    3049             :   // The constrained RHS is built up as R^T F.
    3050    46500069 :   DenseMatrix<Number> R;
    3051             : 
    3052    38089933 :   this->build_constraint_matrix (R, row_dofs);
    3053             : 
    3054     8619032 :   LOG_SCOPE("constrain_elem_vector()", "DofMap");
    3055             : 
    3056             :   // It is possible that the vector is not constrained at all.
    3057    42330702 :   if ((R.m() == rhs.size()) &&
    3058     1610408 :       (R.n() == row_dofs.size())) // if the RHS is constrained
    3059             :     {
    3060             :       // Compute the matrix-vector product
    3061      280298 :       DenseVector<Number> old_rhs(rhs);
    3062     1610408 :       R.vector_mult_transpose(rhs, old_rhs);
    3063             : 
    3064      140149 :       libmesh_assert_equal_to (row_dofs.size(), rhs.size());
    3065             : 
    3066    22095951 :       for (unsigned int i=0,
    3067      280094 :            n_row_dofs = cast_int<unsigned int>(row_dofs.size());
    3068    22376045 :            i != n_row_dofs; i++)
    3069    22581000 :         if (this->is_constrained_dof(row_dofs[i]))
    3070             :           {
    3071             :             // If the DOF is constrained
    3072      684479 :             libmesh_assert (_dof_constraints.find(row_dofs[i]) != _dof_constraints.end());
    3073             : 
    3074     7289629 :             rhs(i) = 0;
    3075             :           }
    3076             :     } // end if the RHS is constrained.
    3077    29679797 : }
    3078             : 
    3079             : 
    3080             : 
    3081       20369 : void DofMap::constrain_element_dyad_matrix (DenseVector<Number> & v,
    3082             :                                             DenseVector<Number> & w,
    3083             :                                             std::vector<dof_id_type> & row_dofs,
    3084             :                                             bool) const
    3085             : {
    3086        1795 :   libmesh_assert_equal_to (v.size(), row_dofs.size());
    3087        1795 :   libmesh_assert_equal_to (w.size(), row_dofs.size());
    3088             : 
    3089             :   // check for easy return
    3090       20369 :   if (this->_dof_constraints.empty())
    3091           0 :     return;
    3092             : 
    3093             :   // The constrained RHS is built up as R^T F.
    3094       23959 :   DenseMatrix<Number> R;
    3095             : 
    3096       20369 :   this->build_constraint_matrix (R, row_dofs);
    3097             : 
    3098        3590 :   LOG_SCOPE("cnstrn_elem_dyad_mat()", "DofMap");
    3099             : 
    3100             :   // It is possible that the vector is not constrained at all.
    3101       22959 :   if ((R.m() == v.size()) &&
    3102        8489 :       (R.n() == row_dofs.size())) // if the RHS is constrained
    3103             :     {
    3104             :       // Compute the matrix-vector products
    3105        1590 :       DenseVector<Number> old_v(v);
    3106        1590 :       DenseVector<Number> old_w(w);
    3107             : 
    3108             :       // compute matrix/vector product
    3109        8489 :       R.vector_mult_transpose(v, old_v);
    3110        8489 :       R.vector_mult_transpose(w, old_w);
    3111             : 
    3112         795 :       libmesh_assert_equal_to (row_dofs.size(), v.size());
    3113         795 :       libmesh_assert_equal_to (row_dofs.size(), w.size());
    3114             : 
    3115             :       /* Constrain only v, not w.  */
    3116             : 
    3117       50829 :       for (unsigned int i=0,
    3118        1590 :            n_row_dofs = cast_int<unsigned int>(row_dofs.size());
    3119       52419 :            i != n_row_dofs; i++)
    3120       48026 :         if (this->is_constrained_dof(row_dofs[i]))
    3121             :           {
    3122             :             // If the DOF is constrained
    3123         916 :             libmesh_assert (_dof_constraints.find(row_dofs[i]) != _dof_constraints.end());
    3124             : 
    3125        9974 :             v(i) = 0;
    3126             :           }
    3127             :     } // end if the RHS is constrained.
    3128       16779 : }
    3129             : 
    3130             : 
    3131             : 
    3132     2379157 : void DofMap::constrain_nothing (std::vector<dof_id_type> & dofs) const
    3133             : {
    3134             :   // check for easy return
    3135     2379157 :   if (this->_dof_constraints.empty())
    3136      199777 :     return;
    3137             : 
    3138             :   // All the work is done by \p build_constraint_matrix.  We just need
    3139             :   // a dummy matrix.
    3140     2593540 :   DenseMatrix<Number> R;
    3141     2179380 :   this->build_constraint_matrix (R, dofs);
    3142     1765220 : }
    3143             : 
    3144             : 
    3145             : 
    3146      849781 : void DofMap::enforce_constraints_exactly (const System & system,
    3147             :                                           NumericVector<Number> * v,
    3148             :                                           bool homogeneous) const
    3149             : {
    3150       21988 :   parallel_object_only();
    3151             : 
    3152      849781 :   if (!this->n_constrained_dofs())
    3153      265852 :     return;
    3154             : 
    3155       28768 :   LOG_SCOPE("enforce_constraints_exactly()","DofMap");
    3156             : 
    3157      576325 :   if (!v)
    3158        5798 :     v = system.solution.get();
    3159             : 
    3160      576325 :   if (!v->closed())
    3161           0 :     v->close();
    3162             : 
    3163       14384 :   NumericVector<Number> * v_local  = nullptr; // will be initialized below
    3164       14384 :   NumericVector<Number> * v_global = nullptr; // will be initialized below
    3165      562349 :   std::unique_ptr<NumericVector<Number>> v_built;
    3166      576325 :   if (v->type() == SERIAL)
    3167             :     {
    3168        2478 :       v_built = NumericVector<Number>::build(this->comm());
    3169        1239 :       v_built->init(this->n_dofs(), this->n_local_dofs(), true, PARALLEL);
    3170        1239 :       v_built->close();
    3171             : 
    3172     1790552 :       for (dof_id_type i=v_built->first_local_index();
    3173     1790552 :            i<v_built->last_local_index(); i++)
    3174     1789313 :         v_built->set(i, (*v)(i));
    3175        1239 :       v_built->close();
    3176           0 :       v_global = v_built.get();
    3177             : 
    3178           0 :       v_local = v;
    3179           0 :       libmesh_assert (v_local->closed());
    3180             :     }
    3181      575086 :   else if (v->type() == PARALLEL)
    3182             :     {
    3183      505874 :       v_built = NumericVector<Number>::build(this->comm());
    3184      268105 :       v_built->init (v->size(), v->local_size(),
    3185             :                      this->get_send_list(), true,
    3186       15168 :                      GHOSTED);
    3187      260521 :       v->localize(*v_built, this->get_send_list());
    3188      260521 :       v_built->close();
    3189        7584 :       v_local = v_built.get();
    3190             : 
    3191        7584 :       v_global = v;
    3192             :     }
    3193      314565 :   else if (v->type() == GHOSTED)
    3194             :     {
    3195        6800 :       v_local = v;
    3196        6800 :       v_global = v;
    3197             :     }
    3198             :   else // unknown v->type()
    3199           0 :     libmesh_error_msg("ERROR: Unsupported NumericVector type == " << Utility::enum_to_string(v->type()));
    3200             : 
    3201             :   // We should never hit these asserts because we should error-out in
    3202             :   // else clause above.  Just to be sure we don't try to use v_local
    3203             :   // and v_global uninitialized...
    3204       14384 :   libmesh_assert(v_local);
    3205       14384 :   libmesh_assert(v_global);
    3206       14384 :   libmesh_assert_equal_to (this, &(system.get_dof_map()));
    3207             : 
    3208    10705176 :   for (const auto & [constrained_dof, constraint_row] : _dof_constraints)
    3209             :     {
    3210    10128851 :       if (!this->local_index(constrained_dof))
    3211     1461886 :         continue;
    3212             : 
    3213      759348 :       Number exact_value = 0;
    3214     8465324 :       if (!homogeneous)
    3215             :         {
    3216     6648800 :           if (auto rhsit = _primal_constraint_values.find(constrained_dof);
    3217      589954 :               rhsit != _primal_constraint_values.end())
    3218      200590 :             exact_value = rhsit->second;
    3219             :         }
    3220    16529791 :       for (const auto & [dof, val] : constraint_row)
    3221     8064467 :         exact_value += val * (*v_local)(dof);
    3222             : 
    3223     8465324 :       v_global->set(constrained_dof, exact_value);
    3224             :     }
    3225             : 
    3226             :   // If the old vector was serial, we probably need to send our values
    3227             :   // to other processors
    3228      576325 :   if (v->type() == SERIAL)
    3229             :     {
    3230             : #ifndef NDEBUG
    3231           0 :       v_global->close();
    3232             : #endif
    3233        1239 :       v_global->localize (*v);
    3234             :     }
    3235      576325 :   v->close();
    3236      547965 : }
    3237             : 
    3238      281126 : void DofMap::enforce_constraints_on_residual (const NonlinearImplicitSystem & system,
    3239             :                                               NumericVector<Number> * rhs,
    3240             :                                               NumericVector<Number> const * solution,
    3241             :                                               bool homogeneous) const
    3242             : {
    3243        5660 :   parallel_object_only();
    3244             : 
    3245      281126 :   if (!this->n_constrained_dofs())
    3246        3265 :     return;
    3247             : 
    3248      277765 :   if (!rhs)
    3249           0 :     rhs = system.rhs;
    3250      277765 :   if (!solution)
    3251           0 :     solution = system.solution.get();
    3252             : 
    3253        5564 :   NumericVector<Number> const * solution_local  = nullptr; // will be initialized below
    3254      272609 :   std::unique_ptr<NumericVector<Number>> solution_built;
    3255      277765 :   if (solution->type() == SERIAL || solution->type() == GHOSTED)
    3256        5564 :       solution_local = solution;
    3257           0 :   else if (solution->type() == PARALLEL)
    3258             :     {
    3259           0 :       solution_built = NumericVector<Number>::build(this->comm());
    3260           0 :       solution_built->init (solution->size(), solution->local_size(),
    3261           0 :                             this->get_send_list(), true, GHOSTED);
    3262           0 :       solution->localize(*solution_built, this->get_send_list());
    3263           0 :       solution_built->close();
    3264           0 :       solution_local = solution_built.get();
    3265             :     }
    3266             :   else // unknown solution->type()
    3267           0 :     libmesh_error_msg("ERROR: Unsupported NumericVector type == " << Utility::enum_to_string(solution->type()));
    3268             : 
    3269             :   // We should never hit these asserts because we should error-out in
    3270             :   // else clause above.  Just to be sure we don't try to use solution_local
    3271        5564 :   libmesh_assert(solution_local);
    3272        5564 :   libmesh_assert_equal_to (this, &(system.get_dof_map()));
    3273             : 
    3274      473917 :   for (const auto & [constrained_dof, constraint_row] : _dof_constraints)
    3275             :     {
    3276      196152 :       if (!this->local_index(constrained_dof))
    3277       72490 :         continue;
    3278             : 
    3279        8833 :       Number exact_value = 0;
    3280      201476 :       for (const auto & [dof, val] : constraint_row)
    3281       82936 :         exact_value -= val * (*solution_local)(dof);
    3282      118540 :       exact_value += (*solution_local)(constrained_dof);
    3283      118540 :       if (!homogeneous)
    3284             :         {
    3285           0 :           if (auto rhsit = _primal_constraint_values.find(constrained_dof);
    3286           0 :               rhsit != _primal_constraint_values.end())
    3287           0 :             exact_value += rhsit->second;
    3288             :         }
    3289             : 
    3290      118540 :       rhs->set(constrained_dof, exact_value);
    3291             :     }
    3292      267045 : }
    3293             : 
    3294      158439 : void DofMap::enforce_constraints_on_jacobian (const NonlinearImplicitSystem & system,
    3295             :                                               SparseMatrix<Number> * jac) const
    3296             : {
    3297        3240 :   parallel_object_only();
    3298             : 
    3299      158439 :   if (!this->n_constrained_dofs())
    3300          76 :     return;
    3301             : 
    3302      155778 :   if (!jac)
    3303           0 :     jac = system.matrix;
    3304             : 
    3305        3164 :   libmesh_assert_equal_to (this, &(system.get_dof_map()));
    3306             : 
    3307      284382 :   for (const auto & [constrained_dof, constraint_row] : _dof_constraints)
    3308             :     {
    3309      128604 :       if (!this->local_index(constrained_dof))
    3310       42318 :         continue;
    3311             : 
    3312      130812 :       for (const auto & j : constraint_row)
    3313       47658 :         jac->set(constrained_dof, j.first, -j.second);
    3314       83154 :       jac->set(constrained_dof, constrained_dof, 1);
    3315             :     }
    3316             : }
    3317             : 
    3318             : 
    3319       60624 : void DofMap::enforce_adjoint_constraints_exactly (NumericVector<Number> & v,
    3320             :                                                   unsigned int q) const
    3321             : {
    3322        1896 :   parallel_object_only();
    3323             : 
    3324       60624 :   if (!this->n_constrained_dofs())
    3325        2033 :     return;
    3326             : 
    3327        3636 :   LOG_SCOPE("enforce_adjoint_constraints_exactly()", "DofMap");
    3328             : 
    3329        1818 :   NumericVector<Number> * v_local  = nullptr; // will be initialized below
    3330        1818 :   NumericVector<Number> * v_global = nullptr; // will be initialized below
    3331       56695 :   std::unique_ptr<NumericVector<Number>> v_built;
    3332       58513 :   if (v.type() == SERIAL)
    3333             :     {
    3334         198 :       v_built = NumericVector<Number>::build(this->comm());
    3335          99 :       v_built->init(this->n_dofs(), this->n_local_dofs(), true, PARALLEL);
    3336          99 :       v_built->close();
    3337             : 
    3338       87318 :       for (dof_id_type i=v_built->first_local_index();
    3339       87318 :            i<v_built->last_local_index(); i++)
    3340       87219 :         v_built->set(i, v(i));
    3341          99 :       v_built->close();
    3342           0 :       v_global = v_built.get();
    3343             : 
    3344           0 :       v_local = &v;
    3345           0 :       libmesh_assert (v_local->closed());
    3346             :     }
    3347       58414 :   else if (v.type() == PARALLEL)
    3348             :     {
    3349       10920 :       v_built = NumericVector<Number>::build(this->comm());
    3350        6088 :       v_built->init (v.size(), v.local_size(),
    3351         628 :                      this->get_send_list(), true, GHOSTED);
    3352        5774 :       v.localize(*v_built, this->get_send_list());
    3353        5774 :       v_built->close();
    3354         314 :       v_local = v_built.get();
    3355             : 
    3356         314 :       v_global = &v;
    3357             :     }
    3358       52640 :   else if (v.type() == GHOSTED)
    3359             :     {
    3360        1504 :       v_local = &v;
    3361        1504 :       v_global = &v;
    3362             :     }
    3363             :   else // unknown v.type()
    3364           0 :     libmesh_error_msg("ERROR: Unknown v.type() == " << v.type());
    3365             : 
    3366             :   // We should never hit these asserts because we should error-out in
    3367             :   // else clause above.  Just to be sure we don't try to use v_local
    3368             :   // and v_global uninitialized...
    3369        1818 :   libmesh_assert(v_local);
    3370        1818 :   libmesh_assert(v_global);
    3371             : 
    3372             :   // Do we have any non_homogeneous constraints?
    3373             :   const AdjointDofConstraintValues::const_iterator
    3374        1818 :     adjoint_constraint_map_it = _adjoint_constraint_values.find(q);
    3375             :   const DofConstraintValueMap * constraint_map =
    3376       59943 :     (adjoint_constraint_map_it == _adjoint_constraint_values.end()) ?
    3377        1430 :     nullptr : &adjoint_constraint_map_it->second;
    3378             : 
    3379      444326 :   for (const auto & [constrained_dof, constraint_row] : _dof_constraints)
    3380             :     {
    3381      385813 :       if (!this->local_index(constrained_dof))
    3382       72566 :         continue;
    3383             : 
    3384       28787 :       Number exact_value = 0;
    3385      309230 :       if (constraint_map)
    3386             :         {
    3387       74368 :           if (const auto adjoint_constraint_it = constraint_map->find(constrained_dof);
    3388        7168 :               adjoint_constraint_it != constraint_map->end())
    3389        4476 :             exact_value = adjoint_constraint_it->second;
    3390             :         }
    3391             : 
    3392      523838 :       for (const auto & j : constraint_row)
    3393      214608 :         exact_value += j.second * (*v_local)(j.first);
    3394             : 
    3395      309230 :       v_global->set(constrained_dof, exact_value);
    3396             :     }
    3397             : 
    3398             :   // If the old vector was serial, we probably need to send our values
    3399             :   // to other processors
    3400       58513 :   if (v.type() == SERIAL)
    3401             :     {
    3402             : #ifndef NDEBUG
    3403           0 :       v_global->close();
    3404             : #endif
    3405          99 :       v_global->localize (v);
    3406             :     }
    3407       58513 :   v.close();
    3408       54877 : }
    3409             : 
    3410             : 
    3411             : 
    3412             : std::pair<Real, Real>
    3413           0 : DofMap::max_constraint_error (const System & system,
    3414             :                               NumericVector<Number> * v) const
    3415             : {
    3416           0 :   if (!v)
    3417           0 :     v = system.solution.get();
    3418           0 :   NumericVector<Number> & vec = *v;
    3419             : 
    3420             :   // We'll assume the vector is closed
    3421           0 :   libmesh_assert (vec.closed());
    3422             : 
    3423           0 :   Real max_absolute_error = 0., max_relative_error = 0.;
    3424             : 
    3425           0 :   const MeshBase & mesh = system.get_mesh();
    3426             : 
    3427           0 :   libmesh_assert_equal_to (this, &(system.get_dof_map()));
    3428             : 
    3429             :   // indices on each element
    3430           0 :   std::vector<dof_id_type> local_dof_indices;
    3431             : 
    3432           0 :   for (const auto & elem : mesh.active_local_element_ptr_range())
    3433             :     {
    3434           0 :       this->dof_indices(elem, local_dof_indices);
    3435           0 :       std::vector<dof_id_type> raw_dof_indices = local_dof_indices;
    3436             : 
    3437             :       // Constraint matrix for each element
    3438           0 :       DenseMatrix<Number> C;
    3439             : 
    3440           0 :       this->build_constraint_matrix (C, local_dof_indices);
    3441             : 
    3442             :       // Continue if the element is unconstrained
    3443           0 :       if (!C.m())
    3444           0 :         continue;
    3445             : 
    3446           0 :       libmesh_assert_equal_to (C.m(), raw_dof_indices.size());
    3447           0 :       libmesh_assert_equal_to (C.n(), local_dof_indices.size());
    3448             : 
    3449           0 :       for (auto i : make_range(C.m()))
    3450             :         {
    3451             :           // Recalculate any constrained dof owned by this processor
    3452           0 :           dof_id_type global_dof = raw_dof_indices[i];
    3453           0 :           if (this->is_constrained_dof(global_dof) &&
    3454           0 :               global_dof >= vec.first_local_index() &&
    3455           0 :               global_dof < vec.last_local_index())
    3456             :             {
    3457             : #ifndef NDEBUG
    3458             :               DofConstraints::const_iterator
    3459           0 :                 pos = _dof_constraints.find(global_dof);
    3460             : 
    3461           0 :               libmesh_assert (pos != _dof_constraints.end());
    3462             : #endif
    3463             : 
    3464           0 :               Number exact_value = 0;
    3465             :               DofConstraintValueMap::const_iterator rhsit =
    3466           0 :                 _primal_constraint_values.find(global_dof);
    3467           0 :               if (rhsit != _primal_constraint_values.end())
    3468           0 :                 exact_value = rhsit->second;
    3469             : 
    3470           0 :               for (auto j : make_range(C.n()))
    3471             :                 {
    3472           0 :                   if (local_dof_indices[j] != global_dof)
    3473           0 :                     exact_value += C(i,j) *
    3474           0 :                       vec(local_dof_indices[j]);
    3475             :                 }
    3476             : 
    3477           0 :               max_absolute_error = std::max(max_absolute_error,
    3478           0 :                                             std::abs(vec(global_dof) - exact_value));
    3479           0 :               max_relative_error = std::max(max_relative_error,
    3480           0 :                                             std::abs(vec(global_dof) - exact_value)
    3481           0 :                                             / std::abs(exact_value));
    3482             :             }
    3483             :         }
    3484           0 :     }
    3485             : 
    3486           0 :   return std::pair<Real, Real>(max_absolute_error, max_relative_error);
    3487             : }
    3488             : 
    3489             : 
    3490             : 
    3491    65736532 : void DofMap::build_constraint_matrix (DenseMatrix<Number> & C,
    3492             :                                       std::vector<dof_id_type> & elem_dofs,
    3493             :                                       const bool called_recursively) const
    3494             : {
    3495    14481540 :   LOG_SCOPE_IF("build_constraint_matrix()", "DofMap", !called_recursively);
    3496             : 
    3497             :   // Create a set containing the DOFs we already depend on
    3498             :   typedef std::set<dof_id_type> RCSet;
    3499     7345320 :   RCSet dof_set;
    3500             : 
    3501     7345320 :   bool we_have_constraints = false;
    3502             : 
    3503             :   // Next insert any other dofs the current dofs might be constrained
    3504             :   // in terms of.  Note that in this case we may not be done: Those
    3505             :   // may in turn depend on others.  So, we need to repeat this process
    3506             :   // in that case until the system depends only on unconstrained
    3507             :   // degrees of freedom.
    3508   435793881 :   for (const auto & dof : elem_dofs)
    3509   370057349 :     if (this->is_constrained_dof(dof))
    3510             :       {
    3511     2648332 :         we_have_constraints = true;
    3512             : 
    3513             :         // If the DOF is constrained
    3514             :         DofConstraints::const_iterator
    3515     2648332 :           pos = _dof_constraints.find(dof);
    3516             : 
    3517     2648332 :         libmesh_assert (pos != _dof_constraints.end());
    3518             : 
    3519     2648332 :         const DofConstraintRow & constraint_row = pos->second;
    3520             : 
    3521             :         // Constraint rows in p refinement may be empty
    3522             :         //libmesh_assert (!constraint_row.empty());
    3523             : 
    3524    64243406 :         for (const auto & item : constraint_row)
    3525    35148046 :           dof_set.insert (item.first);
    3526             :       }
    3527             : 
    3528             :   // May be safe to return at this point
    3529             :   // (but remember to stop the perflog)
    3530    65736532 :   if (!we_have_constraints)
    3531     6752968 :     return;
    3532             : 
    3533    80472851 :   for (const auto & dof : elem_dofs)
    3534     6612988 :     dof_set.erase (dof);
    3535             : 
    3536             :   // If we added any DOFS then we need to do this recursively.
    3537             :   // It is possible that we just added a DOF that is also
    3538             :   // constrained!
    3539             :   //
    3540             :   // Also, we need to handle the special case of an element having DOFs
    3541             :   // constrained in terms of other, local DOFs
    3542     6982125 :   if (!dof_set.empty() ||  // case 1: constrained in terms of other DOFs
    3543      497215 :       !called_recursively) // case 2: constrained in terms of our own DOFs
    3544             :     {
    3545             :       const unsigned int old_size =
    3546      592148 :         cast_int<unsigned int>(elem_dofs.size());
    3547             : 
    3548             :       // Add new dependency dofs to the end of the current dof set
    3549     2946483 :       elem_dofs.insert(elem_dofs.end(),
    3550      888324 :                        dof_set.begin(), dof_set.end());
    3551             : 
    3552             :       // Now we can build the constraint matrix.
    3553             :       // Note that resize also zeros for a DenseMatrix<Number>.
    3554     3242455 :       C.resize (old_size,
    3555             :                 cast_int<unsigned int>(elem_dofs.size()));
    3556             : 
    3557             :       // Create the C constraint matrix.
    3558    37050188 :       for (unsigned int i=0; i != old_size; i++)
    3559    36804239 :         if (this->is_constrained_dof(elem_dofs[i]))
    3560             :           {
    3561             :             // If the DOF is constrained
    3562             :             DofConstraints::const_iterator
    3563     1324166 :               pos = _dof_constraints.find(elem_dofs[i]);
    3564             : 
    3565     1324166 :             libmesh_assert (pos != _dof_constraints.end());
    3566             : 
    3567     1324166 :             const DofConstraintRow & constraint_row = pos->second;
    3568             : 
    3569             :             // p refinement creates empty constraint rows
    3570             :             //    libmesh_assert (!constraint_row.empty());
    3571             : 
    3572    32121703 :             for (const auto & item : constraint_row)
    3573   889722407 :               for (unsigned int j=0,
    3574     3379080 :                    n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    3575   893101487 :                    j != n_elem_dofs; j++)
    3576   961664294 :                 if (elem_dofs[j] == item.first)
    3577    19263359 :                   C(i,j) = item.second;
    3578             :           }
    3579             :         else
    3580             :           {
    3581    18173186 :             C(i,i) = 1.;
    3582             :           }
    3583             : 
    3584             :       // May need to do this recursively.  It is possible
    3585             :       // that we just replaced a constrained DOF with another
    3586             :       // constrained DOF.
    3587     3834603 :       DenseMatrix<Number> Cnew;
    3588             : 
    3589     3242455 :       this->build_constraint_matrix (Cnew, elem_dofs, true);
    3590             : 
    3591     3242455 :       if ((C.n() == Cnew.m()) &&
    3592           0 :           (Cnew.n() == elem_dofs.size())) // If the constraint matrix
    3593           0 :         C.right_multiply(Cnew);           // is constrained...
    3594             : 
    3595      296176 :       libmesh_assert_equal_to (C.n(), elem_dofs.size());
    3596     2650307 :     }
    3597             : }
    3598             : 
    3599             : 
    3600             : 
    3601      837100 : void DofMap::build_constraint_matrix_and_vector (DenseMatrix<Number> & C,
    3602             :                                                  DenseVector<Number> & H,
    3603             :                                                  std::vector<dof_id_type> & elem_dofs,
    3604             :                                                  int qoi_index,
    3605             :                                                  const bool called_recursively) const
    3606             : {
    3607      167668 :   LOG_SCOPE_IF("build_constraint_matrix_and_vector()", "DofMap", !called_recursively);
    3608             : 
    3609             :   // Create a set containing the DOFs we already depend on
    3610             :   typedef std::set<dof_id_type> RCSet;
    3611       83834 :   RCSet dof_set;
    3612             : 
    3613       83834 :   bool we_have_constraints = false;
    3614             : 
    3615             :   // Next insert any other dofs the current dofs might be constrained
    3616             :   // in terms of.  Note that in this case we may not be done: Those
    3617             :   // may in turn depend on others.  So, we need to repeat this process
    3618             :   // in that case until the system depends only on unconstrained
    3619             :   // degrees of freedom.
    3620    17099956 :   for (const auto & dof : elem_dofs)
    3621    16262856 :     if (this->is_constrained_dof(dof))
    3622             :       {
    3623      177422 :         we_have_constraints = true;
    3624             : 
    3625             :         // If the DOF is constrained
    3626             :         DofConstraints::const_iterator
    3627      177422 :           pos = _dof_constraints.find(dof);
    3628             : 
    3629      177422 :         libmesh_assert (pos != _dof_constraints.end());
    3630             : 
    3631      177422 :         const DofConstraintRow & constraint_row = pos->second;
    3632             : 
    3633             :         // Constraint rows in p refinement may be empty
    3634             :         //libmesh_assert (!constraint_row.empty());
    3635             : 
    3636     2096118 :         for (const auto & item : constraint_row)
    3637      258548 :           dof_set.insert (item.first);
    3638             :       }
    3639             : 
    3640             :   // May be safe to return at this point
    3641             :   // (but remember to stop the perflog)
    3642      837100 :   if (!we_have_constraints)
    3643       50334 :     return;
    3644             : 
    3645     6371618 :   for (const auto & dof : elem_dofs)
    3646      588695 :     dof_set.erase (dof);
    3647             : 
    3648             :   // If we added any DOFS then we need to do this recursively.
    3649             :   // It is possible that we just added a DOF that is also
    3650             :   // constrained!
    3651             :   //
    3652             :   // Also, we need to handle the special case of an element having DOFs
    3653             :   // constrained in terms of other, local DOFs
    3654      386313 :   if (!dof_set.empty() ||  // case 1: constrained in terms of other DOFs
    3655       30719 :       !called_recursively) // case 2: constrained in terms of our own DOFs
    3656             :     {
    3657       16750 :       const DofConstraintValueMap * rhs_values = nullptr;
    3658      177797 :       if (qoi_index < 0)
    3659      176117 :         rhs_values = &_primal_constraint_values;
    3660        1820 :       else if (auto it = _adjoint_constraint_values.find(qoi_index);
    3661         140 :                it != _adjoint_constraint_values.end())
    3662        1680 :         rhs_values = &it->second;
    3663             : 
    3664             :       const unsigned int old_size =
    3665       33500 :         cast_int<unsigned int>(elem_dofs.size());
    3666             : 
    3667             :       // Add new dependency dofs to the end of the current dof set
    3668      161047 :       elem_dofs.insert(elem_dofs.end(),
    3669       50250 :                        dof_set.begin(), dof_set.end());
    3670             : 
    3671             :       // Now we can build the constraint matrix and vector.
    3672             :       // Note that resize also zeros for a DenseMatrix and DenseVector
    3673      177797 :       C.resize (old_size,
    3674             :                 cast_int<unsigned int>(elem_dofs.size()));
    3675      161047 :       H.resize (old_size);
    3676             : 
    3677             :       // Create the C constraint matrix.
    3678     3150286 :       for (unsigned int i=0; i != old_size; i++)
    3679     3263934 :         if (this->is_constrained_dof(elem_dofs[i]))
    3680             :           {
    3681             :             // If the DOF is constrained
    3682             :             DofConstraints::const_iterator
    3683       88711 :               pos = _dof_constraints.find(elem_dofs[i]);
    3684             : 
    3685       88711 :             libmesh_assert (pos != _dof_constraints.end());
    3686             : 
    3687       88711 :             const DofConstraintRow & constraint_row = pos->second;
    3688             : 
    3689             :             // p refinement creates empty constraint rows
    3690             :             //    libmesh_assert (!constraint_row.empty());
    3691             : 
    3692     1048059 :             for (const auto & item : constraint_row)
    3693     1839046 :               for (unsigned int j=0,
    3694       21240 :                    n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
    3695     1860286 :                    j != n_elem_dofs; j++)
    3696     1873945 :                 if (elem_dofs[j] == item.first)
    3697      139894 :                   C(i,j) = item.second;
    3698             : 
    3699      918785 :             if (rhs_values)
    3700             :               {
    3701      918785 :                 if (const auto rhsit = rhs_values->find(elem_dofs[i]);
    3702       88711 :                     rhsit != rhs_values->end())
    3703      297107 :                   H(i) = rhsit->second;
    3704             :               }
    3705             :           }
    3706             :         else
    3707             :           {
    3708     1910941 :             C(i,i) = 1.;
    3709             :           }
    3710             : 
    3711             :       // May need to do this recursively.  It is possible
    3712             :       // that we just replaced a constrained DOF with another
    3713             :       // constrained DOF.
    3714      211297 :       DenseMatrix<Number> Cnew;
    3715      177797 :       DenseVector<Number> Hnew;
    3716             : 
    3717      177797 :       this->build_constraint_matrix_and_vector (Cnew, Hnew, elem_dofs,
    3718             :                                                 qoi_index, true);
    3719             : 
    3720      177797 :       if ((C.n() == Cnew.m()) &&          // If the constraint matrix
    3721           0 :           (Cnew.n() == elem_dofs.size())) // is constrained...
    3722             :         {
    3723             :           // If x = Cy + h and y = Dz + g
    3724             :           // Then x = (CD)z + (Cg + h)
    3725           0 :           C.vector_mult_add(H, 1, Hnew);
    3726             : 
    3727           0 :           C.right_multiply(Cnew);
    3728             :         }
    3729             : 
    3730       16750 :       libmesh_assert_equal_to (C.n(), elem_dofs.size());
    3731      144297 :     }
    3732             : }
    3733             : 
    3734             : 
    3735      295851 : void DofMap::allgather_recursive_constraints(MeshBase & mesh)
    3736             : {
    3737             :   // This function must be run on all processors at once
    3738        8500 :   parallel_object_only();
    3739             : 
    3740             :   // Return immediately if there's nothing to gather
    3741      304351 :   if (this->n_processors() == 1)
    3742      263414 :     return;
    3743             : 
    3744             :   // We might get to return immediately if none of the processors
    3745             :   // found any constraints
    3746      279220 :   unsigned int has_constraints = !_dof_constraints.empty()
    3747             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    3748       17283 :     || !_node_constraints.empty()
    3749             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    3750             :     ;
    3751      279220 :   this->comm().max(has_constraints);
    3752      287720 :   if (!has_constraints)
    3753        7150 :     return;
    3754             : 
    3755             :   // If we have heterogeneous adjoint constraints we need to
    3756             :   // communicate those too.
    3757             :   const unsigned int max_qoi_num =
    3758       32437 :     _adjoint_constraint_values.empty() ?
    3759         674 :     0 : _adjoint_constraint_values.rbegin()->first+1;
    3760             : 
    3761             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    3762             :   // We may need to send nodes ahead of data about them
    3763        4050 :   std::vector<Parallel::Request> packed_range_sends;
    3764             : 
    3765             :   // We may be receiving packed_range sends out of order with
    3766             :   // parallel_sync tags, so make sure they're received correctly.
    3767        5400 :   Parallel::MessageTag range_tag = this->comm().get_unique_tag();
    3768             : 
    3769             :   // We only need to do these sends on a distributed mesh
    3770        2700 :   const bool dist_mesh = !mesh.is_serial();
    3771             : #endif
    3772             : 
    3773             :   // We might have calculated constraints for constrained dofs
    3774             :   // which have support on other processors.
    3775             :   // Push these out first.
    3776             :   {
    3777        2700 :     std::map<processor_id_type, std::set<dof_id_type>> pushed_ids;
    3778             : 
    3779             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    3780        2700 :     std::map<processor_id_type, std::set<dof_id_type>> pushed_node_ids;
    3781             : #endif
    3782             : 
    3783        2700 :     const unsigned int sys_num = this->sys_number();
    3784             : 
    3785             :     // Collect the constraints to push to each processor
    3786      322092 :     for (auto & elem : as_range(mesh.active_not_local_elements_begin(),
    3787    12897602 :                                 mesh.active_not_local_elements_end()))
    3788             :       {
    3789     6514561 :         const unsigned short n_nodes = elem->n_nodes();
    3790             : 
    3791             :         // Just checking dof_indices on the foreign element isn't
    3792             :         // enough.  Consider a central hanging node between a coarse
    3793             :         // Q2/Q1 element and its finer neighbors on a higher-ranked
    3794             :         // processor.  The coarse element's processor will own the node,
    3795             :         // and will thereby own the pressure dof on that node, despite
    3796             :         // the fact that that pressure dof doesn't directly exist on the
    3797             :         // coarse element!
    3798             :         //
    3799             :         // So, we loop through dofs manually.
    3800             : 
    3801             :         {
    3802     6514561 :           const unsigned int n_vars = elem->n_vars(sys_num);
    3803    14795501 :           for (unsigned int v=0; v != n_vars; ++v)
    3804             :             {
    3805     8280940 :               const unsigned int n_comp = elem->n_comp(sys_num,v);
    3806    11409917 :               for (unsigned int c=0; c != n_comp; ++c)
    3807             :                 {
    3808             :                   const unsigned int id =
    3809     3128977 :                     elem->dof_number(sys_num,v,c);
    3810     3064246 :                   if (this->is_constrained_dof(id))
    3811           0 :                     pushed_ids[elem->processor_id()].insert(id);
    3812             :                 }
    3813             :             }
    3814             :         }
    3815             : 
    3816    37722346 :         for (unsigned short n = 0; n != n_nodes; ++n)
    3817             :           {
    3818    31207785 :             const Node & node = elem->node_ref(n);
    3819    31207785 :             const unsigned int n_vars = node.n_vars(sys_num);
    3820    73150097 :             for (unsigned int v=0; v != n_vars; ++v)
    3821             :               {
    3822    41942312 :                 const unsigned int n_comp = node.n_comp(sys_num,v);
    3823    87893214 :                 for (unsigned int c=0; c != n_comp; ++c)
    3824             :                   {
    3825             :                     const unsigned int id =
    3826    45950902 :                       node.dof_number(sys_num,v,c);
    3827    44000385 :                     if (this->is_constrained_dof(id))
    3828      255126 :                       pushed_ids[elem->processor_id()].insert(id);
    3829             :                   }
    3830             :               }
    3831             :           }
    3832             : 
    3833             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    3834     3334056 :         for (unsigned short n = 0; n != n_nodes; ++n)
    3835     4214973 :           if (this->is_constrained_node(elem->node_ptr(n)))
    3836       31221 :             pushed_node_ids[elem->processor_id()].insert(elem->node_id(n));
    3837             : #endif
    3838       29737 :       }
    3839             : 
    3840             :     // Rewrite those id sets as vectors for sending and receiving,
    3841             :     // then find the corresponding data for each id, then push it all.
    3842             :     std::map<processor_id_type, std::vector<dof_id_type>>
    3843        2700 :       pushed_id_vecs, received_id_vecs;
    3844       72456 :     for (auto & p : pushed_ids)
    3845       40019 :       pushed_id_vecs[p.first].assign(p.second.begin(), p.second.end());
    3846             : 
    3847             :     std::map<processor_id_type, std::vector<std::vector<std::pair<dof_id_type,Real>>>>
    3848        2700 :       pushed_keys_vals, received_keys_vals;
    3849        2700 :     std::map<processor_id_type, std::vector<std::vector<Number>>> pushed_rhss, received_rhss;
    3850       72456 :     for (auto & p : pushed_id_vecs)
    3851             :       {
    3852       40019 :         auto & keys_vals = pushed_keys_vals[p.first];
    3853       40684 :         keys_vals.reserve(p.second.size());
    3854             : 
    3855       40019 :         auto & rhss = pushed_rhss[p.first];
    3856       40684 :         rhss.reserve(p.second.size());
    3857      198675 :         for (auto & pushed_id : p.second)
    3858             :           {
    3859      158656 :             const DofConstraintRow & row = _dof_constraints[pushed_id];
    3860      162388 :             keys_vals.emplace_back(row.begin(), row.end());
    3861             : 
    3862      313580 :             rhss.push_back(std::vector<Number>(max_qoi_num+1));
    3863        3732 :             std::vector<Number> & rhs = rhss.back();
    3864             :             DofConstraintValueMap::const_iterator rhsit =
    3865        3732 :               _primal_constraint_values.find(pushed_id);
    3866      162388 :             rhs[max_qoi_num] =
    3867      161226 :               (rhsit == _primal_constraint_values.end()) ?
    3868        2570 :               0 : rhsit->second;
    3869      160562 :             for (unsigned int q = 0; q != max_qoi_num; ++q)
    3870             :               {
    3871             :                 AdjointDofConstraintValues::const_iterator adjoint_map_it =
    3872         124 :                   _adjoint_constraint_values.find(q);
    3873             : 
    3874        1906 :                 if (adjoint_map_it == _adjoint_constraint_values.end())
    3875           0 :                   continue;
    3876             : 
    3877             :                 const DofConstraintValueMap & constraint_map =
    3878         124 :                   adjoint_map_it->second;
    3879             : 
    3880             :                 DofConstraintValueMap::const_iterator adj_rhsit =
    3881         124 :                   constraint_map.find(pushed_id);
    3882             : 
    3883        2030 :                 rhs[q] =
    3884        2124 :                   (adj_rhsit == constraint_map.end()) ?
    3885          94 :                   0 : adj_rhsit->second;
    3886             :               }
    3887             :           }
    3888             :       }
    3889             : 
    3890             :     auto ids_action_functor =
    3891       38689 :       [& received_id_vecs]
    3892             :       (processor_id_type pid,
    3893       39354 :        const std::vector<dof_id_type> & data)
    3894             :       {
    3895       40019 :         received_id_vecs[pid] = data;
    3896       33102 :       };
    3897             : 
    3898             :     Parallel::push_parallel_vector_data
    3899       32437 :       (this->comm(), pushed_id_vecs, ids_action_functor);
    3900             : 
    3901             :     auto keys_vals_action_functor =
    3902       38689 :       [& received_keys_vals]
    3903             :       (processor_id_type pid,
    3904       39354 :        const std::vector<std::vector<std::pair<dof_id_type,Real>>> & data)
    3905             :       {
    3906       40019 :         received_keys_vals[pid] = data;
    3907       33102 :       };
    3908             : 
    3909             :     Parallel::push_parallel_vector_data
    3910       32437 :       (this->comm(), pushed_keys_vals, keys_vals_action_functor);
    3911             : 
    3912             :     auto rhss_action_functor =
    3913       38689 :       [& received_rhss]
    3914             :       (processor_id_type pid,
    3915       39354 :        const std::vector<std::vector<Number>> & data)
    3916             :       {
    3917       40019 :         received_rhss[pid] = data;
    3918       33102 :       };
    3919             : 
    3920             :     Parallel::push_parallel_vector_data
    3921       32437 :       (this->comm(), pushed_rhss, rhss_action_functor);
    3922             : 
    3923             :     // Now we have all the DofConstraint rows and rhs values received
    3924             :     // from others, so add the DoF constraints that we've been sent
    3925             : 
    3926             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    3927             :     std::map<processor_id_type, std::vector<dof_id_type>>
    3928        2700 :       pushed_node_id_vecs, received_node_id_vecs;
    3929        4222 :     for (auto & p : pushed_node_ids)
    3930        1522 :       pushed_node_id_vecs[p.first].assign(p.second.begin(), p.second.end());
    3931             : 
    3932             :     std::map<processor_id_type, std::vector<std::vector<std::pair<dof_id_type,Real>>>>
    3933        2700 :       pushed_node_keys_vals, received_node_keys_vals;
    3934        2700 :     std::map<processor_id_type, std::vector<Point>> pushed_offsets, received_offsets;
    3935             : 
    3936        4222 :     for (auto & p : pushed_node_id_vecs)
    3937             :       {
    3938        1522 :         const processor_id_type pid = p.first;
    3939             : 
    3940             :         // FIXME - this could be an unordered set, given a
    3941             :         // hash<pointers> specialization
    3942        1522 :         std::set<const Node *> nodes_requested;
    3943             : 
    3944        1522 :         auto & node_keys_vals = pushed_node_keys_vals[pid];
    3945        2283 :         node_keys_vals.reserve(p.second.size());
    3946             : 
    3947        1522 :         auto & offsets = pushed_offsets[pid];
    3948        2283 :         offsets.reserve(p.second.size());
    3949             : 
    3950       12204 :         for (auto & pushed_node_id : p.second)
    3951             :           {
    3952       10682 :             const Node * node = mesh.node_ptr(pushed_node_id);
    3953       10682 :             NodeConstraintRow & row = _node_constraints[node].first;
    3954        5341 :             const std::size_t row_size = row.size();
    3955             :             node_keys_vals.push_back
    3956       10682 :               (std::vector<std::pair<dof_id_type,Real>>());
    3957             :             std::vector<std::pair<dof_id_type,Real>> & this_node_kv =
    3958        5341 :               node_keys_vals.back();
    3959       10682 :             this_node_kv.reserve(row_size);
    3960       45706 :             for (const auto & j : row)
    3961             :               {
    3962       35024 :                 this_node_kv.emplace_back(j.first->id(), j.second);
    3963             : 
    3964             :                 // If we're not sure whether our send
    3965             :                 // destination already has this node, let's give
    3966             :                 // it a copy.
    3967       35024 :                 if (j.first->processor_id() != pid && dist_mesh)
    3968           0 :                   nodes_requested.insert(j.first);
    3969             :               }
    3970             : 
    3971       10682 :             offsets.push_back(_node_constraints[node].second);
    3972             : 
    3973             :           }
    3974             : 
    3975             :         // Constraining nodes might not even exist on our
    3976             :         // correspondant's subset of a distributed mesh, so let's
    3977             :         // make them exist.
    3978        1522 :         if (dist_mesh)
    3979             :           {
    3980           0 :             packed_range_sends.push_back(Parallel::Request());
    3981           0 :             this->comm().send_packed_range
    3982           0 :               (pid, &mesh, nodes_requested.begin(), nodes_requested.end(),
    3983           0 :                packed_range_sends.back(), range_tag);
    3984             :           }
    3985             :       }
    3986             : 
    3987             :     auto node_ids_action_functor =
    3988             :       [& received_node_id_vecs]
    3989             :       (processor_id_type pid,
    3990         761 :        const std::vector<dof_id_type> & data)
    3991             :       {
    3992        1522 :         received_node_id_vecs[pid] = data;
    3993        3461 :       };
    3994             : 
    3995             :     Parallel::push_parallel_vector_data
    3996        2700 :       (this->comm(), pushed_node_id_vecs, node_ids_action_functor);
    3997             : 
    3998             :     auto node_keys_vals_action_functor =
    3999             :       [& received_node_keys_vals]
    4000             :       (processor_id_type pid,
    4001         761 :        const std::vector<std::vector<std::pair<dof_id_type,Real>>> & data)
    4002             :       {
    4003        1522 :         received_node_keys_vals[pid] = data;
    4004        3461 :       };
    4005             : 
    4006             :     Parallel::push_parallel_vector_data
    4007        2700 :       (this->comm(), pushed_node_keys_vals,
    4008             :        node_keys_vals_action_functor);
    4009             : 
    4010             :     auto node_offsets_action_functor =
    4011             :       [& received_offsets]
    4012             :       (processor_id_type pid,
    4013         761 :        const std::vector<Point> & data)
    4014             :       {
    4015        1522 :         received_offsets[pid] = data;
    4016        3461 :       };
    4017             : 
    4018             :     Parallel::push_parallel_vector_data
    4019        2700 :       (this->comm(), pushed_offsets, node_offsets_action_functor);
    4020             : 
    4021             : #endif
    4022             : 
    4023             :     // Add all the dof constraints that I've been sent
    4024       72456 :     for (auto & [pid, pushed_ids_to_me] : received_id_vecs)
    4025             :       {
    4026         665 :         libmesh_assert(received_keys_vals.count(pid));
    4027         665 :         libmesh_assert(received_rhss.count(pid));
    4028       40019 :         const auto & pushed_keys_vals_to_me = received_keys_vals.at(pid);
    4029       40019 :         const auto & pushed_rhss_to_me = received_rhss.at(pid);
    4030             : 
    4031         665 :         libmesh_assert_equal_to (pushed_ids_to_me.size(),
    4032             :                                  pushed_keys_vals_to_me.size());
    4033         665 :         libmesh_assert_equal_to (pushed_ids_to_me.size(),
    4034             :                                  pushed_rhss_to_me.size());
    4035             : 
    4036      198675 :         for (auto i : index_range(pushed_ids_to_me))
    4037             :           {
    4038      162388 :             dof_id_type constrained = pushed_ids_to_me[i];
    4039             : 
    4040             :             // If we don't already have a constraint for this dof,
    4041             :             // add the one we were sent
    4042      104092 :             if (!this->is_constrained_dof(constrained))
    4043             :               {
    4044       52690 :                 DofConstraintRow & row = _dof_constraints[constrained];
    4045      190925 :                 for (auto & kv : pushed_keys_vals_to_me[i])
    4046             :                   {
    4047        1672 :                     libmesh_assert_less(kv.first, this->n_dofs());
    4048      137306 :                     row[kv.first] = kv.second;
    4049             :                   }
    4050             : 
    4051       53619 :                 const Number primal_rhs = pushed_rhss_to_me[i][max_qoi_num];
    4052             : 
    4053         929 :                 if (libmesh_isnan(primal_rhs))
    4054           0 :                   libmesh_assert(pushed_keys_vals_to_me[i].empty());
    4055             : 
    4056       52690 :                 if (primal_rhs != Number(0))
    4057       11134 :                   _primal_constraint_values[constrained] = primal_rhs;
    4058             :                 else
    4059         539 :                   _primal_constraint_values.erase(constrained);
    4060             : 
    4061       52690 :                 for (unsigned int q = 0; q != max_qoi_num; ++q)
    4062             :                   {
    4063             :                     AdjointDofConstraintValues::iterator adjoint_map_it =
    4064           0 :                       _adjoint_constraint_values.find(q);
    4065             : 
    4066           0 :                     const Number adj_rhs = pushed_rhss_to_me[i][q];
    4067             : 
    4068           0 :                     if ((adjoint_map_it == _adjoint_constraint_values.end()) &&
    4069             :                         adj_rhs == Number(0))
    4070           0 :                       continue;
    4071             : 
    4072           0 :                     if (adjoint_map_it == _adjoint_constraint_values.end())
    4073           0 :                       adjoint_map_it = _adjoint_constraint_values.emplace
    4074           0 :                         (q, DofConstraintValueMap()).first;
    4075             : 
    4076             :                     DofConstraintValueMap & constraint_map =
    4077           0 :                       adjoint_map_it->second;
    4078             : 
    4079           0 :                     if (adj_rhs != Number(0))
    4080           0 :                       constraint_map[constrained] = adj_rhs;
    4081             :                     else
    4082           0 :                       constraint_map.erase(constrained);
    4083             :                   }
    4084             :               }
    4085             :           }
    4086             :       }
    4087             : 
    4088             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    4089             :     // Add all the node constraints that I've been sent
    4090        4222 :     for (auto & [pid, pushed_node_ids_to_me] : received_node_id_vecs)
    4091             :       {
    4092             :         // Before we act on any new constraint rows, we may need to
    4093             :         // make sure we have all the nodes involved!
    4094        1522 :         if (dist_mesh)
    4095           0 :           this->comm().receive_packed_range
    4096           0 :             (pid, &mesh, null_output_iterator<Node>(),
    4097             :              (Node**)nullptr, range_tag);
    4098             : 
    4099         761 :         libmesh_assert(received_node_keys_vals.count(pid));
    4100         761 :         libmesh_assert(received_offsets.count(pid));
    4101        1522 :         const auto & pushed_node_keys_vals_to_me = received_node_keys_vals.at(pid);
    4102        1522 :         const auto & pushed_offsets_to_me = received_offsets.at(pid);
    4103             : 
    4104         761 :         libmesh_assert_equal_to (pushed_node_ids_to_me.size(),
    4105             :                                  pushed_node_keys_vals_to_me.size());
    4106         761 :         libmesh_assert_equal_to (pushed_node_ids_to_me.size(),
    4107             :                                  pushed_offsets_to_me.size());
    4108             : 
    4109       12204 :         for (auto i : index_range(pushed_node_ids_to_me))
    4110             :           {
    4111       10682 :             dof_id_type constrained_id = pushed_node_ids_to_me[i];
    4112             : 
    4113             :             // If we don't already have a constraint for this node,
    4114             :             // add the one we were sent
    4115       10682 :             const Node * constrained = mesh.node_ptr(constrained_id);
    4116        8971 :             if (!this->is_constrained_node(constrained))
    4117             :               {
    4118        3422 :                 NodeConstraintRow & row = _node_constraints[constrained].first;
    4119       17950 :                 for (auto & kv : pushed_node_keys_vals_to_me[i])
    4120             :                   {
    4121       12817 :                     const Node * key_node = mesh.node_ptr(kv.first);
    4122        5561 :                     libmesh_assert(key_node);
    4123       12817 :                     row[key_node] = kv.second;
    4124             :                   }
    4125        5133 :                 _node_constraints[constrained].second = pushed_offsets_to_me[i];
    4126             :               }
    4127             :           }
    4128             :       }
    4129             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    4130             :   }
    4131             : 
    4132             :   // Now start checking for any other constraints we need
    4133             :   // to know about, requesting them recursively.
    4134             : 
    4135             :   // Create sets containing the DOFs and nodes we already depend on
    4136             :   typedef std::set<dof_id_type> DoF_RCSet;
    4137        2700 :   DoF_RCSet unexpanded_dofs;
    4138             : 
    4139     1050739 :   for (const auto & i : _dof_constraints)
    4140     1018302 :     unexpanded_dofs.insert(i.first);
    4141             : 
    4142             :   // Gather all the dof constraints we need
    4143       32437 :   this->gather_constraints(mesh, unexpanded_dofs, false);
    4144             : 
    4145             :   // Gather all the node constraints we need
    4146             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    4147             :   typedef std::set<const Node *> Node_RCSet;
    4148        2700 :   Node_RCSet unexpanded_nodes;
    4149             : 
    4150      176974 :   for (const auto & i : _node_constraints)
    4151      174274 :     unexpanded_nodes.insert(i.first);
    4152             : 
    4153             :   // We have to keep recursing while the unexpanded set is
    4154             :   // nonempty on *any* processor
    4155        2700 :   bool unexpanded_set_nonempty = !unexpanded_nodes.empty();
    4156        2700 :   this->comm().max(unexpanded_set_nonempty);
    4157             : 
    4158       21124 :   while (unexpanded_set_nonempty)
    4159             :     {
    4160             :       // Let's make sure we don't lose sync in this loop.
    4161        9212 :       parallel_object_only();
    4162             : 
    4163             :       // Request sets
    4164       18424 :       Node_RCSet node_request_set;
    4165             : 
    4166             :       // Request sets to send to each processor
    4167             :       std::map<processor_id_type, std::vector<dof_id_type>>
    4168       18424 :         requested_node_ids;
    4169             : 
    4170             :       // And the sizes of each
    4171       18424 :       std::map<processor_id_type, dof_id_type> node_ids_on_proc;
    4172             : 
    4173             :       // Fill (and thereby sort and uniq!) the main request sets
    4174      235448 :       for (const auto & i : unexpanded_nodes)
    4175             :         {
    4176      217024 :           NodeConstraintRow & row = _node_constraints[i].first;
    4177      960631 :           for (const auto & j : row)
    4178             :             {
    4179      743607 :               const Node * const node = j.first;
    4180      340926 :               libmesh_assert(node);
    4181             : 
    4182             :               // If it's non-local and we haven't already got a
    4183             :               // constraint for it, we might need to ask for one
    4184     1224937 :               if ((node->processor_id() != this->processor_id()) &&
    4185       78649 :                   !_node_constraints.count(node))
    4186       26091 :                 node_request_set.insert(node);
    4187             :             }
    4188             :         }
    4189             : 
    4190             :       // Clear the unexpanded constraint sets; we're about to expand
    4191             :       // them
    4192        9212 :       unexpanded_nodes.clear();
    4193             : 
    4194             :       // Count requests by processor
    4195       61960 :       for (const auto & node : node_request_set)
    4196             :         {
    4197       21768 :           libmesh_assert(node);
    4198       21768 :           libmesh_assert_less (node->processor_id(), this->n_processors());
    4199       43536 :           node_ids_on_proc[node->processor_id()]++;
    4200             :         }
    4201             : 
    4202       31606 :       for (auto pair : node_ids_on_proc)
    4203       13182 :         requested_node_ids[pair.first].reserve(pair.second);
    4204             : 
    4205             :       // Prepare each processor's request set
    4206       61960 :       for (const auto & node : node_request_set)
    4207       43536 :         requested_node_ids[node->processor_id()].push_back(node->id());
    4208             : 
    4209             :       typedef std::vector<std::pair<dof_id_type, Real>> row_datum;
    4210             : 
    4211             :       auto node_row_gather_functor =
    4212             :         [this,
    4213             :          & mesh,
    4214             :          dist_mesh,
    4215             :          & packed_range_sends,
    4216             :          & range_tag]
    4217             :         (processor_id_type pid,
    4218             :          const std::vector<dof_id_type> & ids,
    4219      164258 :          std::vector<row_datum> & data)
    4220             :         {
    4221             :           // FIXME - this could be an unordered set, given a
    4222             :           // hash<pointers> specialization
    4223       13182 :           std::set<const Node *> nodes_requested;
    4224             : 
    4225             :           // Fill those requests
    4226       13182 :           const std::size_t query_size = ids.size();
    4227             : 
    4228       13182 :           data.resize(query_size);
    4229       56718 :           for (std::size_t i=0; i != query_size; ++i)
    4230             :             {
    4231       43536 :               dof_id_type constrained_id = ids[i];
    4232       43536 :               const Node * constrained_node = mesh.node_ptr(constrained_id);
    4233       21768 :               if (_node_constraints.count(constrained_node))
    4234             :                 {
    4235       42750 :                   const NodeConstraintRow & row = _node_constraints[constrained_node].first;
    4236       21375 :                   std::size_t row_size = row.size();
    4237       64125 :                   data[i].reserve(row_size);
    4238      186469 :                   for (const auto & j : row)
    4239             :                     {
    4240      143719 :                       const Node * node = j.first;
    4241      225938 :                       data[i].emplace_back(node->id(), j.second);
    4242             : 
    4243             :                       // If we're not sure whether our send
    4244             :                       // destination already has this node, let's give
    4245             :                       // it a copy.
    4246      143719 :                       if (node->processor_id() != pid && dist_mesh)
    4247           0 :                         nodes_requested.insert(node);
    4248             : 
    4249             :                       // We can have 0 nodal constraint
    4250             :                       // coefficients, where no Lagrange constraint
    4251             :                       // exists but non-Lagrange basis constraints
    4252             :                       // might.
    4253             :                       // libmesh_assert(j.second);
    4254             :                     }
    4255             :                 }
    4256             :               else
    4257             :                 {
    4258             :                   // We have to distinguish "constraint with no
    4259             :                   // constraining nodes" (e.g. due to user node
    4260             :                   // constraint equations) from "no constraint".
    4261             :                   // We'll use invalid_id for the latter.
    4262        1179 :                   data[i].emplace_back(DofObject::invalid_id, Real(0));
    4263             :                 }
    4264             :             }
    4265             : 
    4266             :           // Constraining nodes might not even exist on our
    4267             :           // correspondant's subset of a distributed mesh, so let's
    4268             :           // make them exist.
    4269       13182 :           if (dist_mesh)
    4270             :             {
    4271           0 :               packed_range_sends.push_back(Parallel::Request());
    4272           0 :               this->comm().send_packed_range
    4273           0 :                 (pid, &mesh, nodes_requested.begin(), nodes_requested.end(),
    4274           0 :                  packed_range_sends.back(), range_tag);
    4275             :             }
    4276       13182 :         };
    4277             : 
    4278             :       typedef Point node_rhs_datum;
    4279             : 
    4280             :       auto node_rhs_gather_functor =
    4281             :         [this,
    4282             :          & mesh]
    4283             :         (processor_id_type,
    4284             :          const std::vector<dof_id_type> & ids,
    4285      164772 :          std::vector<node_rhs_datum> & data)
    4286             :         {
    4287             :           // Fill those requests
    4288       13182 :           const std::size_t query_size = ids.size();
    4289             : 
    4290       13182 :           data.resize(query_size);
    4291       56718 :           for (std::size_t i=0; i != query_size; ++i)
    4292             :             {
    4293       43536 :               dof_id_type constrained_id = ids[i];
    4294       43536 :               const Node * constrained_node = mesh.node_ptr(constrained_id);
    4295       21768 :               if (_node_constraints.count(constrained_node))
    4296       42750 :                 data[i] = _node_constraints[constrained_node].second;
    4297             :               else
    4298        1179 :                 data[i](0) = std::numeric_limits<Real>::quiet_NaN();
    4299             :             }
    4300       22394 :         };
    4301             : 
    4302             :       auto node_row_action_functor =
    4303             :         [this,
    4304             :          & mesh,
    4305             :          dist_mesh,
    4306             :          & range_tag,
    4307             :          & unexpanded_nodes]
    4308             :         (processor_id_type pid,
    4309             :          const std::vector<dof_id_type> & ids,
    4310      236466 :          const std::vector<row_datum> & data)
    4311             :         {
    4312             :           // Before we act on any new constraint rows, we may need to
    4313             :           // make sure we have all the nodes involved!
    4314       13182 :           if (dist_mesh)
    4315           0 :             this->comm().receive_packed_range
    4316           0 :               (pid, &mesh, null_output_iterator<Node>(),
    4317             :                (Node**)nullptr, range_tag);
    4318             : 
    4319             :           // Add any new constraint rows we've found
    4320       13182 :           const std::size_t query_size = ids.size();
    4321             : 
    4322       56718 :           for (std::size_t i=0; i != query_size; ++i)
    4323             :             {
    4324       43536 :               const dof_id_type constrained_id = ids[i];
    4325             : 
    4326             :               // An empty row is an constraint with an empty row; for
    4327             :               // no constraint we use a "no row" placeholder
    4328       65304 :               if (data[i].empty())
    4329             :                 {
    4330           0 :                   const Node * constrained_node = mesh.node_ptr(constrained_id);
    4331           0 :                   NodeConstraintRow & row = _node_constraints[constrained_node].first;
    4332           0 :                   row.clear();
    4333             :                 }
    4334       43536 :               else if (data[i][0].first != DofObject::invalid_id)
    4335             :                 {
    4336       42750 :                   const Node * constrained_node = mesh.node_ptr(constrained_id);
    4337       42750 :                   NodeConstraintRow & row = _node_constraints[constrained_node].first;
    4338       21375 :                   row.clear();
    4339      207844 :                   for (auto & pair : data[i])
    4340             :                     {
    4341             :                       const Node * key_node =
    4342      143719 :                         mesh.node_ptr(pair.first);
    4343       61500 :                       libmesh_assert(key_node);
    4344      143719 :                       row[key_node] = pair.second;
    4345             :                     }
    4346             : 
    4347             :                   // And prepare to check for more recursive constraints
    4348       21375 :                   unexpanded_nodes.insert(constrained_node);
    4349             :                 }
    4350             :             }
    4351       13182 :         };
    4352             : 
    4353             :       auto node_rhs_action_functor =
    4354             :         [this,
    4355             :          & mesh]
    4356             :         (processor_id_type,
    4357             :          const std::vector<dof_id_type> & ids,
    4358       72288 :          const std::vector<node_rhs_datum> & data)
    4359             :         {
    4360             :           // Add rhs data for any new node constraint rows we've found
    4361       13182 :           const std::size_t query_size = ids.size();
    4362             : 
    4363       56718 :           for (std::size_t i=0; i != query_size; ++i)
    4364             :             {
    4365       43536 :               dof_id_type constrained_id = ids[i];
    4366       43536 :               const Node * constrained_node = mesh.node_ptr(constrained_id);
    4367             : 
    4368       65304 :               if (!libmesh_isnan(data[i](0)))
    4369       42750 :                 _node_constraints[constrained_node].second = data[i];
    4370             :               else
    4371         393 :                 _node_constraints.erase(constrained_node);
    4372             :             }
    4373       22394 :         };
    4374             : 
    4375             :       // Now request node constraint rows from other processors
    4376        9212 :       row_datum * node_row_ex = nullptr;
    4377             :       Parallel::pull_parallel_vector_data
    4378       18424 :         (this->comm(), requested_node_ids, node_row_gather_functor,
    4379             :          node_row_action_functor, node_row_ex);
    4380             : 
    4381             :       // And request node constraint right hand sides from other procesors
    4382        9212 :       node_rhs_datum * node_rhs_ex = nullptr;
    4383             :       Parallel::pull_parallel_vector_data
    4384       18424 :         (this->comm(), requested_node_ids, node_rhs_gather_functor,
    4385             :          node_rhs_action_functor, node_rhs_ex);
    4386             : 
    4387             : 
    4388             :       // We have to keep recursing while the unexpanded set is
    4389             :       // nonempty on *any* processor
    4390       18424 :       unexpanded_set_nonempty = !unexpanded_nodes.empty();
    4391       18424 :       this->comm().max(unexpanded_set_nonempty);
    4392             :     }
    4393        2700 :   Parallel::wait(packed_range_sends);
    4394             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    4395             : }
    4396             : 
    4397             : 
    4398             : 
    4399      295851 : void DofMap::process_constraints (MeshBase & mesh)
    4400             : {
    4401             :   // We've computed our local constraints, but they may depend on
    4402             :   // non-local constraints that we'll need to take into account.
    4403      295851 :   this->allgather_recursive_constraints(mesh);
    4404             : 
    4405      295851 :   if (_error_on_constraint_loop)
    4406             :   {
    4407             :     // Optionally check for constraint loops and throw an error
    4408             :     // if they're detected. We always do this check below in dbg/devel
    4409             :     // mode but here we optionally do it in opt mode as well.
    4410          71 :     check_for_constraint_loops();
    4411             :   }
    4412             : 
    4413             :   // Adjoints will be constrained where the primal is
    4414             :   // Therefore, we will expand the adjoint_constraint_values
    4415             :   // map whenever the primal_constraint_values map is expanded
    4416             : 
    4417             :   // First, figure out the total number of QoIs
    4418             :   const unsigned int max_qoi_num =
    4419      295780 :     _adjoint_constraint_values.empty() ?
    4420         712 :     0 : _adjoint_constraint_values.rbegin()->first+1;
    4421             : 
    4422             :   // Create a set containing the DOFs we already depend on
    4423             :   typedef std::set<dof_id_type> RCSet;
    4424       16996 :   RCSet unexpanded_set;
    4425             : 
    4426     1467123 :   for (const auto & i : _dof_constraints)
    4427     1171343 :     unexpanded_set.insert(i.first);
    4428             : 
    4429      322875 :   while (!unexpanded_set.empty())
    4430      232481 :     for (RCSet::iterator i = unexpanded_set.begin();
    4431     1256205 :          i != unexpanded_set.end(); /* nothing */)
    4432             :       {
    4433             :         // If the DOF is constrained
    4434             :         DofConstraints::iterator
    4435      103303 :           pos = _dof_constraints.find(*i);
    4436             : 
    4437      103303 :         libmesh_assert (pos != _dof_constraints.end());
    4438             : 
    4439     1229110 :         DofConstraintRow & constraint_row = pos->second;
    4440             : 
    4441             :         DofConstraintValueMap::iterator rhsit =
    4442      103303 :           _primal_constraint_values.find(*i);
    4443     1277728 :         Number constraint_rhs = (rhsit == _primal_constraint_values.end()) ?
    4444       89383 :           0 : rhsit->second;
    4445             : 
    4446             :         // A vector of DofConstraintValueMaps for each adjoint variable
    4447      206606 :         std::vector<DofConstraintValueMap::iterator> adjoint_rhs_iterators;
    4448     1229110 :         adjoint_rhs_iterators.resize(max_qoi_num);
    4449             : 
    4450             :         // Another to hold the adjoint constraint rhs
    4451     1332413 :         std::vector<Number> adjoint_constraint_rhs(max_qoi_num, 0.0);
    4452             : 
    4453             :         // Find and gather recursive constraints for each adjoint variable
    4454     1249591 :         for (auto & adjoint_map : _adjoint_constraint_values)
    4455             :           {
    4456       20481 :             const std::size_t q = adjoint_map.first;
    4457       20481 :             adjoint_rhs_iterators[q] = adjoint_map.second.find(*i);
    4458             : 
    4459       22911 :             adjoint_constraint_rhs[q] =
    4460       23661 :               (adjoint_rhs_iterators[q] == adjoint_map.second.end()) ?
    4461         750 :               0 : adjoint_rhs_iterators[q]->second;
    4462             :           }
    4463             : 
    4464      206606 :         std::vector<dof_id_type> constraints_to_expand;
    4465             : 
    4466     3165865 :         for (const auto & item : constraint_row)
    4467     3487200 :           if (item.first != *i && this->is_constrained_dof(item.first))
    4468             :             {
    4469       67902 :               unexpanded_set.insert(item.first);
    4470       67902 :               constraints_to_expand.push_back(item.first);
    4471             :             }
    4472             : 
    4473     1297012 :         for (const auto & expandable : constraints_to_expand)
    4474             :           {
    4475       67902 :             const Real this_coef = constraint_row[expandable];
    4476             : 
    4477             :             DofConstraints::const_iterator
    4478        5529 :               subpos = _dof_constraints.find(expandable);
    4479             : 
    4480        5529 :             libmesh_assert (subpos != _dof_constraints.end());
    4481             : 
    4482        5529 :             const DofConstraintRow & subconstraint_row = subpos->second;
    4483             : 
    4484       72481 :             for (const auto & item : subconstraint_row)
    4485             :               {
    4486             :                 // Assert that the constraint does not form a cycle.
    4487         607 :                 libmesh_assert(item.first != expandable);
    4488        4579 :                 constraint_row[item.first] += item.second * this_coef;
    4489             :               }
    4490             : 
    4491       67902 :             if (auto subrhsit = _primal_constraint_values.find(expandable);
    4492        5529 :                 subrhsit != _primal_constraint_values.end())
    4493       19352 :               constraint_rhs += subrhsit->second * this_coef;
    4494             : 
    4495             :             // Find and gather recursive constraints for each adjoint variable
    4496       67902 :             for (const auto & adjoint_map : _adjoint_constraint_values)
    4497             :               {
    4498           0 :                 if (auto adjoint_subrhsit = adjoint_map.second.find(expandable);
    4499           0 :                     adjoint_subrhsit != adjoint_map.second.end())
    4500           0 :                   adjoint_constraint_rhs[adjoint_map.first] += adjoint_subrhsit->second * this_coef;
    4501             :               }
    4502             : 
    4503        5529 :             constraint_row.erase(expandable);
    4504             :           }
    4505             : 
    4506     1229110 :         if (rhsit == _primal_constraint_values.end())
    4507             :           {
    4508      593895 :             if (constraint_rhs != Number(0))
    4509       11073 :               _primal_constraint_values[*i] = constraint_rhs;
    4510             :             else
    4511       53915 :               _primal_constraint_values.erase(*i);
    4512             :           }
    4513             :         else
    4514             :           {
    4515      566771 :             if (constraint_rhs != Number(0))
    4516      139477 :               rhsit->second = constraint_rhs;
    4517             :             else
    4518      429767 :               _primal_constraint_values.erase(rhsit);
    4519             :           }
    4520             : 
    4521             :         // Finally fill in the adjoint constraints for each adjoint variable if possible
    4522     1249591 :         for (auto & adjoint_map : _adjoint_constraint_values)
    4523             :           {
    4524       20481 :             const std::size_t q = adjoint_map.first;
    4525             : 
    4526       22911 :             if(adjoint_rhs_iterators[q] == adjoint_map.second.end())
    4527             :               {
    4528       11390 :                 if (adjoint_constraint_rhs[q] != Number(0))
    4529           0 :                    (adjoint_map.second)[*i] = adjoint_constraint_rhs[q];
    4530             :                 else
    4531        1680 :                   adjoint_map.second.erase(*i);
    4532             :               }
    4533             :             else
    4534             :               {
    4535        9281 :                 if (adjoint_constraint_rhs[q] != Number(0))
    4536        6810 :                   adjoint_rhs_iterators[q]->second = adjoint_constraint_rhs[q];
    4537             :                 else
    4538        2106 :                   adjoint_map.second.erase(adjoint_rhs_iterators[q]);
    4539             :               }
    4540             :           }
    4541             : 
    4542     1229110 :         if (constraints_to_expand.empty())
    4543     1090368 :           i = unexpanded_set.erase(i);
    4544             :         else
    4545        3225 :           ++i;
    4546             :       }
    4547             : 
    4548             :   // In parallel we can't guarantee that nodes/dofs which constrain
    4549             :   // others are on processors which are aware of that constraint, yet
    4550             :   // we need such awareness for sparsity pattern generation.  So send
    4551             :   // other processors any constraints they might need to know about.
    4552      295780 :   this->scatter_constraints(mesh);
    4553             : 
    4554             :   // Now that we have our root constraint dependencies sorted out, add
    4555             :   // them to the send_list
    4556      295780 :   this->add_constraints_to_send_list(mesh);
    4557      295780 : }
    4558             : 
    4559             : 
    4560             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
    4561           0 : void DofMap::check_for_cyclic_constraints()
    4562             : {
    4563             :   // Eventually make this officially libmesh_deprecated();
    4564           0 :   check_for_constraint_loops();
    4565           0 : }
    4566             : 
    4567          71 : void DofMap::check_for_constraint_loops()
    4568             : {
    4569             :   // Create a set containing the DOFs we already depend on
    4570             :   typedef std::set<dof_id_type> RCSet;
    4571           4 :   RCSet unexpanded_set;
    4572             : 
    4573             :   // Use dof_constraints_copy in this method so that we don't
    4574             :   // mess with _dof_constraints.
    4575           4 :   DofConstraints dof_constraints_copy = _dof_constraints;
    4576             : 
    4577         213 :   for (const auto & i : dof_constraints_copy)
    4578         142 :     unexpanded_set.insert(i.first);
    4579             : 
    4580          71 :   while (!unexpanded_set.empty())
    4581          73 :     for (RCSet::iterator i = unexpanded_set.begin();
    4582         142 :          i != unexpanded_set.end(); /* nothing */)
    4583             :       {
    4584             :         // If the DOF is constrained
    4585             :         DofConstraints::iterator
    4586           4 :           pos = dof_constraints_copy.find(*i);
    4587             : 
    4588           4 :         libmesh_assert (pos != dof_constraints_copy.end());
    4589             : 
    4590         142 :         DofConstraintRow & constraint_row = pos->second;
    4591             : 
    4592             :         // Comment out "rhs" parts of this method copied from process_constraints
    4593             :         // DofConstraintValueMap::iterator rhsit =
    4594             :         //   _primal_constraint_values.find(*i);
    4595             :         // Number constraint_rhs = (rhsit == _primal_constraint_values.end()) ?
    4596             :         //   0 : rhsit->second;
    4597             : 
    4598           8 :         std::vector<dof_id_type> constraints_to_expand;
    4599             : 
    4600         284 :         for (const auto & item : constraint_row)
    4601         142 :           if (item.first != *i && this->is_constrained_dof(item.first))
    4602             :             {
    4603         142 :               unexpanded_set.insert(item.first);
    4604         142 :               constraints_to_expand.push_back(item.first);
    4605             :             }
    4606             : 
    4607         213 :         for (const auto & expandable : constraints_to_expand)
    4608             :           {
    4609         142 :             const Real this_coef = constraint_row[expandable];
    4610             : 
    4611             :             DofConstraints::const_iterator
    4612           4 :               subpos = dof_constraints_copy.find(expandable);
    4613             : 
    4614           4 :             libmesh_assert (subpos != dof_constraints_copy.end());
    4615             : 
    4616           4 :             const DofConstraintRow & subconstraint_row = subpos->second;
    4617             : 
    4618         213 :             for (const auto & item : subconstraint_row)
    4619             :               {
    4620         213 :                 libmesh_error_msg_if(item.first == expandable, "Constraint loop detected");
    4621             : 
    4622          71 :                 constraint_row[item.first] += item.second * this_coef;
    4623             :               }
    4624             : 
    4625             :             // Comment out "rhs" parts of this method copied from process_constraints
    4626             :             // DofConstraintValueMap::const_iterator subrhsit =
    4627             :             //   _primal_constraint_values.find(expandable);
    4628             :             // if (subrhsit != _primal_constraint_values.end())
    4629             :             //   constraint_rhs += subrhsit->second * this_coef;
    4630             : 
    4631           2 :             constraint_row.erase(expandable);
    4632             :           }
    4633             : 
    4634             :         // Comment out "rhs" parts of this method copied from process_constraints
    4635             :         // if (rhsit == _primal_constraint_values.end())
    4636             :         //   {
    4637             :         //     if (constraint_rhs != Number(0))
    4638             :         //       _primal_constraint_values[*i] = constraint_rhs;
    4639             :         //     else
    4640             :         //       _primal_constraint_values.erase(*i);
    4641             :         //   }
    4642             :         // else
    4643             :         //   {
    4644             :         //     if (constraint_rhs != Number(0))
    4645             :         //       rhsit->second = constraint_rhs;
    4646             :         //     else
    4647             :         //       _primal_constraint_values.erase(rhsit);
    4648             :         //   }
    4649             : 
    4650          71 :         if (constraints_to_expand.empty())
    4651           0 :           i = unexpanded_set.erase(i);
    4652             :         else
    4653           2 :           ++i;
    4654             :       }
    4655           0 : }
    4656             : #else
    4657             : void DofMap::check_for_constraint_loops() {}
    4658             : void DofMap::check_for_cyclic_constraints()
    4659             : {
    4660             :   // Do nothing
    4661             : }
    4662             : #endif
    4663             : 
    4664             : 
    4665      295780 : void DofMap::scatter_constraints(MeshBase & mesh)
    4666             : {
    4667             :   // At this point each processor with a constrained node knows
    4668             :   // the corresponding constraint row, but we also need each processor
    4669             :   // with a constrainer node to know the corresponding row(s).
    4670             : 
    4671             :   // This function must be run on all processors at once
    4672        8498 :   parallel_object_only();
    4673             : 
    4674             :   // Return immediately if there's nothing to gather
    4675      304278 :   if (this->n_processors() == 1)
    4676      263412 :     return;
    4677             : 
    4678             :   // We might get to return immediately if none of the processors
    4679             :   // found any constraints
    4680      279153 :   unsigned int has_constraints = !_dof_constraints.empty()
    4681             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    4682       17291 :     || !_node_constraints.empty()
    4683             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    4684             :     ;
    4685      279153 :   this->comm().max(has_constraints);
    4686      287651 :   if (!has_constraints)
    4687        7150 :     return;
    4688             : 
    4689             :   // We may be receiving packed_range sends out of order with
    4690             :   // parallel_sync tags, so make sure they're received correctly.
    4691       35064 :   Parallel::MessageTag range_tag = this->comm().get_unique_tag();
    4692             : 
    4693             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    4694        2696 :   std::map<processor_id_type, std::set<dof_id_type>> pushed_node_ids;
    4695             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    4696             : 
    4697        2696 :   std::map<processor_id_type, std::set<dof_id_type>> pushed_ids;
    4698             : 
    4699             :   // Collect the dof constraints I need to push to each processor
    4700        1348 :   dof_id_type constrained_proc_id = 0;
    4701     1052380 :   for (const auto & [constrained, row] : _dof_constraints)
    4702             :     {
    4703     1209033 :       while (constrained >= _end_df[constrained_proc_id])
    4704       89939 :         constrained_proc_id++;
    4705             : 
    4706     1118587 :       if (constrained_proc_id != this->processor_id())
    4707      100688 :         continue;
    4708             : 
    4709     2317981 :       for (auto & j : row)
    4710             :         {
    4711     1401231 :           const dof_id_type constraining = j.first;
    4712             : 
    4713     1401231 :           processor_id_type constraining_proc_id = 0;
    4714     5428533 :           while (constraining >= _end_df[constraining_proc_id])
    4715     3802688 :             constraining_proc_id++;
    4716             : 
    4717     1579717 :           if (constraining_proc_id != this->processor_id() &&
    4718       27066 :               constraining_proc_id != constrained_proc_id)
    4719      213672 :             pushed_ids[constraining_proc_id].insert(constrained);
    4720             :         }
    4721             :     }
    4722             : 
    4723             :   // Pack the dof constraint rows and rhs's to push
    4724             : 
    4725             :   std::map<processor_id_type,
    4726             :           std::vector<std::vector<std::pair<dof_id_type, Real>>>>
    4727        2696 :     pushed_keys_vals, pushed_keys_vals_to_me;
    4728             : 
    4729             :   std::map<processor_id_type, std::vector<std::pair<dof_id_type, Number>>>
    4730        2696 :     pushed_ids_rhss, pushed_ids_rhss_to_me;
    4731             : 
    4732             :   auto gather_ids =
    4733       59344 :     [this,
    4734             :      & pushed_ids,
    4735             :      & pushed_keys_vals,
    4736             :      & pushed_ids_rhss]
    4737      298831 :     ()
    4738             :     {
    4739      103607 :       for (const auto & [pid, pid_ids] : pushed_ids)
    4740             :         {
    4741         683 :           const std::size_t ids_size = pid_ids.size();
    4742             :           std::vector<std::vector<std::pair<dof_id_type, Real>>> &
    4743       38871 :             keys_vals = pushed_keys_vals[pid];
    4744             :           std::vector<std::pair<dof_id_type,Number>> &
    4745       38871 :             ids_rhss = pushed_ids_rhss[pid];
    4746       38871 :           keys_vals.resize(ids_size);
    4747       38871 :           ids_rhss.resize(ids_size);
    4748             : 
    4749             :           std::size_t push_i;
    4750         683 :           std::set<dof_id_type>::const_iterator it;
    4751       80152 :           for (push_i = 0, it = pid_ids.begin();
    4752      291676 :                it != pid_ids.end(); ++push_i, ++it)
    4753             :             {
    4754      252805 :               const dof_id_type constrained = *it;
    4755      252805 :               DofConstraintRow & row = _dof_constraints[constrained];
    4756      252805 :               keys_vals[push_i].assign(row.begin(), row.end());
    4757             : 
    4758             :               DofConstraintValueMap::const_iterator rhsit =
    4759       20982 :                 _primal_constraint_values.find(constrained);
    4760      252805 :               ids_rhss[push_i].first = constrained;
    4761      252805 :               ids_rhss[push_i].second =
    4762      274120 :                 (rhsit == _primal_constraint_values.end()) ?
    4763         333 :                 0 : rhsit->second;
    4764             :             }
    4765             :         }
    4766       64736 :     };
    4767             : 
    4768       32368 :   gather_ids();
    4769             : 
    4770             :   auto ids_rhss_action_functor =
    4771       37505 :     [& pushed_ids_rhss_to_me]
    4772             :     (processor_id_type pid,
    4773       38188 :      const std::vector<std::pair<dof_id_type, Number>> & data)
    4774             :     {
    4775       38871 :       pushed_ids_rhss_to_me[pid] = data;
    4776       70556 :     };
    4777             : 
    4778             :   auto keys_vals_action_functor =
    4779       37505 :     [& pushed_keys_vals_to_me]
    4780             :     (processor_id_type pid,
    4781       38188 :      const std::vector<std::vector<std::pair<dof_id_type, Real>>> & data)
    4782             :     {
    4783       38871 :       pushed_keys_vals_to_me[pid] = data;
    4784       33051 :     };
    4785             : 
    4786             :   Parallel::push_parallel_vector_data
    4787       32368 :     (this->comm(), pushed_ids_rhss, ids_rhss_action_functor);
    4788             :   Parallel::push_parallel_vector_data
    4789       32368 :     (this->comm(), pushed_keys_vals, keys_vals_action_functor);
    4790             : 
    4791             :   // Now work on traded dof constraint rows
    4792             :   auto receive_dof_constraints =
    4793       59344 :     [this,
    4794             :      & pushed_ids_rhss_to_me,
    4795             :      & pushed_keys_vals_to_me]
    4796      265673 :     ()
    4797             :     {
    4798      103607 :       for (const auto & [pid, ids_rhss] : pushed_ids_rhss_to_me)
    4799             :         {
    4800       38871 :           const auto & keys_vals = pushed_keys_vals_to_me[pid];
    4801             : 
    4802         683 :           libmesh_assert_equal_to
    4803             :             (ids_rhss.size(), keys_vals.size());
    4804             : 
    4805             :           // Add the dof constraints that I've been sent
    4806      291676 :           for (auto i : index_range(ids_rhss))
    4807             :             {
    4808      273787 :               dof_id_type constrained = ids_rhss[i].first;
    4809             : 
    4810             :               // If we don't already have a constraint for this dof,
    4811             :               // add the one we were sent
    4812       89673 :               if (!this->is_constrained_dof(constrained))
    4813             :                 {
    4814      181484 :                   DofConstraintRow & row = _dof_constraints[constrained];
    4815      703108 :                   for (auto & key_val : keys_vals[i])
    4816             :                     {
    4817       47971 :                       libmesh_assert_less(key_val.first, this->n_dofs());
    4818      501957 :                       row[key_val.first] = key_val.second;
    4819             :                     }
    4820      201151 :                   if (ids_rhss[i].second != Number(0))
    4821        7918 :                     _primal_constraint_values[constrained] =
    4822         108 :                       ids_rhss[i].second;
    4823             :                   else
    4824       19559 :                     _primal_constraint_values.erase(constrained);
    4825             :                 }
    4826             :             }
    4827             :         }
    4828       66084 :     };
    4829             : 
    4830       32368 :   receive_dof_constraints();
    4831             : 
    4832             : #ifdef LIBMESH_ENABLE_NODE_CONSTRAINTS
    4833             :   // Collect the node constraints to push to each processor
    4834      219720 :   for (auto & i : _node_constraints)
    4835             :     {
    4836      217024 :       const Node * constrained = i.first;
    4837             : 
    4838      325536 :       if (constrained->processor_id() != this->processor_id())
    4839       24901 :         continue;
    4840             : 
    4841       83611 :       NodeConstraintRow & row = i.second.first;
    4842      743212 :       for (auto & j : row)
    4843             :         {
    4844      575990 :           const Node * constraining = j.first;
    4845             : 
    4846      897278 :           if (constraining->processor_id() != this->processor_id() &&
    4847       28574 :               constraining->processor_id() != constrained->processor_id())
    4848       28574 :             pushed_node_ids[constraining->processor_id()].insert(constrained->id());
    4849             :         }
    4850             :     }
    4851             : 
    4852             :   // Pack the node constraint rows and rhss to push
    4853             :   std::map<processor_id_type,
    4854             :           std::vector<std::vector<std::pair<dof_id_type,Real>>>>
    4855        2696 :     pushed_node_keys_vals, pushed_node_keys_vals_to_me;
    4856             :   std::map<processor_id_type, std::vector<std::pair<dof_id_type, Point>>>
    4857        2696 :     pushed_node_ids_offsets, pushed_node_ids_offsets_to_me;
    4858        2696 :   std::map<processor_id_type, std::vector<const Node *>> pushed_node_vecs;
    4859             : 
    4860        4126 :   for (const auto & [pid, pid_ids]: pushed_node_ids)
    4861             :     {
    4862         715 :       const std::size_t ids_size = pid_ids.size();
    4863             :       std::vector<std::vector<std::pair<dof_id_type,Real>>> &
    4864        1430 :         keys_vals = pushed_node_keys_vals[pid];
    4865             :       std::vector<std::pair<dof_id_type, Point>> &
    4866        1430 :         ids_offsets = pushed_node_ids_offsets[pid];
    4867        1430 :       keys_vals.resize(ids_size);
    4868        1430 :       ids_offsets.resize(ids_size);
    4869        1430 :       std::set<Node *> nodes;
    4870             : 
    4871             :       std::size_t push_i;
    4872         715 :       std::set<dof_id_type>::const_iterator it;
    4873       17339 :       for (push_i = 0, it = pid_ids.begin();
    4874       18054 :            it != pid_ids.end(); ++push_i, ++it)
    4875             :         {
    4876       16624 :           Node * constrained = mesh.node_ptr(*it);
    4877             : 
    4878       16624 :           if (constrained->processor_id() != pid)
    4879        8312 :             nodes.insert(constrained);
    4880             : 
    4881       16624 :           NodeConstraintRow & row = _node_constraints[constrained].first;
    4882        8312 :           std::size_t row_size = row.size();
    4883       24936 :           keys_vals[push_i].reserve(row_size);
    4884       79614 :           for (const auto & j : row)
    4885             :             {
    4886       62990 :               Node * constraining = const_cast<Node *>(j.first);
    4887             : 
    4888       96155 :               keys_vals[push_i].emplace_back(constraining->id(), j.second);
    4889             : 
    4890       62990 :               if (constraining->processor_id() != pid)
    4891       15538 :                 nodes.insert(constraining);
    4892             :             }
    4893             : 
    4894       16624 :           ids_offsets[push_i].first = *it;
    4895       16624 :           ids_offsets[push_i].second = _node_constraints[constrained].second;
    4896             :         }
    4897             : 
    4898        1430 :       if (!mesh.is_serial())
    4899             :         {
    4900           0 :           auto & pid_nodes = pushed_node_vecs[pid];
    4901           0 :           pid_nodes.assign(nodes.begin(), nodes.end());
    4902             :         }
    4903             :     }
    4904             : 
    4905             :   auto node_ids_offsets_action_functor =
    4906             :     [& pushed_node_ids_offsets_to_me]
    4907             :     (processor_id_type pid,
    4908         715 :      const std::vector<std::pair<dof_id_type, Point>> & data)
    4909             :     {
    4910        1430 :       pushed_node_ids_offsets_to_me[pid] = data;
    4911        3411 :     };
    4912             : 
    4913             :   auto node_keys_vals_action_functor =
    4914             :     [& pushed_node_keys_vals_to_me]
    4915             :     (processor_id_type pid,
    4916         715 :      const std::vector<std::vector<std::pair<dof_id_type, Real>>> & data)
    4917             :     {
    4918        1430 :       pushed_node_keys_vals_to_me[pid] = data;
    4919        3411 :     };
    4920             : 
    4921             :   // Trade pushed node constraint rows
    4922             :   Parallel::push_parallel_vector_data
    4923        2696 :     (this->comm(), pushed_node_ids_offsets, node_ids_offsets_action_functor);
    4924             :   Parallel::push_parallel_vector_data
    4925        2696 :     (this->comm(), pushed_node_keys_vals, node_keys_vals_action_functor);
    4926             : 
    4927             :   // Constraining nodes might not even exist on our subset of a
    4928             :   // distributed mesh, so let's make them exist.
    4929             : 
    4930             :   // Node unpack() now automatically adds them to the context mesh
    4931           0 :   auto null_node_functor = [](processor_id_type, const std::vector<const Node *> &){};
    4932             : 
    4933        2696 :   if (!mesh.is_serial())
    4934             :     Parallel::push_parallel_packed_range
    4935          36 :       (this->comm(), pushed_node_vecs, &mesh, null_node_functor);
    4936             : 
    4937        4126 :   for (const auto & [pid, ids_offsets] : pushed_node_ids_offsets_to_me)
    4938             :     {
    4939        1430 :       const auto & keys_vals = pushed_node_keys_vals_to_me[pid];
    4940             : 
    4941         715 :       libmesh_assert_equal_to
    4942             :         (ids_offsets.size(), keys_vals.size());
    4943             : 
    4944             :       // Add the node constraints that I've been sent
    4945       18054 :       for (auto i : index_range(ids_offsets))
    4946             :         {
    4947       16624 :           dof_id_type constrained_id = ids_offsets[i].first;
    4948             : 
    4949             :           // If we don't already have a constraint for this node,
    4950             :           // add the one we were sent
    4951       16624 :           const Node * constrained = mesh.node_ptr(constrained_id);
    4952       11676 :           if (!this->is_constrained_node(constrained))
    4953             :             {
    4954        9896 :               NodeConstraintRow & row = _node_constraints[constrained].first;
    4955       53078 :               for (auto & key_val : keys_vals[i])
    4956             :                 {
    4957       38234 :                   const Node * key_node = mesh.node_ptr(key_val.first);
    4958       38234 :                   row[key_node] = key_val.second;
    4959             :                 }
    4960        9896 :               _node_constraints[constrained].second =
    4961        9896 :                 ids_offsets[i].second;
    4962             :             }
    4963             :         }
    4964             :     }
    4965             : #endif // LIBMESH_ENABLE_NODE_CONSTRAINTS
    4966             : 
    4967             :   // Next we need to push constraints to processors which don't own
    4968             :   // the constrained dof, don't own the constraining dof, but own an
    4969             :   // element supporting the constraining dof.
    4970             :   //
    4971             :   // We need to be able to quickly look up constrained dof ids by what
    4972             :   // constrains them, so that we can handle the case where we see a
    4973             :   // foreign element containing one of our constraining DoF ids and we
    4974             :   // need to push that constraint.
    4975             :   //
    4976             :   // Getting distributed adaptive sparsity patterns right is hard.
    4977             : 
    4978             :   typedef std::map<dof_id_type, std::set<dof_id_type>> DofConstrainsMap;
    4979        2696 :   DofConstrainsMap dof_id_constrains;
    4980             : 
    4981     1166264 :   for (const auto & [constrained, row] : _dof_constraints)
    4982             :     {
    4983     3022174 :       for (const auto & j : row)
    4984             :         {
    4985     1888278 :           const dof_id_type constraining = j.first;
    4986             : 
    4987      195311 :           dof_id_type constraining_proc_id = 0;
    4988     7481667 :           while (constraining >= _end_df[constraining_proc_id])
    4989     5298850 :             constraining_proc_id++;
    4990             : 
    4991     2083589 :           if (constraining_proc_id == this->processor_id())
    4992     1401231 :             dof_id_constrains[constraining].insert(constrained);
    4993             :         }
    4994             :     }
    4995             : 
    4996             :   // Loop over all foreign elements, find any supporting our
    4997             :   // constrained dof indices.
    4998        1348 :   pushed_ids.clear();
    4999             : 
    5000      321940 :   for (const auto & elem : as_range(mesh.active_not_local_elements_begin(),
    5001    12896702 :                                     mesh.active_not_local_elements_end()))
    5002             :     {
    5003      517104 :       std::vector<dof_id_type> my_dof_indices;
    5004     6514239 :       this->dof_indices (elem, my_dof_indices);
    5005             : 
    5006    53840034 :       for (const auto & dof : my_dof_indices)
    5007             :         {
    5008    47325795 :           if (auto dcmi = dof_id_constrains.find(dof);
    5009     1751887 :               dcmi != dof_id_constrains.end())
    5010             :             {
    5011      706756 :               for (const auto & constrained : dcmi->second)
    5012             :                 {
    5013       23440 :                   dof_id_type the_constrained_proc_id = 0;
    5014     2643048 :                   while (constrained >= _end_df[the_constrained_proc_id])
    5015     2059822 :                     the_constrained_proc_id++;
    5016             : 
    5017      550408 :                   const processor_id_type elemproc = elem->processor_id();
    5018      550408 :                   if (elemproc != the_constrained_proc_id)
    5019      352169 :                     pushed_ids[elemproc].insert(constrained);
    5020             :                 }
    5021             :             }
    5022             :         }
    5023       29672 :     }
    5024             : 
    5025        1348 :   pushed_ids_rhss.clear();
    5026        1348 :   pushed_ids_rhss_to_me.clear();
    5027        1348 :   pushed_keys_vals.clear();
    5028        1348 :   pushed_keys_vals_to_me.clear();
    5029             : 
    5030       32368 :   gather_ids();
    5031             : 
    5032             :   // Trade pushed dof constraint rows
    5033             :   Parallel::push_parallel_vector_data
    5034       32368 :     (this->comm(), pushed_ids_rhss, ids_rhss_action_functor);
    5035             :   Parallel::push_parallel_vector_data
    5036       32368 :     (this->comm(), pushed_keys_vals, keys_vals_action_functor);
    5037             : 
    5038       32368 :   receive_dof_constraints();
    5039             : 
    5040             :   // Finally, we need to handle the case of remote dof coupling.  If a
    5041             :   // processor's element is coupled to a ghost element, then the
    5042             :   // processor needs to know about all constraints which affect the
    5043             :   // dofs on that ghost element, so we'll have to query the ghost
    5044             :   // element's owner.
    5045             : 
    5046        2696 :   GhostingFunctor::map_type elements_to_couple;
    5047        2696 :   DofMap::CouplingMatricesSet temporary_coupling_matrices;
    5048             : 
    5049             :   this->merge_ghost_functor_outputs
    5050       98452 :     (elements_to_couple,
    5051             :      temporary_coupling_matrices,
    5052       62040 :      this->coupling_functors_begin(),
    5053       33716 :      this->coupling_functors_end(),
    5054       64736 :      mesh.active_local_elements_begin(),
    5055       64736 :      mesh.active_local_elements_end(),
    5056             :      this->processor_id());
    5057             : 
    5058             :   // Each ghost-coupled element's owner should get a request for its dofs
    5059        2696 :   std::set<dof_id_type> requested_dofs;
    5060             : 
    5061      146535 :   for (const auto & pr : elements_to_couple)
    5062             :     {
    5063      114167 :       const Elem * elem = pr.first;
    5064             : 
    5065             :       // FIXME - optimize for the non-fully-coupled case?
    5066       20198 :       std::vector<dof_id_type> element_dofs;
    5067      114167 :       this->dof_indices(elem, element_dofs);
    5068             : 
    5069      813204 :       for (auto dof : element_dofs)
    5070      662609 :         requested_dofs.insert(dof);
    5071             :     }
    5072             : 
    5073       32368 :   this->gather_constraints(mesh, requested_dofs, false);
    5074       29672 : }
    5075             : 
    5076             : 
    5077       64805 : void DofMap::gather_constraints (MeshBase & /*mesh*/,
    5078             :                                  std::set<dof_id_type> & unexpanded_dofs,
    5079             :                                  bool /*look_for_constrainees*/)
    5080             : {
    5081             :   typedef std::set<dof_id_type> DoF_RCSet;
    5082             : 
    5083             :   // If we have heterogeneous adjoint constraints we need to
    5084             :   // communicate those too.
    5085             :   const unsigned int max_qoi_num =
    5086       64805 :     _adjoint_constraint_values.empty() ?
    5087        1348 :     0 : _adjoint_constraint_values.rbegin()->first+1;
    5088             : 
    5089             :   // We have to keep recursing while the unexpanded set is
    5090             :   // nonempty on *any* processor
    5091       64805 :   bool unexpanded_set_nonempty = !unexpanded_dofs.empty();
    5092       64805 :   this->comm().max(unexpanded_set_nonempty);
    5093             : 
    5094      104469 :   while (unexpanded_set_nonempty)
    5095             :     {
    5096             :       // Let's make sure we don't lose sync in this loop.
    5097        1608 :       parallel_object_only();
    5098             : 
    5099             :       // Request sets
    5100        3216 :       DoF_RCSet   dof_request_set;
    5101             : 
    5102             :       // Request sets to send to each processor
    5103             :       std::map<processor_id_type, std::vector<dof_id_type>>
    5104        3216 :         requested_dof_ids;
    5105             : 
    5106             :       // And the sizes of each
    5107             :       std::map<processor_id_type, dof_id_type>
    5108        3216 :         dof_ids_on_proc;
    5109             : 
    5110             :       // Fill (and thereby sort and uniq!) the main request sets
    5111     1599066 :       for (const auto & unexpanded_dof : unexpanded_dofs)
    5112             :         {
    5113             :           // If we were asked for a DoF and we don't already have a
    5114             :           // constraint for it, then we need to check for one.
    5115     1559402 :           if (auto pos = _dof_constraints.find(unexpanded_dof);
    5116      124577 :               pos == _dof_constraints.end())
    5117             :             {
    5118      517600 :               if (!this->local_index(unexpanded_dof) &&
    5119       19919 :                   !_dof_constraints.count(unexpanded_dof) )
    5120      392756 :                 dof_request_set.insert(unexpanded_dof);
    5121             :             }
    5122             :           // If we were asked for a DoF and we already have a
    5123             :           // constraint for it, then we need to check if the
    5124             :           // constraint is recursive.
    5125             :           else
    5126             :             {
    5127      100702 :               const DofConstraintRow & row = pos->second;
    5128     2778384 :               for (const auto & j : row)
    5129             :                 {
    5130     1716663 :                   const dof_id_type constraining_dof = j.first;
    5131             : 
    5132             :                   // If it's non-local and we haven't already got a
    5133             :                   // constraint for it, we might need to ask for one
    5134     1586050 :                   if (!this->local_index(constraining_dof) &&
    5135       32750 :                       !_dof_constraints.count(constraining_dof))
    5136      381987 :                     dof_request_set.insert(constraining_dof);
    5137             :                 }
    5138             :             }
    5139             :         }
    5140             : 
    5141             :       // Clear the unexpanded constraint set; we're about to expand it
    5142        1608 :       unexpanded_dofs.clear();
    5143             : 
    5144             :       // Count requests by processor
    5145       39664 :       processor_id_type proc_id = 0;
    5146      609059 :       for (const auto & i : dof_request_set)
    5147             :         {
    5148      692593 :           while (i >= _end_df[proc_id])
    5149       94867 :             proc_id++;
    5150      569395 :           dof_ids_on_proc[proc_id]++;
    5151             :         }
    5152             : 
    5153       85849 :       for (auto & pair : dof_ids_on_proc)
    5154             :         {
    5155       46185 :           requested_dof_ids[pair.first].reserve(pair.second);
    5156             :         }
    5157             : 
    5158             :       // Prepare each processor's request set
    5159       39664 :       proc_id = 0;
    5160      609059 :       for (const auto & i : dof_request_set)
    5161             :         {
    5162      692593 :           while (i >= _end_df[proc_id])
    5163       94867 :             proc_id++;
    5164      569395 :           requested_dof_ids[proc_id].push_back(i);
    5165             :         }
    5166             : 
    5167             :       typedef std::vector<std::pair<dof_id_type, Real>> row_datum;
    5168             : 
    5169             :       typedef std::vector<Number> rhss_datum;
    5170             : 
    5171             :       auto row_gather_functor =
    5172       44323 :         [this]
    5173             :         (processor_id_type,
    5174             :          const std::vector<dof_id_type> & ids,
    5175      572182 :          std::vector<row_datum> & data)
    5176             :         {
    5177             :           // Fill those requests
    5178        1862 :           const std::size_t query_size = ids.size();
    5179             : 
    5180       46185 :           data.resize(query_size);
    5181      615580 :           for (std::size_t i=0; i != query_size; ++i)
    5182             :             {
    5183      597326 :               dof_id_type constrained = ids[i];
    5184       27931 :               if (_dof_constraints.count(constrained))
    5185             :                 {
    5186       17565 :                   DofConstraintRow & row = _dof_constraints[constrained];
    5187         925 :                   std::size_t row_size = row.size();
    5188       18490 :                   data[i].reserve(row_size);
    5189       42530 :                   for (const auto & j : row)
    5190             :                     {
    5191       26120 :                       data[i].push_back(j);
    5192             : 
    5193             :                       // We should never have an invalid constraining
    5194             :                       // dof id
    5195        1155 :                       libmesh_assert(j.first != DofObject::invalid_id);
    5196             : 
    5197             :                       // We should never have a 0 constraint
    5198             :                       // coefficient; that's implicit via sparse
    5199             :                       // constraint storage
    5200             :                       //
    5201             :                       // But we can't easily control how users add
    5202             :                       // constraints, so we can't safely assert that
    5203             :                       // we're being efficient here.
    5204             :                       //
    5205             :                       // libmesh_assert(j.second);
    5206             :                     }
    5207             :                 }
    5208             :               else
    5209             :                 {
    5210             :                   // We have to distinguish "constraint with no
    5211             :                   // constraining dofs" (e.g. due to Dirichlet
    5212             :                   // constraint equations) from "no constraint".
    5213             :                   // We'll use invalid_id for the latter.
    5214      578836 :                   data[i].emplace_back(DofObject::invalid_id, Real(0));
    5215             :                 }
    5216             :             }
    5217       84241 :         };
    5218             : 
    5219             :       auto rhss_gather_functor =
    5220       44323 :         [this,
    5221             :          max_qoi_num]
    5222             :         (processor_id_type,
    5223             :          const std::vector<dof_id_type> & ids,
    5224      573101 :          std::vector<rhss_datum> & data)
    5225             :         {
    5226             :           // Fill those requests
    5227        1862 :           const std::size_t query_size = ids.size();
    5228             : 
    5229       46185 :           data.resize(query_size);
    5230      615580 :           for (std::size_t i=0; i != query_size; ++i)
    5231             :             {
    5232      569395 :               dof_id_type constrained = ids[i];
    5233       55862 :               data[i].clear();
    5234       27931 :               if (_dof_constraints.count(constrained))
    5235             :                 {
    5236             :                   DofConstraintValueMap::const_iterator rhsit =
    5237         925 :                     _primal_constraint_values.find(constrained);
    5238         925 :                   data[i].push_back
    5239       17742 :                     ((rhsit == _primal_constraint_values.end()) ?
    5240         177 :                      0 : rhsit->second);
    5241             : 
    5242       17565 :                   for (unsigned int q = 0; q != max_qoi_num; ++q)
    5243             :                     {
    5244             :                       AdjointDofConstraintValues::const_iterator adjoint_map_it =
    5245           0 :                         _adjoint_constraint_values.find(q);
    5246             : 
    5247           0 :                       if (adjoint_map_it == _adjoint_constraint_values.end())
    5248             :                         {
    5249           0 :                           data[i].push_back(0);
    5250           0 :                           continue;
    5251             :                         }
    5252             : 
    5253             :                       const DofConstraintValueMap & constraint_map =
    5254           0 :                         adjoint_map_it->second;
    5255             : 
    5256             :                       DofConstraintValueMap::const_iterator adj_rhsit =
    5257           0 :                         constraint_map.find(constrained);
    5258           0 :                       data[i].push_back
    5259           0 :                         ((adj_rhsit == constraint_map.end()) ?
    5260           0 :                          0 : adj_rhsit->second);
    5261             :                     }
    5262             :                 }
    5263             :             }
    5264       47793 :         };
    5265             : 
    5266             :       auto row_action_functor =
    5267       44323 :         [this,
    5268             :          & unexpanded_dofs]
    5269             :         (processor_id_type,
    5270             :          const std::vector<dof_id_type> & ids,
    5271       34452 :          const std::vector<row_datum> & data)
    5272             :         {
    5273             :           // Add any new constraint rows we've found
    5274        1862 :           const std::size_t query_size = ids.size();
    5275             : 
    5276      615580 :           for (std::size_t i=0; i != query_size; ++i)
    5277             :             {
    5278      569395 :               const dof_id_type constrained = ids[i];
    5279             : 
    5280             :               // An empty row is an constraint with an empty row; for
    5281             :               // no constraint we use a "no row" placeholder
    5282      597326 :               if (data[i].empty())
    5283             :                 {
    5284        1839 :                   DofConstraintRow & row = _dof_constraints[constrained];
    5285         182 :                   row.clear();
    5286             :                 }
    5287      567556 :               else if (data[i][0].first != DofObject::invalid_id)
    5288             :                 {
    5289       15726 :                   DofConstraintRow & row = _dof_constraints[constrained];
    5290         743 :                   row.clear();
    5291       41434 :                   for (auto & pair : data[i])
    5292             :                     {
    5293        1155 :                       libmesh_assert_less(pair.first, this->n_dofs());
    5294       24965 :                       row[pair.first] = pair.second;
    5295             :                     }
    5296             : 
    5297             :                   // And prepare to check for more recursive constraints
    5298       14983 :                   unexpanded_dofs.insert(constrained);
    5299             :                 }
    5300             :             }
    5301       84241 :         };
    5302             : 
    5303             :       auto rhss_action_functor =
    5304       44323 :         [this,
    5305             :          max_qoi_num]
    5306             :         (processor_id_type,
    5307             :          const std::vector<dof_id_type> & ids,
    5308       18916 :          const std::vector<rhss_datum> & data)
    5309             :         {
    5310             :           // Add rhs data for any new constraint rows we've found
    5311        1862 :           const std::size_t query_size = ids.size();
    5312             : 
    5313      615580 :           for (std::size_t i=0; i != query_size; ++i)
    5314             :             {
    5315      597326 :               if (!data[i].empty())
    5316             :                 {
    5317       17565 :                   dof_id_type constrained = ids[i];
    5318       17565 :                   if (data[i][0] != Number(0))
    5319         516 :                     _primal_constraint_values[constrained] = data[i][0];
    5320             :                   else
    5321         914 :                     _primal_constraint_values.erase(constrained);
    5322             : 
    5323       17565 :                   for (unsigned int q = 0; q != max_qoi_num; ++q)
    5324             :                     {
    5325             :                       AdjointDofConstraintValues::iterator adjoint_map_it =
    5326           0 :                         _adjoint_constraint_values.find(q);
    5327             : 
    5328           0 :                       if ((adjoint_map_it == _adjoint_constraint_values.end()) &&
    5329           0 :                           data[i][q+1] == Number(0))
    5330           0 :                         continue;
    5331             : 
    5332           0 :                       if (adjoint_map_it == _adjoint_constraint_values.end())
    5333           0 :                         adjoint_map_it = _adjoint_constraint_values.emplace
    5334           0 :                           (q, DofConstraintValueMap()).first;
    5335             : 
    5336             :                       DofConstraintValueMap & constraint_map =
    5337           0 :                         adjoint_map_it->second;
    5338             : 
    5339           0 :                       if (data[i][q+1] != Number(0))
    5340           0 :                         constraint_map[constrained] =
    5341           0 :                           data[i][q+1];
    5342             :                       else
    5343           0 :                         constraint_map.erase(constrained);
    5344             :                     }
    5345             :                 }
    5346             :             }
    5347             : 
    5348       84241 :         };
    5349             : 
    5350             :       // Now request constraint rows from other processors
    5351        1608 :       row_datum * row_ex = nullptr;
    5352             :       Parallel::pull_parallel_vector_data
    5353       39664 :         (this->comm(), requested_dof_ids, row_gather_functor,
    5354             :          row_action_functor, row_ex);
    5355             : 
    5356             :       // And request constraint right hand sides from other procesors
    5357        1608 :       rhss_datum * rhs_ex = nullptr;
    5358             :       Parallel::pull_parallel_vector_data
    5359       39664 :         (this->comm(), requested_dof_ids, rhss_gather_functor,
    5360             :          rhss_action_functor, rhs_ex);
    5361             : 
    5362             :       // We have to keep recursing while the unexpanded set is
    5363             :       // nonempty on *any* processor
    5364       39664 :       unexpanded_set_nonempty = !unexpanded_dofs.empty();
    5365       39664 :       this->comm().max(unexpanded_set_nonempty);
    5366             :     }
    5367       64805 : }
    5368             : 
    5369      295780 : void DofMap::add_constraints_to_send_list (const MeshBase & mesh)
    5370             : {
    5371             :   // This function must be run on all processors at once
    5372        8498 :   parallel_object_only();
    5373             : 
    5374             :   // Return immediately if there's nothing to gather
    5375      304278 :   if (this->n_processors() == 1)
    5376      292054 :     return;
    5377             : 
    5378             :   // We might get to return immediately if none of the processors
    5379             :   // found any constraints
    5380      287651 :   unsigned int has_constraints = !_dof_constraints.empty();
    5381      279153 :   this->comm().max(has_constraints);
    5382      287651 :   if (!has_constraints)
    5383        7438 :     return;
    5384             : 
    5385     1102538 :   auto add_row = [this](const DofConstraintRow & constraint_row) {
    5386     2343221 :     for (const auto & j : constraint_row)
    5387             :       {
    5388     1420162 :         dof_id_type constraint_dependency = j.first;
    5389             : 
    5390             :         // No point in adding one of our own dofs to the send_list
    5391     1268231 :         if (this->local_index(constraint_dependency))
    5392     1188758 :           continue;
    5393             : 
    5394      231404 :         _send_list.push_back(constraint_dependency);
    5395             :       }
    5396      223076 :   };
    5397             : 
    5398             :   // We usually only need dependencies of our own constrained dofs
    5399     1249005 :   for (const auto & [constrained_dof, constraint_row] : _dof_constraints)
    5400     1217213 :     if (this->local_index(constrained_dof))
    5401      916750 :       add_row(constraint_row);
    5402             : 
    5403             :   // If we only need constraint DoFs constraining DoFs which are
    5404             :   // algebraically local, we're done.
    5405       31792 :   if (!this->has_static_condensation() && !_need_ghost_constraints)
    5406         952 :     return;
    5407             : 
    5408             :   // If we use StaticCondensation, though, we may need constraint DoFs
    5409             :   // constraining DoFs which are not local (they're on someone else's
    5410             :   // node) but which are supported on local elements.  Let's get those
    5411             :   // too if we have to.
    5412             :   //
    5413             :   // Kokkos-MOOSE also requires that, because it cannot use VecSetValues
    5414             :   // and MatSetValues which can dynamically cache remote entries. Instead,
    5415             :   // it allocates ghost entries in advance based on libMesh send_list and
    5416             :   // accumulates to those, and does the assembly at once. When ghost DOFs
    5417             :   // are constrained, it has to add residual/Jacobian contributions to the
    5418             :   // dependencies of ghost DOFs locally as well.
    5419             :   //
    5420             :   // We'll potentially be hitting the same constrained DoFs from
    5421             :   // multiple directions.
    5422      332736 :   for (auto & elem : mesh.active_local_element_ptr_range())
    5423             :     {
    5424       30030 :       std::vector<dof_id_type> di;
    5425      177711 :       this->dof_indices (elem, di);
    5426     2537819 :       for (const auto & dof_id : di)
    5427     2360108 :         if (!this->local_index(dof_id))
    5428      141895 :           if (auto pos = _dof_constraints.find(dof_id);
    5429        4032 :               pos != _dof_constraints.end())
    5430        6309 :             add_row(pos->second);
    5431        3510 :     }
    5432             : }
    5433             : 
    5434             : 
    5435             : 
    5436             : #endif // LIBMESH_ENABLE_CONSTRAINTS
    5437             : 
    5438             : 
    5439             : #ifdef LIBMESH_ENABLE_AMR
    5440             : 
    5441         576 : void DofMap::constrain_p_dofs (unsigned int var,
    5442             :                                const Elem * elem,
    5443             :                                unsigned int s,
    5444             :                                unsigned int p)
    5445             : {
    5446             :   // We're constraining dofs on elem which correspond to p refinement
    5447             :   // levels above p - this only makes sense if elem's p refinement
    5448             :   // level is above p.
    5449          48 :   libmesh_assert_greater (elem->p_level(), p);
    5450          48 :   libmesh_assert_less (s, elem->n_sides());
    5451             : 
    5452          96 :   const unsigned int sys_num = this->sys_number();
    5453         576 :   FEType fe_type = this->variable_type(var);
    5454             : 
    5455         576 :   const unsigned int n_nodes = elem->n_nodes();
    5456        5760 :   for (unsigned int n = 0; n != n_nodes; ++n)
    5457        5184 :     if (elem->is_node_on_side(n, s))
    5458             :       {
    5459         144 :         const Node & node = elem->node_ref(n);
    5460             :         const unsigned int low_nc =
    5461        1728 :           FEInterface::n_dofs_at_node (fe_type, p, elem, n);
    5462             :         const unsigned int high_nc =
    5463        1728 :           FEInterface::n_dofs_at_node (fe_type, elem, n);
    5464             : 
    5465             :         // since we may be running this method concurrently
    5466             :         // on multiple threads we need to acquire a lock
    5467             :         // before modifying the _dof_constraints object.
    5468         288 :         Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
    5469             : 
    5470        1728 :         if (elem->is_vertex(n))
    5471             :           {
    5472             :             // Add "this is zero" constraint rows for high p vertex
    5473             :             // dofs
    5474        1152 :             for (unsigned int i = low_nc; i != high_nc; ++i)
    5475             :               {
    5476           0 :                 _dof_constraints[node.dof_number(sys_num,var,i)].clear();
    5477           0 :                 _primal_constraint_values.erase(node.dof_number(sys_num,var,i));
    5478             :               }
    5479             :           }
    5480             :         else
    5481             :           {
    5482         576 :             const unsigned int total_dofs = node.n_comp(sys_num, var);
    5483          48 :             libmesh_assert_greater_equal (total_dofs, high_nc);
    5484             :             // Add "this is zero" constraint rows for high p
    5485             :             // non-vertex dofs, which are numbered in reverse
    5486        1152 :             for (unsigned int j = low_nc; j != high_nc; ++j)
    5487             :               {
    5488         576 :                 const unsigned int i = total_dofs - j - 1;
    5489         576 :                 _dof_constraints[node.dof_number(sys_num,var,i)].clear();
    5490         576 :                 _primal_constraint_values.erase(node.dof_number(sys_num,var,i));
    5491             :               }
    5492             :           }
    5493             :       }
    5494         576 : }
    5495             : 
    5496             : #endif // LIBMESH_ENABLE_AMR
    5497             : 
    5498             : 
    5499             : #ifdef LIBMESH_ENABLE_DIRICHLET
    5500       11032 : void DofMap::add_dirichlet_boundary (const DirichletBoundary & dirichlet_boundary)
    5501             : {
    5502       21440 :   _dirichlet_boundaries->push_back(std::make_unique<DirichletBoundary>(dirichlet_boundary));
    5503       11032 : }
    5504             : 
    5505             : 
    5506        2311 : void DofMap::add_adjoint_dirichlet_boundary (const DirichletBoundary & dirichlet_boundary,
    5507             :                                              unsigned int qoi_index)
    5508             : {
    5509             :   unsigned int old_size = cast_int<unsigned int>
    5510         132 :     (_adjoint_dirichlet_boundaries.size());
    5511        3502 :   for (unsigned int i = old_size; i <= qoi_index; ++i)
    5512        2348 :     _adjoint_dirichlet_boundaries.push_back(std::make_unique<DirichletBoundaries>());
    5513             : 
    5514             :   // Make copy of DirichletBoundary, owned by _adjoint_dirichlet_boundaries
    5515        2311 :   _adjoint_dirichlet_boundaries[qoi_index]->push_back
    5516        4490 :     (std::make_unique<DirichletBoundary>(dirichlet_boundary));
    5517        2311 : }
    5518             : 
    5519             : 
    5520      132476 : bool DofMap::has_adjoint_dirichlet_boundaries(unsigned int q) const
    5521             : {
    5522      136260 :   if (_adjoint_dirichlet_boundaries.size() > q)
    5523      130772 :     return true;
    5524             : 
    5525          48 :   return false;
    5526             : }
    5527             : 
    5528             : 
    5529             : const DirichletBoundaries *
    5530           0 : DofMap::get_adjoint_dirichlet_boundaries(unsigned int q) const
    5531             : {
    5532           0 :   libmesh_assert_greater(_adjoint_dirichlet_boundaries.size(),q);
    5533           0 :   return _adjoint_dirichlet_boundaries[q].get();
    5534             : }
    5535             : 
    5536             : 
    5537             : DirichletBoundaries *
    5538           0 : DofMap::get_adjoint_dirichlet_boundaries(unsigned int q)
    5539             : {
    5540             :   unsigned int old_size = cast_int<unsigned int>
    5541           0 :     (_adjoint_dirichlet_boundaries.size());
    5542           0 :   for (unsigned int i = old_size; i <= q; ++i)
    5543           0 :     _adjoint_dirichlet_boundaries.push_back(std::make_unique<DirichletBoundaries>());
    5544             : 
    5545           0 :   return _adjoint_dirichlet_boundaries[q].get();
    5546             : }
    5547             : 
    5548             : 
    5549           0 : void DofMap::remove_dirichlet_boundary (const DirichletBoundary & boundary_to_remove)
    5550             : {
    5551             :   // Find a boundary condition matching the one to be removed
    5552           0 :   auto lam = [&boundary_to_remove](const auto & bdy)
    5553           0 :     {return bdy->b == boundary_to_remove.b && bdy->variables == boundary_to_remove.variables;};
    5554             : 
    5555           0 :   auto it = std::find_if(_dirichlet_boundaries->begin(), _dirichlet_boundaries->end(), lam);
    5556             : 
    5557             :   // Assert it was actually found and remove it from the vector
    5558           0 :   libmesh_assert (it != _dirichlet_boundaries->end());
    5559           0 :   _dirichlet_boundaries->erase(it);
    5560           0 : }
    5561             : 
    5562             : 
    5563           0 : void DofMap::remove_adjoint_dirichlet_boundary (const DirichletBoundary & boundary_to_remove,
    5564             :                                                 unsigned int qoi_index)
    5565             : {
    5566           0 :   libmesh_assert_greater(_adjoint_dirichlet_boundaries.size(),
    5567             :                          qoi_index);
    5568             : 
    5569           0 :   auto lam = [&boundary_to_remove](const auto & bdy)
    5570           0 :     {return bdy->b == boundary_to_remove.b && bdy->variables == boundary_to_remove.variables;};
    5571             : 
    5572           0 :   auto it = std::find_if(_adjoint_dirichlet_boundaries[qoi_index]->begin(),
    5573           0 :                          _adjoint_dirichlet_boundaries[qoi_index]->end(),
    5574           0 :                          lam);
    5575             : 
    5576             :   // Assert it was actually found and remove it from the vector
    5577           0 :   libmesh_assert (it != _adjoint_dirichlet_boundaries[qoi_index]->end());
    5578           0 :   _adjoint_dirichlet_boundaries[qoi_index]->erase(it);
    5579           0 : }
    5580             : 
    5581             : 
    5582       24469 : void DofMap::check_dirichlet_bcid_consistency (const MeshBase & mesh,
    5583             :                                                const DirichletBoundary & boundary) const
    5584             : {
    5585             :   const std::set<boundary_id_type>& mesh_side_bcids =
    5586         692 :     mesh.get_boundary_info().get_boundary_ids();
    5587             :   const std::set<boundary_id_type>& mesh_edge_bcids =
    5588         692 :     mesh.get_boundary_info().get_edge_boundary_ids();
    5589             :   const std::set<boundary_id_type>& mesh_node_bcids =
    5590         692 :     mesh.get_boundary_info().get_node_boundary_ids();
    5591         692 :   const std::set<boundary_id_type>& dbc_bcids = boundary.b;
    5592             : 
    5593             :   // DirichletBoundary id sets should be consistent across all ranks
    5594         692 :   libmesh_assert(mesh.comm().verify(dbc_bcids.size()));
    5595             : 
    5596       71241 :   for (const auto & bc_id : dbc_bcids)
    5597             :     {
    5598             :       // DirichletBoundary id sets should be consistent across all ranks
    5599        1324 :       libmesh_assert(mesh.comm().verify(bc_id));
    5600             : 
    5601       19664 :       bool found_bcid = (mesh_side_bcids.find(bc_id) != mesh_side_bcids.end() ||
    5602       65112 :                          mesh_edge_bcids.find(bc_id) != mesh_edge_bcids.end() ||
    5603       45454 :                          mesh_node_bcids.find(bc_id) != mesh_node_bcids.end());
    5604             : 
    5605             :       // On a distributed mesh, boundary id sets may *not* be
    5606             :       // consistent across all ranks, since not all ranks see all
    5607             :       // boundaries
    5608       46772 :       mesh.comm().max(found_bcid);
    5609             : 
    5610       46772 :       libmesh_error_msg_if(!found_bcid,
    5611             :                            "Could not find Dirichlet boundary id " << bc_id << " in mesh!");
    5612             :     }
    5613       24469 : }
    5614             : 
    5615             : #endif // LIBMESH_ENABLE_DIRICHLET
    5616             : 
    5617             : 
    5618             : #ifdef LIBMESH_ENABLE_PERIODIC
    5619             : 
    5620         513 : void DofMap::add_periodic_boundary (const PeriodicBoundaryBase & periodic_boundary)
    5621             : {
    5622         535 :   auto inverse_boundary = periodic_boundary.clone(PeriodicBoundaryBase::INVERSE);
    5623         513 :   this->add_periodic_boundary(periodic_boundary, *inverse_boundary);
    5624         513 : }
    5625             : 
    5626             : 
    5627             : 
    5628         513 : void DofMap::add_periodic_boundary (const PeriodicBoundaryBase & boundary,
    5629             :                                     const PeriodicBoundaryBase & inverse_boundary)
    5630             : {
    5631          22 :   libmesh_assert_equal_to (boundary.myboundary, inverse_boundary.pairedboundary);
    5632          22 :   libmesh_assert_equal_to (boundary.pairedboundary, inverse_boundary.myboundary);
    5633          22 :   libmesh_assert(boundary.get_variables() == inverse_boundary.get_variables());
    5634             : 
    5635             :   // See if we already have a periodic boundary associated myboundary...
    5636             :   PeriodicBoundaryBase * existing_boundary =
    5637         513 :     _periodic_boundaries->boundary(boundary.myboundary);
    5638             : 
    5639         513 :   if (!existing_boundary)
    5640             :     {
    5641             :       // ...if not, clone the inputs and add them to the
    5642             :       // PeriodicBoundaries object.
    5643             :       // These will be cleaned up automatically in the
    5644             :       // _periodic_boundaries destructor.
    5645         535 :       _periodic_boundaries->emplace(boundary.myboundary, boundary.clone());
    5646         535 :       _periodic_boundaries->emplace(inverse_boundary.myboundary, inverse_boundary.clone());
    5647             :     }
    5648             :   else
    5649             :     {
    5650             :       // ...otherwise, merge this object's variable IDs with the
    5651             :       // existing boundary object's.
    5652           0 :       existing_boundary->merge(boundary);
    5653             : 
    5654             :       // Do the same merging process for the inverse boundary.  The
    5655             :       // inverse had better already exist!
    5656             :       PeriodicBoundaryBase * existing_inverse_boundary =
    5657           0 :         _periodic_boundaries->boundary(boundary.pairedboundary);
    5658           0 :       libmesh_assert(existing_inverse_boundary);
    5659           0 :       existing_inverse_boundary->merge(inverse_boundary);
    5660             : 
    5661             :       // If we had to merge different *types* of boundaries then
    5662             :       // something likely has gone wrong.
    5663             : #ifdef LIBMESH_HAVE_RTTI
    5664             :       // typeid needs to be given references, not just pointers, to
    5665             :       // return a derived class name.
    5666           0 :       libmesh_assert(typeid(boundary) == typeid(*existing_boundary));
    5667           0 :       libmesh_assert(typeid(inverse_boundary) == typeid(*existing_inverse_boundary));
    5668             : #endif
    5669             :     }
    5670         513 : }
    5671             : 
    5672             : 
    5673             : #endif
    5674             : 
    5675             : 
    5676             : } // namespace libMesh

Generated by: LCOV version 1.14