Line data Source code
1 : // The libMesh Finite Element Library.
2 : // Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 :
4 : // This library is free software; you can redistribute 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 :
19 :
20 : // library configuration
21 : #include "libmesh/libmesh_config.h"
22 :
23 : // Local includes
24 : #include "libmesh/boundary_info.h"
25 : #include "libmesh/libmesh_logging.h"
26 : #include "libmesh/elem.h"
27 : #include "libmesh/ghost_point_neighbors.h"
28 : #include "libmesh/mesh_base.h"
29 : #include "libmesh/mesh_communication.h"
30 : #include "libmesh/mesh_serializer.h"
31 : #include "libmesh/mesh_tools.h"
32 : #include "libmesh/parallel.h"
33 : #include "libmesh/parallel_algebra.h"
34 : #include "libmesh/parallel_fe_type.h"
35 : #include "libmesh/partitioner.h"
36 : #include "libmesh/point_locator_base.h"
37 : #include "libmesh/sparse_matrix.h"
38 : #include "libmesh/threads.h"
39 : #include "libmesh/enum_elem_type.h"
40 : #include "libmesh/enum_point_locator_type.h"
41 : #include "libmesh/enum_to_string.h"
42 : #include "libmesh/point_locator_nanoflann.h"
43 : #include "libmesh/elem_side_builder.h"
44 : #include "libmesh/elem_range.h"
45 : #include "libmesh/node_range.h"
46 :
47 : // C++ includes
48 : #include <algorithm> // for std::min
49 : #include <map> // for std::multimap
50 : #include <memory>
51 : #include <sstream> // for std::ostringstream
52 : #include <unordered_map>
53 :
54 : #include "libmesh/periodic_boundaries.h"
55 : #include "libmesh/periodic_boundary.h"
56 :
57 : namespace libMesh
58 : {
59 :
60 :
61 :
62 : // ------------------------------------------------------------
63 : // MeshBase class member functions
64 337704 : MeshBase::MeshBase (const Parallel::Communicator & comm_in,
65 337704 : unsigned char d) :
66 : ParallelObject (comm_in),
67 337704 : boundary_info (new BoundaryInfo(*this)), // BoundaryInfo has protected ctor, can't use std::make_unique
68 318092 : _n_parts (1),
69 318092 : _default_mapping_type(LAGRANGE_MAP),
70 318092 : _default_mapping_data(0),
71 318092 : _preparation (),
72 318092 : _element_stored_range (),
73 318092 : _const_active_local_element_stored_range (),
74 318092 : _point_locator (),
75 318092 : _count_lower_dim_elems_in_point_locator(true),
76 318092 : _partitioner (),
77 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
78 318092 : _next_unique_id(DofObject::invalid_unique_id),
79 : #endif
80 318092 : _interior_mesh(this),
81 318092 : _skip_noncritical_partitioning(false),
82 347510 : _skip_all_partitioning(libMesh::on_command_line("--skip-partitioning")),
83 318092 : _skip_renumber_nodes_and_elements(false),
84 318092 : _skip_find_neighbors(false),
85 318092 : _skip_detect_interior_parents(false),
86 318092 : _allow_remote_element_removal(true),
87 318092 : _allow_node_and_elem_unique_id_overlap(false),
88 318092 : _spatial_dimension(d),
89 337704 : _default_ghosting(std::make_unique<GhostPointNeighbors>(*this)),
90 1062142 : _point_locator_close_to_point_tol(0.)
91 : {
92 327898 : _elem_dims.insert(d);
93 337704 : _ghosting_functors.push_back(_default_ghosting.get());
94 9806 : libmesh_assert_less_equal (LIBMESH_DIM, 3);
95 9806 : libmesh_assert_greater_equal (LIBMESH_DIM, d);
96 9806 : libmesh_assert (libMesh::initialized());
97 337704 : }
98 :
99 :
100 :
101 24400 : MeshBase::MeshBase (const MeshBase & other_mesh) :
102 : ParallelObject (other_mesh),
103 24400 : boundary_info (new BoundaryInfo(*this)), // BoundaryInfo has protected ctor, can't use std::make_unique
104 24400 : _n_parts (other_mesh._n_parts),
105 24400 : _default_mapping_type(other_mesh._default_mapping_type),
106 24400 : _default_mapping_data(other_mesh._default_mapping_data),
107 22800 : _preparation (other_mesh._preparation),
108 22800 : _element_stored_range (),
109 22800 : _const_active_local_element_stored_range (),
110 22800 : _point_locator (),
111 24400 : _count_lower_dim_elems_in_point_locator(other_mesh._count_lower_dim_elems_in_point_locator),
112 22800 : _partitioner (),
113 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
114 24400 : _next_unique_id(other_mesh._next_unique_id),
115 : #endif
116 : // If the other mesh interior_parent pointers just go back to
117 : // itself, so should we
118 24400 : _interior_mesh((other_mesh._interior_mesh == &other_mesh) ?
119 : this : other_mesh._interior_mesh),
120 24400 : _skip_noncritical_partitioning(other_mesh._skip_noncritical_partitioning),
121 24400 : _skip_all_partitioning(other_mesh._skip_all_partitioning),
122 24400 : _skip_renumber_nodes_and_elements(other_mesh._skip_renumber_nodes_and_elements),
123 24400 : _skip_find_neighbors(other_mesh._skip_find_neighbors),
124 24400 : _skip_detect_interior_parents(other_mesh._skip_detect_interior_parents),
125 24400 : _allow_remote_element_removal(other_mesh._allow_remote_element_removal),
126 24400 : _allow_node_and_elem_unique_id_overlap(other_mesh._allow_node_and_elem_unique_id_overlap),
127 812 : _elem_dims(other_mesh._elem_dims),
128 812 : _elem_default_orders(other_mesh._elem_default_orders),
129 24400 : _supported_nodal_order(other_mesh._supported_nodal_order),
130 812 : _mesh_subdomains(other_mesh._mesh_subdomains),
131 812 : _elemset_codes_inverse_map(other_mesh._elemset_codes_inverse_map),
132 812 : _all_elemset_ids(other_mesh._all_elemset_ids),
133 24400 : _spatial_dimension(other_mesh._spatial_dimension),
134 24400 : _default_ghosting(std::make_unique<GhostPointNeighbors>(*this)),
135 99964 : _point_locator_close_to_point_tol(other_mesh._point_locator_close_to_point_tol)
136 : {
137 812 : const GhostingFunctor * const other_default_ghosting = other_mesh._default_ghosting.get();
138 :
139 79484 : for (GhostingFunctor * const gf : other_mesh._ghosting_functors)
140 : {
141 : // If the other mesh is using default ghosting, then we will use our own
142 : // default ghosting
143 55084 : if (gf == other_default_ghosting)
144 : {
145 24400 : _ghosting_functors.push_back(_default_ghosting.get());
146 24400 : continue;
147 : }
148 :
149 60352 : std::shared_ptr<GhostingFunctor> clone_gf = gf->clone();
150 : // Some subclasses of GhostingFunctor might not override the
151 : // clone function yet. If this is the case, GhostingFunctor will
152 : // return nullptr by default. The clone function should be overridden
153 : // in all derived classes. This following code ("else") is written
154 : // for API upgrade. That will allow users gradually to update their code.
155 : // Once the API upgrade is done, we will come back and delete "else."
156 30684 : if (clone_gf)
157 : {
158 30684 : clone_gf->set_mesh(this);
159 60352 : add_ghosting_functor(clone_gf);
160 : }
161 : else
162 : {
163 : libmesh_deprecated();
164 0 : add_ghosting_functor(*gf);
165 : }
166 : }
167 :
168 24400 : if (other_mesh._partitioner.get())
169 47988 : _partitioner = other_mesh._partitioner->clone();
170 :
171 : #ifdef LIBMESH_ENABLE_PERIODIC
172 : // Deep copy of all periodic boundaries
173 24400 : if (other_mesh._disjoint_neighbor_boundary_pairs)
174 : {
175 136 : _disjoint_neighbor_boundary_pairs = std::make_unique<PeriodicBoundaries>();
176 :
177 213 : for (const auto & [id, pb] : *other_mesh._disjoint_neighbor_boundary_pairs)
178 142 : if (pb)
179 280 : (*_disjoint_neighbor_boundary_pairs)[id] = pb->clone();
180 : }
181 : #endif
182 :
183 : // _elemset_codes stores pointers to entries in _elemset_codes_inverse_map,
184 : // so it is not possible to simply copy it directly from other_mesh
185 24400 : for (const auto & [set, code] : _elemset_codes_inverse_map)
186 0 : _elemset_codes.emplace(code, &set);
187 24400 : }
188 :
189 426 : MeshBase& MeshBase::operator= (MeshBase && other_mesh)
190 : {
191 12 : LOG_SCOPE("operator=(&&)", "MeshBase");
192 :
193 : // Move assign as a ParallelObject.
194 12 : this->ParallelObject::operator=(other_mesh);
195 :
196 426 : _n_parts = other_mesh.n_partitions();
197 426 : _default_mapping_type = other_mesh.default_mapping_type();
198 426 : _default_mapping_data = other_mesh.default_mapping_data();
199 426 : _preparation = other_mesh._preparation;
200 12 : _element_stored_range = std::move(other_mesh._element_stored_range);
201 12 : _const_active_local_element_stored_range = std::move(other_mesh._const_active_local_element_stored_range);
202 12 : _point_locator = std::move(other_mesh._point_locator);
203 426 : _count_lower_dim_elems_in_point_locator = other_mesh.get_count_lower_dim_elems_in_point_locator();
204 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
205 426 : _next_unique_id = other_mesh.next_unique_id();
206 : #endif
207 : // If the other mesh interior_parent pointers just go back to
208 : // itself, so should we
209 426 : _interior_mesh = (other_mesh._interior_mesh == &other_mesh) ?
210 : this : other_mesh._interior_mesh;
211 426 : _skip_noncritical_partitioning = other_mesh.skip_noncritical_partitioning();
212 426 : _skip_all_partitioning = other_mesh.skip_partitioning();
213 426 : _skip_renumber_nodes_and_elements = !(other_mesh.allow_renumbering());
214 426 : _skip_find_neighbors = !(other_mesh.allow_find_neighbors());
215 426 : _skip_detect_interior_parents = !(other_mesh.allow_detect_interior_parents());
216 426 : _allow_remote_element_removal = other_mesh.allow_remote_element_removal();
217 426 : _allow_node_and_elem_unique_id_overlap = other_mesh.allow_node_and_elem_unique_id_overlap();
218 12 : _block_id_to_name = std::move(other_mesh._block_id_to_name);
219 12 : _elem_dims = std::move(other_mesh.elem_dimensions());
220 12 : _elem_default_orders = std::move(other_mesh.elem_default_orders());
221 426 : _supported_nodal_order = other_mesh.supported_nodal_order();
222 12 : _mesh_subdomains = other_mesh._mesh_subdomains;
223 12 : _elemset_codes = std::move(other_mesh._elemset_codes);
224 12 : _elemset_codes_inverse_map = std::move(other_mesh._elemset_codes_inverse_map);
225 12 : _all_elemset_ids = std::move(other_mesh._all_elemset_ids);
226 426 : _spatial_dimension = other_mesh.spatial_dimension();
227 426 : _elem_integer_names = std::move(other_mesh._elem_integer_names);
228 426 : _elem_integer_default_values = std::move(other_mesh._elem_integer_default_values);
229 426 : _node_integer_names = std::move(other_mesh._node_integer_names);
230 426 : _node_integer_default_values = std::move(other_mesh._node_integer_default_values);
231 426 : _point_locator_close_to_point_tol = other_mesh.get_point_locator_close_to_point_tol();
232 :
233 : #ifdef LIBMESH_ENABLE_PERIODIC
234 : // Deep copy of all periodic boundaries:
235 : // We must clone each PeriodicBoundaryBase in the source map,
236 : // since unique_ptr cannot be copied and we need independent instances
237 426 : if (other_mesh._disjoint_neighbor_boundary_pairs)
238 : {
239 0 : _disjoint_neighbor_boundary_pairs = std::make_unique<PeriodicBoundaries>();
240 :
241 0 : for (const auto & [id, pb] : *other_mesh._disjoint_neighbor_boundary_pairs)
242 0 : if (pb)
243 0 : (*_disjoint_neighbor_boundary_pairs)[id] = pb->clone();
244 : }
245 : #endif
246 :
247 : // This relies on our subclasses *not* invalidating pointers when we
248 : // do their portion of the move assignment later!
249 12 : boundary_info = std::move(other_mesh.boundary_info);
250 12 : boundary_info->set_mesh(*this);
251 :
252 : #ifdef DEBUG
253 : // Make sure that move assignment worked for pointers
254 12 : for (const auto & [set, code] : _elemset_codes_inverse_map)
255 : {
256 0 : auto it = _elemset_codes.find(code);
257 0 : libmesh_assert_msg(it != _elemset_codes.end(),
258 : "Elemset code " << code << " not found in _elmset_codes container.");
259 0 : libmesh_assert_equal_to(it->second, &set);
260 : }
261 : #endif
262 :
263 : // We're *not* really done at this point, but we have the problem
264 : // that some of our data movement might be expecting subclasses data
265 : // movement to happen first. We'll let subclasses handle that by
266 : // calling our post_dofobject_moves()
267 438 : return *this;
268 : }
269 :
270 :
271 15167 : bool MeshBase::operator== (const MeshBase & other_mesh) const
272 : {
273 1078 : LOG_SCOPE("operator==()", "MeshBase");
274 :
275 15167 : bool is_equal = this->locally_equals(other_mesh);
276 15167 : this->comm().min(is_equal);
277 16245 : return is_equal;
278 : }
279 :
280 :
281 15167 : bool MeshBase::locally_equals (const MeshBase & other_mesh) const
282 : {
283 : // Check whether (almost) everything in the base is equal
284 : //
285 : // We don't check _next_unique_id here, because it's expected to
286 : // change in a DistributedMesh prepare_for_use(); it's conceptually
287 : // "mutable".
288 : //
289 : // We use separate if statements instead of logical operators here,
290 : // to make it easy to see the failing condition when using a
291 : // debugger to figure out why a MeshTools::valid_is_prepared(mesh)
292 : // is failing.
293 15167 : if (_n_parts != other_mesh._n_parts)
294 0 : return false;
295 15167 : if (_default_mapping_type != other_mesh._default_mapping_type)
296 0 : return false;
297 15167 : if (_default_mapping_data != other_mesh._default_mapping_data)
298 0 : return false;
299 15167 : if (_preparation != other_mesh._preparation)
300 0 : return false;
301 16653 : if (_count_lower_dim_elems_in_point_locator !=
302 15167 : other_mesh._count_lower_dim_elems_in_point_locator)
303 0 : return false;
304 :
305 : // We should either both have our own interior parents or both not;
306 : // but if we both don't then we can't really assert anything else
307 : // because pointing at the same interior mesh is fair but so is
308 : // pointing at two different copies of "the same" interior mesh.
309 16653 : if ((_interior_mesh == this) !=
310 15167 : (other_mesh._interior_mesh == &other_mesh))
311 0 : return false;
312 :
313 15167 : if (_skip_noncritical_partitioning != other_mesh._skip_noncritical_partitioning)
314 0 : return false;
315 15167 : if (_skip_all_partitioning != other_mesh._skip_all_partitioning)
316 0 : return false;
317 15167 : if (_skip_renumber_nodes_and_elements != other_mesh._skip_renumber_nodes_and_elements)
318 0 : return false;
319 15167 : if (_skip_find_neighbors != other_mesh._skip_find_neighbors)
320 0 : return false;
321 15167 : if (_skip_detect_interior_parents != other_mesh._skip_detect_interior_parents)
322 0 : return false;
323 15167 : if (_allow_remote_element_removal != other_mesh._allow_remote_element_removal)
324 0 : return false;
325 15167 : if (_allow_node_and_elem_unique_id_overlap != other_mesh._allow_node_and_elem_unique_id_overlap)
326 0 : return false;
327 15167 : if (_spatial_dimension != other_mesh._spatial_dimension)
328 0 : return false;
329 15167 : if (_point_locator_close_to_point_tol != other_mesh._point_locator_close_to_point_tol)
330 0 : return false;
331 15167 : if (_block_id_to_name != other_mesh._block_id_to_name)
332 0 : return false;
333 15167 : if (_elem_dims != other_mesh._elem_dims)
334 0 : return false;
335 15167 : if (_elem_default_orders != other_mesh._elem_default_orders)
336 0 : return false;
337 15167 : if (_supported_nodal_order != other_mesh._supported_nodal_order)
338 0 : return false;
339 15167 : if (_mesh_subdomains != other_mesh._mesh_subdomains)
340 6 : return false;
341 14954 : if (_all_elemset_ids != other_mesh._all_elemset_ids)
342 0 : return false;
343 14954 : if (_elem_integer_names != other_mesh._elem_integer_names)
344 0 : return false;
345 15356 : if (_elem_integer_default_values != other_mesh._elem_integer_default_values)
346 0 : return false;
347 14954 : if (_node_integer_names != other_mesh._node_integer_names)
348 0 : return false;
349 15356 : if (_node_integer_default_values != other_mesh._node_integer_default_values)
350 0 : return false;
351 14954 : if (static_cast<bool>(_default_ghosting) != static_cast<bool>(other_mesh._default_ghosting))
352 0 : return false;
353 14954 : if (static_cast<bool>(_partitioner) != static_cast<bool>(other_mesh._partitioner))
354 0 : return false;
355 14954 : if (*boundary_info != *other_mesh.boundary_info)
356 6 : return false;
357 :
358 : // First check whether the "existence" of the two pointers differs (one present, one absent)
359 15807 : if (static_cast<bool>(_disjoint_neighbor_boundary_pairs) !=
360 1066 : static_cast<bool>(other_mesh._disjoint_neighbor_boundary_pairs))
361 0 : return false;
362 : // If both exist, compare the contents (Weak Test: just compare sizes like `_ghosting_functors`)
363 14745 : if (_disjoint_neighbor_boundary_pairs &&
364 1070 : (_disjoint_neighbor_boundary_pairs->size() != other_mesh._disjoint_neighbor_boundary_pairs->size()))
365 0 : return false;
366 :
367 : const constraint_rows_type & other_rows =
368 1066 : other_mesh.get_constraint_rows();
369 66377 : for (const auto & [node, row] : this->_constraint_rows)
370 : {
371 51636 : const dof_id_type node_id = node->id();
372 51636 : const Node * other_node = other_mesh.query_node_ptr(node_id);
373 51636 : if (!other_node)
374 0 : return false;
375 :
376 6416 : auto it = other_rows.find(other_node);
377 51636 : if (it == other_rows.end())
378 0 : return false;
379 :
380 6416 : const auto & other_row = it->second;
381 58052 : if (row.size() != other_row.size())
382 0 : return false;
383 :
384 231893 : for (auto i : index_range(row))
385 : {
386 18096 : const auto & [elem_pair, coef] = row[i];
387 18096 : const auto & [other_elem_pair, other_coef] = other_row[i];
388 18096 : libmesh_assert(elem_pair.first);
389 18096 : libmesh_assert(other_elem_pair.first);
390 180257 : if (elem_pair.first->id() !=
391 342418 : other_elem_pair.first->id() ||
392 180257 : elem_pair.second !=
393 198353 : other_elem_pair.second ||
394 180257 : coef != other_coef)
395 0 : return false;
396 : }
397 : }
398 :
399 14741 : for (const auto & [elemset_code, elemset_ptr] : this->_elemset_codes)
400 0 : if (const auto it = other_mesh._elemset_codes.find(elemset_code);
401 0 : it == other_mesh._elemset_codes.end() || *elemset_ptr != *it->second)
402 0 : return false;
403 :
404 : // FIXME: we have no good way to compare ghosting functors, since
405 : // they're in a vector of pointers, and we have no way *at all*
406 : // to compare ghosting functors, since they don't have operator==
407 : // defined and we encourage users to subclass them. We can check if
408 : // we have the same number, is all.
409 16203 : if (_ghosting_functors.size() !=
410 1462 : other_mesh._ghosting_functors.size())
411 0 : return false;
412 :
413 : // Same deal for partitioners. We tested that we both have one or
414 : // both don't, but are they equivalent? Let's guess "yes".
415 :
416 : // Now let the subclasses decide whether everything else is equal
417 14741 : return this->subclass_locally_equals(other_mesh);
418 : }
419 :
420 :
421 372698 : MeshBase::~MeshBase()
422 : {
423 362104 : this->MeshBase::clear();
424 :
425 10618 : libmesh_exceptionless_assert (!libMesh::closed());
426 1384780 : }
427 :
428 :
429 :
430 11125465 : unsigned int MeshBase::mesh_dimension() const
431 : {
432 11125465 : if (!_elem_dims.empty())
433 11125465 : return cast_int<unsigned int>(*_elem_dims.rbegin());
434 0 : return 0;
435 : }
436 :
437 :
438 :
439 0 : void MeshBase::set_elem_dimensions(std::set<unsigned char> elem_dims)
440 : {
441 : #ifdef DEBUG
442 : // In debug mode, we call cache_elem_data() and then make sure
443 : // the result actually agrees with what the user specified.
444 0 : parallel_object_only();
445 :
446 0 : this->cache_elem_data();
447 0 : libmesh_assert_msg(_elem_dims == elem_dims, \
448 : "Specified element dimensions does not match true element dimensions!");
449 : #endif
450 :
451 0 : _elem_dims = std::move(elem_dims);
452 0 : }
453 :
454 :
455 :
456 1775 : void MeshBase::add_elemset_code(dof_id_type code, MeshBase::elemset_type id_set)
457 : {
458 : // Populate inverse map, stealing id_set's resources
459 1725 : auto [it1, inserted1] = _elemset_codes_inverse_map.emplace(std::move(id_set), code);
460 :
461 : // Reference to the newly inserted (or previously existing) id_set
462 1775 : const auto & inserted_id_set = it1->first;
463 :
464 : // Keep track of all elemset ids ever added for O(1) n_elemsets()
465 : // performance. Only need to do this if we didn't know about this
466 : // id_set before...
467 1775 : if (inserted1)
468 1775 : _all_elemset_ids.insert(inserted_id_set.begin(), inserted_id_set.end());
469 :
470 : // Take the address of the newly emplaced set to use in
471 : // _elemset_codes, avoid duplicating std::set storage
472 1775 : auto [it2, inserted2] = _elemset_codes.emplace(code, &inserted_id_set);
473 :
474 : // Throw an error if this code already exists with a pointer to a
475 : // different set of ids.
476 1775 : libmesh_error_msg_if(!inserted2 && it2->second != &inserted_id_set,
477 : "The elemset code " << code << " already exists with a different id_set.");
478 1775 : }
479 :
480 :
481 :
482 15010 : unsigned int MeshBase::n_elemsets() const
483 : {
484 15010 : return _all_elemset_ids.size();
485 : }
486 :
487 2963 : void MeshBase::get_elemsets(dof_id_type elemset_code, MeshBase::elemset_type & id_set_to_fill) const
488 : {
489 : // If we don't recognize this elemset_code, hand back an empty set
490 102 : id_set_to_fill.clear();
491 :
492 2963 : if (const auto it = _elemset_codes.find(elemset_code);
493 102 : it != _elemset_codes.end())
494 1386 : id_set_to_fill.insert(it->second->begin(), it->second->end());
495 2963 : }
496 :
497 852 : dof_id_type MeshBase::get_elemset_code(const MeshBase::elemset_type & id_set) const
498 : {
499 24 : auto it = _elemset_codes_inverse_map.find(id_set);
500 852 : return (it == _elemset_codes_inverse_map.end()) ? DofObject::invalid_id : it->second;
501 : }
502 :
503 8453 : std::vector<dof_id_type> MeshBase::get_elemset_codes() const
504 : {
505 240 : std::vector<dof_id_type> ret;
506 8453 : ret.reserve(_elemset_codes.size());
507 9234 : for (const auto & pr : _elemset_codes)
508 781 : ret.push_back(pr.first);
509 8453 : return ret;
510 : }
511 :
512 284 : void MeshBase::change_elemset_code(dof_id_type old_code, dof_id_type new_code)
513 : {
514 : // Look up elemset ids for old_code
515 8 : auto it = _elemset_codes.find(old_code);
516 :
517 : // If we don't have the old_code, then do nothing. Alternatively, we
518 : // could throw an error since trying to change an elemset code you
519 : // don't have could indicate there's a problem...
520 284 : if (it == _elemset_codes.end())
521 0 : return;
522 :
523 : // Make copy of the set of elemset ids. We are not changing these,
524 : // only updating the elemset code it corresponds to.
525 284 : elemset_type id_set_copy = *(it->second);
526 :
527 : // Look up the corresponding entry in the inverse map. Note: we want
528 : // the iterator because we are going to remove it.
529 8 : auto inverse_it = _elemset_codes_inverse_map.find(id_set_copy);
530 284 : libmesh_error_msg_if(inverse_it == _elemset_codes_inverse_map.end(),
531 : "Expected _elemset_codes_inverse_map entry for elemset code " << old_code);
532 :
533 : // Erase entry from inverse map
534 276 : _elemset_codes_inverse_map.erase(inverse_it);
535 :
536 : // Erase entry from forward map
537 276 : _elemset_codes.erase(it);
538 :
539 : // Add new code with original set of ids.
540 560 : this->add_elemset_code(new_code, id_set_copy);
541 :
542 : // We can't update any actual elemset codes if there is no extra integer defined for it.
543 284 : if (!this->has_elem_integer("elemset_code"))
544 0 : return;
545 :
546 : // Get index of elemset_code extra integer
547 284 : unsigned int elemset_index = this->get_elem_integer_index("elemset_code");
548 :
549 : // Loop over all elems and update code
550 : Threads::parallel_for
551 284 : (this->element_stored_range(),
552 1240 : [elemset_index, old_code, new_code](const ElemRange & range)
553 : {
554 5254 : for (Elem * elem : range)
555 : {
556 : dof_id_type elemset_code =
557 4970 : elem->get_extra_integer(elemset_index);
558 :
559 4970 : if (elemset_code == old_code)
560 2415 : elem->set_extra_integer(elemset_index, new_code);
561 : }
562 284 : });
563 : }
564 :
565 284 : void MeshBase::change_elemset_id(elemset_id_type old_id, elemset_id_type new_id)
566 : {
567 : // Early return if we don't have old_id
568 8 : if (!_all_elemset_ids.count(old_id))
569 0 : return;
570 :
571 : // Throw an error if the new_id is already used
572 8 : libmesh_error_msg_if(_all_elemset_ids.count(new_id),
573 : "Cannot change elemset id " << old_id <<
574 : " to " << new_id << ", " << new_id << " already exists.");
575 :
576 : // We will build up a new version of the inverse map so we can iterate over
577 : // the current one without invalidating anything.
578 16 : std::map<MeshBase::elemset_type, dof_id_type> new_elemset_codes_inverse_map;
579 852 : for (const auto & [id_set, elemset_code] : _elemset_codes_inverse_map)
580 : {
581 32 : auto id_set_copy = id_set;
582 16 : if (id_set_copy.count(old_id))
583 : {
584 : // Remove old_id, insert new_id
585 8 : id_set_copy.erase(old_id);
586 276 : id_set_copy.insert(new_id);
587 : }
588 :
589 : // Store in new version of map
590 552 : new_elemset_codes_inverse_map.emplace(id_set_copy, elemset_code);
591 : }
592 :
593 : // Swap existing map with newly-built one
594 8 : _elemset_codes_inverse_map.swap(new_elemset_codes_inverse_map);
595 :
596 : // Reconstruct _elemset_codes map
597 8 : _elemset_codes.clear();
598 852 : for (const auto & [id_set, elemset_code] : _elemset_codes_inverse_map)
599 568 : _elemset_codes.emplace(elemset_code, &id_set);
600 :
601 : // Update _all_elemset_ids
602 8 : _all_elemset_ids.erase(old_id);
603 276 : _all_elemset_ids.insert(new_id);
604 : }
605 :
606 248322 : unsigned int MeshBase::spatial_dimension () const
607 : {
608 256628 : return cast_int<unsigned int>(_spatial_dimension);
609 : }
610 :
611 :
612 :
613 280283 : void MeshBase::set_spatial_dimension(unsigned char d)
614 : {
615 : // The user can set the _spatial_dimension however they wish,
616 : // libMesh will only *increase* the spatial dimension, however,
617 : // never decrease it.
618 280283 : _spatial_dimension = d;
619 280283 : }
620 :
621 :
622 :
623 4716 : unsigned int MeshBase::add_elem_integer(std::string name,
624 : bool allocate_data,
625 : dof_id_type default_value)
626 : {
627 5912 : for (auto i : index_range(_elem_integer_names))
628 1416 : if (_elem_integer_names[i] == name)
629 : {
630 8 : libmesh_assert_less(i, _elem_integer_default_values.size());
631 284 : _elem_integer_default_values[i] = default_value;
632 284 : return i;
633 : }
634 :
635 136 : libmesh_assert_equal_to(_elem_integer_names.size(),
636 : _elem_integer_default_values.size());
637 4432 : _elem_integer_names.push_back(std::move(name));
638 4432 : _elem_integer_default_values.push_back(default_value);
639 4432 : if (allocate_data)
640 3844 : this->size_elem_extra_integers();
641 4568 : return _elem_integer_names.size()-1;
642 : }
643 :
644 :
645 :
646 38205 : std::vector<unsigned int> MeshBase::add_elem_integers(const std::vector<std::string> & names,
647 : bool allocate_data,
648 : const std::vector<dof_id_type> * default_values)
649 : {
650 1216 : libmesh_assert(!default_values || default_values->size() == names.size());
651 1216 : libmesh_assert_equal_to(_elem_integer_names.size(), _elem_integer_default_values.size());
652 :
653 2432 : std::unordered_map<std::string, std::size_t> name_indices;
654 39057 : for (auto i : index_range(_elem_integer_names))
655 852 : name_indices[_elem_integer_names[i]] = i;
656 :
657 39397 : std::vector<unsigned int> returnval(names.size());
658 :
659 1216 : bool added_an_integer = false;
660 40710 : for (auto i : index_range(names))
661 : {
662 156 : const std::string & name = names[i];
663 2505 : if (const auto it = name_indices.find(name);
664 78 : it != name_indices.end())
665 : {
666 426 : returnval[i] = it->second;
667 438 : _elem_integer_default_values[it->second] =
668 426 : default_values ? (*default_values)[i] : DofObject::invalid_id;
669 : }
670 : else
671 : {
672 2145 : returnval[i] = _elem_integer_names.size();
673 2079 : name_indices[name] = returnval[i];
674 2079 : _elem_integer_names.push_back(name);
675 : _elem_integer_default_values.push_back
676 2883 : (default_values ? (*default_values)[i] : DofObject::invalid_id);
677 66 : added_an_integer = true;
678 : }
679 : }
680 :
681 38205 : if (allocate_data && added_an_integer)
682 1065 : this->size_elem_extra_integers();
683 :
684 39421 : return returnval;
685 : }
686 :
687 :
688 :
689 3278 : unsigned int MeshBase::get_elem_integer_index(std::string_view name) const
690 : {
691 4130 : for (auto i : index_range(_elem_integer_names))
692 4037 : if (_elem_integer_names[i] == name)
693 3278 : return i;
694 :
695 0 : libmesh_error_msg("Unknown elem integer " << name);
696 : return libMesh::invalid_uint;
697 : }
698 :
699 :
700 :
701 8635 : bool MeshBase::has_elem_integer(std::string_view name) const
702 : {
703 9629 : for (auto & entry : _elem_integer_names)
704 2367 : if (entry == name)
705 40 : return true;
706 :
707 640 : return false;
708 : }
709 :
710 :
711 :
712 16916 : unsigned int MeshBase::add_node_integer(std::string name,
713 : bool allocate_data,
714 : dof_id_type default_value)
715 : {
716 27329 : for (auto i : index_range(_node_integer_names))
717 10208 : if (_node_integer_names[i] == name)
718 : {
719 16 : libmesh_assert_less(i, _node_integer_default_values.size());
720 568 : _node_integer_default_values[i] = default_value;
721 568 : return i;
722 : }
723 :
724 702 : libmesh_assert_equal_to(_node_integer_names.size(),
725 : _node_integer_default_values.size());
726 16348 : _node_integer_names.push_back(std::move(name));
727 16348 : _node_integer_default_values.push_back(default_value);
728 16348 : if (allocate_data)
729 7120 : this->size_node_extra_integers();
730 17050 : return _node_integer_names.size()-1;
731 : }
732 :
733 :
734 :
735 38063 : std::vector<unsigned int> MeshBase::add_node_integers(const std::vector<std::string> & names,
736 : bool allocate_data,
737 : const std::vector<dof_id_type> * default_values)
738 : {
739 1212 : libmesh_assert(!default_values || default_values->size() == names.size());
740 1212 : libmesh_assert_equal_to(_node_integer_names.size(), _node_integer_default_values.size());
741 :
742 2424 : std::unordered_map<std::string, std::size_t> name_indices;
743 38631 : for (auto i : index_range(_node_integer_names))
744 568 : name_indices[_node_integer_names[i]] = i;
745 :
746 39251 : std::vector<unsigned int> returnval(names.size());
747 :
748 1212 : bool added_an_integer = false;
749 40695 : for (auto i : index_range(names))
750 : {
751 192 : const std::string & name = names[i];
752 2632 : if (const auto it = name_indices.find(name);
753 96 : it != name_indices.end())
754 : {
755 0 : returnval[i] = it->second;
756 0 : _node_integer_default_values[it->second] =
757 0 : default_values ? (*default_values)[i] : DofObject::invalid_id;
758 : }
759 : else
760 : {
761 2728 : returnval[i] = _node_integer_names.size();
762 2632 : name_indices[name] = returnval[i];
763 2632 : _node_integer_names.push_back(name);
764 : _node_integer_default_values.push_back
765 3724 : (default_values ? (*default_values)[i] : DofObject::invalid_id);
766 96 : added_an_integer = true;
767 : }
768 : }
769 :
770 38063 : if (allocate_data && added_an_integer)
771 1010 : this->size_node_extra_integers();
772 :
773 39275 : return returnval;
774 : }
775 :
776 :
777 :
778 710 : unsigned int MeshBase::get_node_integer_index(std::string_view name) const
779 : {
780 1988 : for (auto i : index_range(_node_integer_names))
781 1968 : if (_node_integer_names[i] == name)
782 710 : return i;
783 :
784 0 : libmesh_error_msg("Unknown node integer " << name);
785 : return libMesh::invalid_uint;
786 : }
787 :
788 :
789 :
790 1136 : bool MeshBase::has_node_integer(std::string_view name) const
791 : {
792 3134 : for (auto & entry : _node_integer_names)
793 3134 : if (entry == name)
794 32 : return true;
795 :
796 0 : return false;
797 : }
798 :
799 :
800 :
801 45651 : void MeshBase::remove_orphaned_nodes ()
802 : {
803 2796 : LOG_SCOPE("remove_orphaned_nodes()", "MeshBase");
804 :
805 : // Will hold the set of nodes that are currently connected to elements
806 1398 : std::unordered_set<Node *> connected_nodes;
807 :
808 : // Loop over the elements. Find which nodes are connected to at
809 : // least one of them.
810 3975696 : for (const auto & element : this->element_ptr_range())
811 19252824 : for (auto & n : element->node_ref_range())
812 17077085 : connected_nodes.insert(&n);
813 :
814 11270692 : for (const auto & node : this->node_ptr_range())
815 725862 : if (!connected_nodes.count(node))
816 125903 : this->delete_node(node);
817 :
818 45651 : _preparation.has_removed_orphaned_nodes = true;
819 45651 : }
820 :
821 :
822 :
823 : #ifdef LIBMESH_ENABLE_DEPRECATED
824 0 : void MeshBase::prepare_for_use (const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
825 : {
826 : libmesh_deprecated();
827 :
828 : // We only respect the users wish if they tell us to skip renumbering. If they tell us not to
829 : // skip renumbering but someone previously called allow_renumbering(false), then the latter takes
830 : // precedence
831 0 : if (skip_renumber_nodes_and_elements)
832 0 : this->allow_renumbering(false);
833 :
834 : // We always accept the user's value for skip_find_neighbors, in contrast to skip_renumber
835 0 : const bool old_allow_find_neighbors = this->allow_find_neighbors();
836 0 : this->allow_find_neighbors(!skip_find_neighbors);
837 :
838 0 : this->prepare_for_use();
839 :
840 0 : this->allow_find_neighbors(old_allow_find_neighbors);
841 0 : }
842 :
843 0 : void MeshBase::prepare_for_use (const bool skip_renumber_nodes_and_elements)
844 : {
845 : libmesh_deprecated();
846 :
847 : // We only respect the users wish if they tell us to skip renumbering. If they tell us not to
848 : // skip renumbering but someone previously called allow_renumbering(false), then the latter takes
849 : // precedence
850 0 : if (skip_renumber_nodes_and_elements)
851 0 : this->allow_renumbering(false);
852 :
853 0 : this->prepare_for_use();
854 0 : }
855 : #endif // LIBMESH_ENABLE_DEPRECATED
856 :
857 :
858 :
859 :
860 442693 : void MeshBase::prepare_for_use ()
861 : {
862 : // Mark everything as unprepared, except for those things we've been
863 : // told we don't need to prepare, for backwards compatibility
864 442693 : this->clear_point_locator();
865 442693 : this->clear_stored_ranges();
866 442693 : _preparation = false;
867 442693 : _preparation.has_neighbor_ptrs = _skip_find_neighbors;
868 442693 : _preparation.has_removed_remote_elements = !_allow_remote_element_removal;
869 :
870 442693 : this->complete_preparation();
871 442693 : }
872 :
873 :
874 640236 : void MeshBase::complete_preparation()
875 : {
876 37572 : LOG_SCOPE("complete_preparation()", "MeshBase");
877 :
878 18786 : parallel_object_only();
879 :
880 18786 : libmesh_assert(this->comm().verify(this->is_serial()));
881 :
882 : // If we don't go into this method with valid constraint rows, we're
883 : // only going to be able to make that worse.
884 : #ifdef DEBUG
885 18786 : MeshTools::libmesh_assert_valid_constraint_rows(*this);
886 : #endif
887 :
888 : // A distributed mesh may have processors with no elements (or
889 : // processors with no elements of higher dimension, if we ever
890 : // support mixed-dimension meshes), but we want consistent
891 : // mesh_dimension anyways.
892 : //
893 : // cache_elem_data() should get the elem_dimensions() and
894 : // mesh_dimension() correct later, and we don't need it earlier.
895 :
896 :
897 : // Renumber the nodes and elements so that they in contiguous
898 : // blocks. By default, _skip_renumber_nodes_and_elements is false.
899 : //
900 : // Instances where you if prepare_for_use() should not renumber the nodes
901 : // and elements include reading in e.g. an xda/r or gmv file. In
902 : // this case, the ordering of the nodes may depend on an accompanying
903 : // solution, and the node ordering cannot be changed.
904 :
905 :
906 : // Mesh modification operations might not leave us with consistent
907 : // id counts, or might leave us with orphaned nodes we're no longer
908 : // using, but our partitioner might need that consistency and/or
909 : // might be confused by orphaned nodes.
910 640236 : if (!_skip_renumber_nodes_and_elements)
911 : {
912 592171 : if (!_preparation.has_removed_orphaned_nodes ||
913 194916 : !_preparation.has_synched_id_counts)
914 397255 : this->renumber_nodes_and_elements();
915 : }
916 : else
917 : {
918 48065 : if (!_preparation.has_removed_orphaned_nodes)
919 45651 : this->remove_orphaned_nodes();
920 48065 : if (!_preparation.has_synched_id_counts)
921 45651 : this->update_parallel_id_counts();
922 : }
923 :
924 : // Let all the elements find their neighbors
925 640236 : if (!_skip_find_neighbors && !_preparation.has_neighbor_ptrs)
926 431078 : this->find_neighbors();
927 :
928 : // The user may have set boundary conditions. We require that the
929 : // boundary conditions were set consistently. Because we examine
930 : // neighbors when evaluating non-raw boundary condition IDs, this
931 : // assert is only valid when our neighbor links are in place.
932 : #ifdef DEBUG
933 18786 : MeshTools::libmesh_assert_valid_boundary_ids(*this);
934 : #endif
935 :
936 : // Search the mesh for all the dimensions of the elements
937 : // and cache them.
938 640236 : if (!_preparation.has_cached_elem_data)
939 631077 : this->cache_elem_data();
940 :
941 : // libMesh expects every processor to know about every subdomain
942 : // name, but distributed mesh generators may only have set names for
943 : // the part of the mesh they know about, so make sure the map is
944 : // consistent everywhere.
945 640236 : if (!_preparation.has_synched_subdomain_name_map)
946 442693 : this->sync_subdomain_name_map();
947 :
948 : // Search the mesh for elements that have a neighboring element
949 : // of dim+1 and set that element as the interior parent
950 640236 : if (!_preparation.has_interior_parent_ptrs)
951 : {
952 442693 : if (!_skip_detect_interior_parents)
953 442693 : this->detect_interior_parents();
954 : else
955 : {
956 : // We must set the flag that says "interior parent pointers have been set up"
957 : // even though we skip detect_interior_parents().
958 0 : _preparation.has_interior_parent_ptrs = true;
959 : }
960 : }
961 :
962 : // Fix up node unique ids in case mesh generation code didn't take
963 : // exceptional care to do so.
964 : // MeshCommunication().make_node_unique_ids_parallel_consistent(*this);
965 :
966 : // We're going to still require that mesh generation code gets
967 : // element unique ids consistent.
968 : #if defined(DEBUG) && defined(LIBMESH_ENABLE_UNIQUE_ID)
969 18786 : MeshTools::libmesh_assert_valid_unique_ids(*this);
970 : #endif
971 :
972 : // Allow our GhostingFunctor objects to reinit if necessary.
973 : // Do this before partitioning and redistributing, and before
974 : // deleting remote elements.
975 640236 : if (!_preparation.has_reinit_ghosting_functors)
976 442906 : this->reinit_ghosting_functors();
977 :
978 : // Partition the mesh unless *all* partitioning is to be skipped.
979 : // If only noncritical partitioning is to be skipped, the
980 : // partition() call will still check for orphaned nodes.
981 640236 : if (!skip_partitioning() && !_preparation.is_partitioned)
982 13122 : this->partition();
983 401802 : else if (!this->n_unpartitioned_elem() &&
984 5664 : !this->n_unpartitioned_nodes())
985 200901 : _preparation.is_partitioned = true;
986 :
987 : // If we're using DistributedMesh, we'll probably want it
988 : // parallelized.
989 640236 : if (this->_allow_remote_element_removal &&
990 625659 : !_preparation.has_removed_remote_elements)
991 385859 : this->delete_remote_elements();
992 : else
993 254377 : _preparation.has_removed_remote_elements = true;
994 :
995 : // Much of our boundary info may have been for now-remote parts of the mesh,
996 : // in which case we don't want to keep local copies of data meant to be
997 : // local. On the other hand we may have deleted, or the user may have added in
998 : // a distributed fashion, boundary data that is meant to be global. So we
999 : // handle both of those scenarios here
1000 640236 : if (!_preparation.has_boundary_id_sets)
1001 95749 : this->get_boundary_info().regenerate_id_sets();
1002 :
1003 640236 : if (!_skip_renumber_nodes_and_elements)
1004 592171 : this->renumber_nodes_and_elements();
1005 :
1006 : // The mesh is now prepared for use, with the possible exception of
1007 : // partitioning that was supposed to be skipped, and it should know
1008 : // it.
1009 : #ifndef NDEBUG
1010 18786 : Preparation completed_preparation = _preparation;
1011 18786 : if (skip_partitioning())
1012 192 : completed_preparation.is_partitioned = true;
1013 18786 : libmesh_assert(completed_preparation);
1014 : #endif
1015 :
1016 : #ifdef DEBUG
1017 18786 : MeshTools::libmesh_assert_valid_boundary_ids(*this);
1018 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
1019 18786 : MeshTools::libmesh_assert_valid_unique_ids(*this);
1020 : #endif
1021 : #endif
1022 640236 : }
1023 :
1024 : void
1025 442906 : MeshBase::reinit_ghosting_functors()
1026 : {
1027 996104 : for (auto & gf : _ghosting_functors)
1028 : {
1029 16794 : libmesh_assert(gf);
1030 553198 : gf->mesh_reinit();
1031 : }
1032 :
1033 442906 : _preparation.has_reinit_ghosting_functors = true;
1034 442906 : }
1035 :
1036 1028132 : void MeshBase::clear ()
1037 : {
1038 : // Reset the number of partitions
1039 1028132 : _n_parts = 1;
1040 :
1041 : // Reset the preparation flags
1042 1028132 : _preparation = false;
1043 :
1044 : // Clear boundary information
1045 1028132 : if (boundary_info)
1046 1026854 : boundary_info->clear();
1047 :
1048 : // Clear cached element data
1049 30733 : _elem_dims.clear();
1050 30733 : _elem_default_orders.clear();
1051 1028132 : _supported_nodal_order = MAXIMUM;
1052 :
1053 30733 : _elemset_codes.clear();
1054 30733 : _elemset_codes_inverse_map.clear();
1055 :
1056 30733 : _constraint_rows.clear();
1057 :
1058 : // Clear our point locator.
1059 1028132 : this->clear_point_locator();
1060 1028132 : this->clear_stored_ranges();
1061 1028132 : }
1062 :
1063 :
1064 90823 : bool MeshBase::is_prepared() const
1065 : {
1066 90823 : return static_cast<bool>(_preparation);
1067 : }
1068 :
1069 :
1070 2059 : void MeshBase::unset_is_prepared()
1071 : {
1072 2059 : _preparation = false;
1073 2059 : this->clear_point_locator();
1074 2059 : this->clear_stored_ranges();
1075 2059 : }
1076 :
1077 :
1078 1005125 : void MeshBase::add_ghosting_functor(GhostingFunctor & ghosting_functor)
1079 : {
1080 : // We used to implicitly support duplicate inserts to std::set
1081 : #ifdef LIBMESH_ENABLE_DEPRECATED
1082 : _ghosting_functors.erase
1083 947753 : (std::remove(_ghosting_functors.begin(),
1084 : _ghosting_functors.end(),
1085 1005125 : &ghosting_functor),
1086 86058 : _ghosting_functors.end());
1087 : #endif
1088 :
1089 : // We shouldn't have two copies of the same functor
1090 28686 : libmesh_assert(std::find(_ghosting_functors.begin(),
1091 : _ghosting_functors.end(),
1092 : &ghosting_functor) ==
1093 : _ghosting_functors.end());
1094 :
1095 1005125 : _ghosting_functors.push_back(&ghosting_functor);
1096 1005125 : }
1097 :
1098 :
1099 :
1100 974583 : void MeshBase::remove_ghosting_functor(GhostingFunctor & ghosting_functor)
1101 : {
1102 919235 : auto raw_it = std::find(_ghosting_functors.begin(),
1103 1002257 : _ghosting_functors.end(), &ghosting_functor);
1104 :
1105 : // The DofMap has a "to_mesh" parameter that tells it to avoid
1106 : // registering a new functor with the mesh, but it doesn't keep
1107 : // track of which functors weren't added, so we'll support "remove a
1108 : // functor that isn't there" just like we did with set::erase
1109 : // before.
1110 974583 : if (raw_it != _ghosting_functors.end())
1111 973660 : _ghosting_functors.erase(raw_it);
1112 :
1113 : // We shouldn't have had two copies of the same functor
1114 27674 : libmesh_assert(std::find(_ghosting_functors.begin(),
1115 : _ghosting_functors.end(),
1116 : &ghosting_functor) ==
1117 : _ghosting_functors.end());
1118 :
1119 974583 : if (const auto it = _shared_functors.find(&ghosting_functor);
1120 27674 : it != _shared_functors.end())
1121 69 : _shared_functors.erase(it);
1122 974583 : }
1123 :
1124 :
1125 :
1126 27995 : void MeshBase::subdomain_ids (std::set<subdomain_id_type> & ids, const bool global /* = true */) const
1127 : {
1128 : // This requires an inspection on every processor
1129 1298 : if (global)
1130 1298 : parallel_object_only();
1131 :
1132 34 : struct SBDInserter {
1133 : std::set<subdomain_id_type> my_ids;
1134 :
1135 1298 : SBDInserter () {}
1136 36 : SBDInserter (SBDInserter &, Threads::split) {}
1137 :
1138 28101 : void operator()(const ConstElemRange & range) {
1139 2218422 : for (const Elem * elem : range)
1140 2190321 : my_ids.insert(elem->subdomain_id());
1141 28101 : }
1142 :
1143 36 : void join(SBDInserter & other) {
1144 36 : my_ids.merge(other.my_ids);
1145 36 : }
1146 : };
1147 :
1148 2596 : SBDInserter inserter;
1149 27995 : Threads::parallel_reduce(this->active_local_element_stored_range(), inserter);
1150 :
1151 1298 : ids.swap(inserter.my_ids);
1152 :
1153 27995 : if (global)
1154 : {
1155 : // Only include the unpartitioned elements if the user requests the global IDs.
1156 : // In the case of the local subdomain IDs, it doesn't make sense to include the
1157 : // unpartitioned elements because said elements do not have a sense of locality.
1158 58166 : for (const auto & elem : this->active_unpartitioned_element_ptr_range())
1159 28873 : ids.insert(elem->subdomain_id());
1160 :
1161 : // Some subdomains may only live on other processors
1162 27995 : this->comm().set_union(ids);
1163 : }
1164 27995 : }
1165 :
1166 :
1167 :
1168 642728 : void MeshBase::redistribute()
1169 : {
1170 : // We now have all elements and nodes redistributed; our ghosting
1171 : // functors should be ready to redistribute and/or recompute any
1172 : // cached data they use too.
1173 645916 : for (auto & gf : as_range(this->ghosting_functors_begin(),
1174 1452645 : this->ghosting_functors_end()))
1175 780629 : gf->redistribute();
1176 642728 : }
1177 :
1178 :
1179 :
1180 541202 : void MeshBase::update_post_partitioning()
1181 : {
1182 : // A range over all elements might still be fine here, but any range
1183 : // over local elements is obsolete if our partitioner changed the
1184 : // definition of "local".
1185 9870 : _const_active_local_element_stored_range.reset(nullptr);
1186 541202 : }
1187 :
1188 :
1189 :
1190 21170 : subdomain_id_type MeshBase::n_subdomains() const
1191 : {
1192 : // This requires an inspection on every processor
1193 596 : parallel_object_only();
1194 :
1195 1192 : std::set<subdomain_id_type> ids;
1196 :
1197 21170 : this->subdomain_ids (ids);
1198 :
1199 21766 : return cast_int<subdomain_id_type>(ids.size());
1200 : }
1201 :
1202 :
1203 :
1204 0 : subdomain_id_type MeshBase::n_local_subdomains() const
1205 : {
1206 0 : std::set<subdomain_id_type> ids;
1207 :
1208 0 : this->subdomain_ids (ids, /* global = */ false);
1209 :
1210 0 : return cast_int<subdomain_id_type>(ids.size());
1211 : }
1212 :
1213 :
1214 :
1215 :
1216 4105639 : dof_id_type MeshBase::n_nodes_on_proc (const processor_id_type proc_id) const
1217 : {
1218 : // We're either counting a processor's nodes or unpartitioned
1219 : // nodes
1220 11340 : libmesh_assert (proc_id < this->n_processors() ||
1221 : proc_id == DofObject::invalid_processor_id);
1222 :
1223 8199938 : return static_cast<dof_id_type>(std::distance (this->pid_nodes_begin(proc_id),
1224 12305577 : this->pid_nodes_end (proc_id)));
1225 : }
1226 :
1227 :
1228 :
1229 4544446 : dof_id_type MeshBase::n_elem_on_proc (const processor_id_type proc_id) const
1230 : {
1231 : // We're either counting a processor's elements or unpartitioned
1232 : // elements
1233 24472 : libmesh_assert (proc_id < this->n_processors() ||
1234 : proc_id == DofObject::invalid_processor_id);
1235 :
1236 9064420 : return static_cast<dof_id_type>(std::distance (this->pid_elements_begin(proc_id),
1237 13608866 : this->pid_elements_end (proc_id)));
1238 : }
1239 :
1240 :
1241 :
1242 1310412 : dof_id_type MeshBase::n_active_elem_on_proc (const processor_id_type proc_id) const
1243 : {
1244 2184 : libmesh_assert_less (proc_id, this->n_processors());
1245 2618640 : return static_cast<dof_id_type>(std::distance (this->active_pid_elements_begin(proc_id),
1246 3929052 : this->active_pid_elements_end (proc_id)));
1247 : }
1248 :
1249 :
1250 :
1251 0 : dof_id_type MeshBase::n_sub_elem () const
1252 : {
1253 0 : dof_id_type ne=0;
1254 :
1255 0 : for (const auto & elem : this->element_ptr_range())
1256 0 : ne += elem->n_sub_elem();
1257 :
1258 0 : return ne;
1259 : }
1260 :
1261 :
1262 :
1263 32266 : dof_id_type MeshBase::n_active_sub_elem () const
1264 : {
1265 1575 : dof_id_type ne=0;
1266 :
1267 25301373 : for (const auto & elem : this->active_element_ptr_range())
1268 25267532 : ne += elem->n_sub_elem();
1269 :
1270 32266 : return ne;
1271 : }
1272 :
1273 :
1274 :
1275 14995 : std::string MeshBase::get_info(const unsigned int verbosity /* = 0 */, const bool global /* = true */) const
1276 : {
1277 15835 : std::ostringstream oss;
1278 :
1279 14995 : oss << " Mesh Information:" << '\n';
1280 :
1281 14995 : if (!_elem_dims.empty())
1282 : {
1283 14995 : oss << " elem_dimensions()={";
1284 14995 : std::copy(_elem_dims.begin(),
1285 420 : --_elem_dims.end(), // --end() is valid if the set is non-empty
1286 14575 : std::ostream_iterator<unsigned int>(oss, ", "));
1287 14995 : oss << cast_int<unsigned int>(*_elem_dims.rbegin());
1288 14995 : oss << "}";
1289 14995 : if (!this->preparation().has_cached_elem_data)
1290 0 : oss << " (may be out of date)";
1291 14995 : oss << '\n';
1292 : }
1293 :
1294 14995 : if (!_elem_default_orders.empty())
1295 : {
1296 14995 : oss << " elem_default_orders()={";
1297 14995 : std::transform(_elem_default_orders.begin(),
1298 420 : --_elem_default_orders.end(),
1299 14995 : std::ostream_iterator<std::string>(oss, ", "),
1300 8 : [](Order o)
1301 276 : { return Utility::enum_to_string<Order>(o); });
1302 14995 : oss << Utility::enum_to_string<Order>(*_elem_default_orders.rbegin());
1303 14995 : oss << "}";
1304 14995 : if (!this->preparation().has_cached_elem_data)
1305 0 : oss << " (may be out of date)";
1306 14995 : oss << '\n';
1307 : }
1308 :
1309 14995 : oss << " supported_nodal_order()=" << this->_supported_nodal_order;
1310 14995 : if (!this->preparation().has_cached_elem_data)
1311 0 : oss << " (may be out of date)";
1312 14995 : oss << '\n';
1313 :
1314 14995 : oss << " spatial_dimension()=" << int(this->_spatial_dimension);
1315 14995 : if (!this->preparation().has_cached_elem_data)
1316 0 : oss << " (may be out of date)";
1317 14995 : oss << '\n';
1318 :
1319 14995 : oss << " n_nodes()=" << this->n_nodes() << '\n'
1320 14995 : << " n_local_nodes()=" << this->n_local_nodes() << '\n'
1321 15415 : << " n_elem()=" << this->n_elem() << '\n'
1322 29570 : << " n_local_elem()=" << this->n_local_elem() << '\n';
1323 : #ifdef LIBMESH_ENABLE_AMR
1324 29150 : oss << " n_active_elem()=" << this->n_active_elem() << '\n';
1325 : #endif
1326 14995 : if (global)
1327 15415 : oss << " n_subdomains()=" << static_cast<std::size_t>(this->n_subdomains()) << '\n';
1328 : else
1329 0 : oss << " n_local_subdomains()= " << static_cast<std::size_t>(this->n_local_subdomains()) << '\n';
1330 15415 : oss << " n_elemsets()=" << static_cast<std::size_t>(this->n_elemsets()) << '\n';
1331 14995 : if (!_elemset_codes.empty())
1332 0 : oss << " n_elemset_codes=" << _elemset_codes.size() << '\n';
1333 14995 : oss << " n_partitions()=" << static_cast<std::size_t>(this->n_partitions()) << '\n'
1334 15835 : << " n_processors()=" << static_cast<std::size_t>(this->n_processors()) << '\n'
1335 15415 : << " n_threads()=" << static_cast<std::size_t>(libMesh::n_threads()) << '\n'
1336 15835 : << " processor_id()=" << static_cast<std::size_t>(this->processor_id()) << '\n'
1337 15415 : << " is_prepared()=" << (this->is_prepared() ? "true" : "false") << '\n'
1338 43089 : << " is_replicated()=" << (this->is_replicated() ? "true" : "false") << '\n';
1339 :
1340 14995 : if (verbosity > 0)
1341 : {
1342 284 : if (global)
1343 : {
1344 8 : libmesh_parallel_only(this->comm());
1345 292 : if (this->processor_id() != 0)
1346 236 : oss << "\n Detailed global get_info() (verbosity > 0) is reduced and output to only rank 0.";
1347 : }
1348 :
1349 : // Helper for printing element types
1350 672 : const auto elem_type_helper = [](const std::set<int> & elem_types) {
1351 784 : std::stringstream ss;
1352 1344 : for (auto it = elem_types.begin(); it != elem_types.end();)
1353 : {
1354 1288 : ss << Utility::enum_to_string((ElemType)*it);
1355 672 : if (++it != elem_types.end())
1356 0 : ss << ", ";
1357 : }
1358 728 : return ss.str();
1359 560 : };
1360 :
1361 : // Helper for whether or not the given DofObject is to be included. If we're doing
1362 : // a global reduction, we also count unpartitioned objects on rank 0.
1363 871156 : const auto include_object = [this, &global](const DofObject & dof_object) {
1364 978188 : return this->processor_id() == dof_object.processor_id() ||
1365 250756 : (global &&
1366 42324 : this->processor_id() == 0 &&
1367 846328 : dof_object.processor_id() == DofObject::invalid_processor_id);
1368 276 : };
1369 :
1370 8 : Real volume = 0;
1371 :
1372 : // Add bounding box information
1373 284 : const auto bbox = global ? MeshTools::create_bounding_box(*this) : MeshTools::create_local_bounding_box(*this);
1374 284 : if (!global || this->processor_id() == 0)
1375 : oss << "\n " << (global ? "" : "Local ") << "Mesh Bounding Box:\n"
1376 48 : << " Minimum: " << bbox.min() << "\n"
1377 44 : << " Maximum: " << bbox.max() << "\n"
1378 88 : << " Delta: " << (bbox.max() - bbox.min()) << "\n";
1379 :
1380 : // Obtain the global or local element types
1381 16 : std::set<int> elem_types;
1382 49712 : for (const Elem * elem : this->active_local_element_ptr_range())
1383 49420 : elem_types.insert(elem->type());
1384 284 : if (global)
1385 : {
1386 : // Pick up unpartitioned elems on rank 0
1387 292 : if (this->processor_id() == 0)
1388 92 : for (const Elem * elem : this->active_unpartitioned_element_ptr_range())
1389 40 : elem_types.insert(elem->type());
1390 :
1391 284 : this->comm().set_union(elem_types);
1392 : }
1393 :
1394 : // Add element types
1395 284 : if (!global || this->processor_id() == 0)
1396 : oss << "\n " << (global ? "" : "Local ") << "Mesh Element Type(s):\n "
1397 140 : << elem_type_helper(elem_types) << "\n";
1398 :
1399 : // Reduce the nodeset ids
1400 16 : auto nodeset_ids = this->get_boundary_info().get_node_boundary_ids();
1401 284 : if (global)
1402 284 : this->comm().set_union(nodeset_ids);
1403 :
1404 : // Accumulate local information for each nodeset
1405 1608 : struct NodesetInfo
1406 : {
1407 : std::size_t num_nodes = 0;
1408 : BoundingBox bbox;
1409 : };
1410 16 : std::map<boundary_id_type, NodesetInfo> nodeset_info_map;
1411 91376 : for (const auto & [node, id] : this->get_boundary_info().get_nodeset_map())
1412 : {
1413 91092 : if (!include_object(*node))
1414 38364 : continue;
1415 :
1416 48672 : NodesetInfo & info = nodeset_info_map[id];
1417 :
1418 48672 : ++info.num_nodes;
1419 :
1420 48672 : if (verbosity > 1)
1421 48672 : info.bbox.union_with(*node);
1422 : }
1423 :
1424 : // Add nodeset info
1425 284 : if (!global || this->processor_id() == 0)
1426 : {
1427 48 : oss << "\n " << (global ? "" : "Local ") << "Mesh Nodesets:\n";
1428 48 : if (nodeset_ids.empty())
1429 0 : oss << " None\n";
1430 : }
1431 :
1432 8 : const auto & nodeset_name_map = this->get_boundary_info().get_nodeset_name_map();
1433 1988 : for (const auto id : nodeset_ids)
1434 : {
1435 1704 : NodesetInfo & info = nodeset_info_map[id];
1436 :
1437 : // Reduce the local information for this nodeset if required
1438 1704 : if (global)
1439 : {
1440 1704 : this->comm().sum(info.num_nodes);
1441 1704 : if (verbosity > 1)
1442 : {
1443 1752 : this->comm().min(info.bbox.min());
1444 1752 : this->comm().max(info.bbox.max());
1445 : }
1446 : }
1447 :
1448 1704 : const bool has_name = nodeset_name_map.count(id) && nodeset_name_map.at(id).size();
1449 1800 : const std::string name = has_name ? nodeset_name_map.at(id) : "";
1450 96 : if (global)
1451 48 : libmesh_assert(this->comm().verify(name));
1452 :
1453 1704 : if (global ? this->processor_id() == 0 : info.num_nodes > 0)
1454 : {
1455 288 : oss << " Nodeset " << id;
1456 288 : if (has_name)
1457 528 : oss << " (" << name << ")";
1458 312 : oss << ", " << info.num_nodes << " " << (global ? "" : "local ") << "nodes\n";
1459 :
1460 288 : if (verbosity > 1)
1461 : {
1462 288 : oss << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1463 288 : << info.bbox.min() << "\n"
1464 288 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1465 288 : << info.bbox.max() << "\n"
1466 288 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1467 288 : << (info.bbox.max() - info.bbox.min()) << "\n";
1468 : }
1469 : }
1470 : }
1471 :
1472 : // Reduce the sideset ids
1473 16 : auto sideset_ids = this->get_boundary_info().get_side_boundary_ids();
1474 284 : if (global)
1475 284 : this->comm().set_union(sideset_ids);
1476 :
1477 : // Accumulate local information for each sideset
1478 : struct SidesetInfo
1479 : {
1480 : std::size_t num_sides = 0;
1481 : Real volume = 0;
1482 : std::set<int> side_elem_types;
1483 : std::set<int> elem_types;
1484 : std::set<dof_id_type> elem_ids;
1485 : std::set<dof_id_type> node_ids;
1486 : BoundingBox bbox;
1487 : };
1488 16 : ElemSideBuilder side_builder;
1489 16 : std::map<boundary_id_type, SidesetInfo> sideset_info_map;
1490 70396 : for (const auto & pair : this->get_boundary_info().get_sideset_map())
1491 : {
1492 70112 : const Elem * elem = pair.first;
1493 63456 : if (!include_object(*elem))
1494 30176 : continue;
1495 :
1496 39936 : const auto id = pair.second.second;
1497 39936 : SidesetInfo & info = sideset_info_map[id];
1498 :
1499 39936 : const auto s = pair.second.first;
1500 39936 : const Elem & side = side_builder(*elem, s);
1501 :
1502 39936 : ++info.num_sides;
1503 39936 : info.side_elem_types.insert(side.type());
1504 39936 : info.elem_types.insert(elem->type());
1505 39936 : info.elem_ids.insert(elem->id());
1506 :
1507 199680 : for (const Node & node : side.node_ref_range())
1508 146432 : if (include_object(node))
1509 147552 : info.node_ids.insert(node.id());
1510 :
1511 39936 : if (verbosity > 1)
1512 : {
1513 39936 : info.volume += side.volume();
1514 39936 : info.bbox.union_with(side.loose_bounding_box());
1515 : }
1516 : }
1517 :
1518 : // Add sideset info
1519 284 : if (!global || this->processor_id() == 0)
1520 : {
1521 48 : oss << "\n " << (global ? "" : "Local ") << "Mesh Sidesets:\n";
1522 48 : if (sideset_ids.empty())
1523 0 : oss << " None\n";
1524 : }
1525 8 : const auto & sideset_name_map = this->get_boundary_info().get_sideset_name_map();
1526 1988 : for (const auto id : sideset_ids)
1527 : {
1528 1704 : SidesetInfo & info = sideset_info_map[id];
1529 :
1530 1704 : auto num_elems = info.elem_ids.size();
1531 1704 : auto num_nodes = info.node_ids.size();
1532 :
1533 : // Reduce the local information for this sideset if required
1534 1704 : if (global)
1535 : {
1536 1704 : this->comm().sum(info.num_sides);
1537 1704 : this->comm().set_union(info.side_elem_types, 0);
1538 1704 : this->comm().sum(num_elems);
1539 1704 : this->comm().set_union(info.elem_types, 0);
1540 1704 : this->comm().sum(num_nodes);
1541 1704 : if (verbosity > 1)
1542 : {
1543 1704 : this->comm().sum(info.volume);
1544 1752 : this->comm().min(info.bbox.min());
1545 1752 : this->comm().max(info.bbox.max());
1546 : }
1547 : }
1548 :
1549 1704 : const bool has_name = sideset_name_map.count(id) && sideset_name_map.at(id).size();
1550 1800 : const std::string name = has_name ? sideset_name_map.at(id) : "";
1551 96 : if (global)
1552 48 : libmesh_assert(this->comm().verify(name));
1553 :
1554 1704 : if (global ? this->processor_id() == 0 : info.num_sides > 0)
1555 : {
1556 288 : oss << " Sideset " << id;
1557 288 : if (has_name)
1558 528 : oss << " (" << name << ")";
1559 552 : oss << ", " << info.num_sides << " sides (" << elem_type_helper(info.side_elem_types) << ")"
1560 1056 : << ", " << num_elems << " " << (global ? "" : "local ") << "elems (" << elem_type_helper(info.elem_types) << ")"
1561 600 : << ", " << num_nodes << " " << (global ? "" : "local ") << "nodes\n";
1562 :
1563 288 : if (verbosity > 1)
1564 : {
1565 312 : oss << " " << (global ? "Side" : "Local side") << " volume: " << info.volume << "\n"
1566 288 : << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1567 288 : << info.bbox.min() << "\n"
1568 288 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1569 288 : << info.bbox.max() << "\n"
1570 288 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1571 288 : << (info.bbox.max() - info.bbox.min()) << "\n";
1572 : }
1573 : }
1574 : }
1575 :
1576 : // Reduce the edgeset ids
1577 16 : auto edgeset_ids = this->get_boundary_info().get_edge_boundary_ids();
1578 284 : if (global)
1579 284 : this->comm().set_union(edgeset_ids);
1580 :
1581 : // Accumulate local information for each edgeset
1582 0 : struct EdgesetInfo
1583 : {
1584 : std::size_t num_edges = 0;
1585 : std::set<int> edge_elem_types;
1586 : BoundingBox bbox;
1587 : };
1588 16 : std::map<boundary_id_type, EdgesetInfo> edgeset_info_map;
1589 284 : std::unique_ptr<const Elem> edge;
1590 :
1591 284 : for (const auto & pair : this->get_boundary_info().get_edgeset_map())
1592 : {
1593 0 : const Elem * elem = pair.first;
1594 0 : if (!include_object(*elem))
1595 0 : continue;
1596 :
1597 0 : const auto id = pair.second.second;
1598 0 : EdgesetInfo & info = edgeset_info_map[id];
1599 :
1600 0 : elem->build_edge_ptr(edge, pair.second.first);
1601 :
1602 0 : ++info.num_edges;
1603 0 : info.edge_elem_types.insert(edge->type());
1604 :
1605 0 : if (verbosity > 1)
1606 0 : info.bbox.union_with(edge->loose_bounding_box());
1607 : }
1608 :
1609 : // Add edgeset info
1610 284 : if (!global || this->processor_id() == 0)
1611 : {
1612 48 : oss << "\n " << (global ? "" : "Local ") << "Mesh Edgesets:\n";
1613 48 : if (edgeset_ids.empty())
1614 48 : oss << " None\n";
1615 : }
1616 :
1617 8 : const auto & edgeset_name_map = this->get_boundary_info().get_edgeset_name_map();
1618 284 : for (const auto id : edgeset_ids)
1619 : {
1620 0 : EdgesetInfo & info = edgeset_info_map[id];
1621 :
1622 : // Reduce the local information for this edgeset if required
1623 0 : if (global)
1624 : {
1625 0 : this->comm().sum(info.num_edges);
1626 0 : this->comm().set_union(info.edge_elem_types, 0);
1627 0 : if (verbosity > 1)
1628 : {
1629 0 : this->comm().min(info.bbox.min());
1630 0 : this->comm().min(info.bbox.max());
1631 : }
1632 : }
1633 :
1634 0 : const bool has_name = edgeset_name_map.count(id) && edgeset_name_map.at(id).size();
1635 0 : const std::string name = has_name ? edgeset_name_map.at(id) : "";
1636 0 : if (global)
1637 0 : libmesh_assert(this->comm().verify(name));
1638 :
1639 0 : if (global ? this->processor_id() == 0 : info.num_edges > 0)
1640 : {
1641 0 : oss << " Edgeset " << id;
1642 0 : if (has_name)
1643 0 : oss << " (" << name << ")";
1644 0 : oss << ", " << info.num_edges << " " << (global ? "" : "local ") << "edges ("
1645 0 : << elem_type_helper(info.edge_elem_types) << ")\n";
1646 :
1647 0 : if (verbosity > 1)
1648 : {
1649 0 : oss << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1650 0 : << info.bbox.min() << "\n"
1651 0 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1652 0 : << info.bbox.max() << "\n"
1653 0 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1654 0 : << (info.bbox.max() - info.bbox.min()) << "\n";
1655 : }
1656 : }
1657 : }
1658 :
1659 : // Reduce the block IDs and block names
1660 16 : std::set<subdomain_id_type> subdomains;
1661 194032 : for (const Elem * elem : this->active_element_ptr_range())
1662 92640 : if (include_object(*elem))
1663 49420 : subdomains.insert(elem->subdomain_id());
1664 284 : if (global)
1665 284 : this->comm().set_union(subdomains);
1666 :
1667 : // Accumulate local information for each subdomain
1668 0 : struct SubdomainInfo
1669 : {
1670 : std::size_t num_elems = 0;
1671 : Real volume = 0;
1672 : std::set<int> elem_types;
1673 : std::set<dof_id_type> active_node_ids;
1674 : #ifdef LIBMESH_ENABLE_AMR
1675 : std::size_t num_active_elems = 0;
1676 : #endif
1677 : BoundingBox bbox;
1678 : };
1679 16 : std::map<subdomain_id_type, SubdomainInfo> subdomain_info_map;
1680 194032 : for (const Elem * elem : this->element_ptr_range())
1681 92640 : if (include_object(*elem))
1682 : {
1683 49152 : SubdomainInfo & info = subdomain_info_map[elem->subdomain_id()];
1684 :
1685 49152 : ++info.num_elems;
1686 49152 : info.elem_types.insert(elem->type());
1687 :
1688 : #ifdef LIBMESH_ENABLE_AMR
1689 4096 : if (elem->active())
1690 49152 : ++info.num_active_elems;
1691 : #endif
1692 :
1693 442368 : for (const Node & node : elem->node_ref_range())
1694 392704 : if (include_object(node) && node.active())
1695 330608 : info.active_node_ids.insert(node.id());
1696 :
1697 49152 : if (verbosity > 1 && elem->active())
1698 : {
1699 49152 : info.volume += elem->volume();
1700 49152 : info.bbox.union_with(elem->loose_bounding_box());
1701 : }
1702 268 : }
1703 :
1704 : // Add subdomain info
1705 284 : oss << "\n " << (global ? "" : "Local ") << "Mesh Subdomains:\n";
1706 8 : const auto & subdomain_name_map = this->get_subdomain_name_map();
1707 568 : for (const auto id : subdomains)
1708 : {
1709 284 : SubdomainInfo & info = subdomain_info_map[id];
1710 :
1711 284 : auto num_active_nodes = info.active_node_ids.size();
1712 :
1713 : // Reduce the information for this subdomain if needed
1714 284 : if (global)
1715 : {
1716 284 : this->comm().sum(info.num_elems);
1717 : #ifdef LIBMESH_ENABLE_AMR
1718 284 : this->comm().sum(info.num_active_elems);
1719 : #endif
1720 284 : this->comm().sum(num_active_nodes);
1721 284 : this->comm().set_union(info.elem_types, 0);
1722 284 : if (verbosity > 1)
1723 : {
1724 292 : this->comm().min(info.bbox.min());
1725 292 : this->comm().max(info.bbox.max());
1726 284 : this->comm().sum(info.volume);
1727 : }
1728 : }
1729 284 : if (verbosity > 1)
1730 284 : volume += info.volume;
1731 :
1732 8 : const bool has_name = subdomain_name_map.count(id);
1733 292 : const std::string name = has_name ? subdomain_name_map.at(id) : "";
1734 16 : if (global)
1735 8 : libmesh_assert(this->comm().verify(name));
1736 :
1737 284 : if (!global || this->processor_id() == 0)
1738 : {
1739 48 : oss << " Subdomain " << id;
1740 48 : if (has_name)
1741 0 : oss << " (" << name << ")";
1742 48 : oss << ": " << info.num_elems << " " << (global ? "" : "local ") << "elems "
1743 56 : << "(" << elem_type_helper(info.elem_types);
1744 : #ifdef LIBMESH_ENABLE_AMR
1745 48 : oss << ", " << info.num_active_elems << " active";
1746 : #endif
1747 52 : oss << "), " << num_active_nodes << " " << (global ? "" : "local ") << "active nodes\n";
1748 48 : if (verbosity > 1)
1749 : {
1750 52 : oss << " " << (global ? "Volume" : "Local volume") << ": " << info.volume << "\n";
1751 48 : oss << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1752 48 : << info.bbox.min() << "\n"
1753 48 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1754 48 : << info.bbox.max() << "\n"
1755 48 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1756 48 : << (info.bbox.max() - info.bbox.min()) << "\n";
1757 : }
1758 : }
1759 : }
1760 :
1761 560 : oss << " " << (global ? "Global" : "Local") << " mesh volume = " << volume << "\n";
1762 :
1763 268 : }
1764 :
1765 15415 : return oss.str();
1766 14155 : }
1767 :
1768 :
1769 14995 : void MeshBase::print_info(std::ostream & os, const unsigned int verbosity /* = 0 */, const bool global /* = true */) const
1770 : {
1771 15415 : os << this->get_info(verbosity, global)
1772 420 : << std::endl;
1773 14995 : }
1774 :
1775 :
1776 0 : std::ostream & operator << (std::ostream & os, const MeshBase & m)
1777 : {
1778 0 : m.print_info(os);
1779 0 : return os;
1780 : }
1781 :
1782 :
1783 440187 : void MeshBase::partition (const unsigned int n_parts)
1784 : {
1785 : // If we get here and we have unpartitioned elements, we need that
1786 : // fixed.
1787 440187 : if (this->n_unpartitioned_elem() > 0)
1788 : {
1789 9560 : libmesh_assert (partitioner().get());
1790 9560 : libmesh_assert (this->is_serial());
1791 320800 : partitioner()->partition (*this, n_parts);
1792 : }
1793 : // A nullptr partitioner or a skip_partitioning(true) call or a
1794 : // skip_noncritical_partitioning(true) call means don't repartition;
1795 : // skip_noncritical_partitioning() checks all these.
1796 3586 : else if (!skip_noncritical_partitioning())
1797 : {
1798 112658 : partitioner()->partition (*this, n_parts);
1799 : }
1800 : else
1801 : {
1802 : // Adaptive coarsening may have "orphaned" nodes on processors
1803 : // whose elements no longer share them. We need to check for
1804 : // and possibly fix that.
1805 6729 : MeshTools::correct_node_proc_ids(*this);
1806 :
1807 : // Make sure locally cached partition count is correct
1808 6729 : this->recalculate_n_partitions();
1809 :
1810 : // Make sure any other locally cached data is correct
1811 6729 : this->update_post_partitioning();
1812 : }
1813 :
1814 440187 : _preparation.is_partitioned = true;
1815 440187 : }
1816 :
1817 19004 : void MeshBase::all_second_order (const bool full_ordered)
1818 : {
1819 19004 : this->all_second_order_range(this->element_ptr_range(), full_ordered);
1820 19004 : }
1821 :
1822 15255 : void MeshBase::all_complete_order ()
1823 : {
1824 15255 : this->all_complete_order_range(this->element_ptr_range());
1825 15255 : }
1826 :
1827 6729 : unsigned int MeshBase::recalculate_n_partitions()
1828 : {
1829 : // This requires an inspection on every processor
1830 170 : parallel_object_only();
1831 :
1832 6729 : unsigned int max_proc_id=0;
1833 :
1834 225762 : for (const auto & elem : this->active_local_element_ptr_range())
1835 223045 : max_proc_id = std::max(max_proc_id, static_cast<unsigned int>(elem->processor_id()));
1836 :
1837 : // The number of partitions is one more than the max processor ID.
1838 6729 : _n_parts = max_proc_id+1;
1839 :
1840 6729 : this->comm().max(_n_parts);
1841 :
1842 6729 : return _n_parts;
1843 : }
1844 :
1845 :
1846 :
1847 568128 : std::unique_ptr<PointLocatorBase> MeshBase::sub_point_locator () const
1848 : {
1849 : // If there's no master point locator, then we need one.
1850 568128 : if (_point_locator.get() == nullptr)
1851 : {
1852 : // PointLocator construction may not be safe within threads
1853 760 : libmesh_assert(!Threads::in_threads);
1854 :
1855 : // And it may require parallel communication
1856 760 : parallel_object_only();
1857 :
1858 : #ifdef LIBMESH_ENABLE_NANOFLANN_POINTLOCATOR
1859 : _point_locator = PointLocatorBase::build(NANOFLANN, *this);
1860 : #else
1861 43522 : _point_locator = PointLocatorBase::build(TREE_ELEMENTS, *this);
1862 : #endif
1863 :
1864 22521 : if (_point_locator_close_to_point_tol > 0.)
1865 0 : _point_locator->set_close_to_point_tol(_point_locator_close_to_point_tol);
1866 : }
1867 :
1868 : // Otherwise there was a master point locator, and we can grab a
1869 : // sub-locator easily.
1870 : return
1871 : #ifdef LIBMESH_ENABLE_NANOFLANN_POINTLOCATOR
1872 : PointLocatorBase::build(NANOFLANN, *this, _point_locator.get());
1873 : #else
1874 568128 : PointLocatorBase::build(TREE_ELEMENTS, *this, _point_locator.get());
1875 : #endif
1876 : }
1877 :
1878 :
1879 :
1880 108817664 : void MeshBase::clear_point_locator ()
1881 : {
1882 1569643 : _point_locator.reset(nullptr);
1883 108817664 : }
1884 :
1885 :
1886 :
1887 0 : void MeshBase::set_count_lower_dim_elems_in_point_locator(bool count_lower_dim_elems)
1888 : {
1889 0 : _count_lower_dim_elems_in_point_locator = count_lower_dim_elems;
1890 0 : }
1891 :
1892 :
1893 :
1894 6658441 : bool MeshBase::get_count_lower_dim_elems_in_point_locator() const
1895 : {
1896 6658441 : return _count_lower_dim_elems_in_point_locator;
1897 : }
1898 :
1899 :
1900 : #ifdef LIBMESH_ENABLE_DEPRECATED
1901 142 : std::string & MeshBase::subdomain_name(subdomain_id_type id)
1902 : {
1903 : // Use set_subdomain_name() instead
1904 : libmesh_deprecated();
1905 :
1906 : // We'd like to do this so our preparation isn't invalidated by a
1907 : // write, but for all we know the user is just reading from a
1908 : // non-const mesh!
1909 : // this->unset_has_synched_subdomain_name_map();
1910 :
1911 142 : return _block_id_to_name[id];
1912 : }
1913 : #endif // LIBMESH_ENABLE_DEPRECATED
1914 :
1915 :
1916 44153 : const std::string & MeshBase::subdomain_name(subdomain_id_type id) const
1917 : {
1918 : // An empty string to return when no matching subdomain name is found
1919 44153 : static const std::string empty;
1920 :
1921 44153 : if (const auto iter = _block_id_to_name.find(id);
1922 1627 : iter == _block_id_to_name.end())
1923 1421 : return empty;
1924 : else
1925 2476 : return iter->second;
1926 : }
1927 :
1928 1063153 : void MeshBase::set_subdomain_name(const subdomain_id_type id,
1929 : const std::string & name,
1930 : const bool synchronous)
1931 : {
1932 1063153 : if (synchronous)
1933 0 : parallel_object_only();
1934 : else
1935 45264 : this->unset_has_synched_subdomain_name_map();
1936 1063153 : _block_id_to_name[id] = name;
1937 1063153 : }
1938 :
1939 :
1940 0 : subdomain_id_type MeshBase::get_id_by_name(std::string_view name) const
1941 : {
1942 : // Linear search over the map values.
1943 0 : for (const auto & [sbd_id, sbd_name] : _block_id_to_name)
1944 0 : if (sbd_name == name)
1945 0 : return sbd_id;
1946 :
1947 : // If we made it here without returning, we don't have a subdomain
1948 : // with the requested name, so return Elem::invalid_subdomain_id.
1949 0 : return Elem::invalid_subdomain_id;
1950 : }
1951 :
1952 :
1953 1679864 : const ElemRange & MeshBase::element_stored_range()
1954 : {
1955 1679864 : if (!_element_stored_range)
1956 : {
1957 : // Range construction may not be safe within threads
1958 14212 : libmesh_assert(!Threads::in_threads);
1959 :
1960 : _element_stored_range =
1961 2064916 : std::make_unique<ElemRange>(this->elements_begin(),
1962 1409756 : this->elements_end());
1963 : }
1964 :
1965 1679864 : return *_element_stored_range;
1966 : }
1967 :
1968 573087 : const ConstElemRange & MeshBase::active_local_element_stored_range() const
1969 : {
1970 573087 : if (!_const_active_local_element_stored_range)
1971 : {
1972 : // Range construction may not be safe within threads
1973 1766 : libmesh_assert(!Threads::in_threads);
1974 :
1975 : _const_active_local_element_stored_range =
1976 914624 : std::make_unique<ConstElemRange>(this->active_local_elements_begin(),
1977 613870 : this->active_local_elements_end());
1978 : }
1979 :
1980 573087 : return *_const_active_local_element_stored_range;
1981 : }
1982 :
1983 111065934 : void MeshBase::clear_stored_ranges()
1984 : {
1985 1566009 : _element_stored_range.reset(nullptr);
1986 1566009 : _const_active_local_element_stored_range.reset(nullptr);
1987 111065934 : }
1988 :
1989 :
1990 : #ifdef LIBMESH_ENABLE_DEPRECATED
1991 0 : void MeshBase::cache_elem_dims()
1992 : {
1993 : libmesh_deprecated();
1994 :
1995 0 : this->cache_elem_data();
1996 0 : }
1997 : #endif // LIBMESH_ENABLE_DEPRECATED
1998 :
1999 697169 : void MeshBase::cache_elem_data()
2000 : {
2001 : // This requires an inspection on every processor
2002 20628 : parallel_object_only();
2003 :
2004 : // Need to clear containers first in case all elements of a
2005 : // particular dimension/order/subdomain have been deleted.
2006 41232 : _elem_dims.clear();
2007 41232 : _elem_default_orders.clear();
2008 41232 : _mesh_subdomains.clear();
2009 697169 : _supported_nodal_order = MAXIMUM;
2010 :
2011 36253242 : for (const auto & elem : this->active_element_ptr_range())
2012 : {
2013 34879532 : _elem_dims.insert(cast_int<unsigned char>(elem->dim()));
2014 34879532 : _elem_default_orders.insert(elem->default_order());
2015 34879532 : _mesh_subdomains.insert(elem->subdomain_id());
2016 34879532 : _supported_nodal_order =
2017 34879532 : static_cast<Order>
2018 69759064 : (std::min(static_cast<int>(_supported_nodal_order),
2019 35411751 : static_cast<int>(elem->supported_nodal_order())));
2020 655937 : }
2021 :
2022 697169 : if (!this->is_serial())
2023 : {
2024 : // Some different dimension/order/subdomain elements may only live
2025 : // on other processors
2026 293402 : this->comm().set_union(_elem_dims);
2027 293402 : this->comm().set_union(_elem_default_orders);
2028 293402 : this->comm().min(_supported_nodal_order);
2029 293402 : this->comm().set_union(_mesh_subdomains);
2030 : }
2031 :
2032 : // If the largest element dimension found is larger than the current
2033 : // _spatial_dimension, increase _spatial_dimension.
2034 697169 : unsigned int max_dim = this->mesh_dimension();
2035 697169 : if (max_dim > _spatial_dimension)
2036 47220 : _spatial_dimension = cast_int<unsigned char>(max_dim);
2037 :
2038 : // _spatial_dimension may need to increase from 1->2 or 2->3 if the
2039 : // mesh is full of 1D elements but they are not x-aligned, or the
2040 : // mesh is full of 2D elements but they are not in the x-y plane.
2041 : // If the mesh is x-aligned or x-y planar, we will end up checking
2042 : // every node's coordinates and not breaking out of the loop
2043 : // early...
2044 697169 : if (_spatial_dimension < LIBMESH_DIM)
2045 : {
2046 50834798 : for (const auto & node : this->node_ptr_range())
2047 : {
2048 : // Note: the exact floating point comparison is intentional,
2049 : // we don't want to get tripped up by tolerances.
2050 26220978 : if ((*node)(0) != 0. && _spatial_dimension < 1)
2051 0 : _spatial_dimension = 1;
2052 :
2053 26220978 : if ((*node)(1) != 0. && _spatial_dimension < 2)
2054 : {
2055 2643 : _spatial_dimension = 2;
2056 : #if LIBMESH_DIM == 2
2057 : // If libmesh is compiled in 2D mode, this is the
2058 : // largest spatial dimension possible so we can break
2059 : // out.
2060 : break;
2061 : #endif
2062 : }
2063 :
2064 : #if LIBMESH_DIM > 2
2065 26220978 : if ((*node)(2) != 0.)
2066 : {
2067 : // Spatial dimension can't get any higher than this, so
2068 : // we can break out.
2069 11619 : _spatial_dimension = 3;
2070 11619 : break;
2071 : }
2072 : #endif
2073 350368 : }
2074 : }
2075 :
2076 697169 : _preparation.has_cached_elem_data = true;
2077 697169 : }
2078 :
2079 :
2080 492633 : void MeshBase::sync_subdomain_name_map()
2081 : {
2082 : // This requires every processor
2083 14868 : parallel_object_only();
2084 :
2085 492633 : this->comm().set_union(_block_id_to_name);
2086 :
2087 492633 : _preparation.has_synched_subdomain_name_map = true;
2088 492633 : }
2089 :
2090 :
2091 442693 : void MeshBase::detect_interior_parents()
2092 : {
2093 13240 : LOG_SCOPE("detect_interior_parents()", "MeshBase");
2094 :
2095 : // This requires an inspection on every processor
2096 13240 : parallel_object_only();
2097 :
2098 : // This requires up-to-date mesh dimensions, but if we don't have
2099 : // them cached then we can't update them without changing the mesh
2100 : // in unexpected ways that interfere with our tests of
2101 : // partially-prepared meshes in MeshTools::*valid_is_prepared
2102 13240 : std::set<unsigned char> elem_dims_copy;
2103 442693 : if (_preparation.has_cached_elem_data)
2104 13240 : elem_dims_copy = this->elem_dimensions();
2105 : else
2106 : {
2107 0 : for (const auto & elem : this->active_element_ptr_range())
2108 0 : elem_dims_copy.insert(cast_int<unsigned char>(elem->dim()));
2109 0 : if (!this->is_serial())
2110 0 : this->comm().set_union(elem_dims_copy);
2111 : }
2112 :
2113 : // Early return if the mesh is empty or has elements of a single spatial dimension.
2114 442693 : if (elem_dims_copy.size() <= 1)
2115 : {
2116 436008 : _preparation.has_interior_parent_ptrs = true;
2117 436008 : return;
2118 : }
2119 :
2120 : // Convenient elem_dimensions iterators
2121 6497 : const auto dim_start = elem_dims_copy.begin();
2122 188 : const auto dim_end = elem_dims_copy.end();
2123 :
2124 : // In this function we find only +1 dimensional interior parents,
2125 : // (so, for a given element el, the interior parent p must satisfy p.dim() == el.dim() + 1).
2126 : // Therefore, we can avoid checking the existence of interior parents
2127 : // for all those elements el such there there is no p with p.dim() == el.dim() + 1.
2128 : // We store whether to skip any given dimension in the construction of interior parents
2129 : // inside the vector in dimensions_to_skip_for_interior_parents.
2130 6685 : std::vector<bool> skip_dimension_for_interior_parents(/*count=*/LIBMESH_DIM+1, /*value=*/false);
2131 6497 : skip_dimension_for_interior_parents.back() = true;
2132 :
2133 : // Moreover, in the following, we will build a node-to-elem map.
2134 : // It is among the elems of this map that we will look for interior parents.
2135 : // Therefore, we can skip all elems p such that there is no el with el.dim() == p.dim() - 1.
2136 : // We store whether to skip any given dimension in the construction of the node-to-elem map
2137 : // in the vector skip_dimensions_for_node_to_el_map.
2138 6685 : std::vector<bool> skip_dimensions_for_node_to_el_map(/*count=*/LIBMESH_DIM+1, /*value=*/false);
2139 6685 : skip_dimensions_for_node_to_el_map[*dim_start] = true;
2140 :
2141 : // We also create a flag to know if all dimensions should be skipped,
2142 : // and if we should therefore return early.
2143 188 : bool skip_all_dimensions = true;
2144 :
2145 : // Fill dimensions_to_skip_for_interior_parents and dimensions_to_skip_for_node_to_el_map.
2146 6877 : for (auto [it, next] = std::make_tuple(dim_start, std::next(dim_start));
2147 13704 : next != dim_end; ++it, ++next)
2148 : {
2149 6827 : if (*it + 1 != *next) // if sequential dimensions differ by exactly 1
2150 : {
2151 80 : skip_dimension_for_interior_parents[*it] = true;
2152 1431 : skip_dimensions_for_node_to_el_map[*next] = true;
2153 : }
2154 5548 : else if (!skip_dimension_for_interior_parents[*it])
2155 152 : skip_all_dimensions = false;
2156 : }
2157 :
2158 : // There is nothing to do if all dimensions should be
2159 : // skipped. Before returning, we must also set the flag that says
2160 : // "interior parent pointers have been set up" even though we
2161 : // determined there was no work to be done.
2162 6685 : if (skip_all_dimensions)
2163 : {
2164 1289 : _preparation.has_interior_parent_ptrs = true;
2165 36 : return;
2166 : }
2167 :
2168 : // Do we have interior parent pointers going to a different mesh?
2169 : // If so then we'll still check to make sure that's the only place
2170 : // they go, so we can libmesh_not_implemented() if not.
2171 304 : const bool separate_interior_mesh = (&(this->interior_mesh()) != this);
2172 :
2173 : // This map will be used to set interior parents
2174 152 : std::unordered_map<dof_id_type, std::vector<dof_id_type>> node_to_elem;
2175 :
2176 303450 : for (const auto & elem : this->element_ptr_range())
2177 : {
2178 : // Ignore element if it cannot be interior parent of any other elem.
2179 154551 : if (skip_dimensions_for_node_to_el_map[elem->dim()])
2180 64974 : continue;
2181 :
2182 : // Populating the node_to_elem map, same as MeshTools::build_nodes_to_elem_map
2183 495939 : for (auto n : make_range(elem->n_vertices()))
2184 : {
2185 24992 : libmesh_assert_less (elem->id(), this->max_elem_id());
2186 :
2187 433656 : node_to_elem[elem->node_id(n)].push_back(elem->id());
2188 : }
2189 5092 : }
2190 :
2191 : // Automatically set interior parents
2192 303450 : for (const auto & element : this->element_ptr_range())
2193 : {
2194 : // Ignore elements with dimensions to skip
2195 : // or elements that already have an interior parent.
2196 154551 : if (skip_dimension_for_interior_parents[element->dim()] || element->interior_parent())
2197 32778 : continue;
2198 :
2199 : // Start by generating sets of dim+1 dimensional elements that
2200 : // touch each vertex of the current element. If we encounter a
2201 : // vertex not connected to _any_ dim+1 dimensional elements,
2202 : // then we can exit the loop without checking the remaining
2203 : // vertices since an interior parent (if it exists) will be
2204 : // connected to all vertices of the current element.
2205 135413 : std::vector<std::set<dof_id_type>> neighbors( element->n_vertices() );
2206 :
2207 6820 : bool found_interior_parents = true;
2208 :
2209 124968 : for (auto n : make_range(element->n_vertices()))
2210 : {
2211 130856 : auto it = node_to_elem.find(element->node_id(n));
2212 :
2213 : // Check at first that this node is not isolated.
2214 123974 : if (it == node_to_elem.end())
2215 : {
2216 1472 : found_interior_parents = false;
2217 6792 : break; // out of n-loop
2218 : }
2219 :
2220 356987 : for (const auto & vertex_neighbor_id : it->second)
2221 285119 : if (this->elem_ref(vertex_neighbor_id).dim() == element->dim()+1)
2222 8733 : neighbors[n].insert(vertex_neighbor_id);
2223 :
2224 77278 : if (neighbors[n].empty())
2225 : {
2226 : // We have found an empty set for one vertex, no reason
2227 : // to continue.
2228 5320 : found_interior_parents = false;
2229 5320 : break; // out of n-loop
2230 : }
2231 : }
2232 :
2233 : // If we have generated a non-empty set of elements for each
2234 : // vertex, we will now look for a vertex_neighbor_id that
2235 : // appears in _all_ of those sets. If found, this is our interior
2236 : // parent id. If multiple such common ids are found, we will
2237 : // take the lowest such id to be the interior parent id.
2238 6820 : if (found_interior_parents)
2239 : {
2240 56 : std::set<dof_id_type> & neighbors_0 = neighbors[0];
2241 1633 : for (const auto & interior_parent_id : neighbors_0)
2242 : {
2243 40 : found_interior_parents = false;
2244 2627 : for (auto n : make_range(1u, element->n_vertices()))
2245 : {
2246 1633 : if (neighbors[n].count(interior_parent_id))
2247 : {
2248 34 : found_interior_parents = true;
2249 : }
2250 : else
2251 : {
2252 12 : found_interior_parents = false;
2253 12 : break;
2254 : }
2255 : }
2256 :
2257 1420 : if (found_interior_parents)
2258 : {
2259 781 : element->set_interior_parent(this->elem_ptr(interior_parent_id));
2260 22 : break;
2261 : }
2262 : }
2263 :
2264 : // Do we have a mixed dimensional mesh that contains some of
2265 : // its own interior parents, but we already expect to have
2266 : // interior parents on a different mesh? That's going to
2267 : // take some work to support if anyone needs it.
2268 994 : if (separate_interior_mesh)
2269 0 : libmesh_not_implemented_msg
2270 : ("interior_parent() values in multiple meshes are unsupported.");
2271 : }
2272 113225 : }
2273 :
2274 : // This flag doesn't necessarily mean any Elems actually have
2275 : // interior parent pointers, just that we did all the work to
2276 : // determine whether or not they do.
2277 5396 : _preparation.has_interior_parent_ptrs = true;
2278 : }
2279 :
2280 :
2281 :
2282 : #ifdef LIBMESH_ENABLE_PERIODIC
2283 : /**
2284 : * Register a pair of boundaries as disjoint neighbor boundary pairs.
2285 : */
2286 994 : void MeshBase::add_disjoint_neighbor_boundary_pairs(const boundary_id_type b1,
2287 : const boundary_id_type b2,
2288 : const RealVectorValue & translation)
2289 : {
2290 : // Lazily allocate the container the first time it’s needed
2291 994 : if (!_disjoint_neighbor_boundary_pairs)
2292 1496 : _disjoint_neighbor_boundary_pairs = std::make_unique<PeriodicBoundaries>();
2293 :
2294 28 : PeriodicBoundaries & db = *_disjoint_neighbor_boundary_pairs;
2295 :
2296 : // Create forward and inverse boundary mappings
2297 1022 : PeriodicBoundary forward(translation);
2298 994 : PeriodicBoundary inverse(translation * -1.0);
2299 :
2300 994 : forward.myboundary = b1;
2301 994 : forward.pairedboundary = b2;
2302 994 : inverse.myboundary = b2;
2303 994 : inverse.pairedboundary = b1;
2304 :
2305 : // Add both directions into the container
2306 994 : db.emplace(b1, forward.clone());
2307 1022 : db.emplace(b2, inverse.clone());
2308 994 : }
2309 :
2310 573778 : PeriodicBoundaries * MeshBase::get_disjoint_neighbor_boundary_pairs()
2311 : {
2312 573778 : return _disjoint_neighbor_boundary_pairs.get();
2313 : }
2314 :
2315 2704603 : const PeriodicBoundaries * MeshBase::get_disjoint_neighbor_boundary_pairs() const
2316 : {
2317 2704603 : return _disjoint_neighbor_boundary_pairs.get();
2318 : }
2319 :
2320 213 : void MeshBase::remove_disjoint_boundary_pair(const boundary_id_type b1,
2321 : const boundary_id_type b2)
2322 : {
2323 : // Nothing to remove if not allocated or empty
2324 213 : if (!_disjoint_neighbor_boundary_pairs || _disjoint_neighbor_boundary_pairs->empty())
2325 0 : return;
2326 :
2327 6 : auto & pairs = *_disjoint_neighbor_boundary_pairs;
2328 :
2329 : // Helper to check and erase both directions
2330 402 : auto erase_if_match = [](boundary_id_type key,
2331 : boundary_id_type pair,
2332 24 : PeriodicBoundaries & pb_map)
2333 : {
2334 12 : auto it = pb_map.find(key);
2335 426 : if (it != pb_map.end())
2336 : {
2337 12 : const auto & pb = *(it->second);
2338 : // Check both directions
2339 426 : if ((pb.myboundary == key && pb.pairedboundary == pair) ||
2340 0 : (pb.pairedboundary == key && pb.myboundary == pair))
2341 414 : pb_map.erase(it);
2342 : }
2343 426 : };
2344 :
2345 213 : erase_if_match(b1, b2, pairs);
2346 213 : erase_if_match(b2, b1, pairs);
2347 : }
2348 :
2349 :
2350 : #endif
2351 :
2352 :
2353 :
2354 0 : void MeshBase::set_point_locator_close_to_point_tol(Real val)
2355 : {
2356 0 : _point_locator_close_to_point_tol = val;
2357 0 : if (_point_locator)
2358 : {
2359 0 : if (val > 0.)
2360 0 : _point_locator->set_close_to_point_tol(val);
2361 : else
2362 0 : _point_locator->unset_close_to_point_tol();
2363 : }
2364 0 : }
2365 :
2366 :
2367 :
2368 426 : Real MeshBase::get_point_locator_close_to_point_tol() const
2369 : {
2370 426 : return _point_locator_close_to_point_tol;
2371 : }
2372 :
2373 :
2374 :
2375 5335 : void MeshBase::size_elem_extra_integers()
2376 : {
2377 308 : const std::size_t new_size = _elem_integer_names.size();
2378 :
2379 : Threads::parallel_for
2380 5335 : (this->element_stored_range(),
2381 10972 : [new_size, this](const ElemRange & range)
2382 : {
2383 18640 : for (Elem * elem : range)
2384 13305 : elem->add_extra_integers(new_size, this->_elem_integer_default_values);
2385 5181 : });
2386 5335 : }
2387 :
2388 :
2389 :
2390 16602 : void MeshBase::size_node_extra_integers()
2391 : {
2392 966 : const std::size_t new_size = _node_integer_names.size();
2393 417025 : for (auto node : this->node_ptr_range())
2394 216374 : node->add_extra_integers(new_size, _node_integer_default_values);
2395 16602 : }
2396 :
2397 :
2398 : std::pair<std::vector<unsigned int>, std::vector<unsigned int>>
2399 26175 : MeshBase::merge_extra_integer_names(const MeshBase & other)
2400 : {
2401 862 : std::pair<std::vector<unsigned int>, std::vector<unsigned int>> returnval;
2402 26175 : returnval.first = this->add_elem_integers(other._elem_integer_names, true, &other._elem_integer_default_values);
2403 26175 : returnval.second = this->add_node_integers(other._node_integer_names, true, &other._node_integer_default_values);
2404 26175 : return returnval;
2405 : }
2406 :
2407 :
2408 :
2409 : void
2410 426 : MeshBase::post_dofobject_moves(MeshBase && other_mesh)
2411 : {
2412 : // Now that all the DofObject moving is done, we can move the GhostingFunctor objects
2413 : // which include the _default_ghosting,_ghosting_functors and _shared_functors. We also need
2414 : // to set the mesh object associated with these functors to the assignee mesh.
2415 :
2416 : // _default_ghosting
2417 12 : _default_ghosting = std::move(other_mesh._default_ghosting);
2418 426 : _default_ghosting->set_mesh(this);
2419 :
2420 : // _ghosting_functors
2421 426 : _ghosting_functors = std::move(other_mesh._ghosting_functors);
2422 :
2423 852 : for (const auto gf : _ghosting_functors )
2424 : {
2425 426 : gf->set_mesh(this);
2426 : }
2427 :
2428 : // _shared_functors
2429 12 : _shared_functors = std::move(other_mesh._shared_functors);
2430 :
2431 426 : for (const auto & sf : _shared_functors )
2432 : {
2433 0 : (sf.second)->set_mesh(this);
2434 : }
2435 :
2436 : // _constraint_rows
2437 12 : _constraint_rows = std::move(other_mesh._constraint_rows);
2438 :
2439 426 : if (other_mesh.partitioner())
2440 426 : _partitioner = std::move(other_mesh.partitioner());
2441 426 : }
2442 :
2443 :
2444 : void
2445 0 : MeshBase::copy_cached_data(const MeshBase & other_mesh)
2446 : {
2447 0 : this->_spatial_dimension = other_mesh._spatial_dimension;
2448 0 : this->_elem_dims = other_mesh._elem_dims;
2449 0 : this->_elem_default_orders = other_mesh._elem_default_orders;
2450 0 : this->_supported_nodal_order = other_mesh._supported_nodal_order;
2451 0 : this->_mesh_subdomains = other_mesh._mesh_subdomains;
2452 0 : }
2453 :
2454 :
2455 14741 : bool MeshBase::nodes_and_elements_equal(const MeshBase & other_mesh) const
2456 : {
2457 2827090 : for (const auto & other_node : other_mesh.node_ptr_range())
2458 : {
2459 1524405 : const Node * node = this->query_node_ptr(other_node->id());
2460 1524405 : if (!node)
2461 0 : return false;
2462 1524405 : if (*other_node != *node)
2463 0 : return false;
2464 13279 : }
2465 2827090 : for (const auto & node : this->node_ptr_range())
2466 1524405 : if (!other_mesh.query_node_ptr(node->id()))
2467 13279 : return false;
2468 :
2469 1253500 : for (const auto & other_elem : other_mesh.element_ptr_range())
2470 : {
2471 789345 : const Elem * elem = this->query_elem_ptr(other_elem->id());
2472 789345 : if (!elem)
2473 0 : return false;
2474 789345 : if (!other_elem->topologically_equal(*elem))
2475 6 : return false;
2476 13279 : }
2477 1253224 : for (const auto & elem : this->element_ptr_range())
2478 789204 : if (!other_mesh.query_elem_ptr(elem->id()))
2479 13150 : return false;
2480 :
2481 14600 : return true;
2482 : }
2483 :
2484 :
2485 772 : dof_id_type MeshBase::n_constraint_rows() const
2486 : {
2487 772 : dof_id_type n_local_rows=0, n_unpartitioned_rows=0;
2488 772 : for (const auto & [node, node_constraints] : _constraint_rows)
2489 : {
2490 : // Unpartitioned nodes
2491 0 : if (node->processor_id() == DofObject::invalid_processor_id)
2492 0 : n_unpartitioned_rows++;
2493 0 : else if (node->processor_id() == this->processor_id())
2494 0 : n_local_rows++;
2495 : }
2496 :
2497 772 : this->comm().sum(n_local_rows);
2498 :
2499 772 : return n_unpartitioned_rows + n_local_rows;
2500 : }
2501 :
2502 :
2503 : void
2504 24400 : MeshBase::copy_constraint_rows(const MeshBase & other_mesh)
2505 : {
2506 1624 : LOG_SCOPE("copy_constraint_rows(mesh)", "MeshBase");
2507 :
2508 1600 : _constraint_rows.clear();
2509 :
2510 812 : const auto & other_constraint_rows = other_mesh.get_constraint_rows();
2511 72828 : for (const auto & [other_node, other_node_constraints] : other_constraint_rows)
2512 : {
2513 48428 : const Node * const our_node = this->node_ptr(other_node->id());
2514 6416 : constraint_rows_mapped_type our_node_constraints;
2515 219637 : for (const auto & [other_inner_key_pair, constraint_value] : other_node_constraints)
2516 : {
2517 9048 : const auto & [other_elem, local_node_id] = other_inner_key_pair;
2518 171209 : const Elem * const our_elem = this->elem_ptr(other_elem->id());
2519 171209 : our_node_constraints.emplace_back(std::make_pair(our_elem, local_node_id), constraint_value);
2520 : }
2521 48428 : _constraint_rows[our_node] = std::move(our_node_constraints);
2522 : }
2523 24400 : }
2524 :
2525 :
2526 : template <typename T>
2527 : void
2528 565 : MeshBase::copy_constraint_rows(const SparseMatrix<T> & constraint_operator,
2529 : bool precondition_constraint_operator)
2530 : {
2531 32 : LOG_SCOPE("copy_constraint_rows(mat)", "MeshBase");
2532 :
2533 16 : this->_constraint_rows.clear();
2534 :
2535 : // We're not going to support doing this distributed yet; it'd be
2536 : // pointless unless we temporarily had a linear partitioning to
2537 : // better match the constraint operator.
2538 597 : MeshSerializer serialize(*this);
2539 :
2540 : // Our current mesh should already reflect the desired assembly space
2541 565 : libmesh_error_msg_if(this->n_nodes() != constraint_operator.m(),
2542 : "Constraint operator matrix with " <<
2543 : constraint_operator.m() <<
2544 : "rows does not match this mesh with " <<
2545 : this->n_nodes() << " nodes");
2546 :
2547 : // First, find what new unconstrained DoFs we need to add. We can't
2548 : // iterate over columns in a SparseMatrix, so we'll iterate over
2549 : // rows and keep track of columns.
2550 :
2551 : // If we have nodes that will work unconstrained, keep track of
2552 : // their node ids and corresponding column indices.
2553 : // existing_unconstrained_nodes[column_id] = node_id
2554 32 : std::map<dof_id_type, dof_id_type> existing_unconstrained_columns;
2555 32 : std::set<dof_id_type> existing_unconstrained_nodes;
2556 :
2557 : // In case we need new nodes, keep track of their columns.
2558 : // columns[j][k] will be the kth row index and value of column j
2559 : typedef
2560 : std::unordered_map<dof_id_type,
2561 : std::vector<std::pair<dof_id_type, Real>>>
2562 : columns_type;
2563 1114 : columns_type columns(constraint_operator.n());
2564 :
2565 : // If we need to precondition the constraint operator (e.g. it's an
2566 : // unpreconditioned extraction operator for a Flex IGA matrix),
2567 : // we'll want to keep track of the sum of each column, because we'll
2568 : // be dividing each column by that sum (Jacobi preconditioning on
2569 : // the right, which then leads to symmetric preconditioning on a
2570 : // physics Jacobian).
2571 32 : std::unordered_map<dof_id_type, Real> column_sums;
2572 :
2573 : // Work in parallel, though we'll have to sync shortly
2574 4028 : for (auto i : make_range(constraint_operator.row_start(),
2575 533 : constraint_operator.row_stop()))
2576 : {
2577 618 : std::vector<numeric_index_type> indices;
2578 618 : std::vector<T> values;
2579 :
2580 3463 : constraint_operator.get_row(i, indices, values);
2581 309 : libmesh_assert_equal_to(indices.size(), values.size());
2582 :
2583 3847 : if (indices.size() == 1 &&
2584 825 : values[0] == T(1))
2585 : {
2586 : // If we have multiple simple Ui=Uj constraints, let the
2587 : // first one be our "unconstrained" node and let the others
2588 : // be constrained to it.
2589 810 : if (existing_unconstrained_columns.find(indices[0]) !=
2590 138 : existing_unconstrained_columns.end())
2591 : {
2592 36 : const auto j = indices[0];
2593 36 : columns[j].emplace_back(i, 1);
2594 : }
2595 : else
2596 : {
2597 708 : existing_unconstrained_nodes.insert(i);
2598 774 : existing_unconstrained_columns.emplace(indices[0],i);
2599 : }
2600 : }
2601 : else
2602 22028 : for (auto jj : index_range(indices))
2603 : {
2604 19375 : const auto j = indices[jj];
2605 21134 : const Real coef = libmesh_real(values[jj]);
2606 1759 : libmesh_assert_equal_to(coef, values[jj]);
2607 19375 : columns[j].emplace_back(i, coef);
2608 : }
2609 : }
2610 :
2611 : // Merge data from different processors' slabs of the matrix
2612 565 : this->comm().set_union(existing_unconstrained_nodes);
2613 565 : this->comm().set_union(existing_unconstrained_columns);
2614 :
2615 48 : std::vector<columns_type> all_columns;
2616 565 : this->comm().allgather(columns, all_columns);
2617 :
2618 16 : columns.clear();
2619 6106 : for (auto p : index_range(all_columns))
2620 65601 : for (auto & [j, subcol] : all_columns[p])
2621 183429 : for (auto [i, v] : subcol)
2622 123369 : columns[j].emplace_back(i,v);
2623 :
2624 : // Keep track of elements on which unconstrained nodes exist, and
2625 : // their local node indices.
2626 : // node_to_elem_ptrs[node] = [elem_id, local_node_num]
2627 32 : std::unordered_map<const Node *, std::pair<dof_id_type, unsigned int>> node_to_elem_ptrs;
2628 :
2629 : // Find elements attached to any existing nodes that will stay
2630 : // unconstrained. We'll also build a subdomain set here so we don't
2631 : // have to assert that the mesh is already prepared before we pick a
2632 : // new subdomain for any NodeElems we need to add.
2633 32 : std::set<subdomain_id_type> subdomain_ids;
2634 145181 : for (const Elem * elem : this->element_ptr_range())
2635 : {
2636 49738 : subdomain_ids.insert(elem->subdomain_id());
2637 244572 : for (auto n : make_range(elem->n_nodes()))
2638 : {
2639 194834 : const Node * node = elem->node_ptr(n);
2640 194834 : if (existing_unconstrained_nodes.count(node->id()))
2641 12175 : node_to_elem_ptrs.emplace(node, std::make_pair(elem->id(), n));
2642 : }
2643 : }
2644 :
2645 565 : const subdomain_id_type new_sbd_id = *subdomain_ids.rbegin() + 1;
2646 :
2647 23189 : for (auto j : make_range(constraint_operator.n()))
2648 : {
2649 : // If we already have a good node for this then we're done
2650 4800 : if (existing_unconstrained_columns.count(j))
2651 4668 : continue;
2652 :
2653 : // Get a half-decent spot to place a new NodeElem for
2654 : // unconstrained DoF(s) here. Getting a *fully*-decent spot
2655 : // would require finding a Moore-Penrose pseudoinverse, and I'm
2656 : // not going to do that, but scaling a transpose will at least
2657 : // get us a little uniqueness to make visualization reasonable.
2658 264 : Point newpt;
2659 264 : Real total_scaling = 0;
2660 264 : unsigned int total_entries = 0;
2661 :
2662 : // We'll get a decent initial pid choice here too, if only to
2663 : // aid in later repartitioning.
2664 528 : std::map<processor_id_type, int> pids;
2665 :
2666 264 : auto & column = columns[j];
2667 122230 : for (auto [i, r] : column)
2668 : {
2669 112988 : Node & constrained_node = this->node_ref(i);
2670 112988 : const Point constrained_pt = constrained_node;
2671 3228 : newpt += r*constrained_pt;
2672 112988 : total_scaling += r;
2673 112988 : ++total_entries;
2674 112988 : ++pids[constrained_node.processor_id()];
2675 : }
2676 :
2677 9242 : if (precondition_constraint_operator)
2678 0 : column_sums[j] = total_scaling;
2679 :
2680 9242 : libmesh_error_msg_if
2681 : (!total_entries,
2682 : "Empty column " << j <<
2683 : " found in constraint operator matrix");
2684 :
2685 : // If we have *cancellation* here then we can end up dividing by
2686 : // zero; try just evenly scaling across all constrained node
2687 : // points instead.
2688 9242 : if (total_scaling > TOLERANCE)
2689 264 : newpt /= total_scaling;
2690 : else
2691 0 : newpt /= total_entries;
2692 :
2693 9242 : Node *n = this->add_point(newpt);
2694 9506 : std::unique_ptr<Elem> elem = Elem::build(NODEELEM);
2695 9242 : elem->set_node(0, n);
2696 9242 : elem->subdomain_id() = new_sbd_id;
2697 :
2698 9506 : Elem * added_elem = this->add_elem(std::move(elem));
2699 9242 : this->_elem_dims.insert(0);
2700 9242 : this->_elem_default_orders.insert(added_elem->default_order());
2701 9242 : this->_supported_nodal_order =
2702 8714 : static_cast<Order>
2703 18484 : (std::min(static_cast<int>(this->_supported_nodal_order),
2704 9242 : static_cast<int>(added_elem->supported_nodal_order())));
2705 8978 : this->_mesh_subdomains.insert(new_sbd_id);
2706 9242 : node_to_elem_ptrs.emplace(n, std::make_pair(added_elem->id(), 0));
2707 9242 : existing_unconstrained_columns.emplace(j,n->id());
2708 :
2709 : // Repartition the new objects *after* adding them, so a
2710 : // DistributedMesh doesn't get confused and think you're not
2711 : // adding them on all processors at once.
2712 264 : int n_pids = 0;
2713 43912 : for (auto [pid, count] : pids)
2714 34670 : if (count >= n_pids)
2715 : {
2716 324 : n_pids = count;
2717 15469 : added_elem->processor_id() = pid;
2718 15469 : n->processor_id() = pid;
2719 : }
2720 : }
2721 :
2722 : // Calculate constraint rows in an indexed form that's easy for us
2723 : // to allgather
2724 : std::unordered_map<dof_id_type,
2725 : std::vector<std::pair<std::pair<dof_id_type, unsigned int>,Real>>>
2726 32 : indexed_constraint_rows;
2727 :
2728 4028 : for (auto i : make_range(constraint_operator.row_start(),
2729 533 : constraint_operator.row_stop()))
2730 : {
2731 951 : if (existing_unconstrained_nodes.count(i))
2732 774 : continue;
2733 :
2734 486 : std::vector<numeric_index_type> indices;
2735 486 : std::vector<T> values;
2736 :
2737 2689 : constraint_operator.get_row(i, indices, values);
2738 :
2739 486 : std::vector<std::pair<std::pair<dof_id_type, unsigned int>, Real>> constraint_row;
2740 :
2741 22100 : for (auto jj : index_range(indices))
2742 : {
2743 21173 : const dof_id_type node_id =
2744 : existing_unconstrained_columns[indices[jj]];
2745 :
2746 19411 : Node & constraining_node = this->node_ref(node_id);
2747 :
2748 1762 : libmesh_assert(node_to_elem_ptrs.count(&constraining_node));
2749 :
2750 19411 : auto p = node_to_elem_ptrs[&constraining_node];
2751 :
2752 19382 : Real coef = libmesh_real(values[jj]);
2753 1762 : libmesh_assert_equal_to(coef, values[jj]);
2754 :
2755 : // If we're preconditioning and we created a nodeelem then
2756 : // we can scale the meaning of that nodeelem's value to give
2757 : // us a better-conditioned matrix after the constraints are
2758 : // applied.
2759 19411 : if (precondition_constraint_operator)
2760 0 : if (auto sum_it = column_sums.find(indices[jj]);
2761 0 : sum_it != column_sums.end())
2762 : {
2763 0 : const Real scaling = sum_it->second;
2764 :
2765 0 : if (scaling > TOLERANCE)
2766 0 : coef /= scaling;
2767 : }
2768 :
2769 21173 : constraint_row.emplace_back(std::make_pair(p, coef));
2770 : }
2771 :
2772 243 : indexed_constraint_rows.emplace(i, std::move(constraint_row));
2773 : }
2774 :
2775 565 : this->comm().set_union(indexed_constraint_rows);
2776 :
2777 : // Add constraint rows as mesh constraint rows
2778 17591 : for (auto & [node_id, indexed_row] : indexed_constraint_rows)
2779 : {
2780 17026 : Node * constrained_node = this->node_ptr(node_id);
2781 :
2782 972 : constraint_rows_mapped_type constraint_row;
2783 :
2784 140395 : for (auto [p, coef] : indexed_row)
2785 : {
2786 123369 : const Elem * elem = this->elem_ptr(p.first);
2787 7048 : constraint_row.emplace_back
2788 126893 : (std::make_pair(std::make_pair(elem, p.second), coef));
2789 : }
2790 :
2791 16540 : this->_constraint_rows.emplace(constrained_node,
2792 486 : std::move(constraint_row));
2793 : }
2794 1098 : }
2795 :
2796 :
2797 0 : void MeshBase::print_constraint_rows(std::ostream & os,
2798 : bool print_nonlocal) const
2799 : {
2800 0 : parallel_object_only();
2801 :
2802 : std::string local_constraints =
2803 0 : this->get_local_constraints(print_nonlocal);
2804 :
2805 0 : if (this->processor_id())
2806 : {
2807 0 : this->comm().send(0, local_constraints);
2808 : }
2809 : else
2810 : {
2811 0 : os << "Processor 0:\n";
2812 0 : os << local_constraints;
2813 :
2814 0 : for (auto p : IntRange<processor_id_type>(1, this->n_processors()))
2815 : {
2816 0 : this->comm().receive(p, local_constraints);
2817 0 : os << "Processor " << p << ":\n";
2818 0 : os << local_constraints;
2819 : }
2820 : }
2821 0 : }
2822 :
2823 :
2824 :
2825 0 : std::string MeshBase::get_local_constraints(bool print_nonlocal) const
2826 : {
2827 0 : std::ostringstream os;
2828 :
2829 0 : if (print_nonlocal)
2830 0 : os << "All ";
2831 : else
2832 0 : os << "Local ";
2833 :
2834 0 : os << "Mesh Constraint Rows:"
2835 0 : << std::endl;
2836 :
2837 0 : for (const auto & [node, row] : _constraint_rows)
2838 : {
2839 0 : const bool local = (node->processor_id() == this->processor_id());
2840 :
2841 : // Skip non-local dofs if requested
2842 0 : if (!print_nonlocal && !local)
2843 0 : continue;
2844 :
2845 0 : os << "Constraints for " << (local ? "Local" : "Ghost") << " Node " << node->id()
2846 0 : << ": \t";
2847 :
2848 0 : for (const auto & [elem_and_node, coef] : row)
2849 0 : os << " ((" << elem_and_node.first->id() << ',' << elem_and_node.second << "), " << coef << ")\t";
2850 :
2851 0 : os << std::endl;
2852 : }
2853 :
2854 0 : return os.str();
2855 0 : }
2856 :
2857 337704 : MeshBase::Preparation::Preparation() :
2858 318092 : is_partitioned(false),
2859 318092 : has_synched_id_counts(false),
2860 318092 : has_neighbor_ptrs(false),
2861 318092 : has_cached_elem_data(false),
2862 318092 : has_interior_parent_ptrs(false),
2863 318092 : has_removed_remote_elements(false),
2864 318092 : has_removed_orphaned_nodes(false),
2865 318092 : has_boundary_id_sets(false),
2866 318092 : has_reinit_ghosting_functors(false),
2867 337704 : has_synched_subdomain_name_map(false)
2868 337704 : {}
2869 :
2870 109609 : MeshBase::Preparation::operator bool() const
2871 : {
2872 189539 : return is_partitioned &&
2873 79930 : has_synched_id_counts &&
2874 79930 : has_neighbor_ptrs &&
2875 79930 : has_cached_elem_data &&
2876 79924 : has_interior_parent_ptrs &&
2877 79924 : has_removed_remote_elements &&
2878 79924 : has_removed_orphaned_nodes &&
2879 79924 : has_reinit_ghosting_functors &&
2880 236475 : has_boundary_id_sets &&
2881 127750 : has_synched_subdomain_name_map;
2882 : }
2883 :
2884 : MeshBase::Preparation &
2885 1472884 : MeshBase::Preparation::operator= (bool set_all)
2886 : {
2887 1472884 : is_partitioned = set_all;
2888 1472884 : has_synched_id_counts = set_all;
2889 1472884 : has_neighbor_ptrs = set_all;
2890 1472884 : has_cached_elem_data = set_all;
2891 1472884 : has_interior_parent_ptrs = set_all;
2892 1472884 : has_removed_remote_elements = set_all;
2893 1472884 : has_removed_orphaned_nodes = set_all;
2894 1472884 : has_reinit_ghosting_functors = set_all;
2895 1472884 : has_boundary_id_sets = set_all;
2896 1472884 : has_synched_subdomain_name_map = set_all;
2897 :
2898 1472884 : return *this;
2899 : }
2900 :
2901 : bool
2902 15167 : MeshBase::Preparation::operator== (const Preparation & other) const
2903 : {
2904 15167 : if (is_partitioned != other.is_partitioned)
2905 0 : return false;
2906 15167 : if (has_synched_id_counts != other.has_synched_id_counts)
2907 0 : return false;
2908 15167 : if (has_neighbor_ptrs != other.has_neighbor_ptrs)
2909 0 : return false;
2910 15167 : if (has_cached_elem_data != other.has_cached_elem_data)
2911 0 : return false;
2912 15167 : if (has_interior_parent_ptrs != other.has_interior_parent_ptrs)
2913 0 : return false;
2914 15167 : if (has_removed_remote_elements != other.has_removed_remote_elements)
2915 0 : return false;
2916 15167 : if (has_removed_orphaned_nodes != other.has_removed_orphaned_nodes)
2917 0 : return false;
2918 15167 : if (has_reinit_ghosting_functors != other.has_reinit_ghosting_functors)
2919 0 : return false;
2920 15167 : if (has_boundary_id_sets != other.has_boundary_id_sets)
2921 0 : return false;
2922 15167 : if (has_synched_subdomain_name_map != other.has_synched_subdomain_name_map)
2923 0 : return false;
2924 :
2925 1078 : return true;
2926 : }
2927 :
2928 : bool
2929 15167 : MeshBase::Preparation::operator!= (const Preparation & other) const
2930 : {
2931 15167 : return !(*this == other);
2932 : }
2933 :
2934 :
2935 : // Explicit instantiations for our template function
2936 : template LIBMESH_EXPORT void
2937 : MeshBase::copy_constraint_rows(const SparseMatrix<Real> & constraint_operator,
2938 : bool precondition_constraint_operator);
2939 :
2940 : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
2941 : template LIBMESH_EXPORT void
2942 : MeshBase::copy_constraint_rows(const SparseMatrix<Complex> & constraint_operator,
2943 : bool precondition_constraint_operator);
2944 : #endif
2945 :
2946 :
2947 : } // namespace libMesh
|