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