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