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