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