LCOV - code coverage report
Current view: top level - src/systems - system_projection.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4503 (40b389) with base bc1d1a Lines: 529 862 61.4 %
Date: 2026-07-25 09:10:44 Functions: 38 63 60.3 %
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             : // C++ includes
      21             : #include <vector>
      22             : #include <numeric> // std::iota
      23             : 
      24             : // Local includes
      25             : #include "libmesh/libmesh_config.h"
      26             : 
      27             : #ifdef LIBMESH_HAVE_METAPHYSICL
      28             : 
      29             : // With quad precision we need the shim function declarations to
      30             : // precede the MetaPhysicL use of them
      31             : #include "libmesh/libmesh_common.h"
      32             : #include "libmesh/compare_types.h"
      33             : 
      34             : // Template specialization declarations in here need to *precede* code
      35             : // using them.
      36             : #include "metaphysicl/dynamicsparsenumberarray_decl.h"
      37             : 
      38             : using MetaPhysicL::DynamicSparseNumberArray;
      39             : 
      40             : namespace libMesh
      41             : {
      42             : // From the perspective of libMesh gradient vectors, a DSNA is a
      43             : // scalar component
      44             : template <typename T, typename IndexType>
      45             : struct ScalarTraits<MetaPhysicL::DynamicSparseNumberArray<T,IndexType> >
      46             : {
      47             :   static const bool value = true;
      48             : };
      49             : 
      50             : // And although MetaPhysicL knows how to combine DSNA with something
      51             : // else, we need to teach libMesh too.
      52             : template <typename T, typename IndexType, typename T2>
      53             : struct CompareTypes<MetaPhysicL::DynamicSparseNumberArray<T,IndexType>, T2>
      54             : {
      55             :   typedef typename
      56             :   MetaPhysicL::DynamicSparseNumberArray
      57             :   <typename CompareTypes<T,T2>::supertype,IndexType> supertype;
      58             : };
      59             : 
      60             : template <typename T> struct TypeToSend;
      61             : 
      62             : template <typename T, typename IndexType>
      63             : struct TypeToSend<MetaPhysicL::DynamicSparseNumberArray<T,IndexType>> {
      64             :   typedef std::vector<std::pair<IndexType,T>> type;
      65             : };
      66             : 
      67             : template <typename T, typename IndexType>
      68             : const std::vector<std::pair<IndexType,T>>
      69           0 : convert_to_send(MetaPhysicL::DynamicSparseNumberArray<T,IndexType> & in)
      70             : {
      71           0 :   const std::size_t in_size = in.size();
      72           0 :   std::vector<std::pair<IndexType,T>> returnval(in_size);
      73             : 
      74           0 :   for (std::size_t i=0; i != in_size; ++i)
      75             :     {
      76           0 :       returnval[i].first = in.raw_index(i);
      77           0 :       returnval[i].second = in.raw_at(i);
      78             :     }
      79           0 :   return returnval;
      80             : }
      81             : 
      82             : template <typename SendT, typename T, typename IndexType>
      83           0 : void convert_from_receive (SendT & received,
      84             :                            MetaPhysicL::DynamicSparseNumberArray<T,IndexType> & converted)
      85             : {
      86           0 :   const std::size_t received_size = received.size();
      87           0 :   converted.resize(received_size);
      88           0 :   for (std::size_t i=0; i != received_size; ++i)
      89             :     {
      90           0 :       converted.raw_index(i) = received[i].first;
      91           0 :       converted.raw_at(i) = received[i].second;
      92             :     }
      93           0 : }
      94             : 
      95             : }
      96             : 
      97             : 
      98             : #endif
      99             : 
     100             : #include "libmesh/boundary_info.h"
     101             : #include "libmesh/dense_matrix.h"
     102             : #include "libmesh/dense_vector.h"
     103             : #include "libmesh/dof_map.h"
     104             : #include "libmesh/elem.h"
     105             : #include "libmesh/fe_base.h"
     106             : #include "libmesh/fe_interface.h"
     107             : #include "libmesh/generic_projector.h"
     108             : #include "libmesh/int_range.h"
     109             : #include "libmesh/libmesh_logging.h"
     110             : #include "libmesh/linear_solver.h"
     111             : #include "libmesh/mesh_base.h"
     112             : #include "libmesh/numeric_vector.h"
     113             : #include "libmesh/quadrature.h"
     114             : #include "libmesh/sparse_matrix.h"
     115             : #include "libmesh/system.h"
     116             : #include "libmesh/threads.h"
     117             : #include "libmesh/wrapped_function.h"
     118             : #include "libmesh/wrapped_functor.h"
     119             : #include "libmesh/fe_interface.h"
     120             : 
     121             : 
     122             : 
     123             : #ifdef LIBMESH_HAVE_METAPHYSICL
     124             : // Include MetaPhysicL definitions finally
     125             : #include "metaphysicl/dynamicsparsenumberarray.h"
     126             : 
     127             : // And make sure we instantiate the methods we'll need to use on them.
     128             : #include "libmesh/dense_matrix_impl.h"
     129             : 
     130             : namespace libMesh {
     131             : typedef DynamicSparseNumberArray<Real, dof_id_type> DSNAN;
     132             : 
     133             : template LIBMESH_EXPORT void
     134             : DenseMatrix<Real>::cholesky_solve(const DenseVector<DSNAN> &,
     135             :                                   DenseVector<DSNAN> &);
     136             : template LIBMESH_EXPORT void
     137             : DenseMatrix<Real>::_cholesky_back_substitute(const DenseVector<DSNAN> &,
     138             :                                              DenseVector<DSNAN> &) const;
     139             : }
     140             : #endif
     141             : 
     142             : 
     143             : 
     144             : namespace libMesh
     145             : {
     146             : 
     147             : // ------------------------------------------------------------
     148             : // Helper class definitions
     149             : 
     150             : #ifdef LIBMESH_ENABLE_AMR
     151             : 
     152             : /**
     153             :  * This class builds the send_list of old dof indices
     154             :  * whose coefficients are needed to perform a projection.
     155             :  * This may be executed in parallel on multiple threads.
     156             :  * The end result is a \p send_list vector which is
     157             :  * unsorted and may contain duplicate elements.
     158             :  * The \p unique() method can be used to sort and
     159             :  * create a unique list.
     160             :  */
     161             : 
     162       53094 : class BuildProjectionList
     163             : {
     164             : private:
     165             :   const System & system;
     166             : 
     167             : public:
     168        2000 :   BuildProjectionList (const System & system_in) :
     169       53072 :     system(system_in),
     170        4000 :     send_list()
     171        2000 :   {}
     172             : 
     173          66 :   BuildProjectionList (BuildProjectionList & other, Threads::split) :
     174          66 :     system(other.system),
     175          44 :     send_list()
     176          22 :   {}
     177             : 
     178             :   void unique();
     179             :   void operator()(const ConstElemRange & range);
     180             :   void join (const BuildProjectionList & other);
     181             :   std::vector<dof_id_type> send_list;
     182             : };
     183             : 
     184             : #endif // LIBMESH_ENABLE_AMR
     185             : 
     186             : 
     187             : /**
     188             :  * This class implements projecting an arbitrary
     189             :  * boundary function to the current mesh.  This
     190             :  * may be executed in parallel on multiple threads.
     191             :  */
     192          67 : class BoundaryProjectSolution
     193             : {
     194             : private:
     195             :   const std::set<boundary_id_type> & b;
     196             :   const std::vector<unsigned int>  & variables;
     197             :   const System                     & system;
     198             :   std::unique_ptr<FunctionBase<Number>>   f;
     199             :   std::unique_ptr<FunctionBase<Gradient>> g;
     200             :   const Parameters                 & parameters;
     201             :   NumericVector<Number>            & new_vector;
     202             : 
     203             : public:
     204          71 :   BoundaryProjectSolution (const std::set<boundary_id_type> & b_in,
     205             :                            const std::vector<unsigned int> & variables_in,
     206             :                            const System & system_in,
     207             :                            FunctionBase<Number> * f_in,
     208             :                            FunctionBase<Gradient> * g_in,
     209             :                            const Parameters & parameters_in,
     210          71 :                            NumericVector<Number> & new_v_in) :
     211          67 :     b(b_in),
     212          67 :     variables(variables_in),
     213          67 :     system(system_in),
     214          71 :     f(f_in ? f_in->clone() : std::unique_ptr<FunctionBase<Number>>()),
     215          67 :     g(g_in ? g_in->clone() : std::unique_ptr<FunctionBase<Gradient>>()),
     216          67 :     parameters(parameters_in),
     217          73 :     new_vector(new_v_in)
     218             :   {
     219           2 :     libmesh_assert(f.get());
     220          71 :     f->init();
     221          71 :     if (g.get())
     222           0 :       g->init();
     223          71 :   }
     224             : 
     225             :   BoundaryProjectSolution (const BoundaryProjectSolution & in) :
     226             :     b(in.b),
     227             :     variables(in.variables),
     228             :     system(in.system),
     229             :     f(in.f.get() ? in.f->clone() : std::unique_ptr<FunctionBase<Number>>()),
     230             :     g(in.g.get() ? in.g->clone() : std::unique_ptr<FunctionBase<Gradient>>()),
     231             :     parameters(in.parameters),
     232             :     new_vector(in.new_vector)
     233             :   {
     234             :     libmesh_assert(f.get());
     235             :     f->init();
     236             :     if (g.get())
     237             :       g->init();
     238             :   }
     239             : 
     240             :   void operator()(const ConstElemRange & range) const;
     241             : };
     242             : 
     243             : 
     244             : 
     245             : // ------------------------------------------------------------
     246             : // System implementation
     247       57807 : void System::project_vector (NumericVector<Number> & vector,
     248             :                              int is_adjoint,
     249             :                              std::optional<ConstElemRange> active_local_range,
     250             :                              std::optional<std::vector<unsigned int>> variable_numbers) const
     251             : {
     252             :   // Create a copy of the vector, which currently
     253             :   // contains the old data.
     254             :   std::unique_ptr<NumericVector<Number>>
     255       57807 :     old_vector (vector.clone());
     256             : 
     257             :   // Project the old vector to the new vector
     258      113614 :   this->project_vector (*old_vector, vector, is_adjoint, active_local_range, variable_numbers);
     259       57807 : }
     260             : 
     261             : 
     262             : /**
     263             :  * This method projects the vector
     264             :  * via L2 projections or nodal
     265             :  * interpolations on each element.
     266             :  */
     267       57807 : void System::project_vector (const NumericVector<Number> & old_v,
     268             :                              NumericVector<Number> & new_v,
     269             :                              int is_adjoint,
     270             :                              std::optional<ConstElemRange> active_local_range,
     271             :                              std::optional<std::vector<unsigned int>> variable_numbers) const
     272             : {
     273        4000 :   LOG_SCOPE ("project_vector(old,new)", "System");
     274             : 
     275             :   /**
     276             :    * This method projects a solution from an old mesh to a current, refined
     277             :    * mesh.  The input vector \p old_v gives the solution on the
     278             :    * old mesh, while the \p new_v gives the solution (to be computed)
     279             :    * on the new mesh.
     280             :    */
     281       57807 :   new_v.clear();
     282             : 
     283             : #ifdef LIBMESH_ENABLE_AMR
     284             : 
     285             :   // Resize the new vector and get a serial version.
     286        2000 :   NumericVector<Number> * new_vector_ptr = nullptr;
     287       55807 :   std::unique_ptr<NumericVector<Number>> new_vector_built;
     288             :   NumericVector<Number> * local_old_vector;
     289       55807 :   std::unique_ptr<NumericVector<Number>> local_old_vector_built;
     290        2000 :   const NumericVector<Number> * old_vector_ptr = nullptr;
     291             : 
     292       57807 :   if (!active_local_range)
     293             :     {
     294             :       active_local_range.emplace
     295      115614 :         (this->get_mesh().active_local_elements_begin(),
     296      119614 :          this->get_mesh().active_local_elements_end());
     297             :     }
     298             : 
     299             :   // If the old vector was uniprocessor, make the new
     300             :   // vector uniprocessor
     301       57807 :   if (old_v.type() == SERIAL)
     302             :     {
     303         735 :       new_v.init (this->n_dofs(), false, SERIAL);
     304           0 :       new_vector_ptr = &new_v;
     305           0 :       old_vector_ptr = &old_v;
     306             :     }
     307             : 
     308             :   // Otherwise it is a parallel, distributed vector, which
     309             :   // we need to localize.
     310       57072 :   else if (old_v.type() == PARALLEL)
     311             :     {
     312             :       // Build a send list for efficient localization
     313        1260 :       BuildProjectionList projection_list(*this);
     314       37264 :       Threads::parallel_reduce (active_local_range.value(),
     315             :                                 projection_list);
     316             : 
     317             :       // Create a sorted, unique send_list
     318       37264 :       projection_list.unique();
     319             : 
     320       37264 :       new_v.init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
     321       72008 :       new_vector_built = NumericVector<Number>::build(this->comm());
     322       72008 :       local_old_vector_built = NumericVector<Number>::build(this->comm());
     323        1260 :       new_vector_ptr = new_vector_built.get();
     324        1260 :       local_old_vector = local_old_vector_built.get();
     325       37264 :       new_vector_ptr->init(this->n_dofs(), this->n_local_dofs(),
     326        1260 :                            this->get_dof_map().get_send_list(), false,
     327        2520 :                            GHOSTED);
     328       37264 :       local_old_vector->init(old_v.size(), old_v.local_size(),
     329        2520 :                              projection_list.send_list, false, GHOSTED);
     330       37264 :       old_v.localize(*local_old_vector, projection_list.send_list);
     331       37264 :       local_old_vector->close();
     332        1260 :       old_vector_ptr = local_old_vector;
     333             :     }
     334       19808 :   else if (old_v.type() == GHOSTED)
     335             :     {
     336             :       // Build a send list for efficient localization
     337         740 :       BuildProjectionList projection_list(*this);
     338       19808 :       Threads::parallel_reduce (active_local_range.value(),
     339             :                                 projection_list);
     340             : 
     341             :       // Create a sorted, unique send_list
     342       19808 :       projection_list.unique();
     343             : 
     344       19808 :       new_v.init (this->n_dofs(), this->n_local_dofs(),
     345        1480 :                   this->get_dof_map().get_send_list(), false, GHOSTED);
     346             : 
     347       38136 :       local_old_vector_built = NumericVector<Number>::build(this->comm());
     348         740 :       new_vector_ptr = &new_v;
     349         740 :       local_old_vector = local_old_vector_built.get();
     350       19808 :       local_old_vector->init(old_v.size(), old_v.local_size(),
     351        1480 :                              projection_list.send_list, false, GHOSTED);
     352       19808 :       old_v.localize(*local_old_vector, projection_list.send_list);
     353       19808 :       local_old_vector->close();
     354         740 :       old_vector_ptr = local_old_vector;
     355             :     }
     356             :   else // unknown old_v.type()
     357           0 :     libmesh_error_msg("ERROR: Unknown old_v.type() == " << old_v.type());
     358             : 
     359             :   // Note that the above will have zeroed the new_vector.
     360             :   // Just to be sure, assert that new_vector_ptr and old_vector_ptr
     361             :   // were successfully set before trying to deref them.
     362        2000 :   libmesh_assert(new_vector_ptr);
     363        2000 :   libmesh_assert(old_vector_ptr);
     364             : 
     365        2000 :   NumericVector<Number> & new_vector = *new_vector_ptr;
     366        2000 :   const NumericVector<Number> & old_vector = *old_vector_ptr;
     367             : 
     368       57807 :   const unsigned int n_variables = this->n_vars();
     369             : 
     370       57807 :   if (n_variables)
     371             :     {
     372        3784 :       std::vector<unsigned int> vars;
     373       54025 :       if (variable_numbers)
     374             :         {
     375           0 :           vars = *variable_numbers;
     376           0 :           for (auto v : vars)
     377           0 :             if (v >= n_variables)
     378           0 :               libmesh_error_msg("ERROR: variable number " << v <<
     379             :                                " out of range for system with " <<
     380             :                                n_variables << " variables.");
     381             :         }
     382             :       else
     383             :         {
     384       54025 :           vars.resize(n_variables);
     385        1892 :           std::iota(vars.begin(), vars.end(), 0);
     386             :         }
     387             : 
     388        3784 :       std::vector<unsigned int> regular_vars, vector_vars, scalar_vars;
     389      118430 :       for (auto var : vars)
     390             :       {
     391       64405 :         if (this->variable(var).type().family == SCALAR)
     392         142 :           scalar_vars.push_back(var);
     393       64263 :         else if (FEInterface::field_type(this->variable_type(var)) == TYPE_SCALAR)
     394       59080 :           regular_vars.push_back(var);
     395             :         else
     396        5183 :           vector_vars.push_back(var);
     397             :       }
     398             : 
     399        1892 :       VectorSetAction<Number> setter(new_vector);
     400             : 
     401       54025 :       if (!regular_vars.empty())
     402             :         {
     403             :           // Use a typedef to make the calling sequence for parallel_for() a bit more readable
     404             :           typedef
     405             :             GenericProjector<OldSolutionValue<Number,   &FEMContext::point_value>,
     406             :                              OldSolutionValue<Gradient, &FEMContext::point_gradient>,
     407             :                              Number, VectorSetAction<Number>> FEMProjector;
     408             : 
     409             :           OldSolutionValue<Number,   &FEMContext::point_value>
     410        3492 :             f(*this, old_vector, &regular_vars);
     411             :           OldSolutionValue<Gradient, &FEMContext::point_gradient>
     412        3492 :             g(*this, old_vector, &regular_vars);
     413             : 
     414       52334 :           FEMProjector projector(*this, f, &g, setter, regular_vars);
     415       48842 :           projector.project(active_local_range.value());
     416       45350 :         }
     417             : 
     418       54025 :       if (!vector_vars.empty())
     419             :         {
     420             :           typedef
     421             :             GenericProjector<OldSolutionValue<Gradient,   &FEMContext::point_value>,
     422             :                              OldSolutionValue<Tensor, &FEMContext::point_gradient>,
     423             :                              Gradient, VectorSetAction<Number>> FEMVectorProjector;
     424             : 
     425         292 :           OldSolutionValue<Gradient, &FEMContext::point_value> f_vector(*this, old_vector, &vector_vars);
     426         292 :           OldSolutionValue<Tensor, &FEMContext::point_gradient> g_vector(*this, old_vector, &vector_vars);
     427             : 
     428        5475 :           FEMVectorProjector vector_projector(*this, f_vector, &g_vector, setter, vector_vars);
     429        5183 :           vector_projector.project(active_local_range.value());
     430        4891 :         }
     431             : 
     432             :       // Copy the SCALAR dofs from old_vector to new_vector
     433             :       // Note: We assume that all SCALAR dofs are on the
     434             :       // processor with highest ID
     435       55917 :       if (this->processor_id() == (this->n_processors()-1))
     436             :         {
     437         946 :           const DofMap & dof_map = this->get_dof_map();
     438        9403 :           for (auto var : scalar_vars)
     439             :             {
     440             :               // We can just map SCALAR dofs directly across
     441           4 :               std::vector<dof_id_type> new_SCALAR_indices, old_SCALAR_indices;
     442          24 :               dof_map.SCALAR_dof_indices (new_SCALAR_indices, var, false);
     443          24 :               dof_map.SCALAR_dof_indices (old_SCALAR_indices, var, true);
     444          48 :               for (auto i : index_range(new_SCALAR_indices))
     445          26 :                 new_vector.set(new_SCALAR_indices[i], old_vector(old_SCALAR_indices[i]));
     446             :             }
     447             :         }
     448             :     }
     449             : 
     450       57807 :   new_vector.close();
     451             : 
     452             :   // If the old vector was serial, we probably need to send our values
     453             :   // to other processors
     454             :   //
     455             :   // FIXME: I'm not sure how to make a NumericVector do that without
     456             :   // creating a temporary parallel vector to use localize! - RHS
     457       57807 :   if (old_v.type() == SERIAL)
     458             :     {
     459         735 :       std::unique_ptr<NumericVector<Number>> dist_v = NumericVector<Number>::build(this->comm());
     460         735 :       dist_v->init(this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
     461         735 :       dist_v->close();
     462             : 
     463      625585 :       for (auto i : make_range(dist_v->size()))
     464      624115 :         if (new_vector(i) != 0.0)
     465      489129 :           dist_v->set(i, new_vector(i));
     466             : 
     467         735 :       dist_v->close();
     468             : 
     469         735 :       dist_v->localize (new_v, this->get_dof_map().get_send_list());
     470         735 :       new_v.close();
     471         735 :     }
     472             :   // If the old vector was parallel, we need to update it
     473             :   // and free the localized copies
     474       57072 :   else if (old_v.type() == PARALLEL)
     475             :     {
     476             :       // We may have to set dof values that this processor doesn't
     477             :       // own in certain special cases, like LAGRANGE FIRST or
     478             :       // HERMITE THIRD elements on second-order meshes?
     479       37264 :       new_v = new_vector;
     480       37264 :       new_v.close();
     481             :     }
     482             : 
     483             : 
     484             :   // Apply constraints only if we we are asked to
     485       57807 :   if(this->project_with_constraints)
     486             :   {
     487       53973 :     if (is_adjoint == -1)
     488             :     {
     489       50788 :       this->get_dof_map().enforce_constraints_exactly(*this, &new_v);
     490             :     }
     491        3185 :     else if (is_adjoint >= 0)
     492             :     {
     493        3185 :       this->get_dof_map().enforce_adjoint_constraints_exactly(new_v,
     494             :                                                             is_adjoint);
     495             :     }
     496             :   }
     497             : #else
     498             : 
     499             :   // AMR is disabled: simply copy the vector
     500             :   new_v = old_v;
     501             : 
     502             :   libmesh_ignore(is_adjoint, active_local_range, variable_numbers);
     503             : 
     504             : #endif // #ifdef LIBMESH_ENABLE_AMR
     505       57807 : }
     506             : 
     507             : 
     508             : #ifdef LIBMESH_ENABLE_AMR
     509             : #ifdef LIBMESH_HAVE_METAPHYSICL
     510             : 
     511             : template <typename Output>
     512             : class DSNAOutput
     513             : {
     514             : public:
     515             :   typedef DynamicSparseNumberArray<Output, dof_id_type> type;
     516             : };
     517             : 
     518             : template <typename InnerOutput>
     519             : class DSNAOutput<VectorValue<InnerOutput> >
     520             : {
     521             : public:
     522             :   typedef VectorValue<DynamicSparseNumberArray<InnerOutput, dof_id_type> > type;
     523             : };
     524             : 
     525             : /**
     526             :  * The OldSolutionCoefs input functor class can be used with
     527             :  * GenericProjector to read solution transfer coefficients on a
     528             :  * just-refined-and-coarsened mesh.
     529             :  *
     530             :  * \author Roy H. Stogner
     531             :  * \date 2017
     532             :  */
     533             : 
     534             : template <typename Output,
     535             :           void (FEMContext::*point_output) (unsigned int,
     536             :                                             const Point &,
     537             :                                             Output &,
     538             :                                             const Real) const>
     539        1206 : class OldSolutionCoefs : public OldSolutionBase<Output, point_output>
     540             : {
     541             : public:
     542             :   typedef typename DSNAOutput<Output>::type DSNA;
     543             :   typedef DSNA ValuePushType;
     544             :   typedef DSNA FunctorValue;
     545             : 
     546        1260 :   OldSolutionCoefs(const libMesh::System & sys_in,
     547             :                    const std::vector<unsigned int> * vars) :
     548         648 :     OldSolutionBase<Output, point_output>(sys_in, vars)
     549             :   {
     550          36 :     this->old_context.set_algebraic_type(FEMContext::OLD_DOFS_ONLY);
     551          36 :   }
     552             : 
     553        3150 :   OldSolutionCoefs(const OldSolutionCoefs & in) :
     554        3240 :     OldSolutionBase<Output, point_output>(in.sys, in.old_context.active_vars())
     555             :   {
     556          90 :     this->old_context.set_algebraic_type(FEMContext::OLD_DOFS_ONLY);
     557          90 :   }
     558             : 
     559             :   DSNA eval_at_node (const FEMContext & c,
     560             :                      unsigned int i,
     561             :                      unsigned int elem_dim,
     562             :                      const Node & n,
     563             :                      bool extra_hanging_dofs,
     564             :                      Real /* time */ = 0.);
     565             : 
     566             :   DSNA eval_at_point(const FEMContext & c,
     567             :                      unsigned int i,
     568             :                      const Point & p,
     569             :                      Real time,
     570             :                      bool skip_context_check);
     571             : 
     572           0 :   void eval_mixed_derivatives (const FEMContext & libmesh_dbg_var(c),
     573             :                                unsigned int i,
     574             :                                unsigned int dim,
     575             :                                const Node & n,
     576             :                                std::vector<DSNA> & derivs)
     577             :   {
     578           0 :     LOG_SCOPE ("eval_mixed_derivatives", "OldSolutionCoefs");
     579             : 
     580             :     // This should only be called on vertices
     581           0 :     libmesh_assert_less(c.get_elem().get_node_index(&n),
     582             :                         c.get_elem().n_vertices());
     583             : 
     584             :     // Handle offset from non-scalar components in previous variables
     585           0 :     libmesh_assert_less(i, this->component_to_var.size());
     586           0 :     unsigned int var = this->component_to_var[i];
     587             : 
     588             :     // We have 1 mixed derivative in 2D, 4 in 3D
     589           0 :     const unsigned int n_mixed = (dim-1) * (dim-1);
     590           0 :     derivs.resize(n_mixed);
     591             : 
     592             :     // Be sure to handle cases where the variable wasn't defined on
     593             :     // this node (e.g. due to changing subdomain support)
     594           0 :     const DofObject * old_dof_object = n.get_old_dof_object();
     595           0 :     if (old_dof_object &&
     596           0 :         old_dof_object->n_vars(this->sys.number()) &&
     597           0 :         old_dof_object->n_comp(this->sys.number(), var))
     598             :       {
     599           0 :         const dof_id_type first_old_id =
     600           0 :           old_dof_object->dof_number(this->sys.number(), var, dim);
     601           0 :         std::vector<dof_id_type> old_ids(n_mixed);
     602           0 :         std::iota(old_ids.begin(), old_ids.end(), first_old_id);
     603             : 
     604           0 :         for (auto d_i : index_range(derivs))
     605             :           {
     606           0 :             derivs[d_i].resize(1);
     607           0 :             derivs[d_i].raw_at(0) = 1;
     608           0 :             derivs[d_i].raw_index(0) = old_ids[d_i];
     609             :           }
     610             :       }
     611             :     else
     612             :       {
     613           0 :         std::fill(derivs.begin(), derivs.end(), 0);
     614             :       }
     615           0 :   }
     616             : 
     617             : 
     618           0 :   void eval_old_dofs (const Elem & elem,
     619             :                       unsigned int node_num,
     620             :                       unsigned int var_num,
     621             :                       std::vector<dof_id_type> & indices,
     622             :                       std::vector<DSNA> & values)
     623             :   {
     624           0 :     LOG_SCOPE ("eval_old_dofs(node)", "OldSolutionCoefs");
     625             : 
     626             :     // We may be reusing a std::vector here, but the following
     627             :     // dof_indices call appends without first clearing.
     628           0 :     indices.clear();
     629             : 
     630           0 :     this->sys.get_dof_map().dof_indices(elem, node_num, indices, var_num);
     631             : 
     632           0 :     std::vector<dof_id_type> old_indices;
     633             : 
     634           0 :     this->sys.get_dof_map().old_dof_indices(elem, node_num, old_indices, var_num);
     635             : 
     636           0 :     libmesh_assert_equal_to (old_indices.size(), indices.size());
     637             : 
     638           0 :     values.resize(old_indices.size());
     639             : 
     640           0 :     for (auto i : index_range(values))
     641             :       {
     642           0 :         values[i].resize(1);
     643           0 :         values[i].raw_at(0) = 1;
     644           0 :         values[i].raw_index(0) = old_indices[i];
     645             :       }
     646           0 :   }
     647             : 
     648             : 
     649           0 :   void eval_old_dofs (const Elem & elem,
     650             :                       const FEType & fe_type,
     651             :                       unsigned int sys_num,
     652             :                       unsigned int var_num,
     653             :                       std::vector<dof_id_type> & indices,
     654             :                       std::vector<DSNA> & values)
     655             :   {
     656           0 :     LOG_SCOPE ("eval_old_dofs(elem)", "OldSolutionCoefs");
     657             : 
     658             :     // We're only to be asked for old dofs on elements that can copy
     659             :     // them through DO_NOTHING or through refinement.
     660           0 :     const Elem & old_elem =
     661           0 :       (elem.refinement_flag() == Elem::JUST_REFINED) ?
     662           0 :       *elem.parent() : elem;
     663             : 
     664             :     // If there are any element-based DOF numbers, get them
     665           0 :     const unsigned int nc =
     666           0 :       FEInterface::n_dofs_per_elem(fe_type, &elem);
     667             : 
     668           0 :     std::vector<dof_id_type> old_dof_indices(nc);
     669           0 :     indices.resize(nc);
     670             : 
     671             :     // We should never have fewer dofs than necessary on an
     672             :     // element unless we're getting indices on a parent element,
     673             :     // and we should never need those indices
     674           0 :     if (nc != 0)
     675             :       {
     676           0 :         const DofObject & old_dof_object = old_elem.get_old_dof_object_ref();
     677             : 
     678           0 :         const auto [vg, vig] =
     679           0 :           elem.var_to_vg_and_offset(sys_num,var_num);
     680             : 
     681           0 :         const unsigned int n_comp = elem.n_comp_group(sys_num,vg);
     682           0 :         libmesh_assert_greater(elem.n_systems(), sys_num);
     683           0 :         libmesh_assert_greater_equal(n_comp, nc);
     684             : 
     685           0 :         for (unsigned int i=0; i<nc; i++)
     686             :           {
     687           0 :             const dof_id_type d_old =
     688           0 :               old_dof_object.dof_number(sys_num, vg, vig, i, n_comp);
     689           0 :             const dof_id_type d_new =
     690           0 :               elem.dof_number(sys_num, vg, vig, i, n_comp);
     691           0 :             libmesh_assert_not_equal_to (d_old, DofObject::invalid_id);
     692           0 :             libmesh_assert_not_equal_to (d_new, DofObject::invalid_id);
     693             : 
     694           0 :             old_dof_indices[i] = d_old;
     695           0 :             indices[i] = d_new;
     696             :           }
     697             :       }
     698             : 
     699           0 :     values.resize(old_dof_indices.size());
     700             : 
     701           0 :     for (auto i : index_range(values))
     702             :       {
     703           0 :         values[i].resize(1);
     704           0 :         values[i].raw_at(0) = 1;
     705           0 :         values[i].raw_index(0) = old_dof_indices[i];
     706             :       }
     707           0 :   }
     708             : };
     709             : 
     710             : 
     711             : 
     712             : template<>
     713             : inline
     714             : DynamicSparseNumberArray<Real, dof_id_type>
     715      119439 : OldSolutionCoefs<Real, &FEMContext::point_value>::
     716             : eval_at_point(const FEMContext & c,
     717             :               unsigned int i,
     718             :               const Point & p,
     719             :               Real /* time */,
     720             :               bool skip_context_check)
     721             : {
     722       20704 :   LOG_SCOPE ("eval_at_point()", "OldSolutionCoefs");
     723             : 
     724      119439 :   if (!skip_context_check)
     725      119439 :     if (!this->check_old_context(c, p))
     726           0 :       return 0;
     727             : 
     728             :   // Get finite element object
     729       10352 :   FEGenericBase<Real> * fe = nullptr;
     730             :   this->old_context.get_element_fe<Real>
     731       10352 :     (i, fe, this->old_context.get_elem_dim());
     732             : 
     733             :   // Build a FE for calculating phi(p)
     734             :   FEGenericBase<Real> * fe_new =
     735      119439 :     this->old_context.build_new_fe(fe, p);
     736             : 
     737             :   // Get the values and global indices of the shape functions
     738       10352 :   const std::vector<std::vector<Real> > & phi = fe_new->get_phi();
     739             :   const std::vector<dof_id_type> & dof_indices =
     740       10352 :     this->old_context.get_dof_indices(i);
     741             : 
     742       20704 :   const std::size_t n_dofs = phi.size();
     743       10352 :   libmesh_assert_equal_to(n_dofs, dof_indices.size());
     744             : 
     745       20704 :   DynamicSparseNumberArray<Real, dof_id_type> returnval;
     746      109087 :   returnval.resize(n_dofs);
     747             : 
     748     1116532 :   for (auto j : index_range(phi))
     749             :     {
     750      997093 :       returnval.raw_at(j) = phi[j][0];
     751     1170881 :       returnval.raw_index(j) = dof_indices[j];
     752             :     }
     753             : 
     754       10352 :   return returnval;
     755             : }
     756             : 
     757             : 
     758             : 
     759             : template<>
     760             : inline
     761             : VectorValue<DynamicSparseNumberArray<Real, dof_id_type> >
     762           0 : OldSolutionCoefs<RealGradient, &FEMContext::point_gradient>::
     763             : eval_at_point(const FEMContext & c,
     764             :               unsigned int i,
     765             :               const Point & p,
     766             :               Real /* time */,
     767             :               bool skip_context_check)
     768             : {
     769           0 :   LOG_SCOPE ("eval_at_point()", "OldSolutionCoefs");
     770             : 
     771           0 :   if (!skip_context_check)
     772           0 :     if (!this->check_old_context(c, p))
     773           0 :       return 0;
     774             : 
     775             :   // Get finite element object
     776           0 :   FEGenericBase<Real> * fe = nullptr;
     777             :   this->old_context.get_element_fe<Real>
     778           0 :     (i, fe, this->old_context.get_elem_dim());
     779             : 
     780             :   // Build a FE for calculating phi(p)
     781             :   FEGenericBase<Real> * fe_new =
     782           0 :     this->old_context.build_new_fe(fe, p);
     783             : 
     784             :   // Get the values and global indices of the shape functions
     785           0 :   const std::vector<std::vector<RealGradient> > & dphi = fe_new->get_dphi();
     786             :   const std::vector<dof_id_type> & dof_indices =
     787           0 :     this->old_context.get_dof_indices(i);
     788             : 
     789           0 :   const std::size_t n_dofs = dphi.size();
     790           0 :   libmesh_assert_equal_to(n_dofs, dof_indices.size());
     791             : 
     792           0 :   VectorValue<DynamicSparseNumberArray<Real, dof_id_type> > returnval;
     793             : 
     794           0 :   for (unsigned int d = 0; d != LIBMESH_DIM; ++d)
     795           0 :     returnval(d).resize(n_dofs);
     796             : 
     797           0 :   for (auto j : index_range(dphi))
     798           0 :     for (int d = 0; d != LIBMESH_DIM; ++d)
     799             :       {
     800           0 :         returnval(d).raw_at(j) = dphi[j][0](d);
     801           0 :         returnval(d).raw_index(j) = dof_indices[j];
     802             :       }
     803             : 
     804           0 :   return returnval;
     805             : }
     806             : 
     807             : 
     808             : template<>
     809             : inline
     810             : DynamicSparseNumberArray<Real, dof_id_type>
     811       62759 : OldSolutionCoefs<Real, &FEMContext::point_value>::
     812             : eval_at_node(const FEMContext & c,
     813             :              unsigned int i,
     814             :              unsigned int /* elem_dim */,
     815             :              const Node & n,
     816             :              bool extra_hanging_dofs,
     817             :              Real /* time */)
     818             : {
     819       10206 :   LOG_SCOPE ("Real eval_at_node()", "OldSolutionCoefs");
     820             : 
     821             :   // Optimize for the common case, where this node was part of the
     822             :   // old solution.
     823             :   //
     824             :   // Be sure to handle cases where the variable wasn't defined on
     825             :   // this node (due to changing subdomain support) or where the
     826             :   // variable has no components on this node (due to Elem order
     827             :   // exceeding FE order) or where the old_dof_object dofs might
     828             :   // correspond to non-vertex dofs (due to extra_hanging_dofs and
     829             :   // refinement)
     830             : 
     831       10206 :   const Elem::RefinementState flag = c.get_elem().refinement_flag();
     832             : 
     833        5103 :   const DofObject * old_dof_object = n.get_old_dof_object();
     834       66707 :   if (old_dof_object &&
     835       66639 :       (!extra_hanging_dofs ||
     836       56569 :        flag == Elem::JUST_COARSENED ||
     837       61604 :        flag == Elem::DO_NOTHING) &&
     838      185967 :       old_dof_object->n_vars(sys.number()) &&
     839       61604 :       old_dof_object->n_comp(sys.number(), i))
     840             :     {
     841        7658 :       DynamicSparseNumberArray<Real, dof_id_type> returnval;
     842             :       const dof_id_type old_id =
     843       46874 :         old_dof_object->dof_number(sys.number(), i, 0);
     844       43045 :       returnval.resize(1);
     845       46874 :       returnval.raw_at(0) = 1;
     846       46874 :       returnval.raw_index(0) = old_id;
     847        3829 :       return returnval;
     848             :     }
     849             : 
     850       15885 :   return this->eval_at_point(c, i, n, 0, false);
     851             : }
     852             : 
     853             : 
     854             : 
     855             : template<>
     856             : inline
     857             : VectorValue<DynamicSparseNumberArray<Real, dof_id_type> >
     858           0 : OldSolutionCoefs<RealGradient, &FEMContext::point_gradient>::
     859             : eval_at_node(const FEMContext & c,
     860             :              unsigned int i,
     861             :              unsigned int elem_dim,
     862             :              const Node & n,
     863             :              bool extra_hanging_dofs,
     864             :              Real /* time */)
     865             : {
     866           0 :   LOG_SCOPE ("RealGradient eval_at_node()", "OldSolutionCoefs");
     867             : 
     868             :   // Optimize for the common case, where this node was part of the
     869             :   // old solution.
     870             :   //
     871             :   // Be sure to handle cases where the variable wasn't defined on
     872             :   // this node (due to changing subdomain support) or where the
     873             :   // variable has no components on this node (due to Elem order
     874             :   // exceeding FE order) or where the old_dof_object dofs might
     875             :   // correspond to non-vertex dofs (due to extra_hanging_dofs and
     876             :   // refinement)
     877             : 
     878           0 :   const Elem::RefinementState flag = c.get_elem().refinement_flag();
     879             : 
     880           0 :   const DofObject * old_dof_object = n.get_old_dof_object();
     881           0 :   if (old_dof_object &&
     882           0 :       (!extra_hanging_dofs ||
     883           0 :        flag == Elem::JUST_COARSENED ||
     884           0 :        flag == Elem::DO_NOTHING) &&
     885           0 :       old_dof_object->n_vars(sys.number()) &&
     886           0 :       old_dof_object->n_comp(sys.number(), i))
     887             :     {
     888           0 :       VectorValue<DynamicSparseNumberArray<Real, dof_id_type> > g;
     889           0 :       for (unsigned int d = 0; d != elem_dim; ++d)
     890             :         {
     891             :           const dof_id_type old_id =
     892           0 :             old_dof_object->dof_number(sys.number(), i, d+1);
     893           0 :           g(d).resize(1);
     894           0 :           g(d).raw_at(0) = 1;
     895           0 :           g(d).raw_index(0) = old_id;
     896             :         }
     897           0 :       return g;
     898             :     }
     899             : 
     900           0 :   return this->eval_at_point(c, i, n, 0, false);
     901             : }
     902             : 
     903             : 
     904             : 
     905             : /**
     906             :  * The MatrixFillAction output functor class can be used with
     907             :  * GenericProjector to write solution transfer coefficients into a
     908             :  * sparse matrix.
     909             :  *
     910             :  * \author Roy H. Stogner
     911             :  * \date 2017
     912             :  */
     913             : template <typename ValIn, typename ValOut>
     914             : class MatrixFillAction
     915             : {
     916             : public:
     917             :   typedef DynamicSparseNumberArray<ValIn, dof_id_type> InsertInput;
     918             : private:
     919             :   SparseMatrix<ValOut> & target_matrix;
     920             : 
     921             : public:
     922         630 :   MatrixFillAction(SparseMatrix<ValOut> & target_mat) :
     923         630 :     target_matrix(target_mat) {}
     924             : 
     925      166313 :   void insert(dof_id_type id,
     926             :               const DynamicSparseNumberArray<ValIn, dof_id_type> & val)
     927             :   {
     928             :     // Lock the target matrix since it is shared among threads.
     929             :     {
     930       28362 :       Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
     931             : 
     932       14181 :       const std::size_t dnsa_size = val.size();
     933     1210280 :       for (unsigned int j = 0; j != dnsa_size; ++j)
     934             :         {
     935     1043967 :           const dof_id_type dof_j = val.raw_index(j);
     936     1043967 :           const ValIn dof_val = val.raw_at(j);
     937     1043967 :           target_matrix.set(id, dof_j, dof_val);
     938             :         }
     939             :     }
     940      166313 :   }
     941             : 
     942             : 
     943           0 :   void insert(const std::vector<dof_id_type> & dof_indices,
     944             :               const std::vector<DynamicSparseNumberArray<ValIn, dof_id_type> > & Ue)
     945             :   {
     946             :     const numeric_index_type
     947           0 :       begin_dof = target_matrix.row_start(),
     948           0 :       end_dof = target_matrix.row_stop();
     949             : 
     950           0 :     unsigned int size = Ue.size();
     951             : 
     952           0 :     libmesh_assert_equal_to(size, dof_indices.size());
     953             : 
     954             :     // Lock the target matrix since it is shared among threads.
     955             :     {
     956           0 :       Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
     957             : 
     958           0 :       for (unsigned int i = 0; i != size; ++i)
     959             :         {
     960           0 :           const dof_id_type dof_i = dof_indices[i];
     961           0 :           if ((dof_i >= begin_dof) && (dof_i < end_dof))
     962             :             {
     963           0 :               const DynamicSparseNumberArray<ValIn,dof_id_type> & dnsa = Ue[i];
     964           0 :               const std::size_t dnsa_size = dnsa.size();
     965           0 :               for (unsigned int j = 0; j != dnsa_size; ++j)
     966             :                 {
     967           0 :                   const dof_id_type dof_j = dnsa.raw_index(j);
     968           0 :                   const ValIn dof_val = dnsa.raw_at(j);
     969           0 :                   target_matrix.set(dof_i, dof_j, dof_val);
     970             :                 }
     971             :             }
     972             :         }
     973             :     }
     974           0 :   }
     975             : };
     976             : 
     977             : 
     978             : 
     979             : /**
     980             :  * This method creates a projection matrix which corresponds to the
     981             :  * operation of project_vector between old and new solution spaces.
     982             :  */
     983         630 : void System::projection_matrix (SparseMatrix<Number> & proj_mat) const
     984             : {
     985          36 :   LOG_SCOPE ("projection_matrix()", "System");
     986             : 
     987         630 :   const unsigned int n_variables = this->n_vars();
     988             : 
     989         630 :   if (n_variables)
     990             :     {
     991             :       ConstElemRange active_local_elem_range
     992        1260 :         (this->get_mesh().active_local_elements_begin(),
     993        1278 :          this->get_mesh().active_local_elements_end());
     994             : 
     995         648 :       std::vector<unsigned int> vars(n_variables);
     996          18 :       std::iota(vars.begin(), vars.end(), 0);
     997             : 
     998             :       // Use a typedef to make the calling sequence for parallel_for() a bit more readable
     999             :       typedef OldSolutionCoefs<Real, &FEMContext::point_value> OldSolutionValueCoefs;
    1000             :       typedef OldSolutionCoefs<RealGradient, &FEMContext::point_gradient> OldSolutionGradientCoefs;
    1001             : 
    1002             :       typedef
    1003             :         GenericProjector<OldSolutionValueCoefs,
    1004             :                          OldSolutionGradientCoefs,
    1005             :                          DynamicSparseNumberArray<Real,dof_id_type>,
    1006             :                          MatrixFillAction<Real, Number> > ProjMatFiller;
    1007             : 
    1008          36 :       OldSolutionValueCoefs    f(*this, &vars);
    1009          36 :       OldSolutionGradientCoefs g(*this, &vars);
    1010          18 :       MatrixFillAction<Real, Number> setter(proj_mat);
    1011             : 
    1012         666 :       ProjMatFiller mat_filler(*this, f, &g, setter, vars);
    1013         630 :       mat_filler.project(active_local_elem_range);
    1014             : 
    1015             :       // Set the SCALAR dof transfer entries too.
    1016             :       // Note: We assume that all SCALAR dofs are on the
    1017             :       // processor with highest ID
    1018         648 :       if (this->processor_id() == (this->n_processors()-1))
    1019             :         {
    1020           9 :           const DofMap & dof_map = this->get_dof_map();
    1021         376 :           for (auto var : make_range(this->n_vars()))
    1022         187 :             if (this->variable(var).type().family == SCALAR)
    1023             :               {
    1024             :                 // We can just map SCALAR dofs directly across
    1025           0 :                 std::vector<dof_id_type> new_SCALAR_indices, old_SCALAR_indices;
    1026           0 :                 dof_map.SCALAR_dof_indices (new_SCALAR_indices, var, false);
    1027           0 :                 dof_map.SCALAR_dof_indices (old_SCALAR_indices, var, true);
    1028             :                 const unsigned int new_n_dofs =
    1029           0 :                   cast_int<unsigned int>(new_SCALAR_indices.size());
    1030             : 
    1031           0 :                 for (unsigned int i=0; i<new_n_dofs; i++)
    1032             :                   {
    1033           0 :                     proj_mat.set( new_SCALAR_indices[i],
    1034           0 :                                   old_SCALAR_indices[i], 1);
    1035             :                   }
    1036             :               }
    1037             :         }
    1038         594 :     }
    1039         630 : }
    1040             : #endif // LIBMESH_HAVE_METAPHYSICL
    1041             : #endif // LIBMESH_ENABLE_AMR
    1042             : 
    1043             : 
    1044             : 
    1045             : /**
    1046             :  * This method projects an arbitrary function onto the solution via L2
    1047             :  * projections and nodal interpolations on each element.
    1048             :  */
    1049      199154 : void System::project_solution (ValueFunctionPointer fptr,
    1050             :                                GradientFunctionPointer gptr,
    1051             :                                const Parameters & function_parameters,
    1052             :                                std::optional<ConstElemRange> active_local_range,
    1053             :                                std::optional<std::vector<unsigned int>> variable_numbers) const
    1054             : {
    1055       11220 :   WrappedFunction<Number> f(*this, fptr, &function_parameters);
    1056        5610 :   WrappedFunction<Gradient> g(*this, gptr, &function_parameters);
    1057      392698 :   this->project_solution(&f, &g, active_local_range, variable_numbers);
    1058      199154 : }
    1059             : 
    1060             : 
    1061             : /**
    1062             :  * This method projects an arbitrary function onto the solution via L2
    1063             :  * projections and nodal interpolations on each element.
    1064             :  */
    1065      199727 : void System::project_solution (FunctionBase<Number> * f,
    1066             :                                FunctionBase<Gradient> * g,
    1067             :                                std::optional<ConstElemRange> active_local_range,
    1068             :                                std::optional<std::vector<unsigned int>> variable_numbers) const
    1069             : {
    1070      393895 :   this->project_vector(*solution, f, g, /*is_adjoint=*/-1, active_local_range, variable_numbers);
    1071             : 
    1072      205355 :   solution->localize(*current_local_solution, _dof_map->get_send_list());
    1073      199727 : }
    1074             : 
    1075             : 
    1076             : /**
    1077             :  * This method projects an arbitrary function onto the solution via L2
    1078             :  * projections and nodal interpolations on each element.
    1079             :  */
    1080         213 : void System::project_solution (FEMFunctionBase<Number> * f,
    1081             :                                FEMFunctionBase<Gradient> * g,
    1082             :                                std::optional<ConstElemRange> active_local_range,
    1083             :                                std::optional<std::vector<unsigned int>> variable_numbers) const
    1084             : {
    1085         420 :   this->project_vector(*solution, f, g, /*is_adjoint=*/-1, active_local_range, variable_numbers);
    1086             : 
    1087         219 :   solution->localize(*current_local_solution, _dof_map->get_send_list());
    1088         213 : }
    1089             : 
    1090             : 
    1091             : /**
    1092             :  * This method projects an arbitrary function via L2 projections and
    1093             :  * nodal interpolations on each element.
    1094             :  */
    1095         840 : void System::project_vector (ValueFunctionPointer fptr,
    1096             :                              GradientFunctionPointer gptr,
    1097             :                              const Parameters & function_parameters,
    1098             :                              NumericVector<Number> & new_vector,
    1099             :                              int is_adjoint,
    1100             :                              std::optional<ConstElemRange> active_local_range,
    1101             :                              std::optional<std::vector<unsigned int>> variable_numbers) const
    1102             : {
    1103          48 :   WrappedFunction<Number> f(*this, fptr, &function_parameters);
    1104          24 :   WrappedFunction<Gradient> g(*this, gptr, &function_parameters);
    1105        1656 :   this->project_vector(new_vector, &f, &g, is_adjoint, active_local_range, variable_numbers);
    1106         840 : }
    1107             : 
    1108             : /**
    1109             :  * This method projects an arbitrary function via L2 projections and
    1110             :  * nodal interpolations on each element.
    1111             :  */
    1112      201277 : void System::project_vector (NumericVector<Number> & new_vector,
    1113             :                              FunctionBase<Number> * f,
    1114             :                              FunctionBase<Gradient> * g,
    1115             :                              int is_adjoint,
    1116             :                              std::optional<ConstElemRange> active_local_range,
    1117             :                              std::optional<std::vector<unsigned int>> variable_numbers) const
    1118             : {
    1119       11344 :   LOG_SCOPE ("project_vector(FunctionBase)", "System");
    1120             : 
    1121        5672 :   libmesh_assert(f);
    1122             : 
    1123       11344 :   WrappedFunctor<Number> f_fem(*f);
    1124             : 
    1125      201277 :   if (g)
    1126             :     {
    1127        5634 :       WrappedFunctor<Gradient> g_fem(*g);
    1128             : 
    1129      394423 :       this->project_vector(new_vector, &f_fem, &g_fem, is_adjoint, active_local_range, variable_numbers);
    1130             :     }
    1131             :   else
    1132        2528 :     this->project_vector(new_vector, &f_fem, nullptr, is_adjoint, active_local_range, variable_numbers);
    1133      201277 : }
    1134             : 
    1135             : 
    1136             : /**
    1137             :  * This method projects an arbitrary function via L2 projections and
    1138             :  * nodal interpolations on each element.
    1139             :  */
    1140      201490 : void System::project_vector (NumericVector<Number> & new_vector,
    1141             :                              FEMFunctionBase<Number> * f,
    1142             :                              FEMFunctionBase<Gradient> * g,
    1143             :                              int is_adjoint,
    1144             :                              std::optional<ConstElemRange> active_local_range,
    1145             :                              std::optional<std::vector<unsigned int>> variable_numbers) const
    1146             : {
    1147       11356 :   LOG_SCOPE ("project_fem_vector()", "System");
    1148             : 
    1149        5678 :   libmesh_assert (f);
    1150             : 
    1151      201490 :   if (!active_local_range)
    1152             :     {
    1153             :       active_local_range.emplace
    1154      402838 :         (this->get_mesh().active_local_elements_begin(),
    1155      414190 :          this->get_mesh().active_local_elements_end());
    1156             :     }
    1157             : 
    1158        5678 :   VectorSetAction<Number> setter(new_vector);
    1159             : 
    1160      201490 :   const unsigned int n_variables = this->n_vars();
    1161             : 
    1162       11356 :   std::vector<unsigned int> vars;
    1163      201490 :   if (variable_numbers)
    1164             :     {
    1165          71 :       vars = *variable_numbers;
    1166         142 :       for (auto v : vars)
    1167          71 :         if (v >= n_variables)
    1168           0 :           libmesh_error_msg("ERROR: variable number " << v <<
    1169             :                            " out of range for system with " <<
    1170             :                            n_variables << " variables.");
    1171             :     }
    1172             :   else
    1173             :     {
    1174      201419 :       vars.resize(n_variables);
    1175        5676 :       std::iota(vars.begin(), vars.end(), 0);
    1176             :     }
    1177             : 
    1178             : 
    1179             :   // Use a typedef to make the calling sequence for parallel_for() a bit more readable
    1180             :   typedef
    1181             :     GenericProjector<FEMFunctionWrapper<Number>, FEMFunctionWrapper<Gradient>,
    1182             :                      Number, VectorSetAction<Number>> FEMProjector;
    1183             : 
    1184       11356 :   FEMFunctionWrapper<Number> fw(*f);
    1185             : 
    1186      201490 :   if (g)
    1187             :     {
    1188       11268 :       FEMFunctionWrapper<Gradient> gw(*g);
    1189             : 
    1190      211262 :       FEMProjector projector(*this, fw, &gw, setter, vars);
    1191      199994 :       projector.project(active_local_range.value());
    1192      188726 :     }
    1193             :   else
    1194             :     {
    1195        1584 :       FEMProjector projector(*this, fw, nullptr, setter, vars);
    1196        1496 :       projector.project(active_local_range.value());
    1197        1408 :     }
    1198             : 
    1199             :   // Also, load values into the SCALAR dofs
    1200             :   // Note: We assume that all SCALAR dofs are on the
    1201             :   // processor with highest ID
    1202      207168 :   if (this->processor_id() == (this->n_processors()-1))
    1203             :     {
    1204             :       // FIXME: Do we want to first check for SCALAR vars before building this? [PB]
    1205       39716 :       FEMContext context( *this );
    1206             : 
    1207        2839 :       const DofMap & dof_map = this->get_dof_map();
    1208       68760 :       for (auto var : vars)
    1209       34722 :         if (this->variable(var).type().family == SCALAR)
    1210             :           {
    1211             :             // FIXME: We reinit with an arbitrary element in case the user
    1212             :             //        doesn't override FEMFunctionBase::component. Is there
    1213             :             //        any use case we're missing? [PB]
    1214           0 :             context.pre_fe_reinit(*this, *(this->get_mesh().active_local_elements_begin()));
    1215             : 
    1216           0 :             std::vector<dof_id_type> SCALAR_indices;
    1217           0 :             dof_map.SCALAR_dof_indices (SCALAR_indices, var);
    1218             :             const unsigned int n_SCALAR_dofs =
    1219           0 :               cast_int<unsigned int>(SCALAR_indices.size());
    1220             : 
    1221           0 :             for (unsigned int i=0; i<n_SCALAR_dofs; i++)
    1222             :               {
    1223           0 :                 const dof_id_type global_index = SCALAR_indices[i];
    1224             :                 const unsigned int component_index =
    1225           0 :                   this->variable_scalar_number(var,i);
    1226             : 
    1227           0 :                 new_vector.set(global_index, f->component(context, component_index, Point(), this->time));
    1228             :               }
    1229             :           }
    1230       28360 :     }
    1231             : 
    1232      201490 :   new_vector.close();
    1233             : 
    1234             :   // Look for spline bases, in which case we need to backtrack
    1235             :   // to calculate the spline DoF values.
    1236       11356 :   std::vector<const Variable *> rational_vars;
    1237      407027 :   for (auto varnum : vars)
    1238             :     {
    1239      205537 :       const Variable & var = this->get_dof_map().variable(varnum);
    1240      205537 :       if (var.type().family == RATIONAL_BERNSTEIN)
    1241        5412 :         rational_vars.push_back(&var);
    1242             :     }
    1243             : 
    1244             :   // Okay, but are we really using any *spline* bases, or just
    1245             :   // unconstrained rational bases?
    1246      201490 :   bool using_spline_bases = false;
    1247      201490 :   if (!rational_vars.empty())
    1248             :     {
    1249             :       // Look for a spline node: a NodeElem with a rational variable
    1250             :       // on it.
    1251        8906 :       for (auto & elem : active_local_range.value())
    1252        3794 :         if (elem->type() == NODEELEM)
    1253         300 :           for (auto rational_var : rational_vars)
    1254         300 :             if (rational_var->active_on_subdomain(elem->subdomain_id()))
    1255             :               {
    1256         300 :                 using_spline_bases = true;
    1257         292 :                 goto checked_on_splines;
    1258             :               }
    1259             :     }
    1260             : 
    1261      201186 : checked_on_splines:
    1262             : 
    1263             :   // Not every processor may have a NodeElem, especially while
    1264             :   // we're not partitioning them efficiently yet.
    1265      201490 :   this->comm().max(using_spline_bases);
    1266             : 
    1267      201490 :   if (using_spline_bases)
    1268         300 :     this->solve_for_unconstrained_dofs(new_vector, is_adjoint);
    1269             : 
    1270             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
    1271      201490 :   if (is_adjoint == -1)
    1272      201490 :     this->get_dof_map().enforce_constraints_exactly(*this, &new_vector);
    1273           0 :   else if (is_adjoint >= 0)
    1274           0 :     this->get_dof_map().enforce_adjoint_constraints_exactly(new_vector,
    1275             :                                                             is_adjoint);
    1276             : #else
    1277             :   libmesh_ignore(is_adjoint);
    1278             : #endif
    1279      201490 : }
    1280             : 
    1281             : 
    1282             : /**
    1283             :  * This method projects components of an arbitrary boundary function
    1284             :  * onto the solution via L2 projections and nodal interpolations on
    1285             :  * each element.
    1286             :  */
    1287           0 : void System::boundary_project_solution (const std::set<boundary_id_type> & b,
    1288             :                                         const std::vector<unsigned int> & variables,
    1289             :                                         ValueFunctionPointer fptr,
    1290             :                                         GradientFunctionPointer gptr,
    1291             :                                         const Parameters & function_parameters,
    1292             :                                         std::optional<ConstElemRange> active_local_range)
    1293             : 
    1294             : {
    1295           0 :   WrappedFunction<Number> f(*this, fptr, &function_parameters);
    1296           0 :   WrappedFunction<Gradient> g(*this, gptr, &function_parameters);
    1297           0 :   this->boundary_project_solution(b, variables, &f, &g, active_local_range);
    1298           0 : }
    1299             : 
    1300             : 
    1301             : /**
    1302             :  * This method projects an arbitrary boundary function onto the
    1303             :  * solution via L2 projections and nodal interpolations on each
    1304             :  * element.
    1305             :  */
    1306          71 : void System::boundary_project_solution (const std::set<boundary_id_type> & b,
    1307             :                                         const std::vector<unsigned int> & variables,
    1308             :                                         FunctionBase<Number> * f,
    1309             :                                         FunctionBase<Gradient> * g,
    1310             :                                         std::optional<ConstElemRange> active_local_range)
    1311             : {
    1312         140 :   this->boundary_project_vector(b, variables, *solution, f, g, -1 /*is_adjoint*/, active_local_range);
    1313             : 
    1314          73 :   solution->localize(*current_local_solution);
    1315          71 : }
    1316             : 
    1317             : 
    1318             : 
    1319             : 
    1320             : 
    1321             : /**
    1322             :  * This method projects an arbitrary boundary function via L2
    1323             :  * projections and nodal interpolations on each element.
    1324             :  */
    1325           0 : void System::boundary_project_vector (const std::set<boundary_id_type> & b,
    1326             :                                       const std::vector<unsigned int> & variables,
    1327             :                                       ValueFunctionPointer fptr,
    1328             :                                       GradientFunctionPointer gptr,
    1329             :                                       const Parameters & function_parameters,
    1330             :                                       NumericVector<Number> & new_vector,
    1331             :                                       int is_adjoint,
    1332             :                                       std::optional<ConstElemRange> active_local_range) const
    1333             : {
    1334           0 :   WrappedFunction<Number> f(*this, fptr, &function_parameters);
    1335           0 :   WrappedFunction<Gradient> g(*this, gptr, &function_parameters);
    1336           0 :   this->boundary_project_vector(b, variables, new_vector, &f, &g,
    1337             :                                 is_adjoint, active_local_range);
    1338           0 : }
    1339             : 
    1340             : /**
    1341             :  * This method projects an arbitrary function via L2 projections and
    1342             :  * nodal interpolations on each element.
    1343             :  */
    1344          71 : void System::boundary_project_vector (const std::set<boundary_id_type> & b,
    1345             :                                       const std::vector<unsigned int> & variables,
    1346             :                                       NumericVector<Number> & new_vector,
    1347             :                                       FunctionBase<Number> * f,
    1348             :                                       FunctionBase<Gradient> * g,
    1349             :                                       int is_adjoint,
    1350             :                                       std::optional<ConstElemRange> active_local_range) const
    1351             : {
    1352           4 :   LOG_SCOPE ("boundary_project_vector()", "System");
    1353             : 
    1354          71 :   if (!active_local_range)
    1355             :   {
    1356             :     active_local_range.emplace
    1357         142 :       (this->get_mesh().active_local_elements_begin(),
    1358         146 :        this->get_mesh().active_local_elements_end());
    1359             :   }
    1360             : 
    1361             :   Threads::parallel_for
    1362          73 :     (active_local_range.value(),
    1363         138 :      BoundaryProjectSolution(b, variables, *this, f, g,
    1364          71 :                              this->get_equation_systems().parameters,
    1365             :                              new_vector)
    1366             :      );
    1367             : 
    1368             :   // We don't do SCALAR dofs when just projecting the boundary, so
    1369             :   // we're done here.
    1370             : 
    1371          71 :   new_vector.close();
    1372             : 
    1373             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
    1374          71 :   if (is_adjoint == -1)
    1375          71 :     this->get_dof_map().enforce_constraints_exactly(*this, &new_vector);
    1376           0 :   else if (is_adjoint >= 0)
    1377           0 :     this->get_dof_map().enforce_adjoint_constraints_exactly(new_vector,
    1378             :                                                             is_adjoint);
    1379             : #else
    1380             :   libmesh_ignore(is_adjoint);
    1381             : #endif
    1382          71 : }
    1383             : 
    1384             : 
    1385             : 
    1386             : #ifdef LIBMESH_ENABLE_AMR
    1387       57072 : void BuildProjectionList::unique()
    1388             : {
    1389             :   // Sort the send list.  After this duplicated
    1390             :   // elements will be adjacent in the vector
    1391       57072 :   std::sort(this->send_list.begin(),
    1392             :             this->send_list.end());
    1393             : 
    1394             :   // Now use std::unique to remove duplicate entries
    1395             :   std::vector<dof_id_type>::iterator new_end =
    1396       53072 :     std::unique (this->send_list.begin(),
    1397        4000 :                  this->send_list.end());
    1398             : 
    1399             :   // Remove the end of the send_list.  Use the "swap trick"
    1400             :   // from Effective STL
    1401       59072 :   std::vector<dof_id_type>
    1402        2000 :     (this->send_list.begin(), new_end).swap (this->send_list);
    1403       57072 : }
    1404             : 
    1405             : 
    1406             : 
    1407       57138 : void BuildProjectionList::operator()(const ConstElemRange & range)
    1408             : {
    1409             :   // The DofMap for this system
    1410       57138 :   const DofMap & dof_map = system.get_dof_map();
    1411             : 
    1412        2022 :   const dof_id_type first_old_dof = dof_map.first_old_dof();
    1413        2022 :   const dof_id_type end_old_dof   = dof_map.end_old_dof();
    1414             : 
    1415             :   // We can handle all the variables at once.
    1416             :   // The old global DOF indices
    1417        4044 :   std::vector<dof_id_type> di;
    1418             : 
    1419             :   // Iterate over the elements in the range
    1420     4295309 :   for (const auto & elem : range)
    1421             :     {
    1422             :       // If this element doesn't have an old_dof_object with dofs for the
    1423             :       // current system, then it must be newly added, so the user
    1424             :       // is responsible for setting the new dofs.
    1425             : 
    1426             :       // ... but we need a better way to test for that; the code
    1427             :       // below breaks on any FE type for which the elem stores no
    1428             :       // dofs.
    1429             :       // if (!elem->get_old_dof_object() || !elem->get_old_dof_object()->has_dofs(system.number()))
    1430             :       //  continue;
    1431             : 
    1432             :       // Examining refinement flags instead should distinguish
    1433             :       // between refinement-added and user-added elements lacking
    1434             :       // old_dof_object
    1435     4238171 :       const DofObject * old_dof_object = elem->get_old_dof_object();
    1436     2119352 :       if (!old_dof_object &&
    1437     4369953 :           elem->refinement_flag() != Elem::JUST_REFINED &&
    1438       12070 :           elem->refinement_flag() != Elem::JUST_COARSENED)
    1439      124482 :         continue;
    1440             : 
    1441      920100 :       const Elem * parent = elem->parent();
    1442             : 
    1443     4562378 :       if (elem->refinement_flag() == Elem::JUST_REFINED)
    1444             :         {
    1445      141844 :           libmesh_assert(parent);
    1446             : 
    1447             :           // We used to hack_p_level here, but that wasn't thread-safe
    1448             :           // so now we take p refinement flags into account in
    1449             :           // old_dof_indices
    1450             : 
    1451     1492728 :           dof_map.old_dof_indices (parent, di);
    1452             : 
    1453    11415680 :           for (auto & node : elem->node_ref_range())
    1454             :             {
    1455      920916 :               const DofObject * old_dofs = node.get_old_dof_object();
    1456             : 
    1457     9781108 :               if (old_dofs)
    1458             :                 {
    1459     3614276 :                   const unsigned int sysnum = system.number();
    1460     3614276 :                   const unsigned int nvg = old_dofs->n_var_groups(sysnum);
    1461             : 
    1462     7318062 :                   for (unsigned int vg=0; vg != nvg; ++vg)
    1463             :                     {
    1464             :                       const unsigned int nvig =
    1465      349452 :                         old_dofs->n_vars(sysnum, vg);
    1466     7416456 :                       for (unsigned int vig=0; vig != nvig; ++vig)
    1467             :                         {
    1468             :                           const unsigned int n_comp =
    1469      350224 :                             old_dofs->n_comp_group(sysnum, vg);
    1470     7702845 :                           for (unsigned int c=0; c != n_comp; ++c)
    1471             :                             {
    1472             :                               const dof_id_type old_id =
    1473     3615410 :                                 old_dofs->dof_number(sysnum, vg, vig,
    1474     3990175 :                                                      c, n_comp);
    1475             : 
    1476             :                               // We should either have no old id
    1477             :                               // (e.g. on a newly expanded subdomain)
    1478             :                               // or an id from the old system.
    1479      374765 :                               libmesh_assert(old_id < dof_map.n_old_dofs() ||
    1480             :                                              old_id == DofObject::invalid_id);
    1481     3990175 :                               di.push_back(old_id);
    1482             :                             }
    1483             :                         }
    1484             :                     }
    1485             :                 }
    1486             :             }
    1487             : 
    1488     1492728 :           std::sort(di.begin(), di.end());
    1489             :           std::vector<dof_id_type>::iterator new_end =
    1490     1492728 :             std::unique(di.begin(), di.end());
    1491     2843612 :           std::vector<dof_id_type>(di.begin(), new_end).swap(di);
    1492             :         }
    1493     2609600 :       else if (elem->refinement_flag() == Elem::JUST_COARSENED)
    1494             :         {
    1495       45758 :           std::vector<dof_id_type> di_child;
    1496       22879 :           di.clear();
    1497     1055026 :           for (auto & child : elem->child_ref_range())
    1498             :             {
    1499      844232 :               dof_map.old_dof_indices (&child, di_child);
    1500      844232 :               di.insert(di.end(), di_child.begin(), di_child.end());
    1501             :             }
    1502             :         }
    1503             :       else
    1504     2398806 :         dof_map.old_dof_indices (elem, di);
    1505             : 
    1506    33138172 :       for (auto di_i : di)
    1507             :         {
    1508             :           // If we've just expanded a subdomain for a
    1509             :           // subdomain-restricted variable, then we may have an
    1510             :           // old_dof_object that doesn't have an old DoF for every
    1511             :           // local index.
    1512    29035844 :           if (di_i == DofObject::invalid_id)
    1513           0 :             continue;
    1514             : 
    1515     3026757 :           libmesh_assert_less(di_i, dof_map.n_old_dofs());
    1516    29035844 :           if (di_i < first_old_dof || di_i >= end_old_dof)
    1517    13288529 :             this->send_list.push_back(di_i);
    1518             :         }
    1519             :     }  // end elem loop
    1520       57138 : }
    1521             : 
    1522             : 
    1523             : 
    1524          66 : void BuildProjectionList::join(const BuildProjectionList & other)
    1525             : {
    1526             :   // Joining simply requires I add the dof indices from the other object
    1527          44 :   this->send_list.insert(this->send_list.end(),
    1528             :                          other.send_list.begin(),
    1529          88 :                          other.send_list.end());
    1530          66 : }
    1531             : #endif // LIBMESH_ENABLE_AMR
    1532             : 
    1533             : 
    1534             : 
    1535          71 : void BoundaryProjectSolution::operator()(const ConstElemRange & range) const
    1536             : {
    1537             :   // We need data to project
    1538           2 :   libmesh_assert(f.get());
    1539             : 
    1540             :   /**
    1541             :    * This method projects an arbitrary boundary solution to the current
    1542             :    * mesh.  The input function \p f gives the arbitrary solution,
    1543             :    * while the \p new_vector (which should already be correctly sized)
    1544             :    * gives the solution (to be computed) on the current mesh.
    1545             :    */
    1546             : 
    1547             :   // The dimensionality of the current mesh
    1548          71 :   const unsigned int dim = system.get_mesh().mesh_dimension();
    1549             : 
    1550             :   // The DofMap for this system
    1551          71 :   const DofMap & dof_map = system.get_dof_map();
    1552             : 
    1553             :   // Boundary info for the current mesh
    1554             :   const BoundaryInfo & boundary_info =
    1555           4 :     system.get_mesh().get_boundary_info();
    1556             : 
    1557             :   // The element matrix and RHS for projections.
    1558             :   // Note that Ke is always real-valued, whereas
    1559             :   // Fe may be complex valued if complex number
    1560             :   // support is enabled
    1561          75 :   DenseMatrix<Real> Ke;
    1562          71 :   DenseVector<Number> Fe;
    1563             :   // The new element coefficients
    1564          71 :   DenseVector<Number> Ue;
    1565             : 
    1566             : 
    1567             :   // Loop over all the variables we've been requested to project
    1568         142 :   for (auto v : make_range(variables.size()))
    1569             :     {
    1570          71 :       const unsigned int var = variables[v];
    1571             : 
    1572          71 :       const Variable & variable = dof_map.variable(var);
    1573             : 
    1574           2 :       const FEType & fe_type = variable.type();
    1575             : 
    1576          71 :       if (fe_type.family == SCALAR)
    1577           0 :         continue;
    1578             : 
    1579             :       const unsigned int var_component =
    1580          71 :         system.variable_scalar_number(var, 0);
    1581             : 
    1582             :       // Get FE objects of the appropriate type
    1583          73 :       std::unique_ptr<FEBase> fe (FEBase::build(dim, fe_type));
    1584             : 
    1585             :       // Prepare variables for projection
    1586          73 :       std::unique_ptr<QBase> qedgerule (fe_type.default_quadrature_rule(1));
    1587          73 :       std::unique_ptr<QBase> qsiderule (fe_type.default_quadrature_rule(dim-1));
    1588             : 
    1589             :       // The values of the shape functions at the quadrature
    1590             :       // points
    1591           2 :       const std::vector<std::vector<Real>> & phi = fe->get_phi();
    1592             : 
    1593             :       // The gradients of the shape functions at the quadrature
    1594             :       // points on the child element.
    1595           2 :       const std::vector<std::vector<RealGradient>> * dphi = nullptr;
    1596             : 
    1597          71 :       const FEContinuity cont = fe->get_continuity();
    1598             : 
    1599          71 :       if (cont == C_ONE)
    1600             :         {
    1601             :           // We'll need gradient data for a C1 projection
    1602           0 :           libmesh_assert(g.get());
    1603             : 
    1604             :           const std::vector<std::vector<RealGradient>> &
    1605           0 :             ref_dphi = fe->get_dphi();
    1606           0 :           dphi = &ref_dphi;
    1607             :         }
    1608             : 
    1609             :       // The Jacobian * quadrature weight at the quadrature points
    1610             :       const std::vector<Real> & JxW =
    1611           5 :         fe->get_JxW();
    1612             : 
    1613             :       // The XYZ locations of the quadrature points
    1614             :       const std::vector<Point> & xyz_values =
    1615           5 :         fe->get_xyz();
    1616             : 
    1617             :       // The global DOF indices
    1618           4 :       std::vector<dof_id_type> dof_indices;
    1619             :       // Side/edge DOF indices
    1620           4 :       std::vector<unsigned int> side_dofs;
    1621             : 
    1622             :       // Container to catch IDs passed back from BoundaryInfo.
    1623           4 :       std::vector<boundary_id_type> bc_ids;
    1624             : 
    1625             :       // Iterate over all the elements in the range
    1626         395 :       for (const auto & elem : range)
    1627             :         {
    1628             :           // Per-subdomain variables don't need to be projected on
    1629             :           // elements where they're not active
    1630         324 :           if (!variable.active_on_subdomain(elem->subdomain_id()))
    1631           0 :             continue;
    1632             : 
    1633         324 :           const unsigned short n_nodes = elem->n_nodes();
    1634         324 :           const unsigned short n_edges = elem->n_edges();
    1635         324 :           const unsigned short n_sides = elem->n_sides();
    1636             : 
    1637             :           // Find out which nodes, edges and sides are on a requested
    1638             :           // boundary:
    1639         351 :           std::vector<bool> is_boundary_node(n_nodes, false),
    1640         351 :             is_boundary_edge(n_edges, false),
    1641         351 :             is_boundary_side(n_sides, false);
    1642             : 
    1643             :           // We also maintain a separate list of nodeset-based boundary nodes
    1644         351 :           std::vector<bool> is_boundary_nodeset(n_nodes, false);
    1645             : 
    1646        2268 :           for (unsigned char s=0; s != n_sides; ++s)
    1647             :             {
    1648             :               // First see if this side has been requested
    1649        1944 :               boundary_info.boundary_ids (elem, s, bc_ids);
    1650         162 :               bool do_this_side = false;
    1651        2484 :               for (const auto & bc_id : bc_ids)
    1652         648 :                 if (b.count(bc_id))
    1653             :                   {
    1654           9 :                     do_this_side = true;
    1655           9 :                     break;
    1656             :                   }
    1657        1944 :               if (!do_this_side)
    1658        1683 :                 continue;
    1659             : 
    1660          18 :               is_boundary_side[s] = true;
    1661             : 
    1662             :               // Then see what nodes and what edges are on it
    1663         972 :               for (unsigned int n=0; n != n_nodes; ++n)
    1664         864 :                 if (elem->is_node_on_side(n,s))
    1665          72 :                   is_boundary_node[n] = true;
    1666        1404 :               for (unsigned int e=0; e != n_edges; ++e)
    1667        1296 :                 if (elem->is_edge_on_side(e,s))
    1668          72 :                   is_boundary_edge[e] = true;
    1669             :             }
    1670             : 
    1671             :             // We can also project on nodes, so we should also independently
    1672             :             // check whether the nodes have been requested
    1673        2916 :             for (unsigned int n=0; n != n_nodes; ++n)
    1674             :               {
    1675        2808 :                 boundary_info.boundary_ids (elem->node_ptr(n), bc_ids);
    1676             : 
    1677        5196 :                 for (const auto & bc_id : bc_ids)
    1678        2604 :                   if (b.count(bc_id))
    1679             :                     {
    1680          74 :                       is_boundary_node[n] = true;
    1681          74 :                       is_boundary_nodeset[n] = true;
    1682             :                     }
    1683             :               }
    1684             : 
    1685             :             // We can also project on edges, so we should also independently
    1686             :             // check whether the edges have been requested
    1687        4212 :             for (unsigned short e=0; e != n_edges; ++e)
    1688             :               {
    1689        3888 :                 boundary_info.edge_boundary_ids (elem, e, bc_ids);
    1690             : 
    1691        3924 :                 for (const auto & bc_id : bc_ids)
    1692          36 :                   if (b.count(bc_id))
    1693           6 :                     is_boundary_edge[e] = true;
    1694             :               }
    1695             : 
    1696             :           // Update the DOF indices for this element based on
    1697             :           // the current mesh
    1698         324 :           dof_map.dof_indices (elem, dof_indices, var);
    1699             : 
    1700             :           // The number of DOFs on the element
    1701             :           const unsigned int n_dofs =
    1702          54 :             cast_int<unsigned int>(dof_indices.size());
    1703             : 
    1704             :           // Fixed vs. free DoFs on edge/face projections
    1705         351 :           std::vector<char> dof_is_fixed(n_dofs, false); // bools
    1706         351 :           std::vector<int> free_dof(n_dofs, 0);
    1707             : 
    1708             :           // Zero the interpolated values
    1709         297 :           Ue.resize (n_dofs); Ue.zero();
    1710             : 
    1711             :           // In general, we need a series of
    1712             :           // projections to ensure a unique and continuous
    1713             :           // solution.  We start by interpolating boundary nodes, then
    1714             :           // hold those fixed and project boundary edges, then hold
    1715             :           // those fixed and project boundary faces,
    1716             : 
    1717             :           // Interpolate node values first
    1718          27 :           unsigned int current_dof = 0;
    1719        2916 :           for (unsigned short n = 0; n != n_nodes; ++n)
    1720             :             {
    1721             :               // FIXME: this should go through the DofMap,
    1722             :               // not duplicate dof_indices code badly!
    1723             : 
    1724             :               // This call takes into account elem->p_level() internally.
    1725             :               const unsigned int nc =
    1726        2592 :                 FEInterface::n_dofs_at_node (fe_type, elem, n);
    1727             : 
    1728        2771 :               if ((!elem->is_vertex(n) || !is_boundary_node[n]) &&
    1729         574 :                   !is_boundary_nodeset[n])
    1730             :                 {
    1731        2148 :                   current_dof += nc;
    1732        2148 :                   continue;
    1733             :                 }
    1734         444 :               if (cont == DISCONTINUOUS)
    1735             :                 {
    1736           0 :                   libmesh_assert_equal_to (nc, 0);
    1737             :                 }
    1738             :               // Assume that C_ZERO elements have a single nodal
    1739             :               // value shape function
    1740         444 :               else if (cont == C_ZERO)
    1741             :                 {
    1742          37 :                   libmesh_assert_equal_to (nc, 1);
    1743         851 :                   Ue(current_dof) = f->component(var_component,
    1744             :                                                  elem->point(n),
    1745         444 :                                                  system.time);
    1746         444 :                   dof_is_fixed[current_dof] = true;
    1747         444 :                   current_dof++;
    1748             :                 }
    1749             :               // The hermite element vertex shape functions are weird
    1750           0 :               else if (fe_type.family == HERMITE)
    1751             :                 {
    1752           0 :                   Ue(current_dof) = f->component(var_component,
    1753             :                                                  elem->point(n),
    1754           0 :                                                  system.time);
    1755           0 :                   dof_is_fixed[current_dof] = true;
    1756           0 :                   current_dof++;
    1757           0 :                   Gradient grad = g->component(var_component,
    1758             :                                                elem->point(n),
    1759           0 :                                                system.time);
    1760             :                   // x derivative
    1761           0 :                   Ue(current_dof) = grad(0);
    1762           0 :                   dof_is_fixed[current_dof] = true;
    1763           0 :                   current_dof++;
    1764             : #if LIBMESH_DIM > 1
    1765           0 :                   if (dim > 1)
    1766             :                     {
    1767             :                       // We'll finite difference mixed derivatives
    1768           0 :                       Point nxminus = elem->point(n),
    1769           0 :                         nxplus = elem->point(n);
    1770           0 :                       nxminus(0) -= TOLERANCE;
    1771           0 :                       nxplus(0) += TOLERANCE;
    1772           0 :                       Gradient gxminus = g->component(var_component,
    1773             :                                                       nxminus,
    1774           0 :                                                       system.time);
    1775           0 :                       Gradient gxplus = g->component(var_component,
    1776             :                                                      nxplus,
    1777           0 :                                                      system.time);
    1778             :                       // y derivative
    1779           0 :                       Ue(current_dof) = grad(1);
    1780           0 :                       dof_is_fixed[current_dof] = true;
    1781           0 :                       current_dof++;
    1782             :                       // xy derivative
    1783           0 :                       Ue(current_dof) = (gxplus(1) - gxminus(1))
    1784           0 :                         / 2. / TOLERANCE;
    1785           0 :                       dof_is_fixed[current_dof] = true;
    1786           0 :                       current_dof++;
    1787             : 
    1788             : #if LIBMESH_DIM > 2
    1789           0 :                       if (dim > 2)
    1790             :                         {
    1791             :                           // z derivative
    1792           0 :                           Ue(current_dof) = grad(2);
    1793           0 :                           dof_is_fixed[current_dof] = true;
    1794           0 :                           current_dof++;
    1795             :                           // xz derivative
    1796           0 :                           Ue(current_dof) = (gxplus(2) - gxminus(2))
    1797           0 :                             / 2. / TOLERANCE;
    1798           0 :                           dof_is_fixed[current_dof] = true;
    1799           0 :                           current_dof++;
    1800             :                           // We need new points for yz
    1801           0 :                           Point nyminus = elem->point(n),
    1802           0 :                             nyplus = elem->point(n);
    1803           0 :                           nyminus(1) -= TOLERANCE;
    1804           0 :                           nyplus(1) += TOLERANCE;
    1805           0 :                           Gradient gyminus = g->component(var_component,
    1806             :                                                           nyminus,
    1807           0 :                                                           system.time);
    1808           0 :                           Gradient gyplus = g->component(var_component,
    1809             :                                                          nyplus,
    1810           0 :                                                          system.time);
    1811             :                           // xz derivative
    1812           0 :                           Ue(current_dof) = (gyplus(2) - gyminus(2))
    1813           0 :                             / 2. / TOLERANCE;
    1814           0 :                           dof_is_fixed[current_dof] = true;
    1815           0 :                           current_dof++;
    1816             :                           // Getting a 2nd order xyz is more tedious
    1817           0 :                           Point nxmym = elem->point(n),
    1818           0 :                             nxmyp = elem->point(n),
    1819           0 :                             nxpym = elem->point(n),
    1820           0 :                             nxpyp = elem->point(n);
    1821           0 :                           nxmym(0) -= TOLERANCE;
    1822           0 :                           nxmym(1) -= TOLERANCE;
    1823           0 :                           nxmyp(0) -= TOLERANCE;
    1824           0 :                           nxmyp(1) += TOLERANCE;
    1825           0 :                           nxpym(0) += TOLERANCE;
    1826           0 :                           nxpym(1) -= TOLERANCE;
    1827           0 :                           nxpyp(0) += TOLERANCE;
    1828           0 :                           nxpyp(1) += TOLERANCE;
    1829           0 :                           Gradient gxmym = g->component(var_component,
    1830             :                                                         nxmym,
    1831           0 :                                                         system.time);
    1832           0 :                           Gradient gxmyp = g->component(var_component,
    1833             :                                                         nxmyp,
    1834           0 :                                                         system.time);
    1835           0 :                           Gradient gxpym = g->component(var_component,
    1836             :                                                         nxpym,
    1837           0 :                                                         system.time);
    1838           0 :                           Gradient gxpyp = g->component(var_component,
    1839             :                                                         nxpyp,
    1840           0 :                                                         system.time);
    1841           0 :                           Number gxzplus = (gxpyp(2) - gxmyp(2))
    1842           0 :                             / 2. / TOLERANCE;
    1843           0 :                           Number gxzminus = (gxpym(2) - gxmym(2))
    1844           0 :                             / 2. / TOLERANCE;
    1845             :                           // xyz derivative
    1846           0 :                           Ue(current_dof) = (gxzplus - gxzminus)
    1847           0 :                             / 2. / TOLERANCE;
    1848           0 :                           dof_is_fixed[current_dof] = true;
    1849           0 :                           current_dof++;
    1850             :                         }
    1851             : #endif // LIBMESH_DIM > 2
    1852             :                     }
    1853             : #endif // LIBMESH_DIM > 1
    1854             :                 }
    1855             :               // Assume that other C_ONE elements have a single nodal
    1856             :               // value shape function and nodal gradient component
    1857             :               // shape functions
    1858           0 :               else if (cont == C_ONE)
    1859             :                 {
    1860           0 :                   libmesh_assert_equal_to (nc, 1 + dim);
    1861           0 :                   Ue(current_dof) = f->component(var_component,
    1862             :                                                  elem->point(n),
    1863           0 :                                                  system.time);
    1864           0 :                   dof_is_fixed[current_dof] = true;
    1865           0 :                   current_dof++;
    1866           0 :                   Gradient grad = g->component(var_component,
    1867             :                                                elem->point(n),
    1868           0 :                                                system.time);
    1869           0 :                   for (unsigned int i=0; i!= dim; ++i)
    1870             :                     {
    1871           0 :                       Ue(current_dof) = grad(i);
    1872           0 :                       dof_is_fixed[current_dof] = true;
    1873           0 :                       current_dof++;
    1874             :                     }
    1875             :                 }
    1876             :               else
    1877           0 :                 libmesh_error_msg("Unknown continuity " << cont);
    1878             :             }
    1879             : 
    1880             :           // In 3D, project any edge values next
    1881         324 :           if (dim > 2 && cont != DISCONTINUOUS)
    1882        4212 :             for (unsigned short e = 0; e != n_edges; ++e)
    1883             :               {
    1884        4212 :                 if (!is_boundary_edge[e])
    1885        3852 :                   continue;
    1886             : 
    1887         468 :                 FEInterface::dofs_on_edge(elem, dim, fe_type, e,
    1888             :                                           side_dofs);
    1889             : 
    1890             :                 const unsigned int n_side_dofs =
    1891          78 :                   cast_int<unsigned int>(side_dofs.size());
    1892             : 
    1893             :                 // Some edge dofs are on nodes and already
    1894             :                 // fixed, others are free to calculate
    1895          39 :                 unsigned int free_dofs = 0;
    1896        1404 :                 for (auto i : make_range(n_side_dofs))
    1897        1092 :                   if (!dof_is_fixed[side_dofs[i]])
    1898          78 :                     free_dof[free_dofs++] = i;
    1899             : 
    1900             :                 // There may be nothing to project
    1901         468 :                 if (!free_dofs)
    1902         396 :                   continue;
    1903             : 
    1904          33 :                 Ke.resize (free_dofs, free_dofs); Ke.zero();
    1905          33 :                 Fe.resize (free_dofs); Fe.zero();
    1906             :                 // The new edge coefficients
    1907          36 :                 DenseVector<Number> Uedge(free_dofs);
    1908             : 
    1909             :                 // Initialize FE data on the edge
    1910          39 :                 fe->attach_quadrature_rule (qedgerule.get());
    1911          36 :                 fe->edge_reinit (elem, e);
    1912           3 :                 const unsigned int n_qp = qedgerule->n_points();
    1913             : 
    1914             :                 // Loop over the quadrature points
    1915         108 :                 for (unsigned int qp=0; qp<n_qp; qp++)
    1916             :                   {
    1917             :                     // solution at the quadrature point
    1918          78 :                     Number fineval = f->component(var_component,
    1919          72 :                                                   xyz_values[qp],
    1920          72 :                                                   system.time);
    1921             :                     // solution grad at the quadrature point
    1922           6 :                     Gradient finegrad;
    1923          72 :                     if (cont == C_ONE)
    1924           0 :                       finegrad = g->component(var_component,
    1925           0 :                                               xyz_values[qp],
    1926           0 :                                               system.time);
    1927             : 
    1928             :                     // Form edge projection matrix
    1929         204 :                     for (unsigned int sidei=0, freei=0;
    1930         216 :                          sidei != n_side_dofs; ++sidei)
    1931             :                       {
    1932         144 :                         unsigned int i = side_dofs[sidei];
    1933             :                         // fixed DoFs aren't test functions
    1934         156 :                         if (dof_is_fixed[i])
    1935           0 :                           continue;
    1936         300 :                         for (unsigned int sidej=0, freej=0;
    1937         432 :                              sidej != n_side_dofs; ++sidej)
    1938             :                           {
    1939         288 :                             unsigned int j = side_dofs[sidej];
    1940         312 :                             if (dof_is_fixed[j])
    1941           0 :                               Fe(freei) -= phi[i][qp] * phi[j][qp] *
    1942           0 :                                 JxW[qp] * Ue(j);
    1943             :                             else
    1944         384 :                               Ke(freei,freej) += phi[i][qp] *
    1945         336 :                                 phi[j][qp] * JxW[qp];
    1946         288 :                             if (cont == C_ONE)
    1947             :                               {
    1948           0 :                                 if (dof_is_fixed[j])
    1949           0 :                                   Fe(freei) -= ((*dphi)[i][qp] *
    1950           0 :                                                 (*dphi)[j][qp]) *
    1951           0 :                                     JxW[qp] * Ue(j);
    1952             :                                 else
    1953           0 :                                   Ke(freei,freej) += ((*dphi)[i][qp] *
    1954           0 :                                                       (*dphi)[j][qp])
    1955           0 :                                     * JxW[qp];
    1956             :                               }
    1957         312 :                             if (!dof_is_fixed[j])
    1958         288 :                               freej++;
    1959             :                           }
    1960         168 :                         Fe(freei) += phi[i][qp] * fineval * JxW[qp];
    1961         144 :                         if (cont == C_ONE)
    1962           0 :                           Fe(freei) += (finegrad * (*dphi)[i][qp]) *
    1963           0 :                             JxW[qp];
    1964         144 :                         freei++;
    1965             :                       }
    1966             :                   }
    1967             : 
    1968          36 :                 Ke.cholesky_solve(Fe, Uedge);
    1969             : 
    1970             :                 // Transfer new edge solutions to element
    1971         108 :                 for (unsigned int i=0; i != free_dofs; ++i)
    1972             :                   {
    1973          84 :                     Number & ui = Ue(side_dofs[free_dof[i]]);
    1974           6 :                     libmesh_assert(std::abs(ui) < TOLERANCE ||
    1975             :                                    std::abs(ui - Uedge(i)) < TOLERANCE);
    1976          72 :                     ui = Uedge(i);
    1977          78 :                     dof_is_fixed[side_dofs[free_dof[i]]] = true;
    1978             :                   }
    1979             :               }
    1980             : 
    1981             :           // Project any side values (edges in 2D, faces in 3D)
    1982         324 :           if (dim > 1 && cont != DISCONTINUOUS)
    1983        2268 :             for (unsigned short s = 0; s != n_sides; ++s)
    1984             :               {
    1985        2106 :                 if (!is_boundary_side[s])
    1986        1944 :                   continue;
    1987             : 
    1988         108 :                 FEInterface::dofs_on_side(elem, dim, fe_type, s,
    1989             :                                           side_dofs);
    1990             : 
    1991             :                 // Some side dofs are on nodes/edges and already
    1992             :                 // fixed, others are free to calculate
    1993           9 :                 unsigned int free_dofs = 0;
    1994         540 :                 for (auto i : index_range(side_dofs))
    1995         468 :                   if (!dof_is_fixed[side_dofs[i]])
    1996           0 :                     free_dof[free_dofs++] = i;
    1997             : 
    1998             :                 // There may be nothing to project
    1999         108 :                 if (!free_dofs)
    2000          99 :                   continue;
    2001             : 
    2002           0 :                 Ke.resize (free_dofs, free_dofs); Ke.zero();
    2003           0 :                 Fe.resize (free_dofs); Fe.zero();
    2004             :                 // The new side coefficients
    2005           0 :                 DenseVector<Number> Uside(free_dofs);
    2006             : 
    2007             :                 // Initialize FE data on the side
    2008           0 :                 fe->attach_quadrature_rule (qsiderule.get());
    2009           0 :                 fe->reinit (elem, s);
    2010           0 :                 const unsigned int n_qp = qsiderule->n_points();
    2011             : 
    2012             :                 const unsigned int n_side_dofs =
    2013           0 :                   cast_int<unsigned int>(side_dofs.size());
    2014             : 
    2015             :                 // Loop over the quadrature points
    2016           0 :                 for (unsigned int qp=0; qp<n_qp; qp++)
    2017             :                   {
    2018             :                     // solution at the quadrature point
    2019           0 :                     Number fineval = f->component(var_component,
    2020           0 :                                                   xyz_values[qp],
    2021           0 :                                                   system.time);
    2022             :                     // solution grad at the quadrature point
    2023           0 :                     Gradient finegrad;
    2024           0 :                     if (cont == C_ONE)
    2025           0 :                       finegrad = g->component(var_component,
    2026           0 :                                               xyz_values[qp],
    2027           0 :                                               system.time);
    2028             : 
    2029             :                     // Form side projection matrix
    2030           0 :                     for (unsigned int sidei=0, freei=0;
    2031           0 :                          sidei != n_side_dofs; ++sidei)
    2032             :                       {
    2033           0 :                         unsigned int i = side_dofs[sidei];
    2034             :                         // fixed DoFs aren't test functions
    2035           0 :                         if (dof_is_fixed[i])
    2036           0 :                           continue;
    2037           0 :                         for (unsigned int sidej=0, freej=0;
    2038           0 :                              sidej != n_side_dofs; ++sidej)
    2039             :                           {
    2040           0 :                             unsigned int j = side_dofs[sidej];
    2041           0 :                             if (dof_is_fixed[j])
    2042           0 :                               Fe(freei) -= phi[i][qp] * phi[j][qp] *
    2043           0 :                                 JxW[qp] * Ue(j);
    2044             :                             else
    2045           0 :                               Ke(freei,freej) += phi[i][qp] *
    2046           0 :                                 phi[j][qp] * JxW[qp];
    2047           0 :                             if (cont == C_ONE)
    2048             :                               {
    2049           0 :                                 if (dof_is_fixed[j])
    2050           0 :                                   Fe(freei) -= ((*dphi)[i][qp] *
    2051           0 :                                                 (*dphi)[j][qp]) *
    2052           0 :                                     JxW[qp] * Ue(j);
    2053             :                                 else
    2054           0 :                                   Ke(freei,freej) += ((*dphi)[i][qp] *
    2055           0 :                                                       (*dphi)[j][qp])
    2056           0 :                                     * JxW[qp];
    2057             :                               }
    2058           0 :                             if (!dof_is_fixed[j])
    2059           0 :                               freej++;
    2060             :                           }
    2061           0 :                         Fe(freei) += (fineval * phi[i][qp]) * JxW[qp];
    2062           0 :                         if (cont == C_ONE)
    2063           0 :                           Fe(freei) += (finegrad * (*dphi)[i][qp]) *
    2064           0 :                             JxW[qp];
    2065           0 :                         freei++;
    2066             :                       }
    2067             :                   }
    2068             : 
    2069           0 :                 Ke.cholesky_solve(Fe, Uside);
    2070             : 
    2071             :                 // Transfer new side solutions to element
    2072           0 :                 for (unsigned int i=0; i != free_dofs; ++i)
    2073             :                   {
    2074           0 :                     Number & ui = Ue(side_dofs[free_dof[i]]);
    2075           0 :                     libmesh_assert(std::abs(ui) < TOLERANCE ||
    2076             :                                    std::abs(ui - Uside(i)) < TOLERANCE);
    2077           0 :                     ui = Uside(i);
    2078           0 :                     dof_is_fixed[side_dofs[free_dof[i]]] = true;
    2079             :                   }
    2080             :               }
    2081             : 
    2082             :           const dof_id_type
    2083         324 :             first = new_vector.first_local_index(),
    2084         324 :             last  = new_vector.last_local_index();
    2085             : 
    2086             :           // Lock the new_vector since it is shared among threads.
    2087             :           {
    2088          54 :             Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
    2089             : 
    2090        2916 :             for (unsigned int i = 0; i < n_dofs; i++)
    2091        2592 :               if (dof_is_fixed[i] &&
    2092        2628 :                   (dof_indices[i] >= first) &&
    2093          36 :                   (dof_indices[i] <  last))
    2094             :                 {
    2095         434 :                   new_vector.set(dof_indices[i], Ue(i));
    2096             :                 }
    2097             :           }
    2098             :         }  // end elem loop
    2099          67 :     } // end variables loop
    2100          71 : }
    2101             : 
    2102             : 
    2103         300 : void System::solve_for_unconstrained_dofs(NumericVector<Number> & vec,
    2104             :                                           int is_adjoint) const
    2105             : {
    2106           8 :   const DofMap & dof_map = this->get_dof_map();
    2107             : 
    2108             :   std::unique_ptr<SparseMatrix<Number>> mat =
    2109         308 :     SparseMatrix<Number>::build(this->comm());
    2110             : 
    2111         292 :   std::unique_ptr<SparsityPattern::Build> sp;
    2112             : 
    2113         300 :   if (dof_map.computed_sparsity_already())
    2114           0 :     dof_map.update_sparsity_pattern(*mat);
    2115             :   else
    2116             :     {
    2117         300 :       mat->attach_dof_map(dof_map);
    2118         584 :       sp = dof_map.build_sparsity(this->get_mesh());
    2119         300 :       mat->attach_sparsity_pattern(*sp);
    2120             :     }
    2121             : 
    2122         300 :   mat->init();
    2123             : 
    2124           8 :   libmesh_assert_equal_to(vec.size(), dof_map.n_dofs());
    2125           8 :   libmesh_assert_equal_to(vec.local_size(), dof_map.n_local_dofs());
    2126             : 
    2127             :   std::unique_ptr<NumericVector<Number>> rhs =
    2128         308 :     NumericVector<Number>::build(this->comm());
    2129             : 
    2130         308 :   rhs->init(dof_map.n_dofs(), dof_map.n_local_dofs(), false,
    2131          16 :             PARALLEL);
    2132             : 
    2133             :   // Here we start with the unconstrained (and indeterminate) linear
    2134             :   // system, K*u = f, where K is the identity matrix for constrained
    2135             :   // DoFs and 0 elsewhere, and f is the current solution values for
    2136             :   // constrained DoFs and 0 elsewhere.
    2137             :   // We then apply the usual heterogeneous constraint matrix C and
    2138             :   // offset h, where u = C*x + h,
    2139             :   // to get C^T*K*C*x = C^T*f - C^T*K*h
    2140             :   // - a constrained and no-longer-singular system that finds the
    2141             :   // closest approximation for the unconstrained degrees of freedom.
    2142             :   //
    2143             :   // Here, though "closest" is in an algebraic sense; we're
    2144             :   // effectively using a pseudoinverse that optimizes in a
    2145             :   // discretization-dependent norm.  That only seems to give ~0.1%
    2146             :   // excess error even in coarse unit test cases, but at some point it
    2147             :   // might be reasonable to weight K and f properly.
    2148             : 
    2149        2121 :   for (dof_id_type d : IntRange<dof_id_type>(dof_map.first_dof(),
    2150       24936 :                                              dof_map.end_dof()))
    2151             :     {
    2152        6799 :       if (dof_map.is_constrained_dof(d))
    2153             :         {
    2154       19619 :           DenseMatrix<Number> K(1,1);
    2155       16891 :           DenseVector<Number> F(1);
    2156       18255 :           std::vector<dof_id_type> dof_indices(1, d);
    2157       15527 :           K(0,0) = 1;
    2158       16891 :           F(0) = (*this->solution)(d);
    2159             :           dof_map.heterogenously_constrain_element_matrix_and_vector
    2160        1364 :             (K, F, dof_indices, false, is_adjoint);
    2161       16891 :           mat->add_matrix(K, dof_indices);
    2162       15527 :           rhs->add_vector(F, dof_indices);
    2163       14163 :         }
    2164             :     }
    2165             : 
    2166             :   std::unique_ptr<LinearSolver<Number>> linear_solver =
    2167         300 :     LinearSolver<Number>::build(this->comm());
    2168             : 
    2169         584 :   linear_solver->solve(*mat, vec, *rhs,
    2170         300 :                        double(this->get_equation_systems().parameters.get<Real>("linear solver tolerance")),
    2171          40 :                        this->get_equation_systems().parameters.get<unsigned int>("linear solver maximum iterations"));
    2172         300 : }
    2173             : 
    2174             : 
    2175             : } // namespace libMesh

Generated by: LCOV version 1.14