Line data Source code
1 : // The libMesh Finite Element Library.
2 : // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 :
4 : // This library is free software; you can redistribute 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 :
45 : // C++ includes
46 : #include <algorithm> // for std::min
47 : #include <map> // for std::multimap
48 : #include <memory>
49 : #include <sstream> // for std::ostringstream
50 : #include <unordered_map>
51 :
52 : namespace libMesh
53 : {
54 :
55 :
56 :
57 : // ------------------------------------------------------------
58 : // MeshBase class member functions
59 302284 : MeshBase::MeshBase (const Parallel::Communicator & comm_in,
60 302284 : unsigned char d) :
61 : ParallelObject (comm_in),
62 302284 : boundary_info (new BoundaryInfo(*this)), // BoundaryInfo has protected ctor, can't use std::make_unique
63 284640 : _n_parts (1),
64 284640 : _default_mapping_type(LAGRANGE_MAP),
65 284640 : _default_mapping_data(0),
66 284640 : _is_prepared (false),
67 284640 : _point_locator (),
68 284640 : _count_lower_dim_elems_in_point_locator(true),
69 284640 : _partitioner (),
70 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
71 284640 : _next_unique_id(DofObject::invalid_unique_id),
72 : #endif
73 284640 : _interior_mesh(this),
74 284640 : _skip_noncritical_partitioning(false),
75 311106 : _skip_all_partitioning(libMesh::on_command_line("--skip-partitioning")),
76 284640 : _skip_renumber_nodes_and_elements(false),
77 284640 : _skip_find_neighbors(false),
78 284640 : _allow_remote_element_removal(true),
79 284640 : _spatial_dimension(d),
80 302284 : _default_ghosting(std::make_unique<GhostPointNeighbors>(*this)),
81 968606 : _point_locator_close_to_point_tol(0.)
82 : {
83 293462 : _elem_dims.insert(d);
84 302284 : _ghosting_functors.push_back(_default_ghosting.get());
85 8822 : libmesh_assert_less_equal (LIBMESH_DIM, 3);
86 8822 : libmesh_assert_greater_equal (LIBMESH_DIM, d);
87 8822 : libmesh_assert (libMesh::initialized());
88 302284 : }
89 :
90 :
91 :
92 21765 : MeshBase::MeshBase (const MeshBase & other_mesh) :
93 : ParallelObject (other_mesh),
94 21765 : boundary_info (new BoundaryInfo(*this)), // BoundaryInfo has protected ctor, can't use std::make_unique
95 21765 : _n_parts (other_mesh._n_parts),
96 21765 : _default_mapping_type(other_mesh._default_mapping_type),
97 21765 : _default_mapping_data(other_mesh._default_mapping_data),
98 21765 : _is_prepared (other_mesh._is_prepared),
99 20321 : _point_locator (),
100 21765 : _count_lower_dim_elems_in_point_locator(other_mesh._count_lower_dim_elems_in_point_locator),
101 20321 : _partitioner (),
102 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
103 21765 : _next_unique_id(other_mesh._next_unique_id),
104 : #endif
105 : // If the other mesh interior_parent pointers just go back to
106 : // itself, so should we
107 21765 : _interior_mesh((other_mesh._interior_mesh == &other_mesh) ?
108 : this : other_mesh._interior_mesh),
109 21765 : _skip_noncritical_partitioning(other_mesh._skip_noncritical_partitioning),
110 21765 : _skip_all_partitioning(other_mesh._skip_all_partitioning),
111 21765 : _skip_renumber_nodes_and_elements(other_mesh._skip_renumber_nodes_and_elements),
112 21765 : _skip_find_neighbors(other_mesh._skip_find_neighbors),
113 21765 : _allow_remote_element_removal(other_mesh._allow_remote_element_removal),
114 730 : _elem_dims(other_mesh._elem_dims),
115 730 : _elem_default_orders(other_mesh._elem_default_orders),
116 21765 : _supported_nodal_order(other_mesh._supported_nodal_order),
117 730 : _elemset_codes_inverse_map(other_mesh._elemset_codes_inverse_map),
118 730 : _all_elemset_ids(other_mesh._all_elemset_ids),
119 21765 : _spatial_dimension(other_mesh._spatial_dimension),
120 21765 : _default_ghosting(std::make_unique<GhostPointNeighbors>(*this)),
121 89202 : _point_locator_close_to_point_tol(other_mesh._point_locator_close_to_point_tol)
122 : {
123 730 : const GhostingFunctor * const other_default_ghosting = other_mesh._default_ghosting.get();
124 :
125 74214 : for (GhostingFunctor * const gf : other_mesh._ghosting_functors)
126 : {
127 : // If the other mesh is using default ghosting, then we will use our own
128 : // default ghosting
129 52449 : if (gf == other_default_ghosting)
130 : {
131 21765 : _ghosting_functors.push_back(_default_ghosting.get());
132 21765 : continue;
133 : }
134 :
135 60352 : std::shared_ptr<GhostingFunctor> clone_gf = gf->clone();
136 : // Some subclasses of GhostingFunctor might not override the
137 : // clone function yet. If this is the case, GhostingFunctor will
138 : // return nullptr by default. The clone function should be overridden
139 : // in all derived classes. This following code ("else") is written
140 : // for API upgrade. That will allow users gradually to update their code.
141 : // Once the API upgrade is done, we will come back and delete "else."
142 30684 : if (clone_gf)
143 : {
144 30684 : clone_gf->set_mesh(this);
145 60352 : add_ghosting_functor(clone_gf);
146 : }
147 : else
148 : {
149 : libmesh_deprecated();
150 0 : add_ghosting_functor(*gf);
151 : }
152 : }
153 :
154 21765 : if (other_mesh._partitioner.get())
155 42800 : _partitioner = other_mesh._partitioner->clone();
156 :
157 : // _elemset_codes stores pointers to entries in _elemset_codes_inverse_map,
158 : // so it is not possible to simply copy it directly from other_mesh
159 21765 : for (const auto & [set, code] : _elemset_codes_inverse_map)
160 0 : _elemset_codes.emplace(code, &set);
161 21765 : }
162 :
163 426 : MeshBase& MeshBase::operator= (MeshBase && other_mesh)
164 : {
165 12 : LOG_SCOPE("operator=(&&)", "MeshBase");
166 :
167 : // Move assign as a ParallelObject.
168 12 : this->ParallelObject::operator=(other_mesh);
169 :
170 426 : _n_parts = other_mesh.n_partitions();
171 426 : _default_mapping_type = other_mesh.default_mapping_type();
172 426 : _default_mapping_data = other_mesh.default_mapping_data();
173 426 : _is_prepared = other_mesh.is_prepared();
174 12 : _point_locator = std::move(other_mesh._point_locator);
175 426 : _count_lower_dim_elems_in_point_locator = other_mesh.get_count_lower_dim_elems_in_point_locator();
176 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
177 426 : _next_unique_id = other_mesh.next_unique_id();
178 : #endif
179 : // If the other mesh interior_parent pointers just go back to
180 : // itself, so should we
181 426 : _interior_mesh = (other_mesh._interior_mesh == &other_mesh) ?
182 : this : other_mesh._interior_mesh;
183 426 : _skip_noncritical_partitioning = other_mesh.skip_noncritical_partitioning();
184 426 : _skip_all_partitioning = other_mesh.skip_partitioning();
185 426 : _skip_renumber_nodes_and_elements = !(other_mesh.allow_renumbering());
186 426 : _skip_find_neighbors = !(other_mesh.allow_find_neighbors());
187 426 : _allow_remote_element_removal = other_mesh.allow_remote_element_removal();
188 12 : _block_id_to_name = std::move(other_mesh._block_id_to_name);
189 12 : _elem_dims = std::move(other_mesh.elem_dimensions());
190 12 : _elem_default_orders = std::move(other_mesh.elem_default_orders());
191 426 : _supported_nodal_order = other_mesh.supported_nodal_order();
192 12 : _elemset_codes = std::move(other_mesh._elemset_codes);
193 12 : _elemset_codes_inverse_map = std::move(other_mesh._elemset_codes_inverse_map);
194 12 : _all_elemset_ids = std::move(other_mesh._all_elemset_ids);
195 426 : _spatial_dimension = other_mesh.spatial_dimension();
196 426 : _elem_integer_names = std::move(other_mesh._elem_integer_names);
197 426 : _elem_integer_default_values = std::move(other_mesh._elem_integer_default_values);
198 426 : _node_integer_names = std::move(other_mesh._node_integer_names);
199 426 : _node_integer_default_values = std::move(other_mesh._node_integer_default_values);
200 426 : _point_locator_close_to_point_tol = other_mesh.get_point_locator_close_to_point_tol();
201 :
202 : // This relies on our subclasses *not* invalidating pointers when we
203 : // do their portion of the move assignment later!
204 12 : boundary_info = std::move(other_mesh.boundary_info);
205 12 : boundary_info->set_mesh(*this);
206 :
207 : #ifdef DEBUG
208 : // Make sure that move assignment worked for pointers
209 12 : for (const auto & [set, code] : _elemset_codes_inverse_map)
210 : {
211 0 : auto it = _elemset_codes.find(code);
212 0 : libmesh_assert_msg(it != _elemset_codes.end(),
213 : "Elemset code " << code << " not found in _elmset_codes container.");
214 0 : libmesh_assert_equal_to(it->second, &set);
215 : }
216 : #endif
217 :
218 : // We're *not* really done at this point, but we have the problem
219 : // that some of our data movement might be expecting subclasses data
220 : // movement to happen first. We'll let subclasses handle that by
221 : // calling our post_dofobject_moves()
222 438 : return *this;
223 : }
224 :
225 :
226 6250 : bool MeshBase::operator== (const MeshBase & other_mesh) const
227 : {
228 786 : LOG_SCOPE("operator==()", "MeshBase");
229 :
230 6250 : bool is_equal = this->locally_equals(other_mesh);
231 6250 : this->comm().min(is_equal);
232 7036 : return is_equal;
233 : }
234 :
235 :
236 6250 : bool MeshBase::locally_equals (const MeshBase & other_mesh) const
237 : {
238 : // Check whether (almost) everything in the base is equal
239 : //
240 : // We don't check _next_unique_id here, because it's expected to
241 : // change in a DistributedMesh prepare_for_use(); it's conceptually
242 : // "mutable".
243 : //
244 : // We use separate if statements instead of logical operators here,
245 : // to make it easy to see the failing condition when using a
246 : // debugger to figure out why a MeshTools::valid_is_prepared(mesh)
247 : // is failing.
248 6250 : if (_n_parts != other_mesh._n_parts)
249 0 : return false;
250 6250 : if (_default_mapping_type != other_mesh._default_mapping_type)
251 0 : return false;
252 6250 : if (_default_mapping_data != other_mesh._default_mapping_data)
253 0 : return false;
254 6250 : if (_is_prepared != other_mesh._is_prepared)
255 0 : return false;
256 7194 : if (_count_lower_dim_elems_in_point_locator !=
257 6250 : other_mesh._count_lower_dim_elems_in_point_locator)
258 0 : return false;
259 :
260 : // We should either both have our own interior parents or both not;
261 : // but if we both don't then we can't really assert anything else
262 : // because pointing at the same interior mesh is fair but so is
263 : // pointing at two different copies of "the same" interior mesh.
264 7194 : if ((_interior_mesh == this) !=
265 6250 : (other_mesh._interior_mesh == &other_mesh))
266 0 : return false;
267 :
268 6250 : if (_skip_noncritical_partitioning != other_mesh._skip_noncritical_partitioning)
269 0 : return false;
270 6250 : if (_skip_all_partitioning != other_mesh._skip_all_partitioning)
271 0 : return false;
272 6250 : if (_skip_renumber_nodes_and_elements != other_mesh._skip_renumber_nodes_and_elements)
273 0 : return false;
274 6250 : if (_skip_find_neighbors != other_mesh._skip_find_neighbors)
275 0 : return false;
276 6250 : if (_allow_remote_element_removal != other_mesh._allow_remote_element_removal)
277 0 : return false;
278 6250 : if (_spatial_dimension != other_mesh._spatial_dimension)
279 0 : return false;
280 6250 : if (_point_locator_close_to_point_tol != other_mesh._point_locator_close_to_point_tol)
281 0 : return false;
282 6250 : if (_block_id_to_name != other_mesh._block_id_to_name)
283 0 : return false;
284 6250 : if (_elem_dims != other_mesh._elem_dims)
285 0 : return false;
286 6250 : if (_elem_default_orders != other_mesh._elem_default_orders)
287 0 : return false;
288 6250 : if (_supported_nodal_order != other_mesh._supported_nodal_order)
289 0 : return false;
290 6250 : if (_mesh_subdomains != other_mesh._mesh_subdomains)
291 0 : return false;
292 6250 : if (_all_elemset_ids != other_mesh._all_elemset_ids)
293 0 : return false;
294 6250 : if (_elem_integer_names != other_mesh._elem_integer_names)
295 0 : return false;
296 6408 : if (_elem_integer_default_values != other_mesh._elem_integer_default_values)
297 0 : return false;
298 6250 : if (_node_integer_names != other_mesh._node_integer_names)
299 0 : return false;
300 6408 : if (_node_integer_default_values != other_mesh._node_integer_default_values)
301 0 : return false;
302 6250 : if (bool(_default_ghosting) != bool(other_mesh._default_ghosting))
303 0 : return false;
304 6250 : if (bool(_partitioner) != bool(other_mesh._partitioner))
305 0 : return false;
306 6250 : if (*boundary_info != *other_mesh.boundary_info)
307 0 : return false;
308 :
309 : const constraint_rows_type & other_rows =
310 786 : other_mesh.get_constraint_rows();
311 57886 : for (const auto & [node, row] : this->_constraint_rows)
312 : {
313 51636 : const dof_id_type node_id = node->id();
314 51636 : const Node * other_node = other_mesh.query_node_ptr(node_id);
315 51636 : if (!other_node)
316 0 : return false;
317 :
318 6416 : auto it = other_rows.find(other_node);
319 51636 : if (it == other_rows.end())
320 0 : return false;
321 :
322 6416 : const auto & other_row = it->second;
323 58052 : if (row.size() != other_row.size())
324 0 : return false;
325 :
326 231893 : for (auto i : index_range(row))
327 : {
328 18096 : const auto & [elem_pair, coef] = row[i];
329 18096 : const auto & [other_elem_pair, other_coef] = other_row[i];
330 18096 : libmesh_assert(elem_pair.first);
331 18096 : libmesh_assert(other_elem_pair.first);
332 180257 : if (elem_pair.first->id() !=
333 342418 : other_elem_pair.first->id() ||
334 180257 : elem_pair.second !=
335 198353 : other_elem_pair.second ||
336 180257 : coef != other_coef)
337 0 : return false;
338 : }
339 : }
340 :
341 6250 : for (const auto & [elemset_code, elemset_ptr] : this->_elemset_codes)
342 0 : if (const auto it = other_mesh._elemset_codes.find(elemset_code);
343 0 : it == other_mesh._elemset_codes.end() || *elemset_ptr != *it->second)
344 0 : return false;
345 :
346 : // FIXME: we have no good way to compare ghosting functors, since
347 : // they're in a vector of pointers, and we have no way *at all*
348 : // to compare ghosting functors, since they don't have operator==
349 : // defined and we encourage users to subclass them. We can check if
350 : // we have the same number, is all.
351 7194 : if (_ghosting_functors.size() !=
352 944 : other_mesh._ghosting_functors.size())
353 0 : return false;
354 :
355 : // Same deal for partitioners. We tested that we both have one or
356 : // both don't, but are they equivalent? Let's guess "yes".
357 :
358 : // Now let the subclasses decide whether everything else is equal
359 6250 : return this->subclass_locally_equals(other_mesh);
360 : }
361 :
362 :
363 333585 : MeshBase::~MeshBase()
364 : {
365 324049 : this->MeshBase::clear();
366 :
367 9552 : libmesh_exceptionless_assert (!libMesh::closed());
368 1238932 : }
369 :
370 :
371 :
372 21990178 : unsigned int MeshBase::mesh_dimension() const
373 : {
374 21990178 : if (!_elem_dims.empty())
375 21990163 : return cast_int<unsigned int>(*_elem_dims.rbegin());
376 0 : return 0;
377 : }
378 :
379 :
380 :
381 0 : void MeshBase::set_elem_dimensions(std::set<unsigned char> elem_dims)
382 : {
383 : #ifdef DEBUG
384 : // In debug mode, we call cache_elem_data() and then make sure
385 : // the result actually agrees with what the user specified.
386 0 : parallel_object_only();
387 :
388 0 : this->cache_elem_data();
389 0 : libmesh_assert_msg(_elem_dims == elem_dims, \
390 : "Specified element dimensions does not match true element dimensions!");
391 : #endif
392 :
393 0 : _elem_dims = std::move(elem_dims);
394 0 : }
395 :
396 :
397 :
398 1775 : void MeshBase::add_elemset_code(dof_id_type code, MeshBase::elemset_type id_set)
399 : {
400 : // Populate inverse map, stealing id_set's resources
401 1725 : auto [it1, inserted1] = _elemset_codes_inverse_map.emplace(std::move(id_set), code);
402 :
403 : // Reference to the newly inserted (or previously existing) id_set
404 1775 : const auto & inserted_id_set = it1->first;
405 :
406 : // Keep track of all elemset ids ever added for O(1) n_elemsets()
407 : // performance. Only need to do this if we didn't know about this
408 : // id_set before...
409 1775 : if (inserted1)
410 1775 : _all_elemset_ids.insert(inserted_id_set.begin(), inserted_id_set.end());
411 :
412 : // Take the address of the newly emplaced set to use in
413 : // _elemset_codes, avoid duplicating std::set storage
414 1775 : auto [it2, inserted2] = _elemset_codes.emplace(code, &inserted_id_set);
415 :
416 : // Throw an error if this code already exists with a pointer to a
417 : // different set of ids.
418 1775 : libmesh_error_msg_if(!inserted2 && it2->second != &inserted_id_set,
419 : "The elemset code " << code << " already exists with a different id_set.");
420 1775 : }
421 :
422 :
423 :
424 14859 : unsigned int MeshBase::n_elemsets() const
425 : {
426 14859 : return _all_elemset_ids.size();
427 : }
428 :
429 2963 : void MeshBase::get_elemsets(dof_id_type elemset_code, MeshBase::elemset_type & id_set_to_fill) const
430 : {
431 : // If we don't recognize this elemset_code, hand back an empty set
432 102 : id_set_to_fill.clear();
433 :
434 2963 : if (const auto it = _elemset_codes.find(elemset_code);
435 102 : it != _elemset_codes.end())
436 1386 : id_set_to_fill.insert(it->second->begin(), it->second->end());
437 2963 : }
438 :
439 852 : dof_id_type MeshBase::get_elemset_code(const MeshBase::elemset_type & id_set) const
440 : {
441 24 : auto it = _elemset_codes_inverse_map.find(id_set);
442 852 : return (it == _elemset_codes_inverse_map.end()) ? DofObject::invalid_id : it->second;
443 : }
444 :
445 4123 : std::vector<dof_id_type> MeshBase::get_elemset_codes() const
446 : {
447 118 : std::vector<dof_id_type> ret;
448 4123 : ret.reserve(_elemset_codes.size());
449 4904 : for (const auto & pr : _elemset_codes)
450 781 : ret.push_back(pr.first);
451 4123 : return ret;
452 : }
453 :
454 284 : void MeshBase::change_elemset_code(dof_id_type old_code, dof_id_type new_code)
455 : {
456 : // Look up elemset ids for old_code
457 8 : auto it = _elemset_codes.find(old_code);
458 :
459 : // If we don't have the old_code, then do nothing. Alternatively, we
460 : // could throw an error since trying to change an elemset code you
461 : // don't have could indicate there's a problem...
462 284 : if (it == _elemset_codes.end())
463 0 : return;
464 :
465 : // Make copy of the set of elemset ids. We are not changing these,
466 : // only updating the elemset code it corresponds to.
467 284 : elemset_type id_set_copy = *(it->second);
468 :
469 : // Look up the corresponding entry in the inverse map. Note: we want
470 : // the iterator because we are going to remove it.
471 8 : auto inverse_it = _elemset_codes_inverse_map.find(id_set_copy);
472 284 : libmesh_error_msg_if(inverse_it == _elemset_codes_inverse_map.end(),
473 : "Expected _elemset_codes_inverse_map entry for elemset code " << old_code);
474 :
475 : // Erase entry from inverse map
476 276 : _elemset_codes_inverse_map.erase(inverse_it);
477 :
478 : // Erase entry from forward map
479 276 : _elemset_codes.erase(it);
480 :
481 : // Add new code with original set of ids.
482 560 : this->add_elemset_code(new_code, id_set_copy);
483 :
484 : // We can't update any actual elemset codes if there is no extra integer defined for it.
485 284 : if (!this->has_elem_integer("elemset_code"))
486 0 : return;
487 :
488 : // Get index of elemset_code extra integer
489 284 : unsigned int elemset_index = this->get_elem_integer_index("elemset_code");
490 :
491 : // Loop over all elems and update code
492 10220 : for (auto & elem : this->element_ptr_range())
493 : {
494 : dof_id_type elemset_code =
495 4970 : elem->get_extra_integer(elemset_index);
496 :
497 4970 : if (elemset_code == old_code)
498 2415 : elem->set_extra_integer(elemset_index, new_code);
499 268 : }
500 : }
501 :
502 284 : void MeshBase::change_elemset_id(elemset_id_type old_id, elemset_id_type new_id)
503 : {
504 : // Early return if we don't have old_id
505 8 : if (!_all_elemset_ids.count(old_id))
506 0 : return;
507 :
508 : // Throw an error if the new_id is already used
509 8 : libmesh_error_msg_if(_all_elemset_ids.count(new_id),
510 : "Cannot change elemset id " << old_id <<
511 : " to " << new_id << ", " << new_id << " already exists.");
512 :
513 : // We will build up a new version of the inverse map so we can iterate over
514 : // the current one without invalidating anything.
515 16 : std::map<MeshBase::elemset_type, dof_id_type> new_elemset_codes_inverse_map;
516 852 : for (const auto & [id_set, elemset_code] : _elemset_codes_inverse_map)
517 : {
518 32 : auto id_set_copy = id_set;
519 16 : if (id_set_copy.count(old_id))
520 : {
521 : // Remove old_id, insert new_id
522 8 : id_set_copy.erase(old_id);
523 276 : id_set_copy.insert(new_id);
524 : }
525 :
526 : // Store in new version of map
527 552 : new_elemset_codes_inverse_map.emplace(id_set_copy, elemset_code);
528 : }
529 :
530 : // Swap existing map with newly-built one
531 8 : _elemset_codes_inverse_map.swap(new_elemset_codes_inverse_map);
532 :
533 : // Reconstruct _elemset_codes map
534 8 : _elemset_codes.clear();
535 852 : for (const auto & [id_set, elemset_code] : _elemset_codes_inverse_map)
536 568 : _elemset_codes.emplace(elemset_code, &id_set);
537 :
538 : // Update _all_elemset_ids
539 8 : _all_elemset_ids.erase(old_id);
540 276 : _all_elemset_ids.insert(new_id);
541 : }
542 :
543 259771 : unsigned int MeshBase::spatial_dimension () const
544 : {
545 268551 : return cast_int<unsigned int>(_spatial_dimension);
546 : }
547 :
548 :
549 :
550 353402 : void MeshBase::set_spatial_dimension(unsigned char d)
551 : {
552 : // The user can set the _spatial_dimension however they wish,
553 : // libMesh will only *increase* the spatial dimension, however,
554 : // never decrease it.
555 353402 : _spatial_dimension = d;
556 353402 : }
557 :
558 :
559 :
560 4077 : unsigned int MeshBase::add_elem_integer(std::string name,
561 : bool allocate_data,
562 : dof_id_type default_value)
563 : {
564 5273 : for (auto i : index_range(_elem_integer_names))
565 1416 : if (_elem_integer_names[i] == name)
566 : {
567 8 : libmesh_assert_less(i, _elem_integer_default_values.size());
568 284 : _elem_integer_default_values[i] = default_value;
569 284 : return i;
570 : }
571 :
572 118 : libmesh_assert_equal_to(_elem_integer_names.size(),
573 : _elem_integer_default_values.size());
574 3793 : _elem_integer_names.push_back(std::move(name));
575 3793 : _elem_integer_default_values.push_back(default_value);
576 3793 : if (allocate_data)
577 3205 : this->size_elem_extra_integers();
578 3911 : return _elem_integer_names.size()-1;
579 : }
580 :
581 :
582 :
583 27194 : std::vector<unsigned int> MeshBase::add_elem_integers(const std::vector<std::string> & names,
584 : bool allocate_data,
585 : const std::vector<dof_id_type> * default_values)
586 : {
587 898 : libmesh_assert(!default_values || default_values->size() == names.size());
588 898 : libmesh_assert_equal_to(_elem_integer_names.size(), _elem_integer_default_values.size());
589 :
590 1796 : std::unordered_map<std::string, std::size_t> name_indices;
591 28046 : for (auto i : index_range(_elem_integer_names))
592 852 : name_indices[_elem_integer_names[i]] = i;
593 :
594 28076 : std::vector<unsigned int> returnval(names.size());
595 :
596 898 : bool added_an_integer = false;
597 29699 : for (auto i : index_range(names))
598 : {
599 156 : const std::string & name = names[i];
600 2505 : if (const auto it = name_indices.find(name);
601 78 : it != name_indices.end())
602 : {
603 426 : returnval[i] = it->second;
604 438 : _elem_integer_default_values[it->second] =
605 426 : default_values ? (*default_values)[i] : DofObject::invalid_id;
606 : }
607 : else
608 : {
609 2145 : returnval[i] = _elem_integer_names.size();
610 2079 : name_indices[name] = returnval[i];
611 2079 : _elem_integer_names.push_back(name);
612 : _elem_integer_default_values.push_back
613 2883 : (default_values ? (*default_values)[i] : DofObject::invalid_id);
614 66 : added_an_integer = true;
615 : }
616 : }
617 :
618 27194 : if (allocate_data && added_an_integer)
619 1065 : this->size_elem_extra_integers();
620 :
621 28092 : return returnval;
622 : }
623 :
624 :
625 :
626 3278 : unsigned int MeshBase::get_elem_integer_index(std::string_view name) const
627 : {
628 4130 : for (auto i : index_range(_elem_integer_names))
629 4037 : if (_elem_integer_names[i] == name)
630 3278 : return i;
631 :
632 0 : libmesh_error_msg("Unknown elem integer " << name);
633 : return libMesh::invalid_uint;
634 : }
635 :
636 :
637 :
638 8219 : bool MeshBase::has_elem_integer(std::string_view name) const
639 : {
640 9213 : for (auto & entry : _elem_integer_names)
641 2367 : if (entry == name)
642 40 : return true;
643 :
644 622 : return false;
645 : }
646 :
647 :
648 :
649 14636 : unsigned int MeshBase::add_node_integer(std::string name,
650 : bool allocate_data,
651 : dof_id_type default_value)
652 : {
653 24899 : for (auto i : index_range(_node_integer_names))
654 10118 : if (_node_integer_names[i] == name)
655 : {
656 16 : libmesh_assert_less(i, _node_integer_default_values.size());
657 568 : _node_integer_default_values[i] = default_value;
658 568 : return i;
659 : }
660 :
661 582 : libmesh_assert_equal_to(_node_integer_names.size(),
662 : _node_integer_default_values.size());
663 14068 : _node_integer_names.push_back(std::move(name));
664 14068 : _node_integer_default_values.push_back(default_value);
665 14068 : if (allocate_data)
666 7120 : this->size_node_extra_integers();
667 14650 : return _node_integer_names.size()-1;
668 : }
669 :
670 :
671 :
672 27052 : std::vector<unsigned int> MeshBase::add_node_integers(const std::vector<std::string> & names,
673 : bool allocate_data,
674 : const std::vector<dof_id_type> * default_values)
675 : {
676 894 : libmesh_assert(!default_values || default_values->size() == names.size());
677 894 : libmesh_assert_equal_to(_node_integer_names.size(), _node_integer_default_values.size());
678 :
679 1788 : std::unordered_map<std::string, std::size_t> name_indices;
680 27620 : for (auto i : index_range(_node_integer_names))
681 568 : name_indices[_node_integer_names[i]] = i;
682 :
683 27930 : std::vector<unsigned int> returnval(names.size());
684 :
685 894 : bool added_an_integer = false;
686 29684 : for (auto i : index_range(names))
687 : {
688 192 : const std::string & name = names[i];
689 2632 : if (const auto it = name_indices.find(name);
690 96 : it != name_indices.end())
691 : {
692 0 : returnval[i] = it->second;
693 0 : _node_integer_default_values[it->second] =
694 0 : default_values ? (*default_values)[i] : DofObject::invalid_id;
695 : }
696 : else
697 : {
698 2728 : returnval[i] = _node_integer_names.size();
699 2632 : name_indices[name] = returnval[i];
700 2632 : _node_integer_names.push_back(name);
701 : _node_integer_default_values.push_back
702 3724 : (default_values ? (*default_values)[i] : DofObject::invalid_id);
703 96 : added_an_integer = true;
704 : }
705 : }
706 :
707 27052 : if (allocate_data && added_an_integer)
708 1010 : this->size_node_extra_integers();
709 :
710 27946 : return returnval;
711 : }
712 :
713 :
714 :
715 710 : unsigned int MeshBase::get_node_integer_index(std::string_view name) const
716 : {
717 1988 : for (auto i : index_range(_node_integer_names))
718 1968 : if (_node_integer_names[i] == name)
719 710 : return i;
720 :
721 0 : libmesh_error_msg("Unknown node integer " << name);
722 : return libMesh::invalid_uint;
723 : }
724 :
725 :
726 :
727 1136 : bool MeshBase::has_node_integer(std::string_view name) const
728 : {
729 3134 : for (auto & entry : _node_integer_names)
730 3134 : if (entry == name)
731 32 : return true;
732 :
733 0 : return false;
734 : }
735 :
736 :
737 :
738 58231 : void MeshBase::remove_orphaned_nodes ()
739 : {
740 3700 : LOG_SCOPE("remove_orphaned_nodes()", "MeshBase");
741 :
742 : // Will hold the set of nodes that are currently connected to elements
743 3700 : std::unordered_set<Node *> connected_nodes;
744 :
745 : // Loop over the elements. Find which nodes are connected to at
746 : // least one of them.
747 22985286 : for (const auto & element : this->element_ptr_range())
748 70791017 : for (auto & n : element->node_ref_range())
749 58533043 : connected_nodes.insert(&n);
750 :
751 18276420 : for (const auto & node : this->node_ptr_range())
752 1044710 : if (!connected_nodes.count(node))
753 135899 : this->delete_node(node);
754 58231 : }
755 :
756 :
757 :
758 : #ifdef LIBMESH_ENABLE_DEPRECATED
759 0 : void MeshBase::prepare_for_use (const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
760 : {
761 : libmesh_deprecated();
762 :
763 : // We only respect the users wish if they tell us to skip renumbering. If they tell us not to
764 : // skip renumbering but someone previously called allow_renumbering(false), then the latter takes
765 : // precedence
766 0 : if (skip_renumber_nodes_and_elements)
767 0 : this->allow_renumbering(false);
768 :
769 : // We always accept the user's value for skip_find_neighbors, in contrast to skip_renumber
770 0 : const bool old_allow_find_neighbors = this->allow_find_neighbors();
771 0 : this->allow_find_neighbors(!skip_find_neighbors);
772 :
773 0 : this->prepare_for_use();
774 :
775 0 : this->allow_find_neighbors(old_allow_find_neighbors);
776 0 : }
777 :
778 0 : void MeshBase::prepare_for_use (const bool skip_renumber_nodes_and_elements)
779 : {
780 : libmesh_deprecated();
781 :
782 : // We only respect the users wish if they tell us to skip renumbering. If they tell us not to
783 : // skip renumbering but someone previously called allow_renumbering(false), then the latter takes
784 : // precedence
785 0 : if (skip_renumber_nodes_and_elements)
786 0 : this->allow_renumbering(false);
787 :
788 0 : this->prepare_for_use();
789 0 : }
790 : #endif // LIBMESH_ENABLE_DEPRECATED
791 :
792 :
793 :
794 412566 : void MeshBase::prepare_for_use ()
795 : {
796 25172 : LOG_SCOPE("prepare_for_use()", "MeshBase");
797 :
798 12586 : parallel_object_only();
799 :
800 12586 : libmesh_assert(this->comm().verify(this->is_serial()));
801 :
802 : // If we don't go into this method with valid constraint rows, we're
803 : // only going to be able to make that worse.
804 : #ifdef DEBUG
805 12586 : MeshTools::libmesh_assert_valid_constraint_rows(*this);
806 : #endif
807 :
808 : // A distributed mesh may have processors with no elements (or
809 : // processors with no elements of higher dimension, if we ever
810 : // support mixed-dimension meshes), but we want consistent
811 : // mesh_dimension anyways.
812 : //
813 : // cache_elem_data() should get the elem_dimensions() and
814 : // mesh_dimension() correct later, and we don't need it earlier.
815 :
816 :
817 : // Renumber the nodes and elements so that they in contiguous
818 : // blocks. By default, _skip_renumber_nodes_and_elements is false.
819 : //
820 : // Instances where you if prepare_for_use() should not renumber the nodes
821 : // and elements include reading in e.g. an xda/r or gmv file. In
822 : // this case, the ordering of the nodes may depend on an accompanying
823 : // solution, and the node ordering cannot be changed.
824 :
825 :
826 : // Mesh modification operations might not leave us with consistent
827 : // id counts, or might leave us with orphaned nodes we're no longer
828 : // using, but our partitioner might need that consistency and/or
829 : // might be confused by orphaned nodes.
830 412566 : if (!_skip_renumber_nodes_and_elements)
831 354335 : this->renumber_nodes_and_elements();
832 : else
833 : {
834 58231 : this->remove_orphaned_nodes();
835 58231 : this->update_parallel_id_counts();
836 : }
837 :
838 : // Let all the elements find their neighbors
839 412566 : if (!_skip_find_neighbors)
840 380826 : this->find_neighbors();
841 :
842 : // The user may have set boundary conditions. We require that the
843 : // boundary conditions were set consistently. Because we examine
844 : // neighbors when evaluating non-raw boundary condition IDs, this
845 : // assert is only valid when our neighbor links are in place.
846 : #ifdef DEBUG
847 12586 : MeshTools::libmesh_assert_valid_boundary_ids(*this);
848 : #endif
849 :
850 : // Search the mesh for all the dimensions of the elements
851 : // and cache them.
852 412566 : this->cache_elem_data();
853 :
854 : // Search the mesh for elements that have a neighboring element
855 : // of dim+1 and set that element as the interior parent
856 412566 : this->detect_interior_parents();
857 :
858 : // Fix up node unique ids in case mesh generation code didn't take
859 : // exceptional care to do so.
860 : // MeshCommunication().make_node_unique_ids_parallel_consistent(*this);
861 :
862 : // We're going to still require that mesh generation code gets
863 : // element unique ids consistent.
864 : #if defined(DEBUG) && defined(LIBMESH_ENABLE_UNIQUE_ID)
865 12586 : MeshTools::libmesh_assert_valid_unique_ids(*this);
866 : #endif
867 :
868 : // Reset our PointLocator. Any old locator is invalidated any time
869 : // the elements in the underlying elements in the mesh have changed,
870 : // so we clear it here.
871 412566 : this->clear_point_locator();
872 :
873 : // Allow our GhostingFunctor objects to reinit if necessary.
874 : // Do this before partitioning and redistributing, and before
875 : // deleting remote elements.
876 412566 : this->reinit_ghosting_functors();
877 :
878 : // Partition the mesh unless *all* partitioning is to be skipped.
879 : // If only noncritical partitioning is to be skipped, the
880 : // partition() call will still check for orphaned nodes.
881 412566 : if (!skip_partitioning())
882 11768 : this->partition();
883 :
884 : // If we're using DistributedMesh, we'll probably want it
885 : // parallelized.
886 412566 : if (this->_allow_remote_element_removal)
887 380628 : this->delete_remote_elements();
888 :
889 : // Much of our boundary info may have been for now-remote parts of the mesh,
890 : // in which case we don't want to keep local copies of data meant to be
891 : // local. On the other hand we may have deleted, or the user may have added in
892 : // a distributed fashion, boundary data that is meant to be global. So we
893 : // handle both of those scenarios here
894 412566 : this->get_boundary_info().regenerate_id_sets();
895 :
896 412566 : if (!_skip_renumber_nodes_and_elements)
897 354335 : this->renumber_nodes_and_elements();
898 :
899 : // The mesh is now prepared for use.
900 412566 : _is_prepared = true;
901 :
902 : #ifdef DEBUG
903 12586 : MeshTools::libmesh_assert_valid_boundary_ids(*this);
904 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
905 12586 : MeshTools::libmesh_assert_valid_unique_ids(*this);
906 : #endif
907 : #endif
908 412566 : }
909 :
910 : void
911 412566 : MeshBase::reinit_ghosting_functors()
912 : {
913 951666 : for (auto & gf : _ghosting_functors)
914 : {
915 16738 : libmesh_assert(gf);
916 539100 : gf->mesh_reinit();
917 : }
918 412566 : }
919 :
920 930658 : void MeshBase::clear ()
921 : {
922 : // Reset the number of partitions
923 930658 : _n_parts = 1;
924 :
925 : // Reset the _is_prepared flag
926 930658 : _is_prepared = false;
927 :
928 : // Clear boundary information
929 930658 : if (boundary_info)
930 929380 : boundary_info->clear();
931 :
932 : // Clear cached element data
933 28060 : _elem_dims.clear();
934 28060 : _elem_default_orders.clear();
935 930658 : _supported_nodal_order = MAXIMUM;
936 :
937 28060 : _elemset_codes.clear();
938 28060 : _elemset_codes_inverse_map.clear();
939 :
940 28060 : _constraint_rows.clear();
941 :
942 : // Clear our point locator.
943 930658 : this->clear_point_locator();
944 930658 : }
945 :
946 :
947 :
948 983713 : void MeshBase::add_ghosting_functor(GhostingFunctor & ghosting_functor)
949 : {
950 : // We used to implicitly support duplicate inserts to std::set
951 : #ifdef LIBMESH_ENABLE_DEPRECATED
952 : _ghosting_functors.erase
953 927277 : (std::remove(_ghosting_functors.begin(),
954 : _ghosting_functors.end(),
955 983713 : &ghosting_functor),
956 84654 : _ghosting_functors.end());
957 : #endif
958 :
959 : // We shouldn't have two copies of the same functor
960 28218 : libmesh_assert(std::find(_ghosting_functors.begin(),
961 : _ghosting_functors.end(),
962 : &ghosting_functor) ==
963 : _ghosting_functors.end());
964 :
965 983713 : _ghosting_functors.push_back(&ghosting_functor);
966 983713 : }
967 :
968 :
969 :
970 953171 : void MeshBase::remove_ghosting_functor(GhostingFunctor & ghosting_functor)
971 : {
972 898759 : auto raw_it = std::find(_ghosting_functors.begin(),
973 980377 : _ghosting_functors.end(), &ghosting_functor);
974 :
975 : // The DofMap has a "to_mesh" parameter that tells it to avoid
976 : // registering a new functor with the mesh, but it doesn't keep
977 : // track of which functors weren't added, so we'll support "remove a
978 : // functor that isn't there" just like we did with set::erase
979 : // before.
980 953171 : if (raw_it != _ghosting_functors.end())
981 952248 : _ghosting_functors.erase(raw_it);
982 :
983 : // We shouldn't have had two copies of the same functor
984 27206 : libmesh_assert(std::find(_ghosting_functors.begin(),
985 : _ghosting_functors.end(),
986 : &ghosting_functor) ==
987 : _ghosting_functors.end());
988 :
989 953171 : if (const auto it = _shared_functors.find(&ghosting_functor);
990 27206 : it != _shared_functors.end())
991 69 : _shared_functors.erase(it);
992 953171 : }
993 :
994 :
995 :
996 23798 : void MeshBase::subdomain_ids (std::set<subdomain_id_type> & ids, const bool global /* = true */) const
997 : {
998 : // This requires an inspection on every processor
999 1202 : if (global)
1000 1202 : parallel_object_only();
1001 :
1002 1202 : ids.clear();
1003 :
1004 3983786 : for (const auto & elem : this->active_local_element_ptr_range())
1005 2196172 : ids.insert(elem->subdomain_id());
1006 :
1007 23798 : if (global)
1008 : {
1009 : // Only include the unpartitioned elements if the user requests the global IDs.
1010 : // In the case of the local subdomain IDs, it doesn't make sense to include the
1011 : // unpartitioned elements because said elements do not have a sense of locality.
1012 49868 : for (const auto & elem : this->active_unpartitioned_element_ptr_range())
1013 24868 : ids.insert(elem->subdomain_id());
1014 :
1015 : // Some subdomains may only live on other processors
1016 23798 : this->comm().set_union(ids);
1017 : }
1018 23798 : }
1019 :
1020 :
1021 :
1022 567528 : void MeshBase::redistribute()
1023 : {
1024 : // We now have all elements and nodes redistributed; our ghosting
1025 : // functors should be ready to redistribute and/or recompute any
1026 : // cached data they use too.
1027 570304 : for (auto & gf : as_range(this->ghosting_functors_begin(),
1028 1284517 : this->ghosting_functors_end()))
1029 690793 : gf->redistribute();
1030 567528 : }
1031 :
1032 :
1033 :
1034 16973 : subdomain_id_type MeshBase::n_subdomains() const
1035 : {
1036 : // This requires an inspection on every processor
1037 500 : parallel_object_only();
1038 :
1039 1000 : std::set<subdomain_id_type> ids;
1040 :
1041 16973 : this->subdomain_ids (ids);
1042 :
1043 17473 : return cast_int<subdomain_id_type>(ids.size());
1044 : }
1045 :
1046 :
1047 :
1048 0 : subdomain_id_type MeshBase::n_local_subdomains() const
1049 : {
1050 0 : std::set<subdomain_id_type> ids;
1051 :
1052 0 : this->subdomain_ids (ids, /* global = */ false);
1053 :
1054 0 : return cast_int<subdomain_id_type>(ids.size());
1055 : }
1056 :
1057 :
1058 :
1059 :
1060 3204007 : dof_id_type MeshBase::n_nodes_on_proc (const processor_id_type proc_id) const
1061 : {
1062 : // We're either counting a processor's nodes or unpartitioned
1063 : // nodes
1064 5294 : libmesh_assert (proc_id < this->n_processors() ||
1065 : proc_id == DofObject::invalid_processor_id);
1066 :
1067 6402720 : return static_cast<dof_id_type>(std::distance (this->pid_nodes_begin(proc_id),
1068 9606727 : this->pid_nodes_end (proc_id)));
1069 : }
1070 :
1071 :
1072 :
1073 3592607 : dof_id_type MeshBase::n_elem_on_proc (const processor_id_type proc_id) const
1074 : {
1075 : // We're either counting a processor's elements or unpartitioned
1076 : // elements
1077 17072 : libmesh_assert (proc_id < this->n_processors() ||
1078 : proc_id == DofObject::invalid_processor_id);
1079 :
1080 7168142 : return static_cast<dof_id_type>(std::distance (this->pid_elements_begin(proc_id),
1081 10760749 : this->pid_elements_end (proc_id)));
1082 : }
1083 :
1084 :
1085 :
1086 1151467 : dof_id_type MeshBase::n_active_elem_on_proc (const processor_id_type proc_id) const
1087 : {
1088 2004 : libmesh_assert_less (proc_id, this->n_processors());
1089 2300930 : return static_cast<dof_id_type>(std::distance (this->active_pid_elements_begin(proc_id),
1090 3452397 : this->active_pid_elements_end (proc_id)));
1091 : }
1092 :
1093 :
1094 :
1095 0 : dof_id_type MeshBase::n_sub_elem () const
1096 : {
1097 0 : dof_id_type ne=0;
1098 :
1099 0 : for (const auto & elem : this->element_ptr_range())
1100 0 : ne += elem->n_sub_elem();
1101 :
1102 0 : return ne;
1103 : }
1104 :
1105 :
1106 :
1107 32834 : dof_id_type MeshBase::n_active_sub_elem () const
1108 : {
1109 1591 : dof_id_type ne=0;
1110 :
1111 26592389 : for (const auto & elem : this->active_element_ptr_range())
1112 26557964 : ne += elem->n_sub_elem();
1113 :
1114 32834 : return ne;
1115 : }
1116 :
1117 :
1118 :
1119 14844 : std::string MeshBase::get_info(const unsigned int verbosity /* = 0 */, const bool global /* = true */) const
1120 : {
1121 15720 : std::ostringstream oss;
1122 :
1123 14844 : oss << " Mesh Information:" << '\n';
1124 :
1125 14844 : if (!_elem_dims.empty())
1126 : {
1127 14844 : oss << " elem_dimensions()={";
1128 14844 : std::copy(_elem_dims.begin(),
1129 438 : --_elem_dims.end(), // --end() is valid if the set is non-empty
1130 14406 : std::ostream_iterator<unsigned int>(oss, ", "));
1131 14844 : oss << cast_int<unsigned int>(*_elem_dims.rbegin());
1132 14844 : oss << "}\n";
1133 : }
1134 :
1135 14844 : if (!_elem_default_orders.empty())
1136 : {
1137 14844 : oss << " elem_default_orders()={";
1138 14844 : std::transform(_elem_default_orders.begin(),
1139 438 : --_elem_default_orders.end(),
1140 14844 : std::ostream_iterator<std::string>(oss, ", "),
1141 8 : [](Order o)
1142 276 : { return Utility::enum_to_string<Order>(o); });
1143 14844 : oss << Utility::enum_to_string<Order>(*_elem_default_orders.rbegin());
1144 14844 : oss << "}\n";
1145 : }
1146 :
1147 14844 : oss << " supported_nodal_order()=" << this->supported_nodal_order() << '\n'
1148 15720 : << " spatial_dimension()=" << this->spatial_dimension() << '\n'
1149 15282 : << " n_nodes()=" << this->n_nodes() << '\n'
1150 14844 : << " n_local_nodes()=" << this->n_local_nodes() << '\n'
1151 15282 : << " n_elem()=" << this->n_elem() << '\n'
1152 29250 : << " n_local_elem()=" << this->n_local_elem() << '\n';
1153 : #ifdef LIBMESH_ENABLE_AMR
1154 28812 : oss << " n_active_elem()=" << this->n_active_elem() << '\n';
1155 : #endif
1156 14844 : if (global)
1157 15282 : oss << " n_subdomains()=" << static_cast<std::size_t>(this->n_subdomains()) << '\n';
1158 : else
1159 0 : oss << " n_local_subdomains()= " << static_cast<std::size_t>(this->n_local_subdomains()) << '\n';
1160 15282 : oss << " n_elemsets()=" << static_cast<std::size_t>(this->n_elemsets()) << '\n';
1161 14844 : if (!_elemset_codes.empty())
1162 0 : oss << " n_elemset_codes=" << _elemset_codes.size() << '\n';
1163 14844 : oss << " n_partitions()=" << static_cast<std::size_t>(this->n_partitions()) << '\n'
1164 15720 : << " n_processors()=" << static_cast<std::size_t>(this->n_processors()) << '\n'
1165 15282 : << " n_threads()=" << static_cast<std::size_t>(libMesh::n_threads()) << '\n'
1166 15720 : << " processor_id()=" << static_cast<std::size_t>(this->processor_id()) << '\n'
1167 876 : << " is_prepared()=" << (this->is_prepared() ? "true" : "false") << '\n'
1168 28773 : << " is_replicated()=" << (this->is_replicated() ? "true" : "false") << '\n';
1169 :
1170 14844 : if (verbosity > 0)
1171 : {
1172 284 : if (global)
1173 : {
1174 8 : libmesh_parallel_only(this->comm());
1175 292 : if (this->processor_id() != 0)
1176 236 : oss << "\n Detailed global get_info() (verbosity > 0) is reduced and output to only rank 0.";
1177 : }
1178 :
1179 : // Helper for printing element types
1180 672 : const auto elem_type_helper = [](const std::set<int> & elem_types) {
1181 784 : std::stringstream ss;
1182 1344 : for (auto it = elem_types.begin(); it != elem_types.end();)
1183 : {
1184 1288 : ss << Utility::enum_to_string((ElemType)*it);
1185 672 : if (++it != elem_types.end())
1186 0 : ss << ", ";
1187 : }
1188 728 : return ss.str();
1189 560 : };
1190 :
1191 : // Helper for whether or not the given DofObject is to be included. If we're doing
1192 : // a global reduction, we also count unpartitioned objects on rank 0.
1193 871156 : const auto include_object = [this, &global](const DofObject & dof_object) {
1194 978188 : return this->processor_id() == dof_object.processor_id() ||
1195 250756 : (global &&
1196 42324 : this->processor_id() == 0 &&
1197 846328 : dof_object.processor_id() == DofObject::invalid_processor_id);
1198 276 : };
1199 :
1200 8 : Real volume = 0;
1201 :
1202 : // Add bounding box information
1203 284 : const auto bbox = global ? MeshTools::create_bounding_box(*this) : MeshTools::create_local_bounding_box(*this);
1204 284 : if (!global || this->processor_id() == 0)
1205 : oss << "\n " << (global ? "" : "Local ") << "Mesh Bounding Box:\n"
1206 48 : << " Minimum: " << bbox.min() << "\n"
1207 44 : << " Maximum: " << bbox.max() << "\n"
1208 88 : << " Delta: " << (bbox.max() - bbox.min()) << "\n";
1209 :
1210 : // Obtain the global or local element types
1211 16 : std::set<int> elem_types;
1212 49712 : for (const Elem * elem : this->active_local_element_ptr_range())
1213 49420 : elem_types.insert(elem->type());
1214 284 : if (global)
1215 : {
1216 : // Pick up unpartitioned elems on rank 0
1217 292 : if (this->processor_id() == 0)
1218 92 : for (const Elem * elem : this->active_unpartitioned_element_ptr_range())
1219 40 : elem_types.insert(elem->type());
1220 :
1221 284 : this->comm().set_union(elem_types);
1222 : }
1223 :
1224 : // Add element types
1225 284 : if (!global || this->processor_id() == 0)
1226 : oss << "\n " << (global ? "" : "Local ") << "Mesh Element Type(s):\n "
1227 140 : << elem_type_helper(elem_types) << "\n";
1228 :
1229 : // Reduce the nodeset ids
1230 16 : auto nodeset_ids = this->get_boundary_info().get_node_boundary_ids();
1231 284 : if (global)
1232 284 : this->comm().set_union(nodeset_ids);
1233 :
1234 : // Accumulate local information for each nodeset
1235 1608 : struct NodesetInfo
1236 : {
1237 : std::size_t num_nodes = 0;
1238 : BoundingBox bbox;
1239 : };
1240 16 : std::map<boundary_id_type, NodesetInfo> nodeset_info_map;
1241 91376 : for (const auto & [node, id] : this->get_boundary_info().get_nodeset_map())
1242 : {
1243 91092 : if (!include_object(*node))
1244 38364 : continue;
1245 :
1246 48672 : NodesetInfo & info = nodeset_info_map[id];
1247 :
1248 48672 : ++info.num_nodes;
1249 :
1250 48672 : if (verbosity > 1)
1251 48672 : info.bbox.union_with(*node);
1252 : }
1253 :
1254 : // Add nodeset info
1255 284 : if (!global || this->processor_id() == 0)
1256 : {
1257 48 : oss << "\n " << (global ? "" : "Local ") << "Mesh Nodesets:\n";
1258 48 : if (nodeset_ids.empty())
1259 0 : oss << " None\n";
1260 : }
1261 :
1262 8 : const auto & nodeset_name_map = this->get_boundary_info().get_nodeset_name_map();
1263 1988 : for (const auto id : nodeset_ids)
1264 : {
1265 1704 : NodesetInfo & info = nodeset_info_map[id];
1266 :
1267 : // Reduce the local information for this nodeset if required
1268 1704 : if (global)
1269 : {
1270 1704 : this->comm().sum(info.num_nodes);
1271 1704 : if (verbosity > 1)
1272 : {
1273 1752 : this->comm().min(info.bbox.min());
1274 1752 : this->comm().max(info.bbox.max());
1275 : }
1276 : }
1277 :
1278 1704 : const bool has_name = nodeset_name_map.count(id) && nodeset_name_map.at(id).size();
1279 1800 : const std::string name = has_name ? nodeset_name_map.at(id) : "";
1280 96 : if (global)
1281 48 : libmesh_assert(this->comm().verify(name));
1282 :
1283 1704 : if (global ? this->processor_id() == 0 : info.num_nodes > 0)
1284 : {
1285 288 : oss << " Nodeset " << id;
1286 288 : if (has_name)
1287 528 : oss << " (" << name << ")";
1288 312 : oss << ", " << info.num_nodes << " " << (global ? "" : "local ") << "nodes\n";
1289 :
1290 288 : if (verbosity > 1)
1291 : {
1292 288 : oss << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1293 288 : << info.bbox.min() << "\n"
1294 288 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1295 288 : << info.bbox.max() << "\n"
1296 288 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1297 288 : << (info.bbox.max() - info.bbox.min()) << "\n";
1298 : }
1299 : }
1300 : }
1301 :
1302 : // Reduce the sideset ids
1303 16 : auto sideset_ids = this->get_boundary_info().get_side_boundary_ids();
1304 284 : if (global)
1305 284 : this->comm().set_union(sideset_ids);
1306 :
1307 : // Accumulate local information for each sideset
1308 : struct SidesetInfo
1309 : {
1310 : std::size_t num_sides = 0;
1311 : Real volume = 0;
1312 : std::set<int> side_elem_types;
1313 : std::set<int> elem_types;
1314 : std::set<dof_id_type> elem_ids;
1315 : std::set<dof_id_type> node_ids;
1316 : BoundingBox bbox;
1317 : };
1318 16 : ElemSideBuilder side_builder;
1319 16 : std::map<boundary_id_type, SidesetInfo> sideset_info_map;
1320 70396 : for (const auto & pair : this->get_boundary_info().get_sideset_map())
1321 : {
1322 70112 : const Elem * elem = pair.first;
1323 63456 : if (!include_object(*elem))
1324 30176 : continue;
1325 :
1326 39936 : const auto id = pair.second.second;
1327 39936 : SidesetInfo & info = sideset_info_map[id];
1328 :
1329 39936 : const auto s = pair.second.first;
1330 39936 : const Elem & side = side_builder(*elem, s);
1331 :
1332 39936 : ++info.num_sides;
1333 39936 : info.side_elem_types.insert(side.type());
1334 39936 : info.elem_types.insert(elem->type());
1335 39936 : info.elem_ids.insert(elem->id());
1336 :
1337 199680 : for (const Node & node : side.node_ref_range())
1338 146432 : if (include_object(node))
1339 147552 : info.node_ids.insert(node.id());
1340 :
1341 39936 : if (verbosity > 1)
1342 : {
1343 39936 : info.volume += side.volume();
1344 39936 : info.bbox.union_with(side.loose_bounding_box());
1345 : }
1346 : }
1347 :
1348 : // Add sideset info
1349 284 : if (!global || this->processor_id() == 0)
1350 : {
1351 48 : oss << "\n " << (global ? "" : "Local ") << "Mesh Sidesets:\n";
1352 48 : if (sideset_ids.empty())
1353 0 : oss << " None\n";
1354 : }
1355 8 : const auto & sideset_name_map = this->get_boundary_info().get_sideset_name_map();
1356 1988 : for (const auto id : sideset_ids)
1357 : {
1358 1704 : SidesetInfo & info = sideset_info_map[id];
1359 :
1360 1704 : auto num_elems = info.elem_ids.size();
1361 1704 : auto num_nodes = info.node_ids.size();
1362 :
1363 : // Reduce the local information for this sideset if required
1364 1704 : if (global)
1365 : {
1366 1704 : this->comm().sum(info.num_sides);
1367 1704 : this->comm().set_union(info.side_elem_types, 0);
1368 1704 : this->comm().sum(num_elems);
1369 1704 : this->comm().set_union(info.elem_types, 0);
1370 1704 : this->comm().sum(num_nodes);
1371 1704 : if (verbosity > 1)
1372 : {
1373 1704 : this->comm().sum(info.volume);
1374 1752 : this->comm().min(info.bbox.min());
1375 1752 : this->comm().max(info.bbox.max());
1376 : }
1377 : }
1378 :
1379 1704 : const bool has_name = sideset_name_map.count(id) && sideset_name_map.at(id).size();
1380 1800 : const std::string name = has_name ? sideset_name_map.at(id) : "";
1381 96 : if (global)
1382 48 : libmesh_assert(this->comm().verify(name));
1383 :
1384 1704 : if (global ? this->processor_id() == 0 : info.num_sides > 0)
1385 : {
1386 288 : oss << " Sideset " << id;
1387 288 : if (has_name)
1388 528 : oss << " (" << name << ")";
1389 552 : oss << ", " << info.num_sides << " sides (" << elem_type_helper(info.side_elem_types) << ")"
1390 1056 : << ", " << num_elems << " " << (global ? "" : "local ") << "elems (" << elem_type_helper(info.elem_types) << ")"
1391 600 : << ", " << num_nodes << " " << (global ? "" : "local ") << "nodes\n";
1392 :
1393 288 : if (verbosity > 1)
1394 : {
1395 312 : oss << " " << (global ? "Side" : "Local side") << " volume: " << info.volume << "\n"
1396 288 : << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1397 288 : << info.bbox.min() << "\n"
1398 288 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1399 288 : << info.bbox.max() << "\n"
1400 288 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1401 288 : << (info.bbox.max() - info.bbox.min()) << "\n";
1402 : }
1403 : }
1404 : }
1405 :
1406 : // Reduce the edgeset ids
1407 16 : auto edgeset_ids = this->get_boundary_info().get_edge_boundary_ids();
1408 284 : if (global)
1409 284 : this->comm().set_union(edgeset_ids);
1410 :
1411 : // Accumulate local information for each edgeset
1412 0 : struct EdgesetInfo
1413 : {
1414 : std::size_t num_edges = 0;
1415 : std::set<int> edge_elem_types;
1416 : BoundingBox bbox;
1417 : };
1418 16 : std::map<boundary_id_type, EdgesetInfo> edgeset_info_map;
1419 284 : std::unique_ptr<const Elem> edge;
1420 :
1421 284 : for (const auto & pair : this->get_boundary_info().get_edgeset_map())
1422 : {
1423 0 : const Elem * elem = pair.first;
1424 0 : if (!include_object(*elem))
1425 0 : continue;
1426 :
1427 0 : const auto id = pair.second.second;
1428 0 : EdgesetInfo & info = edgeset_info_map[id];
1429 :
1430 0 : elem->build_edge_ptr(edge, pair.second.first);
1431 :
1432 0 : ++info.num_edges;
1433 0 : info.edge_elem_types.insert(edge->type());
1434 :
1435 0 : if (verbosity > 1)
1436 0 : info.bbox.union_with(edge->loose_bounding_box());
1437 : }
1438 :
1439 : // Add edgeset info
1440 284 : if (!global || this->processor_id() == 0)
1441 : {
1442 48 : oss << "\n " << (global ? "" : "Local ") << "Mesh Edgesets:\n";
1443 48 : if (edgeset_ids.empty())
1444 48 : oss << " None\n";
1445 : }
1446 :
1447 8 : const auto & edgeset_name_map = this->get_boundary_info().get_edgeset_name_map();
1448 284 : for (const auto id : edgeset_ids)
1449 : {
1450 0 : EdgesetInfo & info = edgeset_info_map[id];
1451 :
1452 : // Reduce the local information for this edgeset if required
1453 0 : if (global)
1454 : {
1455 0 : this->comm().sum(info.num_edges);
1456 0 : this->comm().set_union(info.edge_elem_types, 0);
1457 0 : if (verbosity > 1)
1458 : {
1459 0 : this->comm().min(info.bbox.min());
1460 0 : this->comm().min(info.bbox.max());
1461 : }
1462 : }
1463 :
1464 0 : const bool has_name = edgeset_name_map.count(id) && edgeset_name_map.at(id).size();
1465 0 : const std::string name = has_name ? edgeset_name_map.at(id) : "";
1466 0 : if (global)
1467 0 : libmesh_assert(this->comm().verify(name));
1468 :
1469 0 : if (global ? this->processor_id() == 0 : info.num_edges > 0)
1470 : {
1471 0 : oss << " Edgeset " << id;
1472 0 : if (has_name)
1473 0 : oss << " (" << name << ")";
1474 0 : oss << ", " << info.num_edges << " " << (global ? "" : "local ") << "edges ("
1475 0 : << elem_type_helper(info.edge_elem_types) << ")\n";
1476 :
1477 0 : if (verbosity > 1)
1478 : {
1479 0 : oss << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1480 0 : << info.bbox.min() << "\n"
1481 0 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1482 0 : << info.bbox.max() << "\n"
1483 0 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1484 0 : << (info.bbox.max() - info.bbox.min()) << "\n";
1485 : }
1486 : }
1487 : }
1488 :
1489 : // Reduce the block IDs and block names
1490 16 : std::set<subdomain_id_type> subdomains;
1491 194032 : for (const Elem * elem : this->active_element_ptr_range())
1492 92640 : if (include_object(*elem))
1493 49420 : subdomains.insert(elem->subdomain_id());
1494 284 : if (global)
1495 284 : this->comm().set_union(subdomains);
1496 :
1497 : // Accumulate local information for each subdomain
1498 0 : struct SubdomainInfo
1499 : {
1500 : std::size_t num_elems = 0;
1501 : Real volume = 0;
1502 : std::set<int> elem_types;
1503 : std::set<dof_id_type> active_node_ids;
1504 : #ifdef LIBMESH_ENABLE_AMR
1505 : std::size_t num_active_elems = 0;
1506 : #endif
1507 : BoundingBox bbox;
1508 : };
1509 16 : std::map<subdomain_id_type, SubdomainInfo> subdomain_info_map;
1510 194032 : for (const Elem * elem : this->element_ptr_range())
1511 92640 : if (include_object(*elem))
1512 : {
1513 49152 : SubdomainInfo & info = subdomain_info_map[elem->subdomain_id()];
1514 :
1515 49152 : ++info.num_elems;
1516 49152 : info.elem_types.insert(elem->type());
1517 :
1518 : #ifdef LIBMESH_ENABLE_AMR
1519 4096 : if (elem->active())
1520 49152 : ++info.num_active_elems;
1521 : #endif
1522 :
1523 442368 : for (const Node & node : elem->node_ref_range())
1524 392704 : if (include_object(node) && node.active())
1525 330608 : info.active_node_ids.insert(node.id());
1526 :
1527 49152 : if (verbosity > 1 && elem->active())
1528 : {
1529 49152 : info.volume += elem->volume();
1530 49152 : info.bbox.union_with(elem->loose_bounding_box());
1531 : }
1532 268 : }
1533 :
1534 : // Add subdomain info
1535 284 : oss << "\n " << (global ? "" : "Local ") << "Mesh Subdomains:\n";
1536 8 : const auto & subdomain_name_map = this->get_subdomain_name_map();
1537 568 : for (const auto id : subdomains)
1538 : {
1539 284 : SubdomainInfo & info = subdomain_info_map[id];
1540 :
1541 284 : auto num_active_nodes = info.active_node_ids.size();
1542 :
1543 : // Reduce the information for this subdomain if needed
1544 284 : if (global)
1545 : {
1546 284 : this->comm().sum(info.num_elems);
1547 : #ifdef LIBMESH_ENABLE_AMR
1548 284 : this->comm().sum(info.num_active_elems);
1549 : #endif
1550 284 : this->comm().sum(num_active_nodes);
1551 284 : this->comm().set_union(info.elem_types, 0);
1552 284 : if (verbosity > 1)
1553 : {
1554 292 : this->comm().min(info.bbox.min());
1555 292 : this->comm().max(info.bbox.max());
1556 284 : this->comm().sum(info.volume);
1557 : }
1558 : }
1559 284 : if (verbosity > 1)
1560 284 : volume += info.volume;
1561 :
1562 8 : const bool has_name = subdomain_name_map.count(id);
1563 292 : const std::string name = has_name ? subdomain_name_map.at(id) : "";
1564 16 : if (global)
1565 8 : libmesh_assert(this->comm().verify(name));
1566 :
1567 284 : if (!global || this->processor_id() == 0)
1568 : {
1569 48 : oss << " Subdomain " << id;
1570 48 : if (has_name)
1571 0 : oss << " (" << name << ")";
1572 48 : oss << ": " << info.num_elems << " " << (global ? "" : "local ") << "elems "
1573 56 : << "(" << elem_type_helper(info.elem_types);
1574 : #ifdef LIBMESH_ENABLE_AMR
1575 48 : oss << ", " << info.num_active_elems << " active";
1576 : #endif
1577 52 : oss << "), " << num_active_nodes << " " << (global ? "" : "local ") << "active nodes\n";
1578 48 : if (verbosity > 1)
1579 : {
1580 52 : oss << " " << (global ? "Volume" : "Local volume") << ": " << info.volume << "\n";
1581 48 : oss << " " << (global ? "Bounding" : "Local bounding") << " box minimum: "
1582 48 : << info.bbox.min() << "\n"
1583 48 : << " " << (global ? "Bounding" : "Local bounding") << " box maximum: "
1584 48 : << info.bbox.max() << "\n"
1585 48 : << " " << (global ? "Bounding" : "Local bounding") << " box delta: "
1586 48 : << (info.bbox.max() - info.bbox.min()) << "\n";
1587 : }
1588 : }
1589 : }
1590 :
1591 560 : oss << " " << (global ? "Global" : "Local") << " mesh volume = " << volume << "\n";
1592 :
1593 268 : }
1594 :
1595 15282 : return oss.str();
1596 13968 : }
1597 :
1598 :
1599 14844 : void MeshBase::print_info(std::ostream & os, const unsigned int verbosity /* = 0 */, const bool global /* = true */) const
1600 : {
1601 15282 : os << this->get_info(verbosity, global)
1602 438 : << std::endl;
1603 14844 : }
1604 :
1605 :
1606 0 : std::ostream & operator << (std::ostream & os, const MeshBase & m)
1607 : {
1608 0 : m.print_info(os);
1609 0 : return os;
1610 : }
1611 :
1612 :
1613 389084 : void MeshBase::partition (const unsigned int n_parts)
1614 : {
1615 : // If we get here and we have unpartitioned elements, we need that
1616 : // fixed.
1617 389084 : if (this->n_unpartitioned_elem() > 0)
1618 : {
1619 8824 : libmesh_assert (partitioner().get());
1620 8824 : libmesh_assert (this->is_serial());
1621 294155 : partitioner()->partition (*this, n_parts);
1622 : }
1623 : // A nullptr partitioner or a skip_partitioning(true) call or a
1624 : // skip_noncritical_partitioning(true) call means don't repartition;
1625 : // skip_noncritical_partitioning() checks all these.
1626 2968 : else if (!skip_noncritical_partitioning())
1627 : {
1628 89052 : partitioner()->partition (*this, n_parts);
1629 : }
1630 : else
1631 : {
1632 : // Adaptive coarsening may have "orphaned" nodes on processors
1633 : // whose elements no longer share them. We need to check for
1634 : // and possibly fix that.
1635 5877 : MeshTools::correct_node_proc_ids(*this);
1636 :
1637 : // Make sure locally cached partition count is correct
1638 5877 : this->recalculate_n_partitions();
1639 :
1640 : // Make sure any other locally cached data is correct
1641 5877 : this->update_post_partitioning();
1642 : }
1643 389084 : }
1644 :
1645 15948 : void MeshBase::all_second_order (const bool full_ordered)
1646 : {
1647 15948 : this->all_second_order_range(this->element_ptr_range(), full_ordered);
1648 15948 : }
1649 :
1650 13916 : void MeshBase::all_complete_order ()
1651 : {
1652 13916 : this->all_complete_order_range(this->element_ptr_range());
1653 13916 : }
1654 :
1655 5877 : unsigned int MeshBase::recalculate_n_partitions()
1656 : {
1657 : // This requires an inspection on every processor
1658 146 : parallel_object_only();
1659 :
1660 5877 : unsigned int max_proc_id=0;
1661 :
1662 217698 : for (const auto & elem : this->active_local_element_ptr_range())
1663 215361 : max_proc_id = std::max(max_proc_id, static_cast<unsigned int>(elem->processor_id()));
1664 :
1665 : // The number of partitions is one more than the max processor ID.
1666 5877 : _n_parts = max_proc_id+1;
1667 :
1668 5877 : this->comm().max(_n_parts);
1669 :
1670 5877 : return _n_parts;
1671 : }
1672 :
1673 :
1674 :
1675 487532 : std::unique_ptr<PointLocatorBase> MeshBase::sub_point_locator () const
1676 : {
1677 : // If there's no master point locator, then we need one.
1678 487532 : if (_point_locator.get() == nullptr)
1679 : {
1680 : // PointLocator construction may not be safe within threads
1681 504 : libmesh_assert(!Threads::in_threads);
1682 :
1683 : // And it may require parallel communication
1684 504 : parallel_object_only();
1685 :
1686 : #ifdef LIBMESH_ENABLE_NANOFLANN_POINTLOCATOR
1687 : _point_locator = PointLocatorBase::build(NANOFLANN, *this);
1688 : #else
1689 24074 : _point_locator = PointLocatorBase::build(TREE_ELEMENTS, *this);
1690 : #endif
1691 :
1692 12541 : if (_point_locator_close_to_point_tol > 0.)
1693 0 : _point_locator->set_close_to_point_tol(_point_locator_close_to_point_tol);
1694 : }
1695 :
1696 : // Otherwise there was a master point locator, and we can grab a
1697 : // sub-locator easily.
1698 : return
1699 : #ifdef LIBMESH_ENABLE_NANOFLANN_POINTLOCATOR
1700 : PointLocatorBase::build(NANOFLANN, *this, _point_locator.get());
1701 : #else
1702 487532 : PointLocatorBase::build(TREE_ELEMENTS, *this, _point_locator.get());
1703 : #endif
1704 : }
1705 :
1706 :
1707 :
1708 1921238 : void MeshBase::clear_point_locator ()
1709 : {
1710 42274 : _point_locator.reset(nullptr);
1711 1921238 : }
1712 :
1713 :
1714 :
1715 0 : void MeshBase::set_count_lower_dim_elems_in_point_locator(bool count_lower_dim_elems)
1716 : {
1717 0 : _count_lower_dim_elems_in_point_locator = count_lower_dim_elems;
1718 0 : }
1719 :
1720 :
1721 :
1722 6385477 : bool MeshBase::get_count_lower_dim_elems_in_point_locator() const
1723 : {
1724 6385477 : return _count_lower_dim_elems_in_point_locator;
1725 : }
1726 :
1727 :
1728 :
1729 882727 : std::string & MeshBase::subdomain_name(subdomain_id_type id)
1730 : {
1731 882727 : return _block_id_to_name[id];
1732 : }
1733 :
1734 43761 : const std::string & MeshBase::subdomain_name(subdomain_id_type id) const
1735 : {
1736 : // An empty string to return when no matching subdomain name is found
1737 43761 : static const std::string empty;
1738 :
1739 43761 : if (const auto iter = _block_id_to_name.find(id);
1740 1602 : iter == _block_id_to_name.end())
1741 1396 : return empty;
1742 : else
1743 2476 : return iter->second;
1744 : }
1745 :
1746 :
1747 :
1748 :
1749 0 : subdomain_id_type MeshBase::get_id_by_name(std::string_view name) const
1750 : {
1751 : // Linear search over the map values.
1752 0 : for (const auto & [sbd_id, sbd_name] : _block_id_to_name)
1753 0 : if (sbd_name == name)
1754 0 : return sbd_id;
1755 :
1756 : // If we made it here without returning, we don't have a subdomain
1757 : // with the requested name, so return Elem::invalid_subdomain_id.
1758 0 : return Elem::invalid_subdomain_id;
1759 : }
1760 :
1761 : #ifdef LIBMESH_ENABLE_DEPRECATED
1762 0 : void MeshBase::cache_elem_dims()
1763 : {
1764 : libmesh_deprecated();
1765 :
1766 0 : this->cache_elem_data();
1767 0 : }
1768 : #endif // LIBMESH_ENABLE_DEPRECATED
1769 :
1770 464662 : void MeshBase::cache_elem_data()
1771 : {
1772 : // This requires an inspection on every processor
1773 14288 : parallel_object_only();
1774 :
1775 : // Need to clear containers first in case all elements of a
1776 : // particular dimension/order/subdomain have been deleted.
1777 28544 : _elem_dims.clear();
1778 28544 : _elem_default_orders.clear();
1779 28544 : _mesh_subdomains.clear();
1780 464662 : _supported_nodal_order = MAXIMUM;
1781 :
1782 41305250 : for (const auto & elem : this->active_element_ptr_range())
1783 : {
1784 40390214 : _elem_dims.insert(cast_int<unsigned char>(elem->dim()));
1785 40390214 : _elem_default_orders.insert(elem->default_order());
1786 40390214 : _mesh_subdomains.insert(elem->subdomain_id());
1787 40390214 : _supported_nodal_order =
1788 40390214 : static_cast<Order>
1789 80780428 : (std::min(static_cast<int>(_supported_nodal_order),
1790 40810066 : static_cast<int>(elem->supported_nodal_order())));
1791 436118 : }
1792 :
1793 464662 : if (!this->is_serial())
1794 : {
1795 : // Some different dimension/order/subdomain elements may only live
1796 : // on other processors
1797 122928 : this->comm().set_union(_elem_dims);
1798 122928 : this->comm().set_union(_elem_default_orders);
1799 122928 : this->comm().min(_supported_nodal_order);
1800 122928 : this->comm().set_union(_mesh_subdomains);
1801 : }
1802 :
1803 : // If the largest element dimension found is larger than the current
1804 : // _spatial_dimension, increase _spatial_dimension.
1805 464662 : unsigned int max_dim = this->mesh_dimension();
1806 464662 : if (max_dim > _spatial_dimension)
1807 29661 : _spatial_dimension = cast_int<unsigned char>(max_dim);
1808 :
1809 : // _spatial_dimension may need to increase from 1->2 or 2->3 if the
1810 : // mesh is full of 1D elements but they are not x-aligned, or the
1811 : // mesh is full of 2D elements but they are not in the x-y plane.
1812 : // If the mesh is x-aligned or x-y planar, we will end up checking
1813 : // every node's coordinates and not breaking out of the loop
1814 : // early...
1815 464662 : if (_spatial_dimension < LIBMESH_DIM)
1816 : {
1817 41540458 : for (const auto & node : this->node_ptr_range())
1818 : {
1819 : // Note: the exact floating point comparison is intentional,
1820 : // we don't want to get tripped up by tolerances.
1821 21609135 : if ((*node)(0) != 0. && _spatial_dimension < 1)
1822 0 : _spatial_dimension = 1;
1823 :
1824 21609135 : if ((*node)(1) != 0. && _spatial_dimension < 2)
1825 : {
1826 2195 : _spatial_dimension = 2;
1827 : #if LIBMESH_DIM == 2
1828 : // If libmesh is compiled in 2D mode, this is the
1829 : // largest spatial dimension possible so we can break
1830 : // out.
1831 : break;
1832 : #endif
1833 : }
1834 :
1835 : #if LIBMESH_DIM > 2
1836 21609135 : if ((*node)(2) != 0.)
1837 : {
1838 : // Spatial dimension can't get any higher than this, so
1839 : // we can break out.
1840 5387 : _spatial_dimension = 3;
1841 5387 : break;
1842 : }
1843 : #endif
1844 240624 : }
1845 : }
1846 464662 : }
1847 :
1848 :
1849 41988 : void MeshBase::sync_subdomain_name_map()
1850 : {
1851 : // This requires every processor
1852 1404 : parallel_object_only();
1853 :
1854 41988 : this->comm().set_union(_block_id_to_name);
1855 41988 : }
1856 :
1857 :
1858 412566 : void MeshBase::detect_interior_parents()
1859 : {
1860 : // This requires an inspection on every processor
1861 12586 : parallel_object_only();
1862 :
1863 : // Check if the mesh contains mixed dimensions. If so, then we may
1864 : // have interior parents to set. Otherwise return.
1865 412566 : if (this->elem_dimensions().size() == 1)
1866 403994 : return;
1867 :
1868 : // Do we have interior parent pointers going to a different mesh?
1869 : // If so then we'll still check to make sure that's the only place
1870 : // they go, so we can libmesh_not_implemented() if not.
1871 460 : const bool separate_interior_mesh = (&(this->interior_mesh()) != this);
1872 :
1873 : // This map will be used to set interior parents
1874 460 : std::unordered_map<dof_id_type, std::vector<dof_id_type>> node_to_elem;
1875 :
1876 20428190 : for (const auto & elem : this->element_ptr_range())
1877 : {
1878 : // Populating the node_to_elem map, same as MeshTools::build_nodes_to_elem_map
1879 51952965 : for (auto n : make_range(elem->n_vertices()))
1880 : {
1881 1206440 : libmesh_assert_less (elem->id(), this->max_elem_id());
1882 :
1883 42647269 : node_to_elem[elem->node_id(n)].push_back(elem->id());
1884 : }
1885 8112 : }
1886 :
1887 : // Automatically set interior parents
1888 20428190 : for (const auto & element : this->element_ptr_range())
1889 : {
1890 : // Ignore an 3D element or an element that already has an interior parent
1891 10512136 : if (element->dim()>=LIBMESH_DIM || element->interior_parent())
1892 10069663 : continue;
1893 :
1894 : // Start by generating a SET of elements that are dim+1 to the current
1895 : // element at each vertex of the current element, thus ignoring interior nodes.
1896 : // If one of the SET of elements is empty, then we will not have an interior parent
1897 : // since an interior parent must be connected to all vertices of the current element
1898 475757 : std::vector<std::set<dof_id_type>> neighbors( element->n_vertices() );
1899 :
1900 16642 : bool found_interior_parents = false;
1901 :
1902 445668 : for (auto n : make_range(element->n_vertices()))
1903 : {
1904 461378 : std::vector<dof_id_type> & element_ids = node_to_elem[element->node_id(n)];
1905 1918983 : for (const auto & eid : element_ids)
1906 1474309 : if (this->elem_ref(eid).dim() == element->dim()+1)
1907 8733 : neighbors[n].insert(eid);
1908 :
1909 461378 : if (neighbors[n].size()>0)
1910 : {
1911 90 : found_interior_parents = true;
1912 : }
1913 : else
1914 : {
1915 : // We have found an empty set, no reason to continue
1916 : // Ensure we set this flag to false before the break since it could have
1917 : // been set to true for previous vertex
1918 16614 : found_interior_parents = false;
1919 16614 : break;
1920 : }
1921 : }
1922 :
1923 : // If we have successfully generated a set of elements for each vertex, we will compare
1924 : // the set for vertex 0 will the sets for the vertices until we find a id that exists in
1925 : // all sets. If found, this is our an interior parent id. The interior parent id found
1926 : // will be the lowest element id if there is potential for multiple interior parents.
1927 442473 : if (found_interior_parents)
1928 : {
1929 56 : std::set<dof_id_type> & neighbors_0 = neighbors[0];
1930 1633 : for (const auto & interior_parent_id : neighbors_0)
1931 : {
1932 40 : found_interior_parents = false;
1933 2627 : for (auto n : make_range(1u, element->n_vertices()))
1934 : {
1935 1633 : if (neighbors[n].count(interior_parent_id))
1936 : {
1937 34 : found_interior_parents = true;
1938 : }
1939 : else
1940 : {
1941 12 : found_interior_parents = false;
1942 12 : break;
1943 : }
1944 : }
1945 :
1946 1420 : if (found_interior_parents)
1947 : {
1948 781 : element->set_interior_parent(this->elem_ptr(interior_parent_id));
1949 22 : break;
1950 : }
1951 : }
1952 :
1953 : // Do we have a mixed dimensional mesh that contains some of
1954 : // its own interior parents, but we already expect to have
1955 : // interior parents on a different mesh? That's going to
1956 : // take some work to support if anyone needs it.
1957 994 : if (separate_interior_mesh)
1958 0 : libmesh_not_implemented_msg
1959 : ("interior_parent() values in multiple meshes are unsupported.");
1960 : }
1961 417301 : }
1962 : }
1963 :
1964 :
1965 :
1966 0 : void MeshBase::set_point_locator_close_to_point_tol(Real val)
1967 : {
1968 0 : _point_locator_close_to_point_tol = val;
1969 0 : if (_point_locator)
1970 : {
1971 0 : if (val > 0.)
1972 0 : _point_locator->set_close_to_point_tol(val);
1973 : else
1974 0 : _point_locator->unset_close_to_point_tol();
1975 : }
1976 0 : }
1977 :
1978 :
1979 :
1980 426 : Real MeshBase::get_point_locator_close_to_point_tol() const
1981 : {
1982 426 : return _point_locator_close_to_point_tol;
1983 : }
1984 :
1985 :
1986 :
1987 4696 : void MeshBase::size_elem_extra_integers()
1988 : {
1989 272 : const std::size_t new_size = _elem_integer_names.size();
1990 35102 : for (auto elem : this->element_ptr_range())
1991 17729 : elem->add_extra_integers(new_size, _elem_integer_default_values);
1992 4696 : }
1993 :
1994 :
1995 :
1996 14472 : void MeshBase::size_node_extra_integers()
1997 : {
1998 846 : const std::size_t new_size = _node_integer_names.size();
1999 216621 : for (auto node : this->node_ptr_range())
2000 110576 : node->add_extra_integers(new_size, _node_integer_default_values);
2001 14472 : }
2002 :
2003 :
2004 : std::pair<std::vector<unsigned int>, std::vector<unsigned int>>
2005 23398 : MeshBase::merge_extra_integer_names(const MeshBase & other)
2006 : {
2007 776 : std::pair<std::vector<unsigned int>, std::vector<unsigned int>> returnval;
2008 23398 : returnval.first = this->add_elem_integers(other._elem_integer_names, true, &other._elem_integer_default_values);
2009 23398 : returnval.second = this->add_node_integers(other._node_integer_names, true, &other._node_integer_default_values);
2010 23398 : return returnval;
2011 : }
2012 :
2013 :
2014 :
2015 : void
2016 426 : MeshBase::post_dofobject_moves(MeshBase && other_mesh)
2017 : {
2018 : // Now that all the DofObject moving is done, we can move the GhostingFunctor objects
2019 : // which include the _default_ghosting,_ghosting_functors and _shared_functors. We also need
2020 : // to set the mesh object associated with these functors to the assignee mesh.
2021 :
2022 : // _default_ghosting
2023 12 : _default_ghosting = std::move(other_mesh._default_ghosting);
2024 426 : _default_ghosting->set_mesh(this);
2025 :
2026 : // _ghosting_functors
2027 426 : _ghosting_functors = std::move(other_mesh._ghosting_functors);
2028 :
2029 852 : for (const auto gf : _ghosting_functors )
2030 : {
2031 426 : gf->set_mesh(this);
2032 : }
2033 :
2034 : // _shared_functors
2035 12 : _shared_functors = std::move(other_mesh._shared_functors);
2036 :
2037 426 : for (const auto & sf : _shared_functors )
2038 : {
2039 0 : (sf.second)->set_mesh(this);
2040 : }
2041 :
2042 : // _constraint_rows
2043 12 : _constraint_rows = std::move(other_mesh._constraint_rows);
2044 :
2045 426 : if (other_mesh.partitioner())
2046 426 : _partitioner = std::move(other_mesh.partitioner());
2047 426 : }
2048 :
2049 :
2050 : void
2051 21765 : MeshBase::copy_cached_data(const MeshBase & other_mesh)
2052 : {
2053 21765 : this->_spatial_dimension = other_mesh._spatial_dimension;
2054 730 : this->_elem_dims = other_mesh._elem_dims;
2055 730 : this->_elem_default_orders = other_mesh._elem_default_orders;
2056 21765 : this->_supported_nodal_order = other_mesh._supported_nodal_order;
2057 730 : this->_mesh_subdomains = other_mesh._mesh_subdomains;
2058 21765 : }
2059 :
2060 :
2061 6250 : bool MeshBase::nodes_and_elements_equal(const MeshBase & other_mesh) const
2062 : {
2063 1188084 : for (const auto & other_node : other_mesh.node_ptr_range())
2064 : {
2065 689575 : const Node * node = this->query_node_ptr(other_node->id());
2066 689575 : if (!node)
2067 0 : return false;
2068 689575 : if (*other_node != *node)
2069 0 : return false;
2070 5306 : }
2071 1188084 : for (const auto & node : this->node_ptr_range())
2072 689575 : if (!other_mesh.query_node_ptr(node->id()))
2073 5306 : return false;
2074 :
2075 719574 : for (const auto & other_elem : other_mesh.element_ptr_range())
2076 : {
2077 523191 : const Elem * elem = this->query_elem_ptr(other_elem->id());
2078 523191 : if (!elem)
2079 0 : return false;
2080 523191 : if (!other_elem->topologically_equal(*elem))
2081 6 : return false;
2082 5306 : }
2083 719298 : for (const auto & elem : this->element_ptr_range())
2084 523050 : if (!other_mesh.query_elem_ptr(elem->id()))
2085 5177 : return false;
2086 :
2087 6109 : return true;
2088 : }
2089 :
2090 :
2091 736 : dof_id_type MeshBase::n_constraint_rows() const
2092 : {
2093 736 : dof_id_type n_local_rows=0, n_unpartitioned_rows=0;
2094 736 : for (const auto & [node, node_constraints] : _constraint_rows)
2095 : {
2096 : // Unpartitioned nodes
2097 0 : if (node->processor_id() == DofObject::invalid_processor_id)
2098 0 : n_unpartitioned_rows++;
2099 0 : else if (node->processor_id() == this->processor_id())
2100 0 : n_local_rows++;
2101 : }
2102 :
2103 736 : this->comm().sum(n_local_rows);
2104 :
2105 736 : return n_unpartitioned_rows + n_local_rows;
2106 : }
2107 :
2108 :
2109 : void
2110 21765 : MeshBase::copy_constraint_rows(const MeshBase & other_mesh)
2111 : {
2112 1460 : LOG_SCOPE("copy_constraint_rows(mesh)", "MeshBase");
2113 :
2114 1444 : _constraint_rows.clear();
2115 :
2116 730 : const auto & other_constraint_rows = other_mesh.get_constraint_rows();
2117 70193 : for (const auto & [other_node, other_node_constraints] : other_constraint_rows)
2118 : {
2119 48428 : const Node * const our_node = this->node_ptr(other_node->id());
2120 6416 : constraint_rows_mapped_type our_node_constraints;
2121 219637 : for (const auto & [other_inner_key_pair, constraint_value] : other_node_constraints)
2122 : {
2123 9048 : const auto & [other_elem, local_node_id] = other_inner_key_pair;
2124 171209 : const Elem * const our_elem = this->elem_ptr(other_elem->id());
2125 171209 : our_node_constraints.emplace_back(std::make_pair(our_elem, local_node_id), constraint_value);
2126 : }
2127 48428 : _constraint_rows[our_node] = std::move(our_node_constraints);
2128 : }
2129 21765 : }
2130 :
2131 :
2132 : template <typename T>
2133 : void
2134 565 : MeshBase::copy_constraint_rows(const SparseMatrix<T> & constraint_operator,
2135 : bool precondition_constraint_operator)
2136 : {
2137 32 : LOG_SCOPE("copy_constraint_rows(mat)", "MeshBase");
2138 :
2139 16 : this->_constraint_rows.clear();
2140 :
2141 : // We're not going to support doing this distributed yet; it'd be
2142 : // pointless unless we temporarily had a linear partitioning to
2143 : // better match the constraint operator.
2144 597 : MeshSerializer serialize(*this);
2145 :
2146 : // Our current mesh should already reflect the desired assembly space
2147 565 : libmesh_error_msg_if(this->n_nodes() != constraint_operator.m(),
2148 : "Constraint operator matrix with " <<
2149 : constraint_operator.m() <<
2150 : "rows does not match this mesh with " <<
2151 : this->n_nodes() << " nodes");
2152 :
2153 : // First, find what new unconstrained DoFs we need to add. We can't
2154 : // iterate over columns in a SparseMatrix, so we'll iterate over
2155 : // rows and keep track of columns.
2156 :
2157 : // If we have nodes that will work unconstrained, keep track of
2158 : // their node ids and corresponding column indices.
2159 : // existing_unconstrained_nodes[column_id] = node_id
2160 32 : std::map<dof_id_type, dof_id_type> existing_unconstrained_columns;
2161 32 : std::set<dof_id_type> existing_unconstrained_nodes;
2162 :
2163 : // In case we need new nodes, keep track of their columns.
2164 : // columns[j][k] will be the kth row index and value of column j
2165 : typedef
2166 : std::unordered_map<dof_id_type,
2167 : std::vector<std::pair<dof_id_type, Real>>>
2168 : columns_type;
2169 1114 : columns_type columns(constraint_operator.n());
2170 :
2171 : // If we need to precondition the constraint operator (e.g. it's an
2172 : // unpreconditioned extraction operator for a Flex IGA matrix),
2173 : // we'll want to keep track of the sum of each column, because we'll
2174 : // be dividing each column by that sum (Jacobi preconditioning on
2175 : // the right, which then leads to symmetric preconditioning on a
2176 : // physics Jacobian).
2177 32 : std::unordered_map<dof_id_type, Real> column_sums;
2178 :
2179 : // Work in parallel, though we'll have to sync shortly
2180 4028 : for (auto i : make_range(constraint_operator.row_start(),
2181 533 : constraint_operator.row_stop()))
2182 : {
2183 618 : std::vector<numeric_index_type> indices;
2184 618 : std::vector<T> values;
2185 :
2186 3463 : constraint_operator.get_row(i, indices, values);
2187 309 : libmesh_assert_equal_to(indices.size(), values.size());
2188 :
2189 3847 : if (indices.size() == 1 &&
2190 825 : values[0] == T(1))
2191 : {
2192 : // If we have multiple simple Ui=Uj constraints, let the
2193 : // first one be our "unconstrained" node and let the others
2194 : // be constrained to it.
2195 810 : if (existing_unconstrained_columns.find(indices[0]) !=
2196 138 : existing_unconstrained_columns.end())
2197 : {
2198 36 : const auto j = indices[0];
2199 36 : columns[j].emplace_back(i, 1);
2200 : }
2201 : else
2202 : {
2203 708 : existing_unconstrained_nodes.insert(i);
2204 774 : existing_unconstrained_columns.emplace(indices[0],i);
2205 : }
2206 : }
2207 : else
2208 22028 : for (auto jj : index_range(indices))
2209 : {
2210 19375 : const auto j = indices[jj];
2211 21134 : const Real coef = libmesh_real(values[jj]);
2212 1759 : libmesh_assert_equal_to(coef, values[jj]);
2213 19375 : columns[j].emplace_back(i, coef);
2214 : }
2215 : }
2216 :
2217 : // Merge data from different processors' slabs of the matrix
2218 565 : this->comm().set_union(existing_unconstrained_nodes);
2219 565 : this->comm().set_union(existing_unconstrained_columns);
2220 :
2221 48 : std::vector<columns_type> all_columns;
2222 565 : this->comm().allgather(columns, all_columns);
2223 :
2224 16 : columns.clear();
2225 6106 : for (auto p : index_range(all_columns))
2226 65601 : for (auto & [j, subcol] : all_columns[p])
2227 183429 : for (auto [i, v] : subcol)
2228 123369 : columns[j].emplace_back(i,v);
2229 :
2230 : // Keep track of elements on which unconstrained nodes exist, and
2231 : // their local node indices.
2232 : // node_to_elem_ptrs[node] = [elem_id, local_node_num]
2233 32 : std::unordered_map<const Node *, std::pair<dof_id_type, unsigned int>> node_to_elem_ptrs;
2234 :
2235 : // Find elements attached to any existing nodes that will stay
2236 : // unconstrained. We'll also build a subdomain set here so we don't
2237 : // have to assert that the mesh is already prepared before we pick a
2238 : // new subdomain for any NodeElems we need to add.
2239 32 : std::set<subdomain_id_type> subdomain_ids;
2240 145181 : for (const Elem * elem : this->element_ptr_range())
2241 : {
2242 49738 : subdomain_ids.insert(elem->subdomain_id());
2243 244572 : for (auto n : make_range(elem->n_nodes()))
2244 : {
2245 194834 : const Node * node = elem->node_ptr(n);
2246 194834 : if (existing_unconstrained_nodes.count(node->id()))
2247 12175 : node_to_elem_ptrs.emplace(node, std::make_pair(elem->id(), n));
2248 : }
2249 : }
2250 :
2251 565 : const subdomain_id_type new_sbd_id = *subdomain_ids.rbegin() + 1;
2252 :
2253 23189 : for (auto j : make_range(constraint_operator.n()))
2254 : {
2255 : // If we already have a good node for this then we're done
2256 4800 : if (existing_unconstrained_columns.count(j))
2257 4668 : continue;
2258 :
2259 : // Get a half-decent spot to place a new NodeElem for
2260 : // unconstrained DoF(s) here. Getting a *fully*-decent spot
2261 : // would require finding a Moore-Penrose pseudoinverse, and I'm
2262 : // not going to do that, but scaling a transpose will at least
2263 : // get us a little uniqueness to make visualization reasonable.
2264 264 : Point newpt;
2265 264 : Real total_scaling = 0;
2266 264 : unsigned int total_entries = 0;
2267 :
2268 : // We'll get a decent initial pid choice here too, if only to
2269 : // aid in later repartitioning.
2270 528 : std::map<processor_id_type, int> pids;
2271 :
2272 264 : auto & column = columns[j];
2273 122230 : for (auto [i, r] : column)
2274 : {
2275 112988 : Node & constrained_node = this->node_ref(i);
2276 112988 : const Point constrained_pt = constrained_node;
2277 3228 : newpt += r*constrained_pt;
2278 112988 : total_scaling += r;
2279 112988 : ++total_entries;
2280 112988 : ++pids[constrained_node.processor_id()];
2281 : }
2282 :
2283 9242 : if (precondition_constraint_operator)
2284 0 : column_sums[j] = total_scaling;
2285 :
2286 9242 : libmesh_error_msg_if
2287 : (!total_entries,
2288 : "Empty column " << j <<
2289 : " found in constraint operator matrix");
2290 :
2291 : // If we have *cancellation* here then we can end up dividing by
2292 : // zero; try just evenly scaling across all constrained node
2293 : // points instead.
2294 9242 : if (total_scaling > TOLERANCE)
2295 264 : newpt /= total_scaling;
2296 : else
2297 0 : newpt /= total_entries;
2298 :
2299 9242 : Node *n = this->add_point(newpt);
2300 9506 : std::unique_ptr<Elem> elem = Elem::build(NODEELEM);
2301 9242 : elem->set_node(0, n);
2302 9242 : elem->subdomain_id() = new_sbd_id;
2303 :
2304 9506 : Elem * added_elem = this->add_elem(std::move(elem));
2305 9242 : this->_elem_dims.insert(0);
2306 9242 : this->_elem_default_orders.insert(added_elem->default_order());
2307 9242 : this->_supported_nodal_order =
2308 8714 : static_cast<Order>
2309 18484 : (std::min(static_cast<int>(this->_supported_nodal_order),
2310 9242 : static_cast<int>(added_elem->supported_nodal_order())));
2311 8978 : this->_mesh_subdomains.insert(new_sbd_id);
2312 9242 : node_to_elem_ptrs.emplace(n, std::make_pair(added_elem->id(), 0));
2313 9242 : existing_unconstrained_columns.emplace(j,n->id());
2314 :
2315 : // Repartition the new objects *after* adding them, so a
2316 : // DistributedMesh doesn't get confused and think you're not
2317 : // adding them on all processors at once.
2318 264 : int n_pids = 0;
2319 43912 : for (auto [pid, count] : pids)
2320 34670 : if (count >= n_pids)
2321 : {
2322 324 : n_pids = count;
2323 15469 : added_elem->processor_id() = pid;
2324 15469 : n->processor_id() = pid;
2325 : }
2326 : }
2327 :
2328 : // Calculate constraint rows in an indexed form that's easy for us
2329 : // to allgather
2330 : std::unordered_map<dof_id_type,
2331 : std::vector<std::pair<std::pair<dof_id_type, unsigned int>,Real>>>
2332 32 : indexed_constraint_rows;
2333 :
2334 4028 : for (auto i : make_range(constraint_operator.row_start(),
2335 533 : constraint_operator.row_stop()))
2336 : {
2337 951 : if (existing_unconstrained_nodes.count(i))
2338 774 : continue;
2339 :
2340 486 : std::vector<numeric_index_type> indices;
2341 486 : std::vector<T> values;
2342 :
2343 2689 : constraint_operator.get_row(i, indices, values);
2344 :
2345 486 : std::vector<std::pair<std::pair<dof_id_type, unsigned int>, Real>> constraint_row;
2346 :
2347 22100 : for (auto jj : index_range(indices))
2348 : {
2349 21173 : const dof_id_type node_id =
2350 : existing_unconstrained_columns[indices[jj]];
2351 :
2352 19411 : Node & constraining_node = this->node_ref(node_id);
2353 :
2354 1762 : libmesh_assert(node_to_elem_ptrs.count(&constraining_node));
2355 :
2356 19411 : auto p = node_to_elem_ptrs[&constraining_node];
2357 :
2358 19382 : Real coef = libmesh_real(values[jj]);
2359 1762 : libmesh_assert_equal_to(coef, values[jj]);
2360 :
2361 : // If we're preconditioning and we created a nodeelem then
2362 : // we can scale the meaning of that nodeelem's value to give
2363 : // us a better-conditioned matrix after the constraints are
2364 : // applied.
2365 19411 : if (precondition_constraint_operator)
2366 0 : if (auto sum_it = column_sums.find(indices[jj]);
2367 0 : sum_it != column_sums.end())
2368 : {
2369 0 : const Real scaling = sum_it->second;
2370 :
2371 0 : if (scaling > TOLERANCE)
2372 0 : coef /= scaling;
2373 : }
2374 :
2375 21173 : constraint_row.emplace_back(std::make_pair(p, coef));
2376 : }
2377 :
2378 243 : indexed_constraint_rows.emplace(i, std::move(constraint_row));
2379 : }
2380 :
2381 565 : this->comm().set_union(indexed_constraint_rows);
2382 :
2383 : // Add constraint rows as mesh constraint rows
2384 17591 : for (auto & [node_id, indexed_row] : indexed_constraint_rows)
2385 : {
2386 17026 : Node * constrained_node = this->node_ptr(node_id);
2387 :
2388 972 : constraint_rows_mapped_type constraint_row;
2389 :
2390 140395 : for (auto [p, coef] : indexed_row)
2391 : {
2392 123369 : const Elem * elem = this->elem_ptr(p.first);
2393 7048 : constraint_row.emplace_back
2394 126893 : (std::make_pair(std::make_pair(elem, p.second), coef));
2395 : }
2396 :
2397 16540 : this->_constraint_rows.emplace(constrained_node,
2398 486 : std::move(constraint_row));
2399 : }
2400 1098 : }
2401 :
2402 :
2403 0 : void MeshBase::print_constraint_rows(std::ostream & os,
2404 : bool print_nonlocal) const
2405 : {
2406 0 : parallel_object_only();
2407 :
2408 : std::string local_constraints =
2409 0 : this->get_local_constraints(print_nonlocal);
2410 :
2411 0 : if (this->processor_id())
2412 : {
2413 0 : this->comm().send(0, local_constraints);
2414 : }
2415 : else
2416 : {
2417 0 : os << "Processor 0:\n";
2418 0 : os << local_constraints;
2419 :
2420 0 : for (auto p : IntRange<processor_id_type>(1, this->n_processors()))
2421 : {
2422 0 : this->comm().receive(p, local_constraints);
2423 0 : os << "Processor " << p << ":\n";
2424 0 : os << local_constraints;
2425 : }
2426 : }
2427 0 : }
2428 :
2429 :
2430 :
2431 0 : std::string MeshBase::get_local_constraints(bool print_nonlocal) const
2432 : {
2433 0 : std::ostringstream os;
2434 :
2435 0 : if (print_nonlocal)
2436 0 : os << "All ";
2437 : else
2438 0 : os << "Local ";
2439 :
2440 0 : os << "Mesh Constraint Rows:"
2441 0 : << std::endl;
2442 :
2443 0 : for (const auto & [node, row] : _constraint_rows)
2444 : {
2445 0 : const bool local = (node->processor_id() == this->processor_id());
2446 :
2447 : // Skip non-local dofs if requested
2448 0 : if (!print_nonlocal && !local)
2449 0 : continue;
2450 :
2451 0 : os << "Constraints for " << (local ? "Local" : "Ghost") << " Node " << node->id()
2452 0 : << ": \t";
2453 :
2454 0 : for (const auto & [elem_and_node, coef] : row)
2455 0 : os << " ((" << elem_and_node.first->id() << ',' << elem_and_node.second << "), " << coef << ")\t";
2456 :
2457 0 : os << std::endl;
2458 : }
2459 :
2460 0 : return os.str();
2461 0 : }
2462 :
2463 :
2464 :
2465 :
2466 : // Explicit instantiations for our template function
2467 : template LIBMESH_EXPORT void
2468 : MeshBase::copy_constraint_rows(const SparseMatrix<Real> & constraint_operator,
2469 : bool precondition_constraint_operator);
2470 :
2471 : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
2472 : template LIBMESH_EXPORT void
2473 : MeshBase::copy_constraint_rows(const SparseMatrix<Complex> & constraint_operator,
2474 : bool precondition_constraint_operator);
2475 : #endif
2476 :
2477 :
2478 : } // namespace libMesh
|