LCOV - code coverage report
Current view: top level - include/systems - generic_projector.h (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4475 (55045b) with base a68cc6 Lines: 1108 1205 92.0 %
Date: 2026-06-03 14:29:06 Functions: 310 393 78.9 %
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             : 
      19             : 
      20             : #ifndef GENERIC_PROJECTOR_H
      21             : #define GENERIC_PROJECTOR_H
      22             : 
      23             : // C++ includes
      24             : #include <vector>
      25             : 
      26             : // libMesh includes
      27             : #include "libmesh/dense_matrix.h"
      28             : #include "libmesh/dense_vector.h"
      29             : #include "libmesh/dof_map.h"
      30             : #include "libmesh/elem.h"
      31             : #include "libmesh/fe_base.h"
      32             : #include "libmesh/fe_interface.h"
      33             : #include "libmesh/int_range.h"
      34             : #include "libmesh/libmesh_logging.h"
      35             : #include "libmesh/mesh_tools.h"
      36             : #include "libmesh/numeric_vector.h"
      37             : #include "libmesh/quadrature.h"
      38             : #include "libmesh/system.h"
      39             : #include "libmesh/threads.h"
      40             : #include "libmesh/tensor_tools.h"
      41             : #include "libmesh/libmesh_common.h"
      42             : 
      43             : // TIMPI includes
      44             : #include "timpi/parallel_sync.h"
      45             : 
      46             : // C++ Includes
      47             : #include <memory>
      48             : 
      49             : namespace libMesh
      50             : {
      51             : 
      52             : /**
      53             :  * For ease of communication, we allow users to translate their own
      54             :  * value types to a more easily computable (typically a vector of some
      55             :  * fixed-size type) output, by specializing these calls using
      56             :  * different types.
      57             :  */
      58             : template <typename T>
      59             : struct TypeToSend {
      60             :   typedef T type;
      61             : };
      62             : 
      63             : template <typename T>
      64        1260 : const typename TypeToSend<T>::type convert_to_send (const T& in)
      65       23340 : { return in; }
      66             : 
      67             : template <typename SendT, typename T>
      68        1260 : void convert_from_receive (SendT & received, T & converted)
      69       23340 : { converted = received; }
      70             : 
      71             : /**
      72             :  * The GenericProjector class implements the core of other projection
      73             :  * operations, using two input functors to read values to be projected
      74             :  * and an output functor to set degrees of freedom in the result.
      75             :  *
      76             :  * This may be executed in parallel on multiple threads.
      77             :  *
      78             :  * \author Roy H. Stogner
      79             :  * \date 2016
      80             :  */
      81             : template <typename FFunctor, typename GFunctor,
      82             :           typename FValue, typename ProjectionAction>
      83             : class GenericProjector
      84             : {
      85             : public:
      86             :   /**
      87             :    * Convenience typedef for the Node-to-attached-Elem mapping that
      88             :    * may be passed in to the constructor.
      89             :    */
      90             :   typedef std::unordered_map<dof_id_type, std::vector<dof_id_type>> NodesToElemMap;
      91             : 
      92             : private:
      93             :   const System & system;
      94             : 
      95             :   // For TBB compatibility and thread safety we'll copy these in
      96             :   // operator()
      97             :   FFunctor & master_f;
      98             : 
      99             :   /**
     100             :    * Needed for C1 type elements only. master_g is either a shallow
     101             :    * copy of a GFunctor pointer passed to the constructor, or to
     102             :    * master_g_deepcopy.get(), depending on which constructor was
     103             :    * called.
     104             :    */
     105             :   std::unique_ptr<GFunctor> master_g_deepcopy;
     106             :   GFunctor * master_g;
     107             : 
     108             :   ProjectionAction & master_action;
     109             :   const std::vector<unsigned int> & variables;
     110             : 
     111             :   /**
     112             :    * nodes_to_elem is either a shallow copy of a map passed in to
     113             :    * the constructor, or points to nodes_to_elem_ourcopy, if no
     114             :    * such map was provided.
     115             :    */
     116             :   NodesToElemMap nodes_to_elem_ourcopy;
     117             :   NodesToElemMap * nodes_to_elem;
     118             : 
     119             :   bool done_saving_ids;
     120             : 
     121             : public:
     122      256003 :   GenericProjector (const System & system_in,
     123             :                     FFunctor & f_in,
     124             :                     GFunctor * g_in,
     125             :                     ProjectionAction & act_in,
     126             :                     const std::vector<unsigned int> & variables_in,
     127             :                     NodesToElemMap * nodes_to_elem_in = nullptr) :
     128      240835 :     system(system_in),
     129      240835 :     master_f(f_in),
     130      240835 :     master_g(g_in),
     131      240835 :     master_action(act_in),
     132      240835 :     variables(variables_in),
     133      271171 :     nodes_to_elem(nodes_to_elem_in)
     134             :   {
     135      256003 :     if (!nodes_to_elem_in)
     136             :       {
     137      256003 :         MeshTools::build_nodes_to_elem_map (system.get_mesh(), nodes_to_elem_ourcopy);
     138      256003 :         nodes_to_elem = &nodes_to_elem_ourcopy;
     139             :       }
     140      256003 :   }
     141             : 
     142             :   GenericProjector (const GenericProjector & in) :
     143             :     system(in.system),
     144             :     master_f(in.master_f),
     145             :     master_g_deepcopy(in.master_g ? std::make_unique<GFunctor>(*in.master_g) : nullptr),
     146             :     master_g(in.master_g ? master_g_deepcopy.get() : nullptr),
     147             :     master_action(in.master_action),
     148             :     variables(in.variables),
     149             :     nodes_to_elem(in.nodes_to_elem)
     150             :   {}
     151             : 
     152      496838 :   ~GenericProjector() = default;
     153             : 
     154             :   // The single "project" function handles all the threaded projection
     155             :   // calculations and intervening MPI communications
     156             :   void project(const ConstElemRange & range);
     157             : 
     158             :   // We generally need to hang on to every value we've calculated
     159             :   // until we're all done, because later projection calculations
     160             :   // depend on boundary data from earlier calculations.
     161             :   std::unordered_map<dof_id_type, typename FFunctor::ValuePushType> ids_to_save;
     162             : 
     163             :   // This needs to be sorted so we can get sorted dof indices cheaply
     164             :   // later
     165             :   typedef std::set<unsigned int> var_set;
     166             : 
     167             :   // We now need to divide our threadable work into stages, to allow
     168             :   // for the potential necessity of MPI communication in between them.
     169             :   struct SubFunctor {
     170             :     GenericProjector & projector;
     171             : 
     172             :     SubFunctor (GenericProjector & p);
     173             : 
     174             :     // While we're working on nodes, also figure out which ghosted
     175             :     // nodes will have data we might need to send to their owners
     176             :     // instead of being acted on by ourselves.
     177             :     //
     178             :     // We keep track of which dof ids we might need to send, and what
     179             :     // values those ids should get (along with a pprocessor_id to
     180             :     // leave invalid in case *we* can't compute those values either).
     181             :     std::unordered_map<dof_id_type,
     182             :                        std::pair<typename FFunctor::ValuePushType,
     183             :                                  processor_id_type>> new_ids_to_push;
     184             : 
     185             :     // We'll hang on to any new ids to save on a per-thread basis
     186             :     // because we won't need them until subsequent phases
     187             :     std::unordered_map<dof_id_type, typename FFunctor::ValuePushType> new_ids_to_save;
     188             : 
     189             :     // Helper function for filling new_ids_to_push
     190             :     void find_dofs_to_send (const Node & node,
     191             :                             const Elem & elem,
     192             :                             unsigned short node_num,
     193             :                             const var_set & vars);
     194             : 
     195             :     // When we have new data to act on, we may also need to save it
     196             :     // and get ready to push it.
     197             :     template <typename InsertInput,
     198             :               typename std::enable_if<
     199             :                   std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
     200             :                   int>::type = 0>
     201             :     void insert_id(dof_id_type id, const InsertInput & val, processor_id_type pid);
     202             : 
     203             :     template <typename InsertInput,
     204             :               typename std::enable_if<
     205             :                   !std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
     206             :                   int>::type = 0>
     207             :     void insert_id(dof_id_type id, const InsertInput & val, processor_id_type pid);
     208             : 
     209             :     template <typename InsertInput,
     210             :               typename std::enable_if<
     211             :                   std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
     212             :                   int>::type = 0>
     213             :     void insert_ids(const std::vector<dof_id_type> & ids,
     214             :                     const std::vector<InsertInput> & vals,
     215             :                     processor_id_type pid);
     216             : 
     217             :     template <typename InsertInput,
     218             :               typename std::enable_if<
     219             :                   !std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
     220             :                   int>::type = 0>
     221             :     void insert_ids(const std::vector<dof_id_type> & ids,
     222             :                     const std::vector<InsertInput> & vals,
     223             :                     processor_id_type pid);
     224             : 
     225             :     // Our subclasses will need to merge saved ids
     226             :     void join (const SubFunctor & other);
     227             : 
     228             :   protected:
     229             :     // For thread safety and efficiency we'll want thread-local copies
     230             :     // of various functors
     231             :     ProjectionAction action;
     232             :     FFunctor f;
     233             : 
     234             :     // Each stage of the work we do will require us to prepare
     235             :     // FEMContext objects to assist in the work.
     236             :     FEMContext context;
     237             : 
     238             :     // These get used often enough to cache them
     239             :     std::vector<FEContinuity> conts;
     240             :     std::vector<FEFieldType> field_types;
     241             : 
     242             :     const System & system;
     243             :   };
     244             : 
     245             : 
     246             :   typedef std::pair<const Node *, std::tuple<const Elem *, unsigned short, var_set>>
     247             :     node_projection;
     248             : 
     249             :   typedef StoredRange<std::vector<node_projection>::const_iterator,
     250             :                       node_projection> node_range;
     251             : 
     252             : 
     253      969401 :   struct SubProjector : public SubFunctor {
     254             :     SubProjector (GenericProjector & p);
     255             : 
     256             :     using SubFunctor::action;
     257             :     using SubFunctor::f;
     258             :     using SubFunctor::system;
     259             :     using SubFunctor::context;
     260             :     using SubFunctor::conts;
     261             :     using SubFunctor::field_types;
     262             :     using SubFunctor::insert_id;
     263             :     using SubFunctor::insert_ids;
     264             : 
     265             :   protected:
     266             :     // Projections of C1 elements require a gradient as well
     267             :     std::unique_ptr<GFunctor> g;
     268             : 
     269             :     void construct_projection
     270             :       (const std::vector<dof_id_type> & dof_indices_var,
     271             :        const std::vector<unsigned int> & involved_dofs,
     272             :        unsigned int var_component,
     273             :        const Node * node,
     274             :        const FEGenericBase<typename FFunctor::RealType> & fe);
     275             :   };
     276             : 
     277             : 
     278             :   // First we'll copy DoFs where we can, while sorting remaining DoFs
     279             :   // for interpolation and projection later.
     280             :   struct SortAndCopy : public SubFunctor {
     281      256003 :     SortAndCopy (GenericProjector & p) : SubFunctor(p) {}
     282             : 
     283        7742 :     SortAndCopy (SortAndCopy & other, Threads::split) : SubFunctor(other.projector) {}
     284             : 
     285             :     using SubFunctor::action;
     286             :     using SubFunctor::f;
     287             :     using SubFunctor::system;
     288             :     using SubFunctor::context;
     289             :     using SubFunctor::insert_ids;
     290             : 
     291             :     void operator() (const ConstElemRange & range);
     292             : 
     293             :     // We need to merge saved multimaps when working from multiple
     294             :     // threads
     295             :     void join (const SortAndCopy & other);
     296             : 
     297             :     // For vertices, edges and sides, we need to know what element,
     298             :     // what local vertex/edge/side number, and what set of variables
     299             :     // to evaluate.
     300             :     typedef std::unordered_multimap<const Node *, std::tuple<const Elem *, unsigned short, var_set>> ves_multimap;
     301             : 
     302             :     ves_multimap vertices, edges, sides;
     303             :     std::vector<const Elem *> interiors;
     304             :   };
     305             : 
     306             : 
     307      251219 :   struct ProjectVertices : public SubProjector {
     308      256003 :     ProjectVertices (GenericProjector & p) : SubProjector(p) {}
     309             : 
     310        5958 :     ProjectVertices (ProjectVertices & p_v, Threads::split) : SubProjector(p_v.projector) {}
     311             : 
     312             :     using SubProjector::action;
     313             :     using SubProjector::f;
     314             :     using SubProjector::g;
     315             :     using SubProjector::system;
     316             :     using SubProjector::context;
     317             :     using SubProjector::conts;
     318             :     using SubFunctor::field_types;
     319             : 
     320             :     using SubProjector::insert_id;
     321             :     using SubProjector::insert_ids;
     322             : 
     323             :     void operator() (const node_range & range);
     324             :   };
     325             : 
     326             : 
     327        8162 :   struct ProjectEdges : public SubProjector {
     328      256003 :     ProjectEdges (GenericProjector & p) : SubProjector(p) {}
     329             : 
     330        1157 :     ProjectEdges (ProjectEdges & p_e, Threads::split) : SubProjector(p_e.projector) {}
     331             : 
     332             :     using SubProjector::action;
     333             :     using SubProjector::f;
     334             :     using SubProjector::g;
     335             :     using SubProjector::system;
     336             :     using SubProjector::context;
     337             :     using SubProjector::conts;
     338             :     using SubFunctor::field_types;
     339             : 
     340             :     using SubProjector::insert_id;
     341             :     using SubProjector::insert_ids;
     342             : 
     343             :     void operator() (const node_range & range);
     344             :   };
     345             : 
     346        9396 :   struct ProjectSides : public SubProjector {
     347      256003 :     ProjectSides (GenericProjector & p) : SubProjector(p) {}
     348             : 
     349        3684 :     ProjectSides (ProjectSides & p_s, Threads::split) : SubProjector(p_s.projector) {}
     350             : 
     351             :     using SubProjector::action;
     352             :     using SubProjector::f;
     353             :     using SubProjector::g;
     354             :     using SubProjector::system;
     355             :     using SubProjector::context;
     356             :     using SubProjector::conts;
     357             :     using SubFunctor::field_types;
     358             : 
     359             :     using SubProjector::insert_id;
     360             :     using SubProjector::insert_ids;
     361             : 
     362             :     void operator() (const node_range & range);
     363             :   };
     364             : 
     365             : 
     366             :   typedef StoredRange<std::vector<const Elem *>::const_iterator,
     367             :                       const Elem *> interior_range;
     368             : 
     369         871 :   struct ProjectInteriors : public SubProjector {
     370      256003 :     ProjectInteriors (GenericProjector & p) : SubProjector(p) {}
     371             : 
     372        1804 :     ProjectInteriors (ProjectInteriors & p_i, Threads::split) : SubProjector(p_i.projector) {}
     373             : 
     374             :     using SubProjector::action;
     375             :     using SubProjector::f;
     376             :     using SubProjector::g;
     377             :     using SubProjector::system;
     378             :     using SubProjector::context;
     379             :     using SubProjector::conts;
     380             :     using SubFunctor::field_types;
     381             : 
     382             :     using SubProjector::insert_id;
     383             :     using SubProjector::insert_ids;
     384             : 
     385             :     void operator() (const interior_range & range);
     386             :   };
     387             : 
     388             :   template <typename Value>
     389             :   void send_and_insert_dof_values
     390             :     (std::unordered_map<dof_id_type, std::pair<Value, processor_id_type>> & ids_to_push,
     391             :      ProjectionAction & action) const;
     392             : };
     393             : 
     394             : 
     395             : /**
     396             :  * The VectorSetAction output functor class can be used with a
     397             :  * GenericProjector to set projection values (which must be of type
     398             :  * Val) as coefficients of the given NumericVector.
     399             :  *
     400             :  * \author Roy H. Stogner
     401             :  * \date 2016
     402             :  */
     403             : 
     404             : template <typename Val>
     405             : class VectorSetAction
     406             : {
     407             : private:
     408             :   NumericVector<Val> & target_vector;
     409             : 
     410             : public:
     411             :   typedef Val InsertInput;
     412             : 
     413      255373 :   VectorSetAction(NumericVector<Val> & target_vec) :
     414      255373 :     target_vector(target_vec) {}
     415             : 
     416    13992925 :   void insert(dof_id_type id,
     417             :               Val val)
     418             :   {
     419             :     // Lock the new vector since it is shared among threads.
     420             :     {
     421     2737484 :       Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
     422    13992925 :       target_vector.set(id, val);
     423             :     }
     424    13992925 :   }
     425             : 
     426             : 
     427     3065862 :   void insert(const std::vector<dof_id_type> & dof_indices,
     428             :               const DenseVector<Val> & Ue)
     429             :   {
     430             :     const numeric_index_type
     431     3065862 :       first = target_vector.first_local_index(),
     432     3065862 :       last  = target_vector.last_local_index();
     433             : 
     434      331229 :     unsigned int size = Ue.size();
     435             : 
     436      331229 :     libmesh_assert_equal_to(size, dof_indices.size());
     437             : 
     438             :     // Lock the new vector since it is shared among threads.
     439             :     {
     440      662458 :       Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
     441             : 
     442     4358337 :       for (unsigned int i = 0; i != size; ++i)
     443     1405783 :         if ((dof_indices[i] >= first) && (dof_indices[i] <  last))
     444     1405783 :           target_vector.set(dof_indices[i], Ue(i));
     445             :     }
     446     3065862 :   }
     447             : 
     448             : };
     449             : 
     450             : 
     451             : /**
     452             :  * The FEMFunctionWrapper input functor class can be used with a
     453             :  * GenericProjector to read values from an FEMFunction.
     454             :  *
     455             :  * \author Roy H. Stogner
     456             :  * \date 2016
     457             :  */
     458             : 
     459             : template <typename Output>
     460      402612 : class FEMFunctionWrapper
     461             : {
     462             : protected:
     463             :   typedef typename TensorTools::MakeBaseNumber<Output>::type DofValueType;
     464             : 
     465             : public:
     466             :   typedef typename TensorTools::MakeReal<Output>::type RealType;
     467             :   typedef DofValueType ValuePushType;
     468             :   typedef Output FunctorValue;
     469             : 
     470      401484 :   FEMFunctionWrapper(const FEMFunctionBase<Output> & f) : _f(f.clone()) {}
     471             : 
     472       53293 :   FEMFunctionWrapper(const FEMFunctionWrapper<Output> & fw) :
     473     1043379 :     _f(fw._f->clone()) {}
     474             : 
     475     1025305 :   void init_context (FEMContext & c) { _f->init_context(c); }
     476             : 
     477       81086 :   Output eval_at_node (const FEMContext & c,
     478             :                        unsigned int i,
     479             :                        unsigned int /*elem_dim*/,
     480             :                        const Node & n,
     481             :                        bool /*extra_hanging_dofs*/,
     482             :                        const Real time)
     483      903277 :   { return _f->component(c, i, n, time); }
     484             : 
     485      555104 :   Output eval_at_point (const FEMContext & c,
     486             :                         unsigned int i,
     487             :                         const Point & n,
     488             :                         const Real time,
     489             :                         bool /*skip_context_check*/)
     490     6901700 :   { return _f->component(c, i, n, time); }
     491             : 
     492           0 :   void eval_mixed_derivatives (const FEMContext & /*c*/,
     493             :                                unsigned int /*i*/,
     494             :                                unsigned int /*dim*/,
     495             :                                const Node & /*n*/,
     496             :                                std::vector<Output> & /*derivs*/)
     497           0 :   { libmesh_error(); } // this is only for grid projections
     498             : 
     499      144337 :   bool is_grid_projection() { return false; }
     500             : 
     501           0 :   void eval_old_dofs (const Elem &,
     502             :                       unsigned int,
     503             :                       unsigned int,
     504             :                       std::vector<dof_id_type> &,
     505             :                       std::vector<Output> &)
     506           0 :   { libmesh_error(); }
     507             : 
     508           0 :   void eval_old_dofs (const Elem &,
     509             :                       const FEType &,
     510             :                       unsigned int,
     511             :                       unsigned int,
     512             :                       std::vector<dof_id_type> &,
     513             :                       std::vector<Output> &)
     514           0 :   { libmesh_error(); }
     515             : 
     516             : private:
     517             :   std::unique_ptr<FEMFunctionBase<Output>> _f;
     518             : };
     519             : 
     520             : 
     521             : /**
     522             :  * The OldSolutionBase input functor abstract base class is the root
     523             :  * of the OldSolutionValue and OldSolutionCoefs classes which allow a
     524             :  * GenericProjector to read old solution values or solution
     525             :  * interpolation coefficients for a just-refined-and-coarsened mesh.
     526             :  *
     527             :  * \author Roy H. Stogner
     528             :  * \date 2016
     529             :  */
     530             : 
     531             : #ifdef LIBMESH_ENABLE_AMR
     532             : template <typename Output,
     533             :           void (FEMContext::*point_output) (unsigned int,
     534             :                                             const Point &,
     535             :                                             Output &,
     536             :                                             const Real) const>
     537      119718 : class OldSolutionBase
     538             : {
     539             : protected:
     540             :   typedef typename TensorTools::MakeBaseNumber<Output>::type DofValueType;
     541             : public:
     542             :   typedef typename TensorTools::MakeReal<Output>::type RealType;
     543             : 
     544      411560 :   OldSolutionBase(const libMesh::System & sys_in,
     545             :                   const std::vector<unsigned int> * vars) :
     546      376081 :     last_elem(nullptr),
     547      376081 :     sys(sys_in),
     548      411560 :     old_context(sys_in, vars, /* allocate_local_matrices= */ false)
     549             :   {
     550             :     // We'll be queried for components but we'll typically be looking
     551             :     // up data by variables, and those indices don't always match
     552     1938488 :     auto make_lookup = [this](unsigned int v)
     553             :       {
     554      488997 :         const unsigned int vcomp = sys.variable_scalar_number(v,0);
     555      508982 :         if (vcomp >= component_to_var.size())
     556      488997 :           component_to_var.resize(vcomp+1, static_cast<unsigned int>(-1));
     557      508982 :         component_to_var[vcomp] = v;
     558             :       };
     559             : 
     560      411560 :     if (vars)
     561      900557 :       for (auto v : *vars)
     562      488997 :         make_lookup(v);
     563             :     else
     564           0 :       for (auto v : make_range(sys.n_vars()))
     565           0 :         make_lookup(v);
     566      411560 :   }
     567             : 
     568             :   OldSolutionBase(const OldSolutionBase & in) :
     569             :     last_elem(nullptr),
     570             :     sys(in.sys),
     571             :     old_context(sys, in.old_context.active_vars(), /* allocate_local_matrices= */ false),
     572             :     component_to_var(in.component_to_var)
     573             :   {
     574             :   }
     575             : 
     576             :   static void get_shape_outputs(FEAbstract & fe);
     577             : 
     578             :   // Integrating on new mesh elements, we won't yet have an up to date
     579             :   // current_local_solution.
     580      302534 :   void init_context (FEMContext & c)
     581             :   {
     582       13923 :     c.set_algebraic_type(FEMContext::DOFS_ONLY);
     583             : 
     584             :     const std::set<unsigned char> & elem_dims =
     585       13923 :       old_context.elem_dimensions();
     586             : 
     587             :     // Loop over variables and dimensions, to prerequest
     588      606163 :     for (const auto & dim : elem_dims)
     589             :       {
     590       13963 :         FEAbstract * fe = nullptr;
     591      303629 :         if (old_context.active_vars())
     592      663099 :           for (auto var : *old_context.active_vars())
     593             :             {
     594      359470 :               old_context.get_element_fe(var, fe, dim);
     595       15740 :               get_shape_outputs(*fe);
     596             :             }
     597             :         else
     598           0 :           for (auto var : make_range(sys.n_vars()))
     599             :             {
     600           0 :               old_context.get_element_fe(var, fe, dim);
     601           0 :               get_shape_outputs(*fe);
     602             :             }
     603             :       }
     604      302534 :   }
     605             : 
     606      722804 :   bool is_grid_projection() { return true; }
     607             : 
     608             : protected:
     609             :   void check_old_context (const FEMContext & c)
     610             :   {
     611             :     LOG_SCOPE ("check_old_context(c)", "OldSolutionBase");
     612             :     const Elem & elem = c.get_elem();
     613             :     if (last_elem != &elem)
     614             :       {
     615             :         if (elem.refinement_flag() == Elem::JUST_REFINED)
     616             :           {
     617             :             old_context.pre_fe_reinit(sys, elem.parent());
     618             :           }
     619             :         else if (elem.refinement_flag() == Elem::JUST_COARSENED)
     620             :           {
     621             :             libmesh_error();
     622             :           }
     623             :         else
     624             :           {
     625             :             if (!elem.get_old_dof_object())
     626             :               {
     627             :                 libmesh_error();
     628             :               }
     629             : 
     630             :             old_context.pre_fe_reinit(sys, &elem);
     631             :           }
     632             : 
     633             :         last_elem = &elem;
     634             :       }
     635             :     else
     636             :       {
     637             :         libmesh_assert(old_context.has_elem());
     638             :       }
     639             :   }
     640             : 
     641             : 
     642     8070991 :   bool check_old_context (const FEMContext & c, const Point & p)
     643             :   {
     644     1363038 :     LOG_SCOPE ("check_old_context(c,p)", "OldSolutionBase");
     645     1363024 :     const Elem & elem = c.get_elem();
     646     8070991 :     if (last_elem != &elem)
     647             :       {
     648     3530520 :         if (elem.refinement_flag() == Elem::JUST_REFINED)
     649             :           {
     650     3273943 :             old_context.pre_fe_reinit(sys, elem.parent());
     651             :           }
     652      236346 :         else if (elem.refinement_flag() == Elem::JUST_COARSENED)
     653             :           {
     654             :             // Find the child with this point.  Use out_of_elem_tol
     655             :             // (in physical space, which may correspond to a large
     656             :             // tolerance in master space!) to allow for out-of-element
     657             :             // finite differencing of mixed gradient terms.  Pray we
     658             :             // have no quadrature locations which are within 1e-5 of
     659             :             // the element subdivision boundary but are not exactly on
     660             :             // that boundary.
     661      228288 :             const Real master_tol = out_of_elem_tol / elem.hmax() * 2;
     662             : 
     663      396650 :             for (auto & child : elem.child_ref_range())
     664      396650 :               if (child.close_to_point(p, master_tol))
     665             :                 {
     666      228288 :                   old_context.pre_fe_reinit(sys, &child);
     667       19530 :                   break;
     668             :                 }
     669             : 
     670       19530 :             libmesh_assert
     671             :               (old_context.get_elem().close_to_point(p, master_tol));
     672             :           }
     673             :         else
     674             :           {
     675        8058 :             if (!elem.get_old_dof_object())
     676           0 :               return false;
     677             : 
     678        8058 :             old_context.pre_fe_reinit(sys, &elem);
     679             :           }
     680             : 
     681     3248082 :         last_elem = &elem;
     682             :       }
     683             :     else
     684             :       {
     685      398964 :         libmesh_assert(old_context.has_elem());
     686             : 
     687     4822909 :         const Real master_tol = out_of_elem_tol / elem.hmax() * 2;
     688             : 
     689     4822909 :         if (!old_context.get_elem().close_to_point(p, master_tol))
     690             :           {
     691       11638 :             libmesh_assert_equal_to
     692             :               (elem.refinement_flag(), Elem::JUST_COARSENED);
     693             : 
     694      423490 :             for (auto & child : elem.child_ref_range())
     695      423490 :               if (child.close_to_point(p, master_tol))
     696             :                 {
     697      143497 :                   old_context.pre_fe_reinit(sys, &child);
     698       11638 :                   break;
     699             :                 }
     700             : 
     701       11638 :             libmesh_assert
     702             :               (old_context.get_elem().close_to_point(p, master_tol));
     703             :           }
     704             :       }
     705             : 
     706      681519 :     return true;
     707             :   }
     708             : 
     709             : protected:
     710             :   const Elem * last_elem;
     711             :   const System & sys;
     712             :   FEMContext old_context;
     713             :   std::vector<unsigned int> component_to_var;
     714             : 
     715             :   static const Real out_of_elem_tol;
     716             : };
     717             : 
     718             : 
     719             : /**
     720             :  * The OldSolutionValue input functor class can be used with
     721             :  * GenericProjector to read values from a solution on a
     722             :  * just-refined-and-coarsened mesh.
     723             :  *
     724             :  * \author Roy H. Stogner
     725             :  * \date 2016
     726             :  */
     727             : template <typename Output,
     728             :           void (FEMContext::*point_output) (unsigned int,
     729             :                                             const Point &,
     730             :                                             Output &,
     731             :                                             const Real) const>
     732      120418 : class OldSolutionValue : public OldSolutionBase<Output, point_output>
     733             : {
     734             : public:
     735             :   using typename OldSolutionBase<Output, point_output>::RealType;
     736             :   using typename OldSolutionBase<Output, point_output>::DofValueType;
     737             :   typedef Output FunctorValue;
     738             :   typedef DofValueType ValuePushType;
     739             : 
     740       57659 :   OldSolutionValue(const libMesh::System & sys_in,
     741             :                    const NumericVector<Number> & old_sol,
     742             :                    const std::vector<unsigned int> * vars) :
     743             :       OldSolutionBase<Output, point_output>(sys_in, vars),
     744       55771 :       old_solution(old_sol)
     745             :   {
     746        3776 :     this->old_context.set_algebraic_type(FEMContext::OLD);
     747        3776 :     this->old_context.set_custom_solution(&old_solution);
     748        3776 :   }
     749             : 
     750      299228 :   OldSolutionValue(const OldSolutionValue & in) :
     751             :       OldSolutionBase<Output, point_output>(in.sys, in.old_context.active_vars()),
     752      299864 :       old_solution(in.old_solution)
     753             :   {
     754       13781 :     this->old_context.set_algebraic_type(FEMContext::OLD);
     755       13781 :     this->old_context.set_custom_solution(&old_solution);
     756      285438 :   }
     757             : 
     758             : 
     759             :   Output eval_at_node (const FEMContext & c,
     760             :                        unsigned int i,
     761             :                        unsigned int elem_dim,
     762             :                        const Node & n,
     763             :                        bool /* extra_hanging_dofs */,
     764             :                        Real /* time */ =0.);
     765             : 
     766     7970932 :   Output eval_at_point(const FEMContext & c,
     767             :                        unsigned int i,
     768             :                        const Point & p,
     769             :                        Real /* time */,
     770             :                        bool skip_context_check)
     771             :   {
     772     1345366 :     LOG_SCOPE ("eval_at_point()", "OldSolutionValue");
     773             : 
     774     7970932 :     if (!skip_context_check)
     775     7951552 :       if (!this->check_old_context(c, p))
     776           0 :         return Output(0);
     777             : 
     778             :     // Handle offset from non-scalar components in previous variables
     779      672683 :     libmesh_assert_less(i, this->component_to_var.size());
     780     7990857 :     unsigned int var = this->component_to_var[i];
     781             : 
     782      314725 :     Output n;
     783     7970932 :     (this->old_context.*point_output)(var, p, n, this->out_of_elem_tol);
     784     7970932 :     return n;
     785             :   }
     786             : 
     787             :   template <typename T = Output,
     788             :             typename std::enable_if<std::is_same<T, Number>::value, int>::type = 0>
     789        2816 :   void eval_mixed_derivatives(const FEMContext & libmesh_dbg_var(c),
     790             :                               unsigned int i,
     791             :                               unsigned int dim,
     792             :                               const Node & n,
     793             :                               std::vector<Output> & derivs)
     794             :   {
     795         716 :     LOG_SCOPE ("eval_mixed_derivatives", "OldSolutionValue");
     796             : 
     797             :     // This should only be called on vertices
     798         358 :     libmesh_assert_less(c.get_elem().get_node_index(&n),
     799             :                         c.get_elem().n_vertices());
     800             : 
     801             :     // Handle offset from non-scalar components in previous variables
     802         358 :     libmesh_assert_less(i, this->component_to_var.size());
     803        2816 :     unsigned int var = this->component_to_var[i];
     804             : 
     805             :     // We have 1 mixed derivative in 2D, 4 in 3D
     806        2816 :     const unsigned int n_mixed = (dim-1) * (dim-1);
     807        2816 :     derivs.resize(n_mixed);
     808             : 
     809             :     // Be sure to handle cases where the variable wasn't defined on
     810             :     // this node (e.g. due to changing subdomain support)
     811         358 :     const DofObject * old_dof_object = n.get_old_dof_object();
     812        2816 :     if (old_dof_object &&
     813        5274 :         old_dof_object->n_vars(this->sys.number()) &&
     814        2458 :         old_dof_object->n_comp(this->sys.number(), var))
     815             :       {
     816         716 :         const dof_id_type first_old_id =
     817        2816 :           old_dof_object->dof_number(this->sys.number(), var, dim);
     818        3174 :         std::vector<dof_id_type> old_ids(n_mixed);
     819         358 :         std::iota(old_ids.begin(), old_ids.end(), first_old_id);
     820        2816 :         old_solution.get(old_ids, derivs);
     821             :       }
     822             :     else
     823             :       {
     824           0 :         std::fill(derivs.begin(), derivs.end(), 0);
     825             :       }
     826        2816 :   }
     827             : 
     828             :   template <typename T = Output,
     829             :             typename std::enable_if<!std::is_same<T, Number>::value, int>::type = 0>
     830           0 :   void eval_mixed_derivatives(
     831             :       const FEMContext &, unsigned int, unsigned int, const Node &, std::vector<Output> &)
     832             :   {
     833           0 :     libmesh_error_msg("eval_mixed_derivatives should only be applicable for Hermite finite element "
     834             :                       "types. I don't know how you got here");
     835             :   }
     836             : 
     837     4606680 :   void eval_old_dofs (const Elem & elem,
     838             :                       unsigned int node_num,
     839             :                       unsigned int var_num,
     840             :                       std::vector<dof_id_type> & indices,
     841             :                       std::vector<DofValueType> & values)
     842             :   {
     843      972762 :     LOG_SCOPE ("eval_old_dofs(node)", "OldSolutionValue");
     844             : 
     845             :     // We may be reusing a std::vector here, but the following
     846             :     // dof_indices call appends without first clearing.
     847      486381 :     indices.clear();
     848             : 
     849     5092821 :     this->sys.get_dof_map().dof_indices(elem, node_num, indices, var_num);
     850             : 
     851      972762 :     std::vector<dof_id_type> old_indices;
     852             : 
     853     5092821 :     this->sys.get_dof_map().old_dof_indices(elem, node_num, old_indices, var_num);
     854             : 
     855      486381 :     libmesh_assert_equal_to (old_indices.size(), indices.size());
     856             : 
     857             :     // We may have invalid_id in cases where no old DoF existed, e.g.
     858             :     // due to expansion of a subdomain-restricted variable's subdomain
     859      486381 :     bool invalid_old_index = false;
     860    10053583 :     for (const auto & di : old_indices)
     861     5446903 :       if (di == DofObject::invalid_id)
     862           0 :         invalid_old_index = true;
     863             : 
     864     4606680 :     values.resize(old_indices.size());
     865     4606680 :     if (invalid_old_index)
     866             :       {
     867           0 :         for (auto i : index_range(old_indices))
     868             :           {
     869           0 :             const dof_id_type di = old_indices[i];
     870           0 :             if (di == DofObject::invalid_id)
     871           0 :               values[i] = 0;
     872             :             else
     873           0 :               values[i] = old_solution(di);
     874             :           }
     875             :       }
     876             :     else
     877     4606680 :       old_solution.get(old_indices, values);
     878     4606680 :   }
     879             : 
     880             : 
     881     2693470 :   void eval_old_dofs (const Elem & elem,
     882             :                       const FEType & fe_type,
     883             :                       unsigned int sys_num,
     884             :                       unsigned int var_num,
     885             :                       std::vector<dof_id_type> & indices,
     886             :                       std::vector<DofValueType> & values)
     887             :   {
     888      592894 :     LOG_SCOPE ("eval_old_dofs(elem)", "OldSolutionValue");
     889             : 
     890             :     // We're only to be asked for old dofs on elements that can copy
     891             :     // them through DO_NOTHING or through refinement.
     892      592396 :     const Elem & old_elem =
     893     2693470 :       (elem.refinement_flag() == Elem::JUST_REFINED) ?
     894         936 :       *elem.parent() : elem;
     895             : 
     896             :     // If there are any element-based DOF numbers, get them
     897     2693470 :     const unsigned int nc = FEInterface::n_dofs_per_elem(fe_type, &elem);
     898             : 
     899     2693470 :     indices.resize(nc);
     900             : 
     901             :     // We should never have fewer dofs than necessary on an
     902             :     // element unless we're getting indices on a parent element,
     903             :     // which we should never need, or getting indices on a newly
     904             :     // expanded subdomain, in which case we initialize new values to
     905             :     // zero.
     906     2693470 :     if (nc != 0)
     907             :       {
     908       35768 :         const DofObject & old_dof_object = old_elem.get_old_dof_object_ref();
     909       35768 :         libmesh_assert_greater(elem.n_systems(), sys_num);
     910             : 
     911             :         const std::pair<unsigned int, unsigned int>
     912      392151 :           vg_and_offset = elem.var_to_vg_and_offset(sys_num,var_num);
     913      392151 :         const unsigned int vg = vg_and_offset.first;
     914      392151 :         const unsigned int vig = vg_and_offset.second;
     915             : 
     916      427919 :         unsigned int n_comp = old_dof_object.n_comp_group(sys_num,vg);
     917      427919 :         n_comp = std::min(n_comp, nc);
     918             : 
     919      427919 :         std::vector<dof_id_type> old_dof_indices(n_comp);
     920             : 
     921     1626926 :         for (unsigned int i=0; i != n_comp; ++i)
     922             :           {
     923      102725 :             const dof_id_type d_old =
     924     1096282 :               old_dof_object.dof_number(sys_num, vg, vig, i, n_comp);
     925      102725 :             const dof_id_type d_new =
     926     1096282 :               elem.dof_number(sys_num, vg, vig, i, n_comp);
     927      102725 :             libmesh_assert_not_equal_to (d_old, DofObject::invalid_id);
     928      102725 :             libmesh_assert_not_equal_to (d_new, DofObject::invalid_id);
     929             : 
     930     1199007 :             old_dof_indices[i] = d_old;
     931     1301732 :             indices[i] = d_new;
     932             :           }
     933             : 
     934      427919 :         values.resize(n_comp);
     935      427919 :         old_solution.get(old_dof_indices, values);
     936             : 
     937      427919 :         for (unsigned int i=n_comp; i != nc; ++i)
     938             :           {
     939           0 :             const dof_id_type d_new =
     940           0 :               elem.dof_number(sys_num, vg, vig, i, n_comp);
     941           0 :             libmesh_assert_not_equal_to (d_new, DofObject::invalid_id);
     942           0 :             indices[i] = d_new;
     943             :           }
     944             : 
     945      427919 :         values.resize(nc, 0);
     946             :       }
     947             :     else
     948      260679 :       values.clear();
     949     2693470 :   }
     950             : 
     951             : private:
     952             :   const NumericVector<Number> & old_solution;
     953             : };
     954             : 
     955             : template<>
     956             : inline void
     957       14134 : OldSolutionBase<Number, &FEMContext::point_value>::get_shape_outputs(FEAbstract & fe)
     958             : {
     959      313247 :   fe.request_phi();
     960       14134 : }
     961             : 
     962             : 
     963             : template<>
     964             : inline void
     965         636 : OldSolutionBase<Gradient, &FEMContext::point_gradient>::get_shape_outputs(FEAbstract & fe)
     966             : {
     967       19588 :   fe.request_dphi();
     968         636 : }
     969             : 
     970             : template<>
     971             : inline void
     972         970 : OldSolutionBase<Gradient, &FEMContext::point_value>::get_shape_outputs(FEAbstract & fe)
     973             : {
     974       26635 :   fe.request_phi();
     975         970 : }
     976             : 
     977             : 
     978             : template<>
     979             : inline void
     980           0 : OldSolutionBase<Tensor, &FEMContext::point_gradient>::get_shape_outputs(FEAbstract & fe)
     981             : {
     982           0 :   fe.request_dphi();
     983           0 : }
     984             : 
     985             : 
     986             : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
     987             : template<>
     988             : inline void
     989             : OldSolutionBase<Real, &FEMContext::point_value>::get_shape_outputs(FEAbstract & fe)
     990             : {
     991           0 :   fe.request_phi();
     992             : }
     993             : 
     994             : 
     995             : template<>
     996             : inline void
     997             : OldSolutionBase<RealGradient, &FEMContext::point_gradient>::get_shape_outputs(FEAbstract & fe)
     998             : {
     999           0 :   fe.request_dphi();
    1000             : }
    1001             : 
    1002             : template<>
    1003             : inline void
    1004             : OldSolutionBase<RealGradient, &FEMContext::point_value>::get_shape_outputs(FEAbstract & fe)
    1005             : {
    1006             :   fe.request_phi();
    1007             : }
    1008             : 
    1009             : 
    1010             : template<>
    1011             : inline void
    1012             : OldSolutionBase<RealTensor, &FEMContext::point_gradient>::get_shape_outputs(FEAbstract & fe)
    1013             : {
    1014             :   fe.request_dphi();
    1015             : }
    1016             : #endif // LIBMESH_USE_COMPLEX_NUMBERS
    1017             : 
    1018             : 
    1019             : template<>
    1020             : inline
    1021             : Number
    1022     1876263 : OldSolutionValue<Number, &FEMContext::point_value>::
    1023             : eval_at_node(const FEMContext & c,
    1024             :              unsigned int i,
    1025             :              unsigned int /* elem_dim */,
    1026             :              const Node & n,
    1027             :              bool extra_hanging_dofs,
    1028             :              Real /* time */)
    1029             : {
    1030      326856 :   LOG_SCOPE ("Number eval_at_node()", "OldSolutionValue");
    1031             : 
    1032             :   // This should only be called on vertices
    1033      163428 :   libmesh_assert_less(c.get_elem().get_node_index(&n),
    1034             :                       c.get_elem().n_vertices());
    1035             : 
    1036             :   // Handle offset from non-scalar components in previous variables
    1037      163428 :   libmesh_assert_less(i, this->component_to_var.size());
    1038     1876263 :   unsigned int var = this->component_to_var[i];
    1039             : 
    1040             :   // Optimize for the common case, where this node was part of the
    1041             :   // old solution.
    1042             :   //
    1043             :   // Be sure to handle cases where the variable wasn't defined on
    1044             :   // this node (due to changing subdomain support) or where the
    1045             :   // variable has no components on this node (due to Elem order
    1046             :   // exceeding FE order) or where the old_dof_object dofs might
    1047             :   // correspond to non-vertex dofs (due to extra_hanging_dofs and
    1048             :   // refinement)
    1049             : 
    1050      326813 :   const Elem::RefinementState flag = c.get_elem().refinement_flag();
    1051             : 
    1052      163428 :   const DofObject * old_dof_object = n.get_old_dof_object();
    1053     1399169 :   if (old_dof_object &&
    1054     1340616 :       (!extra_hanging_dofs ||
    1055     1140138 :        flag == Elem::JUST_COARSENED ||
    1056     1115205 :        flag == Elem::DO_NOTHING) &&
    1057     4115881 :       old_dof_object->n_vars(sys.number()) &&
    1058     1115205 :       old_dof_object->n_comp(sys.number(), var))
    1059             :     {
    1060             :       const dof_id_type old_id =
    1061     1092062 :         old_dof_object->dof_number(sys.number(), var, 0);
    1062     1092062 :       return old_solution(old_id);
    1063             :     }
    1064             : 
    1065      784201 :   return this->eval_at_point(c, i, n, 0, false);
    1066             : }
    1067             : 
    1068             : template <>
    1069             : inline
    1070             : Gradient
    1071       21744 : OldSolutionValue<Gradient, &FEMContext::point_value>::
    1072             : eval_at_node(const FEMContext & c,
    1073             :              unsigned int i,
    1074             :              unsigned int /* elem_dim */,
    1075             :              const Node & n,
    1076             :              bool extra_hanging_dofs,
    1077             :              Real /* time */)
    1078             : {
    1079        2604 :   LOG_SCOPE ("Number eval_at_node()", "OldSolutionValue");
    1080             : 
    1081             :   // This should only be called on vertices
    1082        1302 :   libmesh_assert_less(c.get_elem().get_node_index(&n),
    1083             :                       c.get_elem().n_vertices());
    1084             : 
    1085             :   // Handle offset from non-scalar components in previous variables
    1086        1302 :   libmesh_assert_less(i, this->component_to_var.size());
    1087       21744 :   unsigned int var = this->component_to_var[i];
    1088             : 
    1089             :   // Optimize for the common case, where this node was part of the
    1090             :   // old solution.
    1091             :   //
    1092             :   // Be sure to handle cases where the variable wasn't defined on
    1093             :   // this node (due to changing subdomain support) or where the
    1094             :   // variable has no components on this node (due to Elem order
    1095             :   // exceeding FE order) or where the old_dof_object dofs might
    1096             :   // correspond to non-vertex dofs (due to extra_hanging_dofs and
    1097             :   // refinement)
    1098             : 
    1099        2604 :   const auto & elem = c.get_elem();
    1100             : 
    1101        2604 :   const Elem::RefinementState flag = elem.refinement_flag();
    1102             : 
    1103        1302 :   const DofObject * old_dof_object = n.get_old_dof_object();
    1104       16518 :   if (old_dof_object &&
    1105       16152 :       (!extra_hanging_dofs ||
    1106       14280 :        flag == Elem::JUST_COARSENED ||
    1107       15216 :        flag == Elem::DO_NOTHING) &&
    1108       52176 :       old_dof_object->n_vars(sys.number()) &&
    1109       15216 :       old_dof_object->n_comp(sys.number(), var))
    1110             :     {
    1111         936 :       Gradient return_val;
    1112             : 
    1113       58437 :       for (auto dim : make_range(elem.dim()))
    1114             :       {
    1115             :         const dof_id_type old_id =
    1116       43221 :           old_dof_object->dof_number(sys.number(), var, dim);
    1117       43221 :         return_val(dim) = old_solution(old_id);
    1118             :       }
    1119             : 
    1120       15216 :       return return_val;
    1121             :     }
    1122             : 
    1123        6528 :   return this->eval_at_point(c, i, n, 0, false);
    1124             : }
    1125             : 
    1126             : 
    1127             : 
    1128             : template<>
    1129             : inline
    1130             : Gradient
    1131       12582 : OldSolutionValue<Gradient, &FEMContext::point_gradient>::
    1132             : eval_at_node(const FEMContext & c,
    1133             :              unsigned int i,
    1134             :              unsigned int elem_dim,
    1135             :              const Node & n,
    1136             :              bool extra_hanging_dofs,
    1137             :              Real /* time */)
    1138             : {
    1139        2116 :   LOG_SCOPE ("Gradient eval_at_node()", "OldSolutionValue");
    1140             : 
    1141             :   // This should only be called on vertices
    1142        1058 :   libmesh_assert_less(c.get_elem().get_node_index(&n),
    1143             :                       c.get_elem().n_vertices());
    1144             : 
    1145             :   // Handle offset from non-scalar components in previous variables
    1146        1058 :   libmesh_assert_less(i, this->component_to_var.size());
    1147       12582 :   unsigned int var = this->component_to_var[i];
    1148             : 
    1149             :   // Optimize for the common case, where this node was part of the
    1150             :   // old solution.
    1151             :   //
    1152             :   // Be sure to handle cases where the variable wasn't defined on
    1153             :   // this node (due to changing subdomain support) or where the
    1154             :   // variable has no components on this node (due to Elem order
    1155             :   // exceeding FE order) or where the old_dof_object dofs might
    1156             :   // correspond to non-vertex dofs (due to extra_hanging_dofs and
    1157             :   // refinement)
    1158             : 
    1159        2114 :   const Elem::RefinementState flag = c.get_elem().refinement_flag();
    1160             : 
    1161        1058 :   const DofObject * old_dof_object = n.get_old_dof_object();
    1162       13640 :   if (old_dof_object &&
    1163       13638 :       (!extra_hanging_dofs ||
    1164       12582 :        flag == Elem::JUST_COARSENED ||
    1165         464 :        flag == Elem::DO_NOTHING) &&
    1166       14529 :       old_dof_object->n_vars(sys.number()) &&
    1167         464 :       old_dof_object->n_comp(sys.number(), var))
    1168             :     {
    1169          39 :       Gradient g;
    1170         928 :       for (unsigned int d = 0; d != elem_dim; ++d)
    1171             :         {
    1172             :           const dof_id_type old_id =
    1173         464 :             old_dof_object->dof_number(sys.number(), var, d+1);
    1174         464 :           g(d) = old_solution(old_id);
    1175             :         }
    1176         464 :       return g;
    1177             :     }
    1178             : 
    1179       12118 :   return this->eval_at_point(c, i, n, 0, false);
    1180             : }
    1181             : 
    1182             : template<>
    1183             : inline
    1184             : Tensor
    1185           0 : OldSolutionValue<Tensor, &FEMContext::point_gradient>::
    1186             : eval_at_node(const FEMContext &,
    1187             :              unsigned int,
    1188             :              unsigned int,
    1189             :              const Node &,
    1190             :              bool,
    1191             :              Real)
    1192             : {
    1193           0 :   libmesh_error_msg("You shouldn't need to call eval_at_node for the gradient "
    1194             :                     "functor for a vector-valued finite element type");
    1195             :   return Tensor();
    1196             : }
    1197             : 
    1198             : template <typename Output,
    1199             :           void (FEMContext::*point_output) (unsigned int,
    1200             :                                             const Point &,
    1201             :                                             Output &,
    1202             :                                             const Real) const>
    1203             : const Real OldSolutionBase<Output, point_output>::out_of_elem_tol = 10 * TOLERANCE;
    1204             : 
    1205             : #endif // LIBMESH_ENABLE_AMR
    1206             : 
    1207             : /**
    1208             :  * Function definitions
    1209             :  */
    1210             : 
    1211             : template <typename FFunctor, typename GFunctor,
    1212             :           typename FValue, typename ProjectionAction>
    1213      256003 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::project
    1214             :   (const ConstElemRange & range)
    1215             : {
    1216       15168 :   LOG_SCOPE ("project", "GenericProjector");
    1217             : 
    1218             :   // Unless we split sort and copy into two passes we can't know for
    1219             :   // sure ahead of time whether we need to save the copied ids
    1220      256003 :   done_saving_ids = false;
    1221             : 
    1222      271171 :   SortAndCopy sort_work(*this);
    1223      256003 :   Threads::parallel_reduce (range, sort_work);
    1224      256003 :   ProjectionAction action(master_action);
    1225             : 
    1226             :   // Keep track of dof ids and values to send to other ranks
    1227             :   std::unordered_map<dof_id_type, std::pair<typename FFunctor::ValuePushType, processor_id_type>>
    1228       15168 :       ids_to_push;
    1229             : 
    1230      248419 :   ids_to_push.insert(sort_work.new_ids_to_push.begin(),
    1231             :                      sort_work.new_ids_to_push.end());
    1232      248419 :   ids_to_save.insert(sort_work.new_ids_to_save.begin(),
    1233             :                      sort_work.new_ids_to_save.end());
    1234             : 
    1235      271171 :   std::vector<node_projection> vertices(sort_work.vertices.begin(),
    1236             :                                         sort_work.vertices.end());
    1237             : 
    1238      498615 :   done_saving_ids = sort_work.edges.empty() &&
    1239      464648 :     sort_work.sides.empty() && sort_work.interiors.empty();
    1240             : 
    1241             :   {
    1242       15168 :     ProjectVertices project_vertices(*this);
    1243      504422 :     Threads::parallel_reduce (node_range(&vertices), project_vertices);
    1244        7584 :     libmesh_merge_move(ids_to_push, project_vertices.new_ids_to_push);
    1245        7584 :     libmesh_merge_move(ids_to_save, project_vertices.new_ids_to_save);
    1246             :   }
    1247             : 
    1248      256003 :   done_saving_ids = sort_work.sides.empty() && sort_work.interiors.empty();
    1249             : 
    1250      256003 :   this->send_and_insert_dof_values(ids_to_push, action);
    1251             : 
    1252             :   {
    1253      271171 :     std::vector<node_projection> edges(sort_work.edges.begin(), sort_work.edges.end());
    1254       15168 :     ProjectEdges project_edges(*this);
    1255      504422 :     Threads::parallel_reduce (node_range(&edges), project_edges);
    1256        7584 :     libmesh_merge_move(ids_to_push, project_edges.new_ids_to_push);
    1257        7584 :     libmesh_merge_move(ids_to_save, project_edges.new_ids_to_save);
    1258      240835 :   }
    1259             : 
    1260      256003 :   done_saving_ids = sort_work.interiors.empty();
    1261             : 
    1262      256003 :   this->send_and_insert_dof_values(ids_to_push, action);
    1263             : 
    1264             :   {
    1265      271171 :     std::vector<node_projection> sides(sort_work.sides.begin(), sort_work.sides.end());
    1266       15168 :     ProjectSides project_sides(*this);
    1267      504422 :     Threads::parallel_reduce (node_range(&sides), project_sides);
    1268        7584 :     libmesh_merge_move(ids_to_push, project_sides.new_ids_to_push);
    1269        7584 :     libmesh_merge_move(ids_to_save, project_sides.new_ids_to_save);
    1270      240835 :   }
    1271             : 
    1272      256003 :   done_saving_ids = true;
    1273      256003 :   this->send_and_insert_dof_values(ids_to_push, action);
    1274             : 
    1275             :   // No ids to save or push this time, but we still use a reduce since
    1276             :   // nominally ProjectInteriors still has non-const operator()
    1277        7584 :   ProjectInteriors project_interiors(*this);
    1278      504422 :   Threads::parallel_reduce (interior_range(&sort_work.interiors),
    1279             :                             project_interiors);
    1280      496838 : }
    1281             : 
    1282             : 
    1283             : template <typename FFunctor, typename GFunctor,
    1284             :           typename FValue, typename ProjectionAction>
    1285     1306911 : GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubFunctor::SubFunctor
    1286             :   (GenericProjector & p) :
    1287     1212570 :   projector(p),
    1288     1306911 :   action(p.master_action),
    1289     1306911 :   f(p.master_f),
    1290     1306911 :   context(p.system, &p.variables, /* allocate_local_matrices= */ false),
    1291     1306911 :   conts(p.system.n_vars()),
    1292     1401252 :   field_types(p.system.n_vars()), system(p.system)
    1293             : {
    1294             :   // Loop over all the variables we've been requested to project, to
    1295             :   // pre-request
    1296     2688108 :   for (const auto & var : this->projector.variables)
    1297             :     {
    1298             :       // FIXME: Need to generalize this to vector-valued elements. [PB]
    1299       49783 :       FEAbstract * fe = nullptr;
    1300       49783 :       FEAbstract * side_fe = nullptr;
    1301       49783 :       FEAbstract * edge_fe = nullptr;
    1302             : 
    1303             :       const std::set<unsigned char> & elem_dims =
    1304       49783 :         context.elem_dimensions();
    1305             : 
    1306     2765107 :       for (const auto & dim : elem_dims)
    1307             :         {
    1308     1383910 :           context.get_element_fe( var, fe, dim );
    1309     1383910 :           if (fe->get_fe_type().family == SCALAR)
    1310           0 :             continue;
    1311     1383910 :           if (dim > 1)
    1312       44636 :             context.get_side_fe( var, side_fe, dim );
    1313     1383910 :           if (dim > 2)
    1314       17742 :             context.get_edge_fe( var, edge_fe );
    1315             : 
    1316      118354 :           fe->get_JxW();
    1317      118354 :           fe->get_xyz();
    1318      118354 :           fe->get_JxW();
    1319             : 
    1320     1383910 :           fe->request_phi();
    1321     1383910 :           if (dim > 1)
    1322             :             {
    1323      105561 :               side_fe->get_JxW();
    1324      105561 :               side_fe->get_xyz();
    1325     1217256 :               side_fe->request_phi();
    1326             :             }
    1327     1383910 :           if (dim > 2)
    1328             :             {
    1329       42839 :               edge_fe->get_JxW();
    1330       42839 :               edge_fe->get_xyz();
    1331      530647 :               edge_fe->request_phi();
    1332             :             }
    1333             : 
    1334     1383910 :           const FEContinuity cont = fe->get_continuity();
    1335     1383910 :           this->conts[var] = cont;
    1336     1383910 :           if (cont == C_ONE)
    1337             :             {
    1338       48796 :               fe->request_dphi();
    1339       48796 :               if (dim > 1)
    1340       27153 :                 side_fe->request_dphi();
    1341       48796 :               if (dim > 2)
    1342        3222 :                 edge_fe->request_dphi();
    1343             :             }
    1344             : 
    1345     1383910 :           this->field_types[var] = FEInterface::field_type(fe->get_fe_type());
    1346             :         }
    1347             :     }
    1348             : 
    1349             :   // Now initialize any user requested shape functions, xyz vals, etc
    1350      316825 :   f.init_context(context);
    1351     1306911 : }
    1352             : 
    1353             : 
    1354             : template <typename FFunctor, typename GFunctor,
    1355             :           typename FValue, typename ProjectionAction>
    1356     1043166 : GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubProjector::SubProjector
    1357             :   (GenericProjector & p) :
    1358     1043166 :   SubFunctor(p)
    1359             : {
    1360             : 
    1361             :   // Our C1 elements need gradient information
    1362     2106391 :   for (const auto & var : this->projector.variables)
    1363     1141085 :     if (this->conts[var] == C_ONE)
    1364             :       {
    1365        1306 :         libmesh_assert(p.master_g);
    1366       76698 :         g = std::make_unique<GFunctor>(*p.master_g);
    1367       39002 :         g->init_context(context);
    1368        1306 :         return;
    1369             :       }
    1370           0 : }
    1371             : 
    1372             : template <typename FFunctor, typename GFunctor, typename FValue, typename ProjectionAction>
    1373             : template <typename InsertInput,
    1374             :           typename std::enable_if<
    1375             :               !std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
    1376             :               int>::type>
    1377             : void
    1378           0 : GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubFunctor::insert_id(
    1379             :     dof_id_type, const InsertInput & , processor_id_type)
    1380             : {
    1381           0 :   libmesh_error_msg("ID insertion should only occur when the provided input matches that "
    1382             :                     "expected by the ProjectionAction");
    1383             : }
    1384             : 
    1385             : template <typename FFunctor, typename GFunctor, typename FValue, typename ProjectionAction>
    1386             : template <typename InsertInput,
    1387             :           typename std::enable_if<
    1388             :               std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
    1389             :               int>::type>
    1390             : void
    1391     5419099 : GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubFunctor::insert_id(
    1392             :     dof_id_type id, const InsertInput & val, processor_id_type pid)
    1393             : {
    1394             :   // We may see invalid ids when expanding a subdomain with a
    1395             :   // restricted variable
    1396     5419099 :   if (id == DofObject::invalid_id)
    1397           0 :     return;
    1398             : 
    1399      456919 :   auto iter = new_ids_to_push.find(id);
    1400     5419099 :   if (iter == new_ids_to_push.end())
    1401     5413155 :     action.insert(id, val);
    1402             :   else
    1403             :     {
    1404         197 :       libmesh_assert(pid != DofObject::invalid_processor_id);
    1405        1095 :       iter->second = std::make_pair(val, pid);
    1406             :     }
    1407     5419099 :   if (!this->projector.done_saving_ids)
    1408             :     {
    1409      216841 :       libmesh_assert(!new_ids_to_save.count(id));
    1410     2581719 :       new_ids_to_save[id] = val;
    1411             :     }
    1412             : }
    1413             : 
    1414             : template <typename FFunctor, typename GFunctor, typename FValue, typename ProjectionAction>
    1415             : template <typename InsertInput,
    1416             :           typename std::enable_if<
    1417             :               !std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
    1418             :               int>::type>
    1419             : void
    1420             : GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubFunctor::insert_ids(
    1421             :     const std::vector<dof_id_type> &,
    1422             :     const std::vector<InsertInput> &,
    1423             :     processor_id_type)
    1424             : {
    1425             :   libmesh_error_msg("ID insertion should only occur when the provided input matches that "
    1426             :                     "expected by the ProjectionAction");
    1427             : }
    1428             : 
    1429             : template <typename FFunctor, typename GFunctor, typename FValue, typename ProjectionAction>
    1430             : template <typename InsertInput,
    1431             :           typename std::enable_if<
    1432             :               std::is_same<typename ProjectionAction::InsertInput, InsertInput>::value,
    1433             :               int>::type>
    1434             : void
    1435     5231721 : GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubFunctor::insert_ids(
    1436             :     const std::vector<dof_id_type> & ids,
    1437             :     const std::vector<InsertInput> & vals,
    1438             :     processor_id_type pid)
    1439             : {
    1440      531609 :   libmesh_assert_equal_to(ids.size(), vals.size());
    1441    15321332 :   for (auto i : index_range(ids))
    1442             :     {
    1443    10089611 :       const dof_id_type id = ids[i];
    1444             : 
    1445             :       // We may see invalid ids when expanding a subdomain with a
    1446             :       // restricted variable
    1447    10089611 :       if (id == DofObject::invalid_id)
    1448           0 :         continue;
    1449             : 
    1450     1849726 :       const InsertInput & val = vals[i];
    1451             : 
    1452      924977 :       auto iter = new_ids_to_push.find(id);
    1453    10089611 :       if (iter == new_ids_to_push.end())
    1454    10084328 :         action.insert(id, val);
    1455             :       else
    1456             :         {
    1457         504 :           libmesh_assert(pid != DofObject::invalid_processor_id);
    1458        1008 :           iter->second = std::make_pair(val, pid);
    1459             :         }
    1460    10089611 :       if (!this->projector.done_saving_ids)
    1461             :         {
    1462      618069 :           libmesh_assert(!new_ids_to_save.count(id));
    1463     6280986 :           new_ids_to_save[id] = val;
    1464             :         }
    1465             :     }
    1466     5231721 : }
    1467             : 
    1468             : template <typename FFunctor, typename GFunctor,
    1469             :           typename FValue, typename ProjectionAction>
    1470       26896 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubFunctor::join
    1471             :   (const GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubFunctor & other)
    1472             : {
    1473       17641 :   new_ids_to_push.insert(other.new_ids_to_push.begin(), other.new_ids_to_push.end());
    1474       17641 :   new_ids_to_save.insert(other.new_ids_to_save.begin(), other.new_ids_to_save.end());
    1475       26896 : }
    1476             : 
    1477             : 
    1478             : template <typename FFunctor, typename GFunctor,
    1479             :           typename FValue, typename ProjectionAction>
    1480      263745 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SortAndCopy::operator()
    1481             :   (const ConstElemRange & range)
    1482             : {
    1483       20576 :   LOG_SCOPE ("SortAndCopy::operator()","GenericProjector");
    1484             : 
    1485             :   // Look at all the elements in the range.  Directly copy values from
    1486             :   // unchanged elements.  For other elements, determine sets of
    1487             :   // vertices, edge nodes, and side nodes to project.
    1488             :   //
    1489             :   // As per our other weird nomenclature, "sides" means faces in 3D
    1490             :   // and edges in 2D, and "edges" gets skipped in 2D
    1491             :   //
    1492             :   // This gets tricky in the case of subdomain-restricted
    1493             :   // variables, for which we might need to do the same projection
    1494             :   // from different elements when evaluating different variables.
    1495             :   // We'll keep track of which variables can be projected from which
    1496             :   // elements.
    1497             : 
    1498             :   // If we copy DoFs on a node, keep track of it so we don't bother
    1499             :   // with any redundant interpolations or projections later.
    1500             :   //
    1501             :   // We still need to keep track of *which* variables get copied,
    1502             :   // since we may be able to copy elements which lack some
    1503             :   // subdomain-restricted variables.
    1504             :   //
    1505             :   // For extra_hanging_dofs FE types, we'll keep track of which
    1506             :   // variables were copied from vertices, and which from edges/sides,
    1507             :   // because those types need copies made from *both* on hanging
    1508             :   // nodes.
    1509       20576 :   std::unordered_map<const Node *, std::pair<var_set, var_set>> copied_nodes;
    1510             : 
    1511       65391 :   const unsigned int sys_num = system.number();
    1512             : 
    1513             :   // At hanging nodes for variables with extra hanging dofs we'll need
    1514             :   // to do projections *separately* from vertex elements and side/edge
    1515             :   // elements.
    1516       20576 :   std::vector<unsigned short> extra_hanging_dofs;
    1517       10288 :   bool all_extra_hanging_dofs = true;
    1518      542715 :   for (auto v_num : this->projector.variables)
    1519             :     {
    1520      289904 :       if (extra_hanging_dofs.size() <= v_num)
    1521      278970 :         extra_hanging_dofs.resize(v_num+1, false);
    1522      300838 :       extra_hanging_dofs[v_num] =
    1523      278970 :         FEInterface::extra_hanging_dofs(system.variable(v_num).type());
    1524             : 
    1525      278970 :       if (!extra_hanging_dofs[v_num])
    1526        8345 :         all_extra_hanging_dofs = false;
    1527             :     }
    1528             : 
    1529     5792492 :   for (const auto & elem : range)
    1530             :     {
    1531             :       // If we're doing AMR, we might be able to copy more DoFs than
    1532             :       // we interpolate or project.
    1533      554745 :       bool copy_this_elem = false;
    1534             : 
    1535             : #ifdef LIBMESH_ENABLE_AMR
    1536             :       // If we're projecting from an old grid
    1537      554745 :       if (f.is_grid_projection())
    1538             :         {
    1539             :           // If this element doesn't have an old_dof_object, but it
    1540             :           // wasn't just refined or just coarsened into activity, then
    1541             :           // it must be newly added, so the user is responsible for
    1542             :           // setting the new dofs on it during a grid projection.
    1543     4446925 :           const DofObject * old_dof_object = elem->get_old_dof_object();
    1544      916804 :           const Elem::RefinementState h_flag = elem->refinement_flag();
    1545      916804 :           const Elem::RefinementState p_flag = elem->p_refinement_flag();
    1546     4905316 :           if (!old_dof_object &&
    1547     3988525 :               h_flag != Elem::JUST_REFINED &&
    1548             :               h_flag != Elem::JUST_COARSENED)
    1549         156 :             continue;
    1550             : 
    1551             :           // If this is an unchanged element, just copy everything
    1552     4446769 :           if (h_flag != Elem::JUST_REFINED &&
    1553      295380 :               h_flag != Elem::JUST_COARSENED &&
    1554     2657609 :               p_flag != Elem::JUST_REFINED &&
    1555             :               p_flag != Elem::JUST_COARSENED)
    1556      294753 :             copy_this_elem = true;
    1557             :           else
    1558             :             {
    1559      163647 :               bool reinitted = false;
    1560             : 
    1561      327302 :               const unsigned int p_level = elem->p_level();
    1562             : 
    1563             :               // If this element has a low order monomial which has
    1564             :               // merely been h refined, copy it.
    1565      163647 :               const bool copy_possible =
    1566     1958938 :                 p_level == 0 &&
    1567     1958930 :                 h_flag != Elem::JUST_COARSENED &&
    1568             :                 p_flag != Elem::JUST_COARSENED;
    1569             : 
    1570     1961148 :               std::vector<typename FFunctor::ValuePushType> Ue(1);
    1571     1959660 :               std::vector<dof_id_type> elem_dof_ids(1);
    1572             : 
    1573     3644682 :               for (auto v_num : this->projector.variables)
    1574             :                 {
    1575     1848669 :                   const Variable & var = system.variable(v_num);
    1576     1848669 :                   if (!var.active_on_subdomain(elem->subdomain_id()))
    1577        7368 :                     continue;
    1578     1841301 :                   const FEType fe_type = var.type();
    1579             : 
    1580       41682 :                   if (fe_type.family == MONOMIAL &&
    1581     1849983 :                       fe_type.order == CONSTANT &&
    1582             :                       copy_possible)
    1583             :                     {
    1584        5670 :                       if (!reinitted)
    1585             :                         {
    1586         468 :                           reinitted = true;
    1587        5670 :                           context.pre_fe_reinit(system, elem);
    1588             :                         }
    1589             : 
    1590        5670 :                       f.eval_old_dofs(*elem, fe_type, sys_num, v_num,
    1591             :                                       elem_dof_ids, Ue);
    1592             : 
    1593        5670 :                       action.insert(elem_dof_ids[0], Ue[0]);
    1594             :                     }
    1595             :                 }
    1596       13392 :             }
    1597             :         }
    1598             : #endif // LIBMESH_ENABLE_AMR
    1599             : 
    1600     5528591 :       const int dim = elem->dim();
    1601             : 
    1602     5528591 :       const unsigned int n_vertices = elem->n_vertices();
    1603     5528591 :       const unsigned int n_edges = elem->n_edges();
    1604     5528591 :       const unsigned int n_nodes = elem->n_nodes();
    1605             : 
    1606             :       // In 1-D we already handle our sides as vertices
    1607     5528591 :       const unsigned int n_sides = (dim > 1) * elem->n_sides();
    1608             : 
    1609             :       // What variables are supported on each kind of node on this elem?
    1610     1109464 :       var_set vertex_vars, edge_vars, side_vars;
    1611             : 
    1612             :       // If we have non-vertex nodes, the first is an edge node, but
    1613             :       // if we're in 2D we'll call that a side node
    1614     5528591 :       const bool has_poly_midnode = (elem->type() == C0POLYHEDRON) && (n_nodes == n_vertices + 1);
    1615     5528591 :       const bool has_edge_nodes = (n_nodes > n_vertices + has_poly_midnode && dim > 2);
    1616             : 
    1617             :       // If we have even more nodes, the next is a side node.
    1618      554732 :       const bool has_side_nodes =
    1619     5528591 :         (n_nodes > n_vertices + ((dim > 2) * n_edges));
    1620             : 
    1621             :       // We may be out of nodes at this point or we have interior
    1622             :       // nodes which may have DoFs to project too
    1623      554732 :       const bool has_interior_nodes =
    1624     5528591 :         (n_nodes > n_vertices + ((dim > 2) * n_edges) + n_sides);
    1625             : 
    1626    11159056 :       for (auto v_num : this->projector.variables)
    1627             :         {
    1628     5630465 :           const Variable & var = this->projector.system.variable(v_num);
    1629     5630465 :           if (!var.active_on_subdomain(elem->subdomain_id()))
    1630      548031 :             continue;
    1631     5615219 :           const FEType fe_type = var.type();
    1632     5615219 :           const bool add_p_level = fe_type.p_refinement;
    1633             : 
    1634             :           // If we're trying to do projections on an isogeometric
    1635             :           // analysis mesh, only the finite element nodes constrained
    1636             :           // by those spline nodes truly have delta function degrees
    1637             :           // of freedom.  We'll have to back out the spline degrees of
    1638             :           // freedom indirectly later.
    1639     5627933 :           if (fe_type.family == RATIONAL_BERNSTEIN &&
    1640       12714 :               elem->type() == NODEELEM)
    1641        5435 :             continue;
    1642             : 
    1643     5609311 :           if (FEInterface::n_dofs_at_node(fe_type, elem, 0, add_p_level))
    1644     4843373 :             vertex_vars.insert(vertex_vars.end(), v_num);
    1645             : 
    1646             :           // The first non-vertex node is always an edge node if those
    1647             :           // exist.  All edge nodes have the same number of DoFs
    1648     5609311 :           if (has_edge_nodes)
    1649      191472 :             if (FEInterface::n_dofs_at_node(fe_type, elem, n_vertices, add_p_level))
    1650       58013 :               edge_vars.insert(edge_vars.end(), v_num);
    1651             : 
    1652     5609311 :           if (has_side_nodes)
    1653             :             {
    1654     1587426 :               if (dim != 3)
    1655             :                 {
    1656     1435746 :                   if (FEInterface::n_dofs_at_node(fe_type, elem, n_vertices, add_p_level))
    1657     1218665 :                     side_vars.insert(side_vars.end(), v_num);
    1658             :                 }
    1659             :               else
    1660             :                 // In 3D, not all face nodes always have the same number of
    1661             :                 // DoFs!  We'll loop over all sides to be safe.
    1662     2276558 :                 for (unsigned int n = 0; n != n_nodes; ++n)
    1663     2235408 :                   if (elem->is_face(n))
    1664      302116 :                     if (FEInterface::n_dofs_at_node(fe_type, elem, n, add_p_level))
    1665             :                       {
    1666      100722 :                         side_vars.insert(side_vars.end(), v_num);
    1667        9808 :                         break;
    1668             :                       }
    1669             :             }
    1670             : 
    1671     5684418 :           if (FEInterface::n_dofs_per_elem(fe_type, elem, add_p_level) ||
    1672      855303 :               (has_interior_nodes &&
    1673      855303 :                FEInterface::n_dofs_at_node(fe_type, elem, n_nodes-1, add_p_level)))
    1674             :             {
    1675             : #ifdef LIBMESH_ENABLE_AMR
    1676             :               // We may have already just copied constant monomials,
    1677             :               // or we may be about to copy the whole element
    1678     1783712 :               if ((f.is_grid_projection() &&
    1679     1344688 :                    fe_type.family == MONOMIAL &&
    1680       12565 :                    fe_type.order == CONSTANT &&
    1681        9213 :                    elem->p_level() == 0 &&
    1682        9201 :                    elem->refinement_flag() != Elem::JUST_COARSENED &&
    1683        1528 :                    elem->p_refinement_flag() != Elem::JUST_COARSENED)
    1684     1457866 :                   || copy_this_elem
    1685             :                   )
    1686      480073 :                 continue;
    1687             : #endif // LIBMESH_ENABLE_AMR
    1688             : 
    1689             :               // We need to project any other variables
    1690      931916 :               if (interiors.empty() || interiors.back() != elem)
    1691      910596 :                 interiors.push_back(elem);
    1692             :             }
    1693             :         }
    1694             : 
    1695             :       // We'll use a greedy algorithm in most cases: if another
    1696             :       // element has already claimed some of our DoFs, we'll let it do
    1697             :       // the work.
    1698    33693692 :       auto erase_covered_vars = []
    1699     6463860 :         (const Node * node, var_set & remaining, const ves_multimap & covered)
    1700             :         {
    1701     3232042 :           auto covered_range = covered.equal_range(node);
    1702    42468869 :           for (const auto & v_ent : as_range(covered_range))
    1703    17684614 :             for (const unsigned int var_covered :
    1704      794438 :                  std::get<2>(v_ent.second))
    1705      807026 :               remaining.erase(var_covered);
    1706             :         };
    1707             : 
    1708    29216732 :       auto erase_nonhanging_vars = [&extra_hanging_dofs]
    1709     5258220 :         (const Node * node, var_set & remaining, const ves_multimap & covered)
    1710             :         {
    1711     2629170 :           auto covered_range = covered.equal_range(node);
    1712    23691836 :           for (const auto & v_ent : as_range(covered_range))
    1713        8406 :             for (const unsigned int var_covered :
    1714         257 :                  std::get<2>(v_ent.second))
    1715        4711 :               if (!extra_hanging_dofs[var_covered])
    1716         273 :                 remaining.erase(var_covered);
    1717             :         };
    1718             : 
    1719    24238060 :       auto erase_copied_vars = [&copied_nodes, &extra_hanging_dofs]
    1720     3498880 :         (const Node * node, bool is_vertex, var_set & remaining)
    1721             :         {
    1722     1749490 :           auto copying_range = copied_nodes.equal_range(node);
    1723    24725649 :           for (const auto & v_ent : as_range(copying_range))
    1724             :             {
    1725    14887076 :               for (const unsigned int var_covered :
    1726             :                    v_ent.second.first)
    1727     7098571 :                 if (is_vertex || !extra_hanging_dofs[var_covered])
    1728      783391 :                   remaining.erase(var_covered);
    1729             : 
    1730     8717289 :               for (const unsigned int var_covered :
    1731             :                    v_ent.second.second)
    1732      928784 :                 if (!is_vertex || !extra_hanging_dofs[var_covered])
    1733       70262 :                   remaining.erase(var_covered);
    1734             :             }
    1735             :         };
    1736             : 
    1737             :       // Treat polyhedron midnode as a vertex
    1738             :       // NOTE: if we start having edge or side nodes on polyhedra, we need to use that +1 offset
    1739             :       // in the edge and side nodes code as well!
    1740             : #ifndef NDEBUG
    1741     1116338 :       for (auto v_num : this->projector.variables)
    1742             :         {
    1743      561606 :           const auto & family = this->projector.system.variable(v_num).type().family;
    1744             :           // Add to the list once known to be correctly functioning with the midnode
    1745      561606 :           libmesh_assert(!has_poly_midnode || (family == LAGRANGE || family == MONOMIAL || family == XYZ));
    1746             :         }
    1747             : #endif
    1748    26405933 :       for (const auto v : make_range(n_vertices + has_poly_midnode))
    1749             :         {
    1750    22993536 :           const Node * node = elem->node_ptr(v);
    1751             : 
    1752     2116274 :           auto remaining_vars = vertex_vars;
    1753             : 
    1754    20877342 :           erase_covered_vars(node, remaining_vars, vertices);
    1755             : 
    1756    20877342 :           if (remaining_vars.empty())
    1757     7503562 :             continue;
    1758             : 
    1759    12629621 :           if (!all_extra_hanging_dofs)
    1760             :             {
    1761    10693835 :               erase_nonhanging_vars(node, remaining_vars, edges);
    1762    10693835 :               if (remaining_vars.empty())
    1763           0 :                 continue;
    1764             : 
    1765    10693835 :               erase_nonhanging_vars(node, remaining_vars, sides);
    1766    10693835 :               if (remaining_vars.empty())
    1767        2169 :                 continue;
    1768             :             }
    1769             : 
    1770    12627190 :           erase_copied_vars(node, true, remaining_vars);
    1771    12627190 :           if (remaining_vars.empty())
    1772     6223577 :             continue;
    1773             : 
    1774     4785037 :           if (copy_this_elem)
    1775             :             {
    1776      657832 :               std::vector<dof_id_type> node_dof_ids;
    1777      657832 :               std::vector<typename FFunctor::ValuePushType> values;
    1778             : 
    1779     5653062 :               for (auto var : remaining_vars)
    1780             :                 {
    1781     2879056 :                   f.eval_old_dofs(*elem, v, var, node_dof_ids, values);
    1782     2879056 :                   insert_ids(node_dof_ids, values, node->processor_id());
    1783             :                 }
    1784     2774006 :               copied_nodes[node].first.insert(remaining_vars.begin(),
    1785             :                                               remaining_vars.end());
    1786     2774006 :               this->find_dofs_to_send(*node, *elem, v, remaining_vars);
    1787           0 :             }
    1788             :           else
    1789             :             vertices.emplace
    1790     3103328 :               (node, std::make_tuple(elem, v, std::move(remaining_vars)));
    1791             :         }
    1792             : 
    1793     5528591 :       if (has_edge_nodes)
    1794             :         {
    1795     1491282 :           for (unsigned int e=0; e != n_edges; ++e)
    1796             :             {
    1797     1423830 :               const Node * node = elem->node_ptr(n_vertices+e);
    1798             : 
    1799      115212 :               auto remaining_vars = edge_vars;
    1800             : 
    1801     1308618 :               erase_covered_vars(node, remaining_vars, edges);
    1802     1308618 :               if (remaining_vars.empty())
    1803      921995 :                 continue;
    1804             : 
    1805      300953 :               erase_covered_vars(node, remaining_vars, sides);
    1806      300953 :               if (remaining_vars.empty())
    1807           0 :                 continue;
    1808             : 
    1809      300953 :               if (!all_extra_hanging_dofs)
    1810             :                 {
    1811      205736 :                   erase_nonhanging_vars(node, remaining_vars, vertices);
    1812      205736 :                   if (remaining_vars.empty())
    1813         383 :                     continue;
    1814             :                 }
    1815             : 
    1816      300570 :               erase_copied_vars(node, false, remaining_vars);
    1817      300570 :               if (remaining_vars.empty())
    1818       16711 :                 continue;
    1819             : 
    1820      127774 :               if (copy_this_elem)
    1821             :                 {
    1822        7764 :                   std::vector<dof_id_type> edge_dof_ids;
    1823        7764 :                   std::vector<typename FFunctor::ValuePushType> values;
    1824             : 
    1825       52764 :                   for (auto var : remaining_vars)
    1826             :                     {
    1827       26382 :                       f.eval_old_dofs(*elem, n_vertices+e, var, edge_dof_ids, values);
    1828       26382 :                       insert_ids(edge_dof_ids, values, node->processor_id());
    1829             :                     }
    1830             : 
    1831       26382 :                   copied_nodes[node].second.insert(remaining_vars.begin(),
    1832             :                                                    remaining_vars.end());
    1833       26382 :                   this->find_dofs_to_send(*node, *elem, n_vertices+e, remaining_vars);
    1834           0 :                 }
    1835             :               else
    1836             :                 edges.emplace
    1837      273901 :                   (node, std::make_tuple(elem, e, std::move(remaining_vars)));
    1838             :             }
    1839             :         }
    1840             : 
    1841     5528591 :       if (has_side_nodes)
    1842             :         {
    1843     7320750 :           for (unsigned int side=0; side != n_sides; ++side)
    1844             :             {
    1845     5795808 :               const Node * node = nullptr;
    1846     5795808 :               unsigned short node_num = n_vertices+(dim>2)*n_edges+side;
    1847     5795808 :               if (dim != 3)
    1848     5607419 :                 node = elem->node_ptr(node_num);
    1849             :               else
    1850             :                 {
    1851             :                   // In 3D only some sides may have nodes
    1852     9840228 :                   for (unsigned int n = 0; n != n_nodes; ++n)
    1853             :                     {
    1854     9822816 :                       if (!elem->is_face(n))
    1855     7371150 :                         continue;
    1856             : 
    1857     1734426 :                       if (elem->is_node_on_side(n, side))
    1858             :                         {
    1859      152038 :                           node_num = n;
    1860      617544 :                           node = elem->node_ptr(node_num);
    1861      617544 :                           break;
    1862             :                         }
    1863             :                     }
    1864             :                 }
    1865             : 
    1866     5795808 :               if (!node)
    1867     2424957 :                 continue;
    1868             : 
    1869      500057 :               auto remaining_vars = side_vars;
    1870             : 
    1871     5778396 :               erase_covered_vars(node, remaining_vars, edges);
    1872     5778396 :               if (remaining_vars.empty())
    1873      320913 :                 continue;
    1874             : 
    1875     5428383 :               erase_covered_vars(node, remaining_vars, sides);
    1876     5428383 :               if (remaining_vars.empty())
    1877     1295374 :                 continue;
    1878             : 
    1879     4010199 :               if (!all_extra_hanging_dofs)
    1880             :                 {
    1881     2094453 :                   erase_nonhanging_vars(node, remaining_vars, vertices);
    1882     2094453 :                   if (remaining_vars.empty())
    1883         815 :                     continue;
    1884             :                 }
    1885             : 
    1886     4009384 :               erase_copied_vars(node, false, remaining_vars);
    1887     4009384 :               if (remaining_vars.empty())
    1888      576680 :                 continue;
    1889             : 
    1890     3053817 :               if (copy_this_elem)
    1891             :                 {
    1892      228174 :                   std::vector<dof_id_type> side_dof_ids;
    1893      228174 :                   std::vector<typename FFunctor::ValuePushType> values;
    1894             : 
    1895     2624612 :                   for (auto var : remaining_vars)
    1896             :                     {
    1897     1323180 :                       f.eval_old_dofs(*elem, node_num, var, side_dof_ids, values);
    1898     1323180 :                       insert_ids(side_dof_ids, values, node->processor_id());
    1899             :                     }
    1900             : 
    1901     1301432 :                   copied_nodes[node].second.insert(remaining_vars.begin(),
    1902             :                                                    remaining_vars.end());
    1903     1301432 :                   this->find_dofs_to_send(*node, *elem, node_num, remaining_vars);
    1904           0 :                 }
    1905             :               else
    1906             :                 sides.emplace
    1907     2241710 :                   (node, std::make_tuple(elem, side, std::move(remaining_vars)));
    1908             :             }
    1909             :         }
    1910             : 
    1911             :       // Elements with elemental dofs might need those copied too.
    1912     4543101 :       if (copy_this_elem)
    1913             :         {
    1914      589506 :           std::vector<typename FFunctor::ValuePushType> U;
    1915      589506 :           std::vector<dof_id_type> dof_ids;
    1916             : 
    1917     5343374 :           for (auto v_num : this->projector.variables)
    1918             :             {
    1919     2692618 :               const Variable & var = system.variable(v_num);
    1920     2692618 :               if (!var.active_on_subdomain(elem->subdomain_id()))
    1921        4818 :                 continue;
    1922     2687800 :               FEType fe_type = var.type();
    1923             : 
    1924     2687800 :               f.eval_old_dofs(*elem, fe_type, sys_num, v_num,
    1925             :                               dof_ids, U);
    1926     4783672 :               action.insert(dof_ids, U);
    1927             : 
    1928     2687800 :               if (has_interior_nodes)
    1929             :                 {
    1930      378062 :                   f.eval_old_dofs(*elem, n_nodes-1, v_num, dof_ids, U);
    1931      720874 :                   action.insert(dof_ids, U);
    1932             :                 }
    1933             :             }
    1934           0 :         }
    1935             :     }
    1936      263745 : }
    1937             : 
    1938             : 
    1939             : template <typename FFunctor, typename GFunctor,
    1940             :           typename FValue, typename ProjectionAction>
    1941        7742 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SortAndCopy::join
    1942             :   (const GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SortAndCopy & other)
    1943             : {
    1944       38340 :   auto merge_multimaps = [](ves_multimap & self, const ves_multimap & other_mm)
    1945             :     {
    1946      657710 :       for (const auto & pair : other_mm)
    1947             :         {
    1948      634484 :           const Node * node = pair.first;
    1949      433166 :           auto remaining_vars = std::get<2>(pair.second);
    1950             : 
    1951      216583 :           auto my_range = self.equal_range(node);
    1952      705579 :           for (const auto & v_ent : as_range(my_range))
    1953      143447 :             for (const unsigned int var_covered :
    1954       23937 :                  std::get<2>(v_ent.second))
    1955       24356 :               remaining_vars.erase(var_covered);
    1956             : 
    1957      634484 :           if (!remaining_vars.empty())
    1958             :             self.emplace
    1959      756819 :               (node, std::make_tuple(std::get<0>(pair.second),
    1960      192844 :                                      std::get<1>(pair.second),
    1961      192844 :                                      std::move(remaining_vars)));
    1962             :         }
    1963             :     };
    1964             : 
    1965        7742 :   merge_multimaps(vertices, other.vertices);
    1966        7742 :   merge_multimaps(edges, other.edges);
    1967        7742 :   merge_multimaps(sides, other.sides);
    1968             : 
    1969        7742 :   std::sort(interiors.begin(), interiors.end());
    1970       10446 :   std::vector<const Elem *> other_interiors = other.interiors;
    1971        7742 :   std::sort(other_interiors.begin(), other_interiors.end());
    1972        5408 :   std::vector<const Elem *> merged_interiors;
    1973        7742 :   std::set_union(interiors.begin(), interiors.end(),
    1974             :                  other_interiors.begin(), other_interiors.end(),
    1975             :                  std::back_inserter(merged_interiors));
    1976        2704 :   interiors.swap(merged_interiors);
    1977             : 
    1978        7742 :   SubFunctor::join(other);
    1979        7742 : }
    1980             : 
    1981             : namespace
    1982             : {
    1983             : template <typename Output, typename Input>
    1984             : typename std::enable_if<ScalarTraits<Input>::value, Output>::type
    1985           0 : raw_value(const Input & input, unsigned int /*index*/)
    1986             : {
    1987           0 :   return input;
    1988             : }
    1989             : 
    1990             : template <typename Output, template <typename> class WrapperClass, typename T>
    1991             : typename std::enable_if<ScalarTraits<T>::value &&
    1992             :                             TensorTools::MathWrapperTraits<WrapperClass<T>>::value,
    1993             :                         Output>::type
    1994       16143 : raw_value(const WrapperClass<T> & input, unsigned int index)
    1995             : {
    1996      233178 :   return input.slice(index);
    1997             : }
    1998             : 
    1999             : template <typename T>
    2000             : typename T::value_type
    2001             : grad_component(const T &, unsigned int);
    2002             : 
    2003             : template <typename T>
    2004             : typename VectorValue<T>::value_type
    2005        2380 : grad_component(const VectorValue<T> & grad, unsigned int component)
    2006             : {
    2007        4709 :   return grad(component);
    2008             : }
    2009             : 
    2010             : template <typename T>
    2011             : typename TensorValue<T>::value_type
    2012           0 : grad_component(const TensorValue<T> & grad, unsigned int component)
    2013             : {
    2014           0 :   libmesh_error_msg("This function should only be called for Hermites. "
    2015             :                     "I don't know how you got here");
    2016             :   return grad(component, component);
    2017             : }
    2018             : 
    2019             : 
    2020             : }
    2021             : 
    2022             : template <typename FFunctor, typename GFunctor,
    2023             :           typename FValue, typename ProjectionAction>
    2024      265122 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::ProjectVertices::operator()
    2025             :   (const node_range & range)
    2026             : {
    2027       21484 :   LOG_SCOPE ("project_vertices","GenericProjector");
    2028             : 
    2029      265122 :   const unsigned int sys_num = system.number();
    2030             : 
    2031             :   // Variables with extra hanging dofs can't safely use eval_at_node
    2032             :   // in as many places as variables without can.
    2033       21484 :   std::vector<unsigned short> extra_hanging_dofs;
    2034      545274 :   for (auto v_num : this->projector.variables)
    2035             :     {
    2036      291478 :       if (extra_hanging_dofs.size() <= v_num)
    2037      280152 :         extra_hanging_dofs.resize(v_num+1, false);
    2038      280152 :       extra_hanging_dofs[v_num] =
    2039      280152 :         FEInterface::extra_hanging_dofs(system.variable(v_num).type());
    2040             :     }
    2041             : 
    2042     3061038 :   for (const auto & v_pair : range)
    2043             :     {
    2044     2795916 :       const Node & vertex = *v_pair.first;
    2045     2795916 :       const Elem & elem = *std::get<0>(v_pair.second);
    2046     2795916 :       const unsigned int n = std::get<1>(v_pair.second);
    2047      241749 :       const var_set & vertex_vars = std::get<2>(v_pair.second);
    2048             : 
    2049     2795916 :       context.pre_fe_reinit(system, &elem);
    2050             : 
    2051     2795916 :       this->find_dofs_to_send(vertex, elem, n, vertex_vars);
    2052             : 
    2053             :       // Look at all the variables we're supposed to interpolate from
    2054             :       // this element on this vertex
    2055     5651175 :       for (const auto & var : vertex_vars)
    2056             :         {
    2057     2855259 :           const Variable & variable = system.variable(var);
    2058      246527 :           const FEType & base_fe_type = variable.type();
    2059      739538 :           const unsigned int var_component =
    2060     2855259 :             system.variable_scalar_number(var, 0);
    2061             : 
    2062     2855259 :           if (base_fe_type.family == SCALAR)
    2063           0 :             continue;
    2064             : 
    2065     2855259 :           const FEContinuity cont = this->conts[var];
    2066     2855259 :           const FEFieldType field_type = this->field_types[var];
    2067             : 
    2068     2855259 :           if (cont == DISCONTINUOUS)
    2069             :             {
    2070           0 :               libmesh_assert_equal_to(vertex.n_comp(sys_num, var), 0);
    2071             :             }
    2072     3101743 :           else if (cont == C_ZERO ||
    2073     2608732 :                    cont == SIDE_DISCONTINUOUS)
    2074             :             {
    2075     2768372 :               if (cont == SIDE_DISCONTINUOUS &&
    2076        1518 :                   elem.dim() != 1)
    2077             :                 {
    2078           0 :                   libmesh_assert_equal_to(vertex.n_comp(sys_num, var), 0);
    2079           0 :                   continue;
    2080             :                 }
    2081             : 
    2082     3554833 :               const FValue val = f.eval_at_node
    2083     1860979 :                 (context, var_component, /*dim=*/ 0, // Don't care w/C0
    2084     2766854 :                  vertex, extra_hanging_dofs[var], system.time);
    2085             : 
    2086     2766854 :               if (field_type == TYPE_VECTOR)
    2087             :               {
    2088        1302 :                 libmesh_assert_equal_to(vertex.n_comp(sys_num, var), elem.dim());
    2089             : 
    2090             :                 // We will have a number of nodal value DoFs equal to the elem dim
    2091       83664 :                 for (auto i : make_range(elem.dim()))
    2092             :                 {
    2093       61920 :                   const dof_id_type id = vertex.dof_number(sys_num, var, i);
    2094             : 
    2095             :                   // Need this conversion so that this method
    2096             :                   // will compile for TYPE_SCALAR instantiations
    2097       59112 :                   const auto insert_val =
    2098        6498 :                     raw_value<typename ProjectionAction::InsertInput>(val, i);
    2099             : 
    2100       61920 :                   insert_id(id, insert_val, vertex.processor_id());
    2101             :                 }
    2102             :               }
    2103             :               else
    2104             :               {
    2105             :                 // C_ZERO elements have a single nodal value DoF at
    2106             :                 // vertices.  We can't assert n_comp==1 here,
    2107             :                 // because if this is a hanging node then it may have
    2108             :                 // more face/edge DoFs, but we don't need to deal with
    2109             :                 // those here.
    2110             : 
    2111     2745110 :                 const dof_id_type id = vertex.dof_number(sys_num, var, 0);
    2112     2745110 :                 insert_id(id, val, vertex.processor_id());
    2113      239040 :               }
    2114             :             }
    2115       88405 :           else if (cont == C_ONE)
    2116             :             {
    2117        7487 :               libmesh_assert(vertex.n_comp(sys_num, var));
    2118       88405 :               const dof_id_type first_id = vertex.dof_number(sys_num, var, 0);
    2119             : 
    2120             :               // C_ONE elements have a single nodal value and dim
    2121             :               // gradient values at vertices, as well as cross
    2122             :               // gradients for HERMITE.  We need to have an element in
    2123             :               // hand to figure out dim and to have in case this
    2124             :               // vertex is a new vertex.
    2125       88405 :               const int dim = elem.dim();
    2126             : #ifndef NDEBUG
    2127             :               // For now all C1 elements at a vertex had better have
    2128             :               // the same dimension.  If anyone hits these asserts let
    2129             :               // me know; we could probably support a mixed-dimension
    2130             :               // mesh IFF the 2D elements were all parallel to xy and
    2131             :               // the 1D elements all parallel to x.
    2132       33290 :               for (const auto e_id : (*this->projector.nodes_to_elem)[vertex.id()])
    2133             :                 {
    2134       25803 :                   const Elem & e = system.get_mesh().elem_ref(e_id);
    2135       25803 :                   libmesh_assert_equal_to(dim, e.dim());
    2136             :                 }
    2137             : #endif
    2138             : #ifdef LIBMESH_ENABLE_AMR
    2139        7487 :               bool is_old_vertex = true;
    2140       88405 :               if (elem.refinement_flag() == Elem::JUST_REFINED)
    2141             :                 {
    2142        3056 :                   const int i_am_child =
    2143       42272 :                     elem.parent()->which_child_am_i(&elem);
    2144             :                   is_old_vertex =
    2145       42272 :                     elem.parent()->is_vertex_on_parent(i_am_child, n);
    2146             :                 }
    2147             : #else
    2148             :               const bool is_old_vertex = false;
    2149             : #endif
    2150             : 
    2151             :               // The hermite element vertex shape functions are weird
    2152       88405 :               if (base_fe_type.family == HERMITE)
    2153             :                 {
    2154       98797 :                   const FValue val =
    2155       29385 :                     f.eval_at_node(context,
    2156             :                                    var_component,
    2157             :                                    dim,
    2158             :                                    vertex,
    2159             :                                    extra_hanging_dofs[var],
    2160       74698 :                                    system.time);
    2161       74698 :                   insert_id(first_id, val, vertex.processor_id());
    2162             : 
    2163       13134 :                   typename GFunctor::FunctorValue grad =
    2164       69103 :                     is_old_vertex ?
    2165       13836 :                     g->eval_at_node(context,
    2166             :                                     var_component,
    2167             :                                     dim,
    2168             :                                     vertex,
    2169             :                                     extra_hanging_dofs[var],
    2170       58018 :                                     system.time) :
    2171       15369 :                     g->eval_at_point(context,
    2172             :                                      var_component,
    2173             :                                      vertex,
    2174       16680 :                                      system.time,
    2175             :                                      false);
    2176             :                   // x derivative. Use slice because grad may be a tensor type
    2177       74698 :                   insert_id(first_id+1, grad.slice(0),
    2178             :                             vertex.processor_id());
    2179             : #if LIBMESH_DIM > 1
    2180       70414 :                   if (dim > 1 && is_old_vertex && f.is_grid_projection())
    2181             :                     {
    2182        5632 :                       for (int i = 1; i < dim; ++i)
    2183        3174 :                         insert_id(first_id+i+1, grad.slice(i),
    2184             :                                   vertex.processor_id());
    2185             : 
    2186             :                       // We can directly copy everything else too
    2187         716 :                       std::vector<FValue> derivs;
    2188        1074 :                       f.eval_mixed_derivatives
    2189        2458 :                         (context, var_component, dim, vertex, derivs);
    2190        5632 :                       for (auto i : index_range(derivs))
    2191        3174 :                         insert_id(first_id+dim+1+i, derivs[i],
    2192             :                                   vertex.processor_id());
    2193           0 :                     }
    2194       70442 :                   else if (dim > 1)
    2195             :                     {
    2196             :                       // We'll finite difference mixed derivatives.
    2197             :                       // This delta_x used to be TOLERANCE*hmin, but
    2198             :                       // the factor of 10 improved the accuracy in
    2199             :                       // some unit test projections
    2200       11418 :                       Real delta_x = TOLERANCE * 10 * elem.hmin();
    2201             : 
    2202       11418 :                       Point nxminus = elem.point(n),
    2203       11418 :                             nxplus = elem.point(n);
    2204       11418 :                       nxminus(0) -= delta_x;
    2205       11418 :                       nxplus(0) += delta_x;
    2206        1660 :                       typename GFunctor::FunctorValue gxminus =
    2207        9076 :                         g->eval_at_point(context,
    2208             :                                          var_component,
    2209             :                                          nxminus,
    2210       11418 :                                          system.time,
    2211             :                                          true);
    2212        1660 :                       typename GFunctor::FunctorValue gxplus =
    2213        9076 :                         g->eval_at_point(context,
    2214             :                                          var_component,
    2215             :                                          nxplus,
    2216       11418 :                                          system.time,
    2217             :                                          true);
    2218             :                       // y derivative
    2219       11418 :                       insert_id(first_id+2, grad.slice(1),
    2220             :                                 vertex.processor_id());
    2221             :                       // xy derivative
    2222       12320 :                       insert_id(first_id+3,
    2223       11418 :                         (grad_component(gxplus, 1) - grad_component(gxminus, 1)) / 2. / delta_x,
    2224             :                         vertex.processor_id());
    2225             : 
    2226             : #if LIBMESH_DIM > 2
    2227       11418 :                       if (dim > 2)
    2228             :                         {
    2229             :                           // z derivative
    2230         864 :                           insert_id(first_id+4, grad.slice(2),
    2231             :                                     vertex.processor_id());
    2232             :                           // xz derivative
    2233         936 :                           insert_id(first_id+5,
    2234         864 :                             (grad_component(gxplus, 2) - grad_component(gxminus, 2)) / 2. / delta_x,
    2235             :                             vertex.processor_id());
    2236             : 
    2237             :                           // We need new points for yz
    2238         864 :                           Point nyminus = elem.point(n),
    2239         864 :                             nyplus = elem.point(n);
    2240         864 :                           nyminus(1) -= delta_x;
    2241         864 :                           nyplus(1) += delta_x;
    2242          72 :                           typename GFunctor::FunctorValue gyminus =
    2243          72 :                             g->eval_at_point(context,
    2244             :                                              var_component,
    2245             :                                              nyminus,
    2246         864 :                                              system.time,
    2247             :                                              true);
    2248          72 :                           typename GFunctor::FunctorValue gyplus =
    2249          72 :                             g->eval_at_point(context,
    2250             :                                              var_component,
    2251             :                                              nyplus,
    2252         864 :                                              system.time,
    2253             :                                              true);
    2254             :                           // yz derivative
    2255         936 :                           insert_id(first_id+6,
    2256         864 :                             (grad_component(gyplus, 2) - grad_component(gyminus, 2)) / 2. / delta_x,
    2257             :                             vertex.processor_id());
    2258             :                           // Getting a 2nd order xyz is more tedious
    2259         864 :                           Point nxmym = elem.point(n),
    2260         864 :                             nxmyp = elem.point(n),
    2261         864 :                             nxpym = elem.point(n),
    2262         864 :                             nxpyp = elem.point(n);
    2263         864 :                           nxmym(0) -= delta_x;
    2264         864 :                           nxmym(1) -= delta_x;
    2265         864 :                           nxmyp(0) -= delta_x;
    2266         864 :                           nxmyp(1) += delta_x;
    2267         864 :                           nxpym(0) += delta_x;
    2268         864 :                           nxpym(1) -= delta_x;
    2269         864 :                           nxpyp(0) += delta_x;
    2270         864 :                           nxpyp(1) += delta_x;
    2271          72 :                           typename GFunctor::FunctorValue gxmym =
    2272          72 :                             g->eval_at_point(context,
    2273             :                                              var_component,
    2274             :                                              nxmym,
    2275         864 :                                              system.time,
    2276             :                                              true);
    2277          72 :                           typename GFunctor::FunctorValue gxmyp =
    2278          72 :                             g->eval_at_point(context,
    2279             :                                              var_component,
    2280             :                                              nxmyp,
    2281         864 :                                              system.time,
    2282             :                                              true);
    2283          72 :                           typename GFunctor::FunctorValue gxpym =
    2284          72 :                             g->eval_at_point(context,
    2285             :                                              var_component,
    2286             :                                              nxpym,
    2287         864 :                                              system.time,
    2288             :                                              true);
    2289          72 :                           typename GFunctor::FunctorValue gxpyp =
    2290          72 :                             g->eval_at_point(context,
    2291             :                                              var_component,
    2292             :                                              nxpyp,
    2293         864 :                                              system.time,
    2294             :                                              true);
    2295         864 :                           FValue gxzplus = (grad_component(gxpyp, 2) - grad_component(gxmyp, 2))
    2296         792 :                             / 2. / delta_x;
    2297         864 :                           FValue gxzminus = (grad_component(gxpym, 2) - grad_component(gxmym, 2))
    2298         792 :                             / 2. / delta_x;
    2299             :                           // xyz derivative
    2300         936 :                           insert_id(first_id+7,
    2301         864 :                             (gxzplus - gxzminus) / 2. / delta_x,
    2302             :                             vertex.processor_id());
    2303             :                         }
    2304             : #endif // LIBMESH_DIM > 2
    2305             :                     }
    2306             : #endif // LIBMESH_DIM > 1
    2307             :                 }
    2308             :               else
    2309             :                 {
    2310             :                   // Currently other C_ONE elements have a single nodal
    2311             :                   // value shape function and nodal gradient component
    2312             :                   // shape functions
    2313         918 :                   libmesh_assert_equal_to(
    2314             :                       FEInterface::n_dofs_at_node(
    2315             :                           base_fe_type,
    2316             :                           &elem,
    2317             :                           elem.get_node_index(&vertex),
    2318             :                           base_fe_type.p_refinement),
    2319             :                       (unsigned int)(1 + dim));
    2320             : 
    2321       16543 :                   const FValue val =
    2322       11853 :                     f.eval_at_node(context, var_component, dim,
    2323             :                                    vertex, extra_hanging_dofs[var],
    2324       13707 :                                    system.time);
    2325       13707 :                   insert_id(first_id, val, vertex.processor_id());
    2326        1836 :                   typename GFunctor::FunctorValue grad =
    2327       12875 :                     is_old_vertex ?
    2328        2082 :                     g->eval_at_node(context, var_component, dim,
    2329             :                                     vertex, extra_hanging_dofs[var],
    2330        3284 :                                     system.time) :
    2331        9699 :                     g->eval_at_point(context, var_component, vertex,
    2332       10423 :                                      system.time, false);
    2333       41121 :                   for (int i=0; i!= dim; ++i)
    2334       29250 :                     insert_id(first_id + i + 1, grad.slice(i),
    2335             :                               vertex.processor_id());
    2336             :                 }
    2337             :             }
    2338             :           else
    2339           0 :             libmesh_error_msg("Unknown continuity " << cont);
    2340             :         }
    2341             :     }
    2342      265122 : }
    2343             : 
    2344             : 
    2345             : template <typename FFunctor, typename GFunctor,
    2346             :           typename FValue, typename ProjectionAction>
    2347      257739 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::ProjectEdges::operator()
    2348             :   (const node_range & range)
    2349             : {
    2350       16326 :   LOG_SCOPE ("project_edges","GenericProjector");
    2351             : 
    2352      257739 :   const unsigned int sys_num = system.number();
    2353             : 
    2354      502294 :   for (const auto & e_pair : range)
    2355             :     {
    2356      244555 :       const Elem & elem = *std::get<0>(e_pair.second);
    2357             : 
    2358             :       // If this is an unchanged element then we already copied all
    2359             :       // its dofs
    2360             : #ifdef LIBMESH_ENABLE_AMR
    2361      103562 :       if (f.is_grid_projection() &&
    2362       13692 :           (elem.refinement_flag() != Elem::JUST_REFINED &&
    2363           0 :            elem.refinement_flag() != Elem::JUST_COARSENED &&
    2364           0 :            elem.p_refinement_flag() != Elem::JUST_REFINED &&
    2365           0 :            elem.p_refinement_flag() != Elem::JUST_COARSENED))
    2366           0 :         continue;
    2367             : #endif // LIBMESH_ENABLE_AMR
    2368             : 
    2369      244555 :       const Node & edge_node = *e_pair.first;
    2370      244555 :       const int dim = elem.dim();
    2371       18274 :       const var_set & edge_vars = std::get<2>(e_pair.second);
    2372             : 
    2373      244555 :       const unsigned short edge_num = std::get<1>(e_pair.second);
    2374      244555 :       const unsigned short node_num = elem.n_vertices() + edge_num;
    2375      244555 :       context.edge = edge_num;
    2376      244555 :       context.pre_fe_reinit(system, &elem);
    2377             : 
    2378      244555 :       this->find_dofs_to_send(edge_node, elem, node_num, edge_vars);
    2379             : 
    2380             :       // Look at all the variables we're supposed to interpolate from
    2381             :       // this element on this edge
    2382      489110 :       for (const auto & var : edge_vars)
    2383             :         {
    2384      244555 :           const Variable & variable = system.variable(var);
    2385       18274 :           const FEType & base_fe_type = variable.type();
    2386       54822 :           const unsigned int var_component =
    2387      244555 :             system.variable_scalar_number(var, 0);
    2388             : 
    2389      244555 :           if (base_fe_type.family == SCALAR)
    2390      131941 :             continue;
    2391             : 
    2392      244555 :           FEType fe_type = base_fe_type;
    2393             : 
    2394             :           // This may be a p refined element
    2395       36548 :           fe_type.order = fe_type.order + elem.p_level();
    2396             : 
    2397             :           // If this is a Lagrange element with DoFs on edges then by
    2398             :           // convention we interpolate at the node rather than project
    2399             :           // along the edge.
    2400      244555 :           if (fe_type.family == LAGRANGE || fe_type.family == LAGRANGE_VEC)
    2401             :             {
    2402      131941 :               if (fe_type.order > 1)
    2403             :                 {
    2404             :                   const processor_id_type pid =
    2405       20288 :                     edge_node.processor_id();
    2406      136192 :                   FValue fval = f.eval_at_point
    2407      131941 :                     (context, var_component, edge_node, system.time,
    2408             :                      false);
    2409      131941 :                   if (fe_type.family == LAGRANGE_VEC)
    2410             :                   {
    2411             :                     // We will have a number of nodal value DoFs equal to the elem dim
    2412      142392 :                     for (auto i : make_range(elem.dim()))
    2413             :                     {
    2414             :                       const dof_id_type dof_id =
    2415      106794 :                         edge_node.dof_number(sys_num, var, i);
    2416             : 
    2417             :                       // Need this conversion so that this method
    2418             :                       // will compile for TYPE_SCALAR instantiations
    2419      100350 :                       const auto insert_val =
    2420       13896 :                         raw_value<typename ProjectionAction::InsertInput>(fval, i);
    2421             : 
    2422      106794 :                       insert_id(dof_id, insert_val, pid);
    2423             :                     }
    2424             :                   }
    2425             :                   else // We are LAGRANGE
    2426             :                   {
    2427             :                     const dof_id_type dof_id =
    2428       96343 :                       edge_node.dof_number(sys_num, var, 0);
    2429       96343 :                     insert_id(dof_id, fval, pid);
    2430             :                   }
    2431             :                 }
    2432      131941 :               continue;
    2433      111653 :             }
    2434             : 
    2435             : #ifdef LIBMESH_ENABLE_AMR
    2436             :           // If this is a low order monomial element which has merely
    2437             :           // been h refined then we already copied all its dofs
    2438           0 :           if (fe_type.family == MONOMIAL &&
    2439           0 :               fe_type.order == CONSTANT &&
    2440      112614 :               elem.refinement_flag() != Elem::JUST_COARSENED &&
    2441           0 :               elem.p_refinement_flag() != Elem::JUST_COARSENED)
    2442           0 :             continue;
    2443             : #endif // LIBMESH_ENABLE_AMR
    2444             : 
    2445             :           // FIXME: Need to generalize this to vector-valued elements. [PB]
    2446        8130 :           FEGenericBase<typename FFunctor::RealType> * fe = nullptr;
    2447      112614 :           context.get_element_fe( var, fe, dim );
    2448        8130 :           FEGenericBase<typename FFunctor::RealType> * edge_fe = nullptr;
    2449        8130 :           context.get_edge_fe( var, edge_fe );
    2450             : 
    2451             :           // If we're JUST_COARSENED we'll need a custom
    2452             :           // evaluation, not just the standard edge FE
    2453      112614 :           const FEGenericBase<typename FFunctor::RealType> & proj_fe =
    2454             : #ifdef LIBMESH_ENABLE_AMR
    2455       16260 :             (elem.refinement_flag() == Elem::JUST_COARSENED) ?
    2456             :             *fe :
    2457             : #endif
    2458             :             *edge_fe;
    2459             : 
    2460             : #ifdef LIBMESH_ENABLE_AMR
    2461      112614 :           if (elem.refinement_flag() == Elem::JUST_COARSENED)
    2462             :             {
    2463           0 :               std::vector<Point> fine_points;
    2464             : 
    2465           0 :               std::unique_ptr<FEGenericBase<typename FFunctor::RealType>> fine_fe
    2466             :                 (FEGenericBase<typename FFunctor::RealType>::build (dim, base_fe_type));
    2467             : 
    2468           0 :               std::unique_ptr<QBase> qrule
    2469             :                 (base_fe_type.default_quadrature_rule(1));
    2470           0 :               fine_fe->attach_quadrature_rule(qrule.get());
    2471             : 
    2472           0 :               const std::vector<Point> & child_xyz =
    2473           0 :                         fine_fe->get_xyz();
    2474             : 
    2475           0 :               for (unsigned int c = 0, nc = elem.n_children();
    2476           0 :                    c != nc; ++c)
    2477             :                 {
    2478           0 :                   if (!elem.is_child_on_edge(c, context.edge))
    2479           0 :                     continue;
    2480             : 
    2481           0 :                   fine_fe->edge_reinit(elem.child_ptr(c), context.edge);
    2482           0 :                   fine_points.insert(fine_points.end(),
    2483             :                                      child_xyz.begin(),
    2484             :                                      child_xyz.end());
    2485             :                 }
    2486             : 
    2487           0 :               std::vector<Point> fine_qp;
    2488           0 :               FEMap::inverse_map (dim, &elem, fine_points, fine_qp);
    2489             : 
    2490           0 :               context.elem_fe_reinit(&fine_qp);
    2491           0 :             }
    2492             :           else
    2493             : #endif // LIBMESH_ENABLE_AMR
    2494      112614 :             context.edge_fe_reinit();
    2495             : 
    2496       16260 :           const std::vector<dof_id_type> & dof_indices =
    2497       96354 :             context.get_dof_indices(var);
    2498             : 
    2499       16260 :           std::vector<unsigned int> edge_dofs;
    2500      112614 :           FEInterface::dofs_on_edge(&elem, dim, base_fe_type,
    2501      112614 :                                     context.edge, edge_dofs);
    2502             : 
    2503       16260 :           this->construct_projection
    2504       96354 :             (dof_indices, edge_dofs, var_component,
    2505             :              &edge_node, proj_fe);
    2506             :         }
    2507             :     }
    2508      257739 : }
    2509             : 
    2510             : 
    2511             : template <typename FFunctor, typename GFunctor,
    2512             :           typename FValue, typename ProjectionAction>
    2513      261562 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::ProjectSides::operator()
    2514             :   (const node_range & range)
    2515             : {
    2516       18912 :   LOG_SCOPE ("project_sides","GenericProjector");
    2517             : 
    2518      261562 :   const unsigned int sys_num = system.number();
    2519             : 
    2520     2317880 :   for (const auto & s_pair : range)
    2521             :     {
    2522     2056318 :       const Elem & elem = *std::get<0>(s_pair.second);
    2523             : 
    2524             :       // If this is an unchanged element then we already copied all
    2525             :       // its dofs
    2526             : #ifdef LIBMESH_ENABLE_AMR
    2527     1883835 :       if (f.is_grid_projection() &&
    2528      426982 :           (elem.refinement_flag() != Elem::JUST_REFINED &&
    2529       14467 :            elem.refinement_flag() != Elem::JUST_COARSENED &&
    2530         308 :            elem.p_refinement_flag() != Elem::JUST_REFINED &&
    2531          20 :            elem.p_refinement_flag() != Elem::JUST_COARSENED))
    2532           0 :         continue;
    2533             : #endif // LIBMESH_ENABLE_AMR
    2534             : 
    2535     2056318 :       const Node & side_node = *s_pair.first;
    2536     2056318 :       const int dim = elem.dim();
    2537      167888 :       const var_set & side_vars = std::get<2>(s_pair.second);
    2538             : 
    2539     2056318 :       const unsigned int side_num = std::get<1>(s_pair.second);
    2540     2056318 :       unsigned short node_num = elem.n_vertices()+side_num;
    2541             :       // In 3D only some sides may have nodes
    2542     2056318 :       if (dim == 3)
    2543     5091294 :         for (auto n : make_range(elem.n_nodes()))
    2544             :           {
    2545     5091294 :             if (!elem.is_face(n))
    2546     3856492 :               continue;
    2547             : 
    2548      904677 :             if (elem.is_node_on_side(n, side_num))
    2549             :               {
    2550      322626 :                 node_num = n;
    2551      322626 :                 break;
    2552             :               }
    2553             :           }
    2554             : 
    2555     2056318 :       context.side = side_num;
    2556     2056318 :       context.pre_fe_reinit(system, &elem);
    2557             : 
    2558     2056318 :       this->find_dofs_to_send(side_node, elem, node_num, side_vars);
    2559             : 
    2560             :       // Look at all the variables we're supposed to interpolate from
    2561             :       // this element on this side
    2562     4162181 :       for (const auto & var : side_vars)
    2563             :         {
    2564     2105863 :           const Variable & variable = system.variable(var);
    2565      172091 :           const FEType & base_fe_type = variable.type();
    2566      516309 :           const unsigned int var_component =
    2567     2105863 :             system.variable_scalar_number(var, 0);
    2568             : 
    2569     2105863 :           if (base_fe_type.family == SCALAR)
    2570     1494554 :             continue;
    2571             : 
    2572     2105863 :           FEType fe_type = base_fe_type;
    2573     2105863 :           const bool add_p_level = base_fe_type.p_refinement;
    2574             : 
    2575             :           // This may be a p refined element
    2576     2277990 :           fe_type.order = fe_type.order + add_p_level*elem.p_level();
    2577             : 
    2578             :           // If this is a Lagrange element with DoFs on sides then by
    2579             :           // convention we interpolate at the node rather than project
    2580             :           // along the side.
    2581     2105863 :           if (fe_type.family == LAGRANGE || fe_type.family == LAGRANGE_VEC)
    2582             :             {
    2583             :               // If order==1 then we only have DoFs on vertices, so
    2584             :               // skip all this.
    2585             :               // If order>1 ... we might still have something tricky
    2586             :               // like order==2 PRISM20, where some side nodes have
    2587             :               // DoFs but others don't.  Gotta check.
    2588     2989108 :               if (fe_type.order > 1 &&
    2589     1494554 :                   side_node.n_comp(sys_num, var))
    2590             :                 {
    2591             :                   const processor_id_type pid =
    2592      245984 :                     side_node.processor_id();
    2593     1946694 :                   FValue fval = f.eval_at_point
    2594     1489775 :                     (context, var_component, side_node, system.time,
    2595             :                      false);
    2596             : 
    2597     1489775 :                   if (fe_type.family == LAGRANGE_VEC)
    2598             :                   {
    2599             :                     // We will have a number of nodal value DoFs equal to the elem dim
    2600       84492 :                     for (auto i : make_range(elem.dim()))
    2601             :                     {
    2602       62736 :                       const dof_id_type dof_id = side_node.dof_number(sys_num, var, i);
    2603             : 
    2604             :                       // Need this conversion so that this method
    2605             :                       // will compile for TYPE_SCALAR instantiations
    2606       58188 :                       const auto insert_val =
    2607        9405 :                         raw_value<typename ProjectionAction::InsertInput>(fval, i);
    2608             : 
    2609       62736 :                       insert_id(dof_id, insert_val, pid);
    2610             :                     }
    2611             :                   }
    2612             :                   else // We are LAGRANGE
    2613             :                   {
    2614             :                     const dof_id_type dof_id =
    2615     1468019 :                       side_node.dof_number(sys_num, var, 0);
    2616     1468019 :                     insert_id(dof_id, fval, pid);
    2617             :                   }
    2618             :                 }
    2619     1494554 :               continue;
    2620     1247838 :             }
    2621             : 
    2622             : #ifdef LIBMESH_ENABLE_AMR
    2623             :           // If this is a low order monomial element which has merely
    2624             :           // been h refined then we already copied all its dofs
    2625           0 :           if (fe_type.family == MONOMIAL &&
    2626           0 :               fe_type.order == CONSTANT &&
    2627      611309 :               elem.refinement_flag() != Elem::JUST_COARSENED &&
    2628           0 :               elem.p_refinement_flag() != Elem::JUST_COARSENED)
    2629           0 :             continue;
    2630             : #endif // LIBMESH_ENABLE_AMR
    2631             : 
    2632             :           // FIXME: Need to generalize this to vector-valued elements. [PB]
    2633       48751 :           FEGenericBase<typename FFunctor::RealType> * fe = nullptr;
    2634      611309 :           context.get_element_fe( var, fe, dim );
    2635      562558 :           FEGenericBase<typename FFunctor::RealType> * side_fe = nullptr;
    2636      562558 :           context.get_side_fe( var, side_fe );
    2637             : 
    2638             :           // If we're JUST_COARSENED we'll need a custom
    2639             :           // evaluation, not just the standard side FE
    2640      611309 :           const FEGenericBase<typename FFunctor::RealType> & proj_fe =
    2641             : #ifdef LIBMESH_ENABLE_AMR
    2642       97502 :             (elem.refinement_flag() == Elem::JUST_COARSENED) ?
    2643             :             *fe :
    2644             : #endif
    2645             :             *side_fe;
    2646             : 
    2647             : #ifdef LIBMESH_ENABLE_AMR
    2648      611309 :           if (elem.refinement_flag() == Elem::JUST_COARSENED)
    2649             :             {
    2650        9252 :               std::vector<Point> fine_points;
    2651             : 
    2652       60105 :               std::unique_ptr<FEGenericBase<typename FFunctor::RealType>> fine_fe
    2653             :                 (FEGenericBase<typename FFunctor::RealType>::build (dim, base_fe_type));
    2654             : 
    2655       60105 :               std::unique_ptr<QBase> qrule
    2656             :                 (base_fe_type.default_quadrature_rule(1));
    2657       60105 :               fine_fe->attach_quadrature_rule(qrule.get());
    2658             : 
    2659        9252 :               const std::vector<Point> & child_xyz =
    2660        4626 :                         fine_fe->get_xyz();
    2661             : 
    2662      277395 :               for (unsigned int c = 0, nc = elem.n_children();
    2663      277395 :                    c != nc; ++c)
    2664             :                 {
    2665      221916 :                   if (!elem.is_child_on_side(c, context.side))
    2666      101706 :                     continue;
    2667             : 
    2668      120210 :                   fine_fe->reinit(elem.child_ptr(c), context.side);
    2669      120210 :                   fine_points.insert(fine_points.end(),
    2670             :                                      child_xyz.begin(),
    2671             :                                      child_xyz.end());
    2672             :                 }
    2673             : 
    2674        9252 :               std::vector<Point> fine_qp;
    2675       55479 :               FEMap::inverse_map (dim, &elem, fine_points, fine_qp);
    2676             : 
    2677       55479 :               context.elem_fe_reinit(&fine_qp);
    2678       46227 :             }
    2679             :           else
    2680             : #endif // LIBMESH_ENABLE_AMR
    2681      555830 :             context.side_fe_reinit();
    2682             : 
    2683       97502 :           const std::vector<dof_id_type> & dof_indices =
    2684      513807 :             context.get_dof_indices(var);
    2685             : 
    2686       97502 :           std::vector<unsigned int> side_dofs;
    2687      708811 :           FEInterface::dofs_on_side(&elem, dim, base_fe_type,
    2688      611309 :                                     context.side, side_dofs, add_p_level);
    2689             : 
    2690       97502 :           this->construct_projection
    2691      513807 :             (dof_indices, side_dofs, var_component,
    2692             :              &side_node, proj_fe);
    2693             :         }
    2694             :     }
    2695      261562 : }
    2696             : 
    2697             : 
    2698             : template <typename FFunctor, typename GFunctor,
    2699             :           typename FValue, typename ProjectionAction>
    2700      258743 : void GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::ProjectInteriors::operator()
    2701             :   (const interior_range & range)
    2702             : {
    2703       17034 :   LOG_SCOPE ("project_interiors","GenericProjector");
    2704             : 
    2705      258743 :   const unsigned int sys_num = system.number();
    2706             : 
    2707             :   // Iterate over all dof-bearing element interiors in the range
    2708     1169339 :   for (const auto & elem : range)
    2709             :     {
    2710      910596 :       unsigned char dim = cast_int<unsigned char>(elem->dim());
    2711             : 
    2712      910596 :       context.pre_fe_reinit(system, elem);
    2713             : 
    2714             :       // Loop over all the variables we've been requested to project, to
    2715             :       // do the projection
    2716     1871196 :       for (const auto & var : this->projector.variables)
    2717             :         {
    2718      960600 :           const Variable & variable = system.variable(var);
    2719             : 
    2720      960600 :           if (!variable.active_on_subdomain(elem->subdomain_id()))
    2721      680202 :             continue;
    2722             : 
    2723       81115 :           const FEType & base_fe_type = variable.type();
    2724             : 
    2725      957096 :           if (base_fe_type.family == SCALAR)
    2726           0 :             continue;
    2727             : 
    2728       81115 :           FEGenericBase<typename FFunctor::RealType> * fe = nullptr;
    2729      957096 :           context.get_element_fe( var, fe, dim );
    2730             : 
    2731      957096 :           FEType fe_type = base_fe_type;
    2732      957096 :           const bool add_p_level = fe_type.p_refinement;
    2733             : 
    2734             :           // This may be a p refined element
    2735     1038221 :           fe_type.order = fe_type.order + add_p_level * elem->p_level();
    2736             : 
    2737      243355 :           const unsigned int var_component =
    2738      957096 :             system.variable_scalar_number(var, 0);
    2739             : 
    2740             :           // If this is a Lagrange element with interior DoFs then by
    2741             :           // convention we interpolate at the interior node rather
    2742             :           // than project along the interior.
    2743      957096 :           if (fe_type.family == LAGRANGE || fe_type.family == LAGRANGE_VEC)
    2744             :             {
    2745      676698 :               if (fe_type.order > 1)
    2746             :                 {
    2747      654728 :                   const unsigned int first_interior_node =
    2748      654728 :                     (elem->n_vertices() +
    2749      654728 :                      ((elem->dim() > 2) * elem->n_edges()) +
    2750      654728 :                      ((elem->dim() > 1) * elem->n_sides()));
    2751      654728 :                   const unsigned int n_nodes = elem->n_nodes();
    2752             : 
    2753             :                   // < vs != is important here for HEX20, QUAD8!
    2754     1309456 :                   for (unsigned int n = first_interior_node; n < n_nodes; ++n)
    2755             :                     {
    2756      654728 :                       const Node & interior_node = elem->node_ref(n);
    2757             : 
    2758      868297 :                       FValue fval = f.eval_at_point
    2759      573349 :                         (context, var_component, interior_node,
    2760      654728 :                          system.time, false);
    2761             :                       const processor_id_type pid =
    2762      110906 :                         interior_node.processor_id();
    2763             : 
    2764      654728 :                       if (fe_type.family == LAGRANGE_VEC)
    2765             :                       {
    2766             :                         // We will have a number of nodal value DoFs equal to the elem dim
    2767        2448 :                         for (unsigned int i = 0; i < elem->dim(); ++i)
    2768             :                         {
    2769        1728 :                           const dof_id_type dof_id = interior_node.dof_number(sys_num, var, i);
    2770             : 
    2771             :                           // Need this conversion so that this method
    2772             :                           // will compile for TYPE_SCALAR instantiations
    2773        1584 :                           const auto insert_val =
    2774         288 :                             raw_value<typename ProjectionAction::InsertInput>(fval, i);
    2775             : 
    2776        1728 :                           insert_id(dof_id, insert_val, pid);
    2777             :                         }
    2778             :                       }
    2779             :                       else // We are LAGRANGE
    2780             :                       {
    2781      110786 :                         const dof_id_type dof_id =
    2782      543222 :                           interior_node.dof_number(sys_num, var, 0);
    2783      654008 :                         insert_id(dof_id, fval, pid);
    2784             :                       }
    2785             :                     }
    2786             :                 }
    2787      676698 :               continue;
    2788      561886 :             }
    2789             : 
    2790             : #ifdef LIBMESH_ENABLE_AMR
    2791      280398 :           if (elem->refinement_flag() == Elem::JUST_COARSENED)
    2792             :             {
    2793        4242 :               std::vector<Point> fine_points;
    2794             : 
    2795       26106 :               std::unique_ptr<FEGenericBase<typename FFunctor::RealType>> fine_fe
    2796             :                 (FEGenericBase<typename FFunctor::RealType>::build (dim, base_fe_type));
    2797             : 
    2798       26106 :               std::unique_ptr<QBase> qrule
    2799             :                 (base_fe_type.default_quadrature_rule(dim));
    2800       26106 :               fine_fe->attach_quadrature_rule(qrule.get());
    2801             : 
    2802        4242 :               const std::vector<Point> & child_xyz =
    2803        2145 :                 fine_fe->get_xyz();
    2804             : 
    2805      123198 :               for (auto & child : elem->child_ref_range())
    2806             :                 {
    2807       97092 :                   fine_fe->reinit(&child);
    2808      105672 :                   fine_points.insert(fine_points.end(),
    2809             :                                      child_xyz.begin(),
    2810             :                                      child_xyz.end());
    2811             :                 }
    2812             : 
    2813        4242 :               std::vector<Point> fine_qp;
    2814       23985 :               FEMap::inverse_map (dim, elem, fine_points, fine_qp);
    2815             : 
    2816       23985 :               context.elem_fe_reinit(&fine_qp);
    2817       19743 :             }
    2818             :           else
    2819             : #endif // LIBMESH_ENABLE_AMR
    2820      256413 :             context.elem_fe_reinit();
    2821             : 
    2822       47428 :           const std::vector<dof_id_type> & dof_indices =
    2823      232970 :             context.get_dof_indices(var);
    2824             : 
    2825             :           const unsigned int n_dofs =
    2826       47428 :             cast_int<unsigned int>(dof_indices.size());
    2827             : 
    2828      304113 :           std::vector<unsigned int> all_dofs(n_dofs);
    2829       23715 :           std::iota(all_dofs.begin(), all_dofs.end(), 0);
    2830             : 
    2831       47428 :           this->construct_projection
    2832      232970 :             (dof_indices, all_dofs, var_component,
    2833             :              nullptr, *fe);
    2834             :         } // end variables loop
    2835             :     } // end elements loop
    2836      258743 : }
    2837             : 
    2838             : 
    2839             : 
    2840             : template <typename FFunctor, typename GFunctor,
    2841             :           typename FValue, typename ProjectionAction>
    2842             : void
    2843     9198609 : GenericProjector<FFunctor, GFunctor, FValue,
    2844             :                  ProjectionAction>::SubFunctor::find_dofs_to_send
    2845             :   (const Node & node, const Elem & elem, unsigned short node_num, const var_set & vars)
    2846             : {
    2847      874796 :   libmesh_assert (&node == elem.node_ptr(node_num));
    2848             : 
    2849             :   // Any ghosted node in our range might have an owner who needs our
    2850             :   // data
    2851     1749375 :   const processor_id_type owner = node.processor_id();
    2852    10073188 :   if (owner != system.processor_id())
    2853             :     {
    2854       77612 :       const MeshBase & mesh = system.get_mesh();
    2855       38839 :       const DofMap & dof_map = system.get_dof_map();
    2856             : 
    2857             :       // But let's check and see if we can be certain the owner can
    2858             :       // compute any or all of its own dof coefficients on that node.
    2859       77678 :       std::vector<dof_id_type> node_dof_ids, patch_dof_ids;
    2860     1925568 :       for (const auto & var : vars)
    2861             :         {
    2862      991573 :           const Variable & variable = system.variable(var);
    2863             : 
    2864      991573 :           if (!variable.active_on_subdomain(elem.subdomain_id()))
    2865           0 :             continue;
    2866             : 
    2867      991573 :           dof_map.dof_indices(elem, node_num, node_dof_ids, var);
    2868             :         }
    2869       38839 :       libmesh_assert(std::is_sorted(node_dof_ids.begin(),
    2870             :                                     node_dof_ids.end()));
    2871       77678 :       const std::vector<dof_id_type> & patch =
    2872      933995 :         (*this->projector.nodes_to_elem)[node.id()];
    2873     2621236 :       for (const auto & elem_id : patch)
    2874             :         {
    2875     2599447 :           const Elem & patch_elem = mesh.elem_ref(elem_id);
    2876     1451844 :           if (!patch_elem.active() || owner != patch_elem.processor_id())
    2877     1651007 :             continue;
    2878      948440 :           dof_map.dof_indices(&patch_elem, patch_dof_ids);
    2879      948440 :           std::sort(patch_dof_ids.begin(), patch_dof_ids.end());
    2880             : 
    2881             :           // Remove any node_dof_ids that we see can be calculated on
    2882             :           // this element
    2883      987909 :           std::vector<dof_id_type> diff_ids(node_dof_ids.size());
    2884      948440 :           auto it = std::set_difference(node_dof_ids.begin(), node_dof_ids.end(),
    2885             :                                         patch_dof_ids.begin(), patch_dof_ids.end(), diff_ids.begin());
    2886      948440 :           diff_ids.resize(it-diff_ids.begin());
    2887       39535 :           node_dof_ids.swap(diff_ids);
    2888      948440 :           if (node_dof_ids.empty())
    2889       37889 :             break;
    2890             :         }
    2891             : 
    2892             :       // Give ids_to_push default invalid pid: not yet computed
    2893      969362 :       for (auto id : node_dof_ids)
    2894       35367 :         new_ids_to_push[id].second = DofObject::invalid_processor_id;
    2895             :     }
    2896     9198609 : }
    2897             : 
    2898             : 
    2899             : 
    2900             : template <typename FFunctor, typename GFunctor,
    2901             :           typename FValue, typename ProjectionAction>
    2902             : template <typename Value>
    2903             : void
    2904      768009 : GenericProjector<FFunctor, GFunctor, FValue,
    2905             :                  ProjectionAction>::send_and_insert_dof_values
    2906             :   (std::unordered_map<dof_id_type, std::pair<Value, processor_id_type>> & ids_to_push,
    2907             :    ProjectionAction & action) const
    2908             : {
    2909       45504 :   LOG_SCOPE ("send_and_insert_dof_values", "GenericProjector");
    2910             : 
    2911             :   // See if we calculated any ids that need to be pushed; get them
    2912             :   // ready to push.
    2913             :   std::unordered_map<processor_id_type, std::vector<dof_id_type>>
    2914       45504 :     pushed_dof_ids, received_dof_ids;
    2915             :   std::unordered_map<processor_id_type, std::vector<typename TypeToSend<Value>::type>>
    2916       45504 :     pushed_dof_values, received_dof_values;
    2917      864489 :   for (auto & id_val_pid : ids_to_push)
    2918             :     {
    2919       96480 :       processor_id_type pid = id_val_pid.second.second;
    2920       96480 :       if (pid != DofObject::invalid_processor_id)
    2921             :         {
    2922       24600 :           pushed_dof_ids[pid].push_back(id_val_pid.first);
    2923       24600 :           pushed_dof_values[pid].push_back(convert_to_send(id_val_pid.second.first));
    2924             :         }
    2925             :     }
    2926             : 
    2927             :   // If and when we get ids pushed to us, act on them if we have
    2928             :   // corresponding values or save them if not
    2929      768333 :   auto ids_action_functor =
    2930        8418 :     [&action, &received_dof_ids, &received_dof_values]
    2931             :     (processor_id_type pid,
    2932         324 :      const std::vector<dof_id_type> & data)
    2933             :     {
    2934         162 :       auto iter = received_dof_values.find(pid);
    2935        8742 :       if (iter == received_dof_values.end())
    2936             :         {
    2937         162 :           libmesh_assert(received_dof_ids.find(pid) ==
    2938             :                          received_dof_ids.end());
    2939        8742 :           received_dof_ids[pid] = data;
    2940             :         }
    2941             :       else
    2942             :         {
    2943           0 :           auto & vals = iter->second;
    2944           0 :           std::size_t vals_size = vals.size();
    2945           0 :           libmesh_assert_equal_to(vals_size, data.size());
    2946           0 :           for (std::size_t i=0; i != vals_size; ++i)
    2947             :             {
    2948           0 :               Value val;
    2949           0 :               convert_from_receive(vals[i], val);
    2950           0 :               action.insert(data[i], val);
    2951             :             }
    2952           0 :           received_dof_values.erase(iter);
    2953             :         }
    2954             :     };
    2955             : 
    2956             :   // If and when we get values pushed to us, act on them if we have
    2957             :   // corresponding ids or save them if not
    2958      768333 :   auto values_action_functor =
    2959       38916 :     [&action, &received_dof_ids, &received_dof_values]
    2960             :     (processor_id_type pid,
    2961         324 :      const std::vector<typename TypeToSend<Value>::type> & data)
    2962             :     {
    2963         162 :       auto iter = received_dof_ids.find(pid);
    2964        8742 :       if (iter == received_dof_ids.end())
    2965             :         {
    2966             :           // We get no more than 1 values vector from anywhere
    2967           0 :           libmesh_assert(received_dof_values.find(pid) ==
    2968             :                          received_dof_values.end());
    2969           0 :           received_dof_values[pid] = data;
    2970             :         }
    2971             :       else
    2972             :         {
    2973         162 :           auto & ids = iter->second;
    2974         324 :           std::size_t ids_size = ids.size();
    2975         162 :           libmesh_assert_equal_to(ids_size, data.size());
    2976       33342 :           for (std::size_t i=0; i != ids_size; ++i)
    2977             :             {
    2978           0 :               Value val;
    2979        2520 :               convert_from_receive(data[i], val);
    2980       25860 :               action.insert(ids[i], val);
    2981             :             }
    2982         162 :           received_dof_ids.erase(iter);
    2983             :         }
    2984             :     };
    2985             : 
    2986             :   Parallel::push_parallel_vector_data
    2987      768009 :     (system.comm(), pushed_dof_ids, ids_action_functor);
    2988             : 
    2989             :   Parallel::push_parallel_vector_data
    2990      768009 :     (system.comm(), pushed_dof_values, values_action_functor);
    2991             : 
    2992             :   // At this point we shouldn't have any unprocessed data left
    2993       22752 :   libmesh_assert(received_dof_ids.empty());
    2994       22752 :   libmesh_assert(received_dof_values.empty());
    2995             : 
    2996      768009 : }
    2997             : 
    2998             : 
    2999             : 
    3000             : template <typename FFunctor, typename GFunctor,
    3001             :           typename FValue, typename ProjectionAction>
    3002             : void
    3003     1004321 : GenericProjector<FFunctor, GFunctor, FValue, ProjectionAction>::SubProjector::construct_projection
    3004             :   (const std::vector<dof_id_type> & dof_indices_var,
    3005             :    const std::vector<unsigned int> & involved_dofs,
    3006             :    unsigned int var_component,
    3007             :    const Node * node,
    3008             :    const FEGenericBase<typename FFunctor::RealType> & fe)
    3009             : {
    3010     1004321 :   const auto & JxW = fe.get_JxW();
    3011       80596 :   const auto & phi = fe.get_phi();
    3012       80596 :   const std::vector<std::vector<typename FEGenericBase<typename FFunctor::RealType>::OutputGradient>> * dphi = nullptr;
    3013      208786 :   const std::vector<Point> & xyz_values = fe.get_xyz();
    3014     1004321 :   const FEContinuity cont = fe.get_continuity();
    3015       80596 :   const std::unordered_map<dof_id_type, typename FFunctor::ValuePushType> & ids_to_save =
    3016     1004321 :     this->projector.ids_to_save;
    3017             : 
    3018     1004321 :   if (cont == C_ONE)
    3019        2806 :     dphi = &(fe.get_dphi());
    3020             : 
    3021             :   const unsigned int n_involved_dofs =
    3022      161190 :     cast_int<unsigned int>(involved_dofs.size());
    3023             : 
    3024       80596 :   std::vector<dof_id_type> free_dof_ids;
    3025      923727 :   DenseVector<typename FFunctor::ValuePushType> Uinvolved(n_involved_dofs);
    3026     1004321 :   std::vector<char> dof_is_fixed(n_involved_dofs, false); // bools
    3027             : 
    3028     9350574 :   for (auto i : make_range(n_involved_dofs))
    3029             :     {
    3030     9703017 :       const dof_id_type id = dof_indices_var[involved_dofs[i]];
    3031      678408 :       auto iter = ids_to_save.find(id);
    3032     8346253 :       if (iter == ids_to_save.end())
    3033     4741846 :         free_dof_ids.push_back(id);
    3034             :       else
    3035             :         {
    3036     3604407 :           dof_is_fixed[i] = true;
    3037     3901149 :           Uinvolved(i) = iter->second;
    3038             :         }
    3039             :     }
    3040             : 
    3041     1004321 :   const unsigned int free_dofs = free_dof_ids.size();
    3042             : 
    3043             :   // There may be nothing to project
    3044     1004321 :   if (!free_dofs)
    3045         118 :     return;
    3046             : 
    3047             :   // The element matrix and RHS for projections.
    3048             :   // Note that Ke is always real-valued, whereas
    3049             :   // Fe may be complex valued if complex number
    3050             :   // support is enabled
    3051     1164057 :   DenseMatrix<Real> Ke(free_dofs, free_dofs);
    3052     1003105 :   DenseVector<typename FFunctor::ValuePushType> Fe(free_dofs);
    3053             :   // The new degree of freedom coefficients to solve for
    3054     1003105 :   DenseVector<typename FFunctor::ValuePushType> Ufree(free_dofs);
    3055             : 
    3056             :   const unsigned int n_qp =
    3057      160954 :     cast_int<unsigned int>(xyz_values.size());
    3058             : 
    3059             :   // Loop over the quadrature points
    3060    12705333 :   for (unsigned int qp=0; qp<n_qp; qp++)
    3061             :     {
    3062             :       // solution at the quadrature point
    3063    11702230 :       FValue fineval = f.eval_at_point(context,
    3064             :                                        var_component,
    3065             :                                        xyz_values[qp],
    3066    11702230 :                                        system.time,
    3067             :                                        false);
    3068             :       // solution grad at the quadrature point
    3069      961601 :       typename GFunctor::FunctorValue finegrad;
    3070    11702230 :       if (cont == C_ONE)
    3071      158847 :         finegrad = g->eval_at_point(context,
    3072             :                                     var_component,
    3073             :                                     xyz_values[qp],
    3074      147174 :                                     system.time,
    3075             :                                     false);
    3076             : 
    3077             :       // Form edge projection matrix
    3078   229418392 :       for (unsigned int sidei=0, freei=0;
    3079   231003706 :            sidei != n_involved_dofs; ++sidei)
    3080             :         {
    3081   219301476 :           unsigned int i = involved_dofs[sidei];
    3082             :           // fixed DoFs aren't test functions
    3083   237456158 :           if (dof_is_fixed[sidei])
    3084    75457824 :             continue;
    3085  4997928672 :           for (unsigned int sidej=0, freej=0;
    3086  5123617488 :                sidej != n_involved_dofs; ++sidej)
    3087             :             {
    3088  4986664160 :               unsigned int j = involved_dofs[sidej];
    3089  5400324120 :               if (dof_is_fixed[sidej])
    3090   657767204 :                 Fe(freei) -= phi[i][qp] * phi[j][qp] *
    3091   444996952 :                   JxW[qp] * Uinvolved(sidej);
    3092             :               else
    3093  5679552392 :                 Ke(freei,freej) += phi[i][qp] *
    3094  5679551674 :                   phi[j][qp] * JxW[qp];
    3095  4986664160 :               if (cont == C_ONE)
    3096             :                 {
    3097     1301847 :                   if (dof_is_fixed[sidej])
    3098     1225224 :                     Fe(freei) -= ( TensorTools::inner_product((*dphi)[i][qp],
    3099     1146224 :                                                               (*dphi)[j][qp]) ) *
    3100      989772 :                       JxW[qp] * Uinvolved(sidej);
    3101             :                   else
    3102      268599 :                     Ke(freei,freej) += ( TensorTools::inner_product((*dphi)[i][qp],
    3103      234007 :                                                                     (*dphi)[j][qp]) )
    3104      234007 :                       * JxW[qp];
    3105             :                 }
    3106  5400324120 :               if (!dof_is_fixed[sidej])
    3107  4548246016 :                 freej++;
    3108             :             }
    3109   170746402 :           Fe(freei) += phi[i][qp] * fineval * JxW[qp];
    3110   136953328 :           if (cont == C_ONE)
    3111      174815 :             Fe(freei) += (TensorTools::inner_product(finegrad,
    3112      187539 :                                                      (*dphi)[i][qp]) ) *
    3113       12921 :               JxW[qp];
    3114   136953328 :           freei++;
    3115             :         }
    3116             :     }
    3117             : 
    3118     1003103 :   Ke.cholesky_solve(Fe, Ufree);
    3119             : 
    3120             :   // Transfer new edge solutions to element
    3121     1003103 :   const processor_id_type pid = node ?
    3122      113574 :     node->processor_id() : DofObject::invalid_processor_id;
    3123     1003103 :   insert_ids(free_dof_ids, Ufree.get_values(), pid);
    3124      842149 : }
    3125             : 
    3126             : 
    3127             : } // namespace libMesh
    3128             : 
    3129             : #endif // GENERIC_PROJECTOR_H

Generated by: LCOV version 1.14