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