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