https://mooseframework.inl.gov
Loading...
Searching...
No Matches
MooseMeshUtils.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
10// MOOSE includes
11#include "MooseMeshUtils.h"
12
13#include "libmesh/elem.h"
14#include "libmesh/boundary_info.h"
15#include "libmesh/id_types.h"
16#include "libmesh/int_range.h"
17#include "libmesh/parallel.h"
18#include "libmesh/parallel_algebra.h"
19#include "libmesh/utility.h"
20
21#include "libmesh/distributed_mesh.h"
22#include "libmesh/parallel_elem.h"
23#include "libmesh/parallel_node.h"
24#include "libmesh/compare_elems_by_level.h"
25#include "libmesh/mesh_communication.h"
26#include "libmesh/edge_edge3.h"
27#include "libmesh/enum_to_string.h"
28#include "libmesh/unstructured_mesh.h"
29
30#include "timpi/parallel_sync.h"
31
32namespace MooseMeshUtils
33{
34
35void
37{
38 // We check if we have the same boundary name with different IDs. If we do, we assign the
39 // first ID to every occurrence.
40 const auto & side_bd_name_map = mesh.get_boundary_info().get_sideset_name_map();
41 const auto & node_bd_name_map = mesh.get_boundary_info().get_nodeset_name_map();
42 std::map<boundary_id_type, boundary_id_type> same_name_ids;
43
44 auto populate_map = [](const std::map<boundary_id_type, std::string> & map,
45 std::map<boundary_id_type, boundary_id_type> & same_ids)
46 {
47 for (const auto & pair_outer : map)
48 for (const auto & pair_inner : map)
49 // The last condition is needed to make sure we only store one combination
50 if (pair_outer.second == pair_inner.second && pair_outer.first != pair_inner.first &&
51 same_ids.find(pair_inner.first) == same_ids.end())
52 same_ids[pair_outer.first] = pair_inner.first;
53 };
54
55 populate_map(side_bd_name_map, same_name_ids);
56 populate_map(node_bd_name_map, same_name_ids);
57
58 for (const auto & [id1, id2] : same_name_ids)
59 mesh.get_boundary_info().renumber_id(id2, id1);
60}
61
62void
63changeBoundaryId(MeshBase & mesh,
64 const boundary_id_type old_id,
65 const boundary_id_type new_id,
66 bool delete_prev)
67{
68 // Get a reference to our BoundaryInfo object, we will use it several times below...
69 BoundaryInfo & boundary_info = mesh.get_boundary_info();
70
71 // Container to catch ids passed back from BoundaryInfo
72 std::vector<boundary_id_type> old_ids;
73
74 // Only level-0 elements store BCs. Loop over them.
75 for (auto & elem : as_range(mesh.level_elements_begin(0), mesh.level_elements_end(0)))
76 {
77 unsigned int n_sides = elem->n_sides();
78 for (const auto s : make_range(n_sides))
79 {
80 boundary_info.boundary_ids(elem, s, old_ids);
81 if (std::find(old_ids.begin(), old_ids.end(), old_id) != old_ids.end())
82 {
83 std::vector<boundary_id_type> new_ids(old_ids);
84 std::replace(new_ids.begin(), new_ids.end(), old_id, new_id);
85 if (delete_prev)
86 {
87 boundary_info.remove_side(elem, s);
88 boundary_info.add_side(elem, s, new_ids);
89 }
90 else
91 boundary_info.add_side(elem, s, new_ids);
92 }
93 }
94 }
95
96 // Remove any remaining references to the old ID from the
97 // BoundaryInfo object. This prevents things like empty sidesets
98 // from showing up when printing information, etc.
99 if (delete_prev)
100 boundary_info.remove_id(old_id);
101
102 // global information may now be out of sync
103 mesh.unset_is_prepared();
104}
105
106std::vector<boundary_id_type>
107getBoundaryIDs(const MeshBase & mesh,
108 const std::vector<BoundaryName> & boundary_name,
109 bool generate_unknown)
110{
111 return getBoundaryIDs(
112 mesh, boundary_name, generate_unknown, mesh.get_boundary_info().get_boundary_ids());
113}
114
115std::vector<boundary_id_type>
116getBoundaryIDs(const MeshBase & mesh,
117 const std::vector<BoundaryName> & boundary_name,
118 bool generate_unknown,
119 const std::set<BoundaryID> & mesh_boundary_ids)
120{
121 const BoundaryInfo & boundary_info = mesh.get_boundary_info();
122 const std::map<BoundaryID, std::string> & sideset_map = boundary_info.get_sideset_name_map();
123 const std::map<BoundaryID, std::string> & nodeset_map = boundary_info.get_nodeset_name_map();
124
125 /* It is required to generate a new ID for a given name. It is used often in mesh modifiers such
126 * as SideSetsBetweenSubdomains. Then we need to check the current boundary ids since they are
127 * changing during "mesh modify()", and figure out the right max boundary ID. Most of mesh
128 * modifiers are running in serial, and we won't involve a global communication.
129 */
130 BoundaryID max_boundary_id = 0;
131 if (generate_unknown)
132 {
133 bool has_boundary_id_sets = mesh.preparation().has_boundary_id_sets;
134
135 // Ideally, this requirement should be able to be enforced earlier when we
136 // get the name maps. However, that preparedness check on sideset and
137 // nodeset maps doesn't exist yet.
138 if (!has_boundary_id_sets)
139 libmesh_parallel_only(mesh.comm());
140
141 const auto & bids = has_boundary_id_sets ? boundary_info.get_global_boundary_ids()
142 : boundary_info.get_boundary_ids();
143 if (bids.size())
144 max_boundary_id = *(bids.rbegin());
145 if (!has_boundary_id_sets)
146 mesh.comm().max(max_boundary_id);
147 }
148
149 std::vector<BoundaryID> ids(boundary_name.size());
150 for (const auto i : index_range(boundary_name))
151 {
152 if (boundary_name[i] == "ANY_BOUNDARY_ID")
153 {
154 ids.assign(mesh_boundary_ids.begin(), mesh_boundary_ids.end());
155 if (i)
156 mooseWarning("You passed \"ANY_BOUNDARY_ID\" in addition to other boundary_names. This "
157 "may be a logic error.");
158 break;
159 }
160
161 if (boundary_name[i].empty() && !generate_unknown)
162 mooseError("Incoming boundary name is empty and we are not generating unknown boundary IDs. "
163 "This is invalid.");
164
165 BoundaryID id;
166
167 if (boundary_name[i].empty() || !MooseUtils::isDigits(boundary_name[i]))
168 {
174 if (generate_unknown &&
175 !MooseUtils::doesMapContainValue(sideset_map, std::string(boundary_name[i])) &&
176 !MooseUtils::doesMapContainValue(nodeset_map, std::string(boundary_name[i])))
177 id = ++max_boundary_id;
178 else
179 id = boundary_info.get_id_by_name(boundary_name[i]);
180 }
181 else
182 id = getIDFromName<BoundaryName, BoundaryID>(boundary_name[i]);
183
184 ids[i] = id;
185 }
186
187 return ids;
188}
189
190std::set<BoundaryID>
191getBoundaryIDSet(const MeshBase & mesh,
192 const std::vector<BoundaryName> & boundary_name,
193 bool generate_unknown)
194{
195 auto boundaries = getBoundaryIDs(mesh, boundary_name, generate_unknown);
196 return std::set<BoundaryID>(boundaries.begin(), boundaries.end());
197}
198
199std::vector<subdomain_id_type>
200getSubdomainIDs(const MeshBase & mesh, const std::vector<SubdomainName> & subdomain_names)
201{
202 std::vector<subdomain_id_type> ids;
203
204 // shortcut for "ANY_BLOCK_ID"
205 if (subdomain_names.size() == 1 && subdomain_names[0] == "ANY_BLOCK_ID")
206 {
207 // since get_mesh_subdomains() requires a prepared mesh, we need to check that here
208 mooseAssert(mesh.is_prepared(),
209 "getSubdomainIDs() should only be called on a prepared mesh if ANY_BLOCK_ID is "
210 "used to query all block IDs");
211 ids.assign(mesh.get_mesh_subdomains().begin(), mesh.get_mesh_subdomains().end());
212 return ids;
213 }
214
215 // loop through subdomain names and get IDs (this preserves the order of subdomain_names)
216 ids.resize(subdomain_names.size());
217 for (auto i : index_range(subdomain_names))
218 {
219 if (subdomain_names[i] == "ANY_BLOCK_ID")
220 mooseError("getSubdomainIDs() accepts \"ANY_BLOCK_ID\" if and only if it is the only "
221 "subdomain name being queried.");
222 ids[i] = MooseMeshUtils::getSubdomainID(subdomain_names[i], mesh);
223 }
224
225 return ids;
226}
227
228std::set<subdomain_id_type>
229getSubdomainIDs(const MeshBase & mesh, const std::set<SubdomainName> & subdomain_names)
230{
231 const auto blk_ids = getSubdomainIDs(
232 mesh, std::vector<SubdomainName>(subdomain_names.begin(), subdomain_names.end()));
233 return {blk_ids.begin(), blk_ids.end()};
234}
235
237getBoundaryID(const BoundaryName & boundary_name, const MeshBase & mesh)
238{
240 if (boundary_name.empty())
241 return id;
242
243 if (!MooseUtils::isDigits(boundary_name))
244 id = mesh.get_boundary_info().get_id_by_name(boundary_name);
245 else
246 id = getIDFromName<BoundaryName, BoundaryID>(boundary_name);
247
248 return id;
249}
250
252getSubdomainID(const SubdomainName & subdomain_name, const MeshBase & mesh)
253{
254 if (subdomain_name == "ANY_BLOCK_ID")
255 mooseError("getSubdomainID() does not work with \"ANY_BLOCK_ID\"");
256
258 if (subdomain_name.empty())
259 return id;
260
261 if (!MooseUtils::isDigits(subdomain_name))
262 id = mesh.get_id_by_name(subdomain_name);
263 else
264 id = getIDFromName<SubdomainName, SubdomainID>(subdomain_name);
265
266 return id;
267}
268
269void
270changeSubdomainId(MeshBase & mesh, const subdomain_id_type old_id, const subdomain_id_type new_id)
271{
272 for (const auto & elem : mesh.element_ptr_range())
273 if (elem->subdomain_id() == old_id)
274 elem->subdomain_id() = new_id;
275
276 // global cached information may now be out of sync
277 mesh.unset_is_prepared();
278}
279
280Point
281meshCentroidCalculator(const MeshBase & mesh)
282{
283 Point centroid_pt = Point(0.0, 0.0, 0.0);
284 Real vol_tmp = 0.0;
285 for (const auto & elem : mesh.active_local_element_ptr_range())
286 {
287 Real elem_vol = elem->volume();
288 centroid_pt += (elem->true_centroid()) * elem_vol;
289 vol_tmp += elem_vol;
290 }
291 mesh.comm().sum(centroid_pt);
292 mesh.comm().sum(vol_tmp);
293 centroid_pt /= vol_tmp;
294 return centroid_pt;
295}
296
297Point
298boundaryCentroidCalculator(const BoundaryName & boundary, MeshBase & mesh)
299{
300 // Need boundaries to be synchronized
301 if (!mesh.preparation().has_boundary_id_sets)
302 mesh.get_boundary_info().synchronize_global_id_set();
303 BoundaryInfo & mesh_boundary_info = mesh.get_boundary_info();
304 boundary_id_type boundary_id = mesh_boundary_info.get_id_by_name(boundary);
305 const auto side_list = mesh_boundary_info.build_side_list();
306
307 // Initialize sums
308 Real volume_sum = 0;
309 Point volume_weighted_centroid_sum(0, 0, 0);
310
311 for (const auto & [eid, side_i, bid] : side_list)
312 {
313 if (bid != boundary_id)
314 continue;
315
316 // Get the side
317 const auto elem = mesh.elem_ptr(eid);
318 const auto side = elem->side_ptr(side_i);
319
320 volume_sum += side->volume();
321 volume_weighted_centroid_sum += side->volume() * side->true_centroid();
322 }
323 // Sum across processes
324 mesh.comm().sum(volume_weighted_centroid_sum);
325 mesh.comm().sum(volume_sum);
326
327 return volume_weighted_centroid_sum / volume_sum;
328}
329
330RealVectorValue
331boundaryWeightedNormal(const BoundaryName & boundary, MeshBase & mesh)
332{
333 // Need boundaries to be synchronized
334 if (!mesh.preparation().has_boundary_id_sets)
335 mesh.get_boundary_info().synchronize_global_id_set();
336 BoundaryInfo & mesh_boundary_info = mesh.get_boundary_info();
337 boundary_id_type boundary_id = mesh_boundary_info.get_id_by_name(boundary);
338 const auto side_list = mesh_boundary_info.build_side_list();
339
340 // Initialize sums
341 Real volume_sum = 0;
342 RealVectorValue volume_weighted_normal_sum(0, 0, 0);
343
344 for (const auto & [eid, side_i, bid] : side_list)
345 {
346 if (bid != boundary_id)
347 continue;
348
349 // Get the side
350 const auto elem = mesh.elem_ptr(eid);
351 const auto side = elem->side_ptr(side_i);
352
353 volume_sum += side->volume();
354 volume_weighted_normal_sum += side->volume() * elem->side_vertex_average_normal(side_i);
355 }
356 // Sum across processes
357 mesh.comm().sum(volume_weighted_normal_sum);
358 mesh.comm().sum(volume_sum);
359
360 return volume_weighted_normal_sum / volume_sum;
361}
362
363Real
364computeMaxDistanceToAxis(const MeshBase & mesh,
365 const Point & origin,
366 const RealVectorValue & direction)
367{
368 Real distance = 0;
369 mooseAssert(MooseUtils::absoluteFuzzyEqual(direction.norm_sq(), 1),
370 "Direction should be normalized");
371 for (const auto & node : mesh.node_ptr_range())
372 if (const auto dist_node = (*node - origin).cross(direction).norm(); dist_node > distance)
373 distance = dist_node;
374 mesh.comm().max(distance);
375 return distance;
376}
377
378std::unordered_map<dof_id_type, dof_id_type>
379getExtraIDUniqueCombinationMap(const MeshBase & mesh,
380 const std::set<SubdomainID> & block_ids,
381 std::vector<ExtraElementIDName> extra_ids)
382{
383 // check block restriction
384 const bool block_restricted = !block_ids.empty();
385 // get element id name of interest in recursive parsing algorithm
386 ExtraElementIDName id_name = extra_ids.back();
387 extra_ids.pop_back();
388 const auto id_index = mesh.get_elem_integer_index(id_name);
389
390 // create base parsed id set
391 if (extra_ids.empty())
392 {
393 // get set of extra id values;
394 std::vector<dof_id_type> ids;
395 {
396 std::set<dof_id_type> ids_set;
397 for (const auto & elem : mesh.active_element_ptr_range())
398 {
399 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
400 continue;
401 const auto id = elem->get_extra_integer(id_index);
402 ids_set.insert(id);
403 }
404 mesh.comm().set_union(ids_set);
405 ids.assign(ids_set.begin(), ids_set.end());
406 }
407
408 // determine new extra id values;
409 std::unordered_map<dof_id_type, dof_id_type> parsed_ids;
410 for (auto & elem : mesh.active_element_ptr_range())
411 {
412 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
413 continue;
414 parsed_ids[elem->id()] = std::distance(
415 ids.begin(), std::lower_bound(ids.begin(), ids.end(), elem->get_extra_integer(id_index)));
416 }
417 return parsed_ids;
418 }
419
420 // if extra_ids is not empty, recursively call getExtraIDUniqueCombinationMap
421 const auto base_parsed_ids =
423 // parsing extra ids based on ref_parsed_ids
424 std::vector<std::pair<dof_id_type, dof_id_type>> unique_ids;
425 {
426 std::set<std::pair<dof_id_type, dof_id_type>> unique_ids_set;
427 for (const auto & elem : mesh.active_element_ptr_range())
428 {
429 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
430 continue;
431 const dof_id_type id1 = libmesh_map_find(base_parsed_ids, elem->id());
432 const dof_id_type id2 = elem->get_extra_integer(id_index);
433 const std::pair<dof_id_type, dof_id_type> ids = std::make_pair(id1, id2);
434 unique_ids_set.insert(ids);
435 }
436 mesh.comm().set_union(unique_ids_set);
437 unique_ids.assign(unique_ids_set.begin(), unique_ids_set.end());
438 }
439
440 std::unordered_map<dof_id_type, dof_id_type> parsed_ids;
441
442 for (const auto & elem : mesh.active_element_ptr_range())
443 {
444 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
445 continue;
446 const dof_id_type id1 = libmesh_map_find(base_parsed_ids, elem->id());
447 const dof_id_type id2 = elem->get_extra_integer(id_index);
448 const dof_id_type new_id = std::distance(
449 unique_ids.begin(),
450 std::lower_bound(unique_ids.begin(), unique_ids.end(), std::make_pair(id1, id2)));
451 parsed_ids[elem->id()] = new_id;
452 }
453
454 return parsed_ids;
455}
456
457bool
458isCoPlanar(const std::vector<Point> & vec_pts, const Point plane_nvec, const Point fixed_pt)
459{
460 for (const auto & pt : vec_pts)
461 if (!MooseUtils::absoluteFuzzyEqual((pt - fixed_pt) * plane_nvec, 0.0))
462 return false;
463 return true;
464}
465
466bool
467isCoPlanar(const std::vector<Point> & vec_pts, const Point plane_nvec)
468{
469 return isCoPlanar(vec_pts, plane_nvec, vec_pts.front());
470}
471
472bool
473isCoPlanar(const std::vector<Point> & vec_pts)
474{
475 // Assuming that overlapped Points are allowed, the Points that are overlapped with vec_pts[0] are
476 // removed before further calculation.
477 std::vector<Point> vec_pts_nonzero{vec_pts[0]};
478 for (const auto i : index_range(vec_pts))
479 if (!MooseUtils::absoluteFuzzyEqual((vec_pts[i] - vec_pts[0]).norm(), 0.0))
480 vec_pts_nonzero.push_back(vec_pts[i]);
481 // 3 or fewer points are always coplanar
482 if (vec_pts_nonzero.size() <= 3)
483 return true;
484 else
485 {
486 for (const auto i : make_range(vec_pts_nonzero.size() - 1))
487 {
488 const Point tmp_pt = (vec_pts_nonzero[i] - vec_pts_nonzero[0])
489 .cross(vec_pts_nonzero[i + 1] - vec_pts_nonzero[0]);
490 // if the three points are not collinear, use cross product as the normal vector of the plane
491 if (!MooseUtils::absoluteFuzzyEqual(tmp_pt.norm(), 0.0))
492 return isCoPlanar(vec_pts_nonzero, tmp_pt.unit());
493 }
494 }
495 // If all the points are collinear, they are also coplanar
496 return true;
497}
498
500getNextFreeSubdomainID(MeshBase & input_mesh)
501{
502 // Call this to get most up to date block id information
503 input_mesh.cache_elem_data();
504
505 std::set<SubdomainID> preexisting_subdomain_ids;
506 input_mesh.subdomain_ids(preexisting_subdomain_ids);
507 if (preexisting_subdomain_ids.empty())
508 return 0;
509 else
510 {
511 const auto highest_subdomain_id =
512 *std::max_element(preexisting_subdomain_ids.begin(), preexisting_subdomain_ids.end());
513 mooseAssert(highest_subdomain_id < std::numeric_limits<SubdomainID>::max(),
514 "A SubdomainID with max possible value was found");
515 return highest_subdomain_id + 1;
516 }
517}
518
520getNextFreeBoundaryID(MeshBase & input_mesh)
521{
522 if (!input_mesh.preparation().has_boundary_id_sets)
523 input_mesh.get_boundary_info().regenerate_id_sets();
524
525 auto boundary_ids = input_mesh.get_boundary_info().get_boundary_ids();
526 if (boundary_ids.empty())
527 return 0;
528 return (*boundary_ids.rbegin() + 1);
529}
530
531bool
532hasSubdomainID(const MeshBase & input_mesh, const SubdomainID & id)
533{
534 std::set<SubdomainID> mesh_blocks;
535 input_mesh.subdomain_ids(mesh_blocks);
536
537 // On a distributed mesh we may have sideset IDs that only exist on
538 // other processors
539 if (!input_mesh.is_replicated())
540 input_mesh.comm().set_union(mesh_blocks);
541
542 return mesh_blocks.count(id) && (id != Moose::INVALID_BLOCK_ID);
543}
544
545bool
546hasSubdomainName(const MeshBase & input_mesh, const SubdomainName & name)
547{
548 const auto id = getSubdomainID(name, input_mesh);
549 return hasSubdomainID(input_mesh, id);
550}
551
552bool
553hasBoundaryID(const MeshBase & input_mesh, const BoundaryID id)
554{
555 const BoundaryInfo & boundary_info = input_mesh.get_boundary_info();
556 std::set<boundary_id_type> boundary_ids = boundary_info.get_boundary_ids();
557
558 // On a distributed mesh we may have boundary IDs that only exist on
559 // other processors
560 if (!input_mesh.is_replicated())
561 input_mesh.comm().set_union(boundary_ids);
562
563 return boundary_ids.count(id) && (id != Moose::INVALID_BOUNDARY_ID);
564}
565
566bool
567hasBoundaryName(const MeshBase & mesh, const BoundaryName & name)
568{
569 const BoundaryInfo & boundary_info = mesh.get_boundary_info();
570 const BoundaryID id = boundary_info.get_id_by_name(name);
571 return id != Moose::INVALID_BOUNDARY_ID;
572}
573
574bool
575hasBoundaryNameOrID(const MeshBase & mesh, const BoundaryName & name_or_id)
576{
577 const auto id = getBoundaryID(name_or_id, mesh);
578 return hasBoundaryID(mesh, id);
579}
580
581void
582makeOrderedNodeList(std::vector<std::pair<dof_id_type, dof_id_type>> & node_assm,
583 std::vector<dof_id_type> & elem_id_list,
584 std::vector<dof_id_type> & midpoint_node_list,
585 std::vector<dof_id_type> & ordered_node_list,
586 std::vector<dof_id_type> & ordered_elem_id_list)
587{
588 // a flag to indicate if the ordered_node_list has been reversed
589 bool is_flipped = false;
590 // Start from the first element, try to find a chain of nodes
591 mooseAssert(node_assm.size(), "Node list must not be empty");
592 ordered_node_list.push_back(node_assm.front().first);
593 if (midpoint_node_list.front() != DofObject::invalid_id)
594 ordered_node_list.push_back(midpoint_node_list.front());
595 ordered_node_list.push_back(node_assm.front().second);
596 ordered_elem_id_list.push_back(elem_id_list.front());
597 // Remove the element that has just been added to ordered_node_list
598 node_assm.erase(node_assm.begin());
599 midpoint_node_list.erase(midpoint_node_list.begin());
600 elem_id_list.erase(elem_id_list.begin());
601 const unsigned int node_assm_size_0 = node_assm.size();
602 for (unsigned int i = 0; i < node_assm_size_0; i++)
603 {
604 // Find nodes to expand the chain
605 dof_id_type end_node_id = ordered_node_list.back();
606 auto isMatch1 = [end_node_id](std::pair<dof_id_type, dof_id_type> old_id_pair)
607 { return old_id_pair.first == end_node_id; };
608 auto isMatch2 = [end_node_id](std::pair<dof_id_type, dof_id_type> old_id_pair)
609 { return old_id_pair.second == end_node_id; };
610 auto result = std::find_if(node_assm.begin(), node_assm.end(), isMatch1);
611 bool match_first;
612 if (result == node_assm.end())
613 {
614 match_first = false;
615 result = std::find_if(node_assm.begin(), node_assm.end(), isMatch2);
616 }
617 else
618 {
619 match_first = true;
620 }
621 // If found, add the node to boundary_ordered_node_list
622 if (result != node_assm.end())
623 {
624 const auto elem_index = std::distance(node_assm.begin(), result);
625 if (midpoint_node_list[elem_index] != DofObject::invalid_id)
626 ordered_node_list.push_back(midpoint_node_list[elem_index]);
627 ordered_node_list.push_back(match_first ? (*result).second : (*result).first);
628 node_assm.erase(result);
629 midpoint_node_list.erase(midpoint_node_list.begin() + elem_index);
630 ordered_elem_id_list.push_back(elem_id_list[elem_index]);
631 elem_id_list.erase(elem_id_list.begin() + elem_index);
632 }
633 // If there are still elements in node_assm and result ==
634 // node_assm.end(), this means the curve is not a loop, the
635 // ordered_node_list is flipped and try the other direction that has not
636 // been examined yet.
637 else
638 {
639 if (is_flipped)
640 // Flipped twice; this means the node list has at least two segments.
641 throw MooseException("The node list provided has more than one segments.");
642
643 // mark the first flip event.
644 is_flipped = true;
645 std::reverse(ordered_node_list.begin(), ordered_node_list.end());
646 std::reverse(midpoint_node_list.begin(), midpoint_node_list.end());
647 std::reverse(ordered_elem_id_list.begin(), ordered_elem_id_list.end());
648 // As this iteration is wasted, set the iterator backward
649 i--;
650 }
651 }
652}
653
654void
655makeOrderedNodeList(std::vector<std::pair<dof_id_type, dof_id_type>> & node_assm,
656 std::vector<dof_id_type> & elem_id_list,
657 std::vector<dof_id_type> & ordered_node_list,
658 std::vector<dof_id_type> & ordered_elem_id_list)
659{
660 std::vector<dof_id_type> dummy_midpoint_node_list(node_assm.size(), DofObject::invalid_id);
662 node_assm, elem_id_list, dummy_midpoint_node_list, ordered_node_list, ordered_elem_id_list);
663}
664
665void
666swapNodesInElem(Elem & elem, const unsigned int nd1, const unsigned int nd2)
667{
668 Node * n_temp = elem.node_ptr(nd1);
669 elem.set_node(nd1, elem.node_ptr(nd2));
670 elem.set_node(nd2, n_temp);
671}
672
673void
675 const std::string & class_name,
676 const unsigned int num_sections,
677 const unsigned int num_integers,
678 const std::vector<std::vector<std::vector<dof_id_type>>> & elem_integers_swaps,
679 std::vector<std::unordered_map<dof_id_type, dof_id_type>> & elem_integers_swap_pairs)
680{
681 elem_integers_swap_pairs.reserve(num_sections * num_integers);
682 for (const auto i : make_range(num_integers))
683 {
684 const auto & elem_integer_swaps = elem_integers_swaps[i];
685 std::vector<std::unordered_map<dof_id_type, dof_id_type>> elem_integer_swap_pairs;
686 try
687 {
689 "elem_integers_swaps",
690 elem_integer_swaps,
691 elem_integer_swap_pairs,
692 i * num_sections);
693 }
694 catch (const MooseException & e)
695 {
696 throw MooseException(e.what());
697 }
698
699 elem_integers_swap_pairs.insert(elem_integers_swap_pairs.end(),
700 elem_integer_swap_pairs.begin(),
701 elem_integer_swap_pairs.end());
702 }
703}
704
705std::unique_ptr<ReplicatedMesh>
706buildBoundaryMesh(const MeshBase & input_mesh, const boundary_id_type boundary_id)
707{
708 if (!input_mesh.is_serial())
709 ::mooseError("Input mesh should be serialized for extracting the boundary mesh.\nInput mesh:" +
710 input_mesh.get_info());
711 auto poly_mesh = std::make_unique<ReplicatedMesh>(input_mesh.comm());
712
713 auto side_list = input_mesh.get_boundary_info().build_side_list();
714
715 std::unordered_map<dof_id_type, dof_id_type> old_new_node_map;
716 for (const auto & [eid, side_i, bid] : side_list)
717 {
718 if (bid != boundary_id)
719 continue;
720
721 // Get the side
722 const auto elem = input_mesh.elem_ptr(eid);
723 const auto side = elem->side_ptr(side_i);
724 auto side_elem = elem->build_side_ptr(side_i);
725 auto copy = side_elem->build(side_elem->type());
726
727 for (const auto i : side_elem->node_index_range())
728 {
729 auto & n = side_elem->node_ref(i);
730
731 if (old_new_node_map.count(n.id()))
732 copy->set_node(i, poly_mesh->node_ptr(old_new_node_map[n.id()]));
733 else
734 {
735 Node * node = poly_mesh->add_point(side_elem->point(i));
736 copy->set_node(i, node);
737 old_new_node_map[n.id()] = node->id();
738 }
739 }
740 poly_mesh->add_elem(copy.release());
741 }
742 poly_mesh->skip_partitioning(true);
743 poly_mesh->prepare_for_use();
744 if (poly_mesh->n_elem() == 0)
745 mooseError("The input mesh to extract the boundary from does not have a boundary with id ",
746 boundary_id,
747 ".\n",
748 input_mesh);
749
750 return poly_mesh;
751}
752
753std::unique_ptr<ReplicatedMesh>
754buildLoopBoundaryOf2DMesh(const MeshBase & input_mesh, const boundary_id_type boundary_id)
755{
756 if (!input_mesh.is_serial())
758 "Input 2D mesh should be serialized for extracting the loop boundary mesh.\nInput mesh:" +
759 input_mesh.get_info());
760 auto edge_mesh = std::make_unique<ReplicatedMesh>(input_mesh.comm());
761 auto side_list = input_mesh.get_boundary_info().build_side_list();
762 std::set<BoundaryInfo::BCTuple> visited;
763 bool already_seen_this_side_tuple = false;
764 BoundaryInfo::BCTuple first_side_visited = {libMesh::invalid_uint, 0, 0};
765
766 // Helps move elem to elem at a given node
767 const auto node_to_elem_map = buildBoundaryNodeToElemMap(input_mesh, boundary_id);
768 // Helps check if a node is part of a boundary
769 const auto & node_to_bids = input_mesh.get_boundary_info().get_nodeset_map();
770
771 // Traverse from the first side (edge) in the side_list that matches the boundary_id
772 for (const auto & bside : side_list)
773 {
774 if (std::get<2>(bside) != boundary_id)
775 continue;
776
777 // Check that we are not starting 'another' loop
778 if (bside != first_side_visited)
779 {
780 if (visited.size() && !visited.count(bside))
782 "Boundary " + std::to_string(boundary_id) +
783 " is not a (contiguous) loop. Boundary side: (" + Moose::stringify(bside) +
784 ") was not visited after a single pass around the boundary. Boundary sides visited: " +
785 Moose::stringify(visited));
786 else if (visited.empty())
787 first_side_visited = bside;
788 else
789 continue;
790 }
791
792 // Form the element to be able to find the side
793 // These three variables will be updated while traversing the loop boundary
794 const Elem * elem = input_mesh.elem_ptr(std::get<0>(bside));
795 auto current_side = std::get<1>(bside);
796 auto side_elem = elem->build_side_ptr(current_side);
797
798 // 3D elements should not be part of this boundary
799 if (elem->dim() != 2)
801 "Finding the loop boundary of a 2D mesh cannot be done with non-2D elements such as ",
802 *elem);
803
804 // Start from node 0 of the side (on the boundary), set the next node as the other node
805 // one that side, and keep going from tht next node
806 bool looped_back = false;
807 const Node * starting_node = side_elem->node_ptr(0);
808 const auto new_mesh_starting_node = edge_mesh->add_point(side_elem->point(0));
809 Node * new_first_node = new_mesh_starting_node;
810 [[maybe_unused]] dof_id_type first_node_index = starting_node->id();
811 dof_id_type second_node_index = input_mesh.node_ptr(side_elem->node_id(1))->id();
812
813 while (!looped_back && !already_seen_this_side_tuple)
814 {
815 if (MooseUtils::absoluteFuzzyEqual(input_mesh.point(second_node_index),
816 Point(*starting_node)))
817 looped_back = true;
818
819 // Get the opposite node (the next node) and add it to the edge mesh
820 Node * new_second_node = looped_back
821 ? new_mesh_starting_node
822 : edge_mesh->add_point(input_mesh.point(second_node_index));
823
824 // Add a copy of the edge side element to the mesh
825 side_elem = elem->build_side_ptr(current_side);
826 auto copy = side_elem->build(side_elem->type());
827 copy->set_node(0, new_first_node);
828 copy->set_node(1, new_second_node);
829 edge_mesh->add_elem(copy.release());
830
831 // Make this side as 'visited'
832 std::tuple<dof_id_type, unsigned short int, boundary_id_type> bc_tuple = {
833 elem->id(), current_side, boundary_id};
834 const auto & visit_iter = visited.insert(bc_tuple);
835 if (!looped_back && !visit_iter.second)
836 already_seen_this_side_tuple = true;
837
838 // Find the next element and side_elem
839 auto & connected_elems = libmesh_map_find(node_to_elem_map, second_node_index);
840 bool found_match = false;
841 const auto current_eid = elem->id();
842
843 for (const auto eid : connected_elems)
844 {
845 mooseAssert(!found_match,
846 "We should only find one node on a connected element on this boundary");
847 if (eid != current_eid)
848 {
849 // Update the element (on the input mesh)
850 elem = input_mesh.elem_ptr(eid);
851
852 // Find the side and the opposite node index in that side
853 for (const auto si : elem->side_index_range())
854 {
855 // Check that second node is on the side
856 const auto local_second_node_index =
857 elem->get_node_index(input_mesh.node_ptr(second_node_index));
858 // 2 sides should match this
859 if (elem->is_node_on_side(local_second_node_index, si))
860 {
861 // Only one side should be on the same boundary (node is connected to two elements)
862 // Form a bc_tuple and check the list of boundary sides
863 std::tuple<dof_id_type, unsigned short int, boundary_id_type> side_bc_tuple = {
864 elem->id(), si, boundary_id};
865
866 if (std::find(side_list.begin(), side_list.end(), side_bc_tuple) == side_list.end())
867 continue;
868
869 // We are on the right boundary, just need to get the other node
870 for (const auto local_side_node_id : elem->nodes_on_side(si))
871 {
872 const auto side_node_id = elem->node_id(local_side_node_id);
873
874 // Skip current node (use global index to compare)
875 if (side_node_id == second_node_index)
876 continue;
877 mooseAssert(side_node_id != first_node_index,
878 "Somehow looped back in a single element");
879
880 current_side = si;
881 second_node_index = side_node_id;
882 found_match = true;
883 break;
884 }
885 }
886 // No need to examine more sides
887 if (found_match)
888 break;
889 }
890
891 // No need to examine more elements
892 if (found_match)
893 break;
894 }
895 // next node could be on the same element, just moving on to the next side
896 else if (connected_elems.size() == 1)
897 {
898 elem = input_mesh.elem_ptr(eid);
899 const auto local_second_node_index =
900 elem->get_node_index(input_mesh.node_ptr(second_node_index));
901
902 // Move on to the next side
903 for (const auto si : elem->side_index_range())
904 if (si != current_side && elem->is_node_on_side(local_second_node_index, si))
905 {
906 // Check all nodes on that next side
907 for (const auto local_side_node_id : elem->nodes_on_side(si))
908 {
909 const auto side_node_id = elem->node_id(local_side_node_id);
910 // Skip current node
911 if (side_node_id == second_node_index)
912 continue;
913 mooseAssert((side_node_id != first_node_index) ||
914 (side_list.size() == elem->n_sides()),
915 "Somehow looped back in a single element");
916
917 // Check all the boundaries the other node (on the edge side) is part of
918 const auto bids_range = node_to_bids.equal_range(input_mesh.node_ptr(side_node_id));
919
920 for (auto iter = bids_range.first; iter != bids_range.second; iter++)
921 if (iter->second == boundary_id)
922 {
923 current_side = si;
924 second_node_index = side_node_id;
925 found_match = true;
926 }
927 }
928
929 // no need to examine other sides
930 if (found_match)
931 break;
932 }
933 }
934 }
935
936 // Set current node to opposite node of new element
937 // NOTE: do not use new_first_node or new_second_node to search in the input mesh!
938 new_first_node = new_second_node;
939 first_node_index = second_node_index;
940
941 // Handle loop ending criterion
942 if (!found_match)
943 {
944 mooseWarning("Search for next element in loop boundary failed. Is boundary '" +
945 std::to_string(boundary_id) + "' of mesh ",
946 input_mesh,
947 " a loop boundary?");
948 break;
949 }
950 }
951 }
952
953 if (already_seen_this_side_tuple)
954 mooseWarning("Boundary " + std::to_string(boundary_id) +
955 " seems to have cycles. A single-cycle loop should be used");
956
957 edge_mesh->skip_partitioning(true);
958 edge_mesh->prepare_for_use();
959 if (edge_mesh->n_elem() == 0)
960 mooseError("The input mesh to extract the boundary from does not have a boundary with id ",
961 boundary_id,
962 "\n",
963 input_mesh);
964
965 return edge_mesh;
966}
967
968std::unordered_map<dof_id_type, std::unordered_set<dof_id_type>>
969buildBoundaryNodeToElemMap(const MeshBase & input_mesh, const boundary_id_type boundary_id)
970{
971 if (!input_mesh.is_serial())
973 "Input 2D mesh should be serialized for extracting the loop boundary mesh.\nInput mesh:" +
974 input_mesh.get_info());
975
976 // Get all nodes on that boundary
977 // Boundary ID might be a sideset or a nodeset, get nodes regardless
978 const auto particular_node_ids = getBoundaryNodes(input_mesh, boundary_id);
979
980 std::unordered_map<dof_id_type, std::unordered_set<dof_id_type>> nid_to_eids_map;
981 // Fill the map from looping over elements
982 for (const auto & elem :
983 as_range(input_mesh.active_elements_begin(), input_mesh.active_elements_end()))
984 {
985 for (const auto & nd : elem->node_ref_range())
986 {
987 // Only add the element id if the node is on the boundary
988 if (!particular_node_ids.count(nd.id()))
989 continue;
990
991 auto & elem_ids = nid_to_eids_map[nd.id()];
992 elem_ids.insert(elem->id());
993 }
994 }
995 return nid_to_eids_map;
996}
997
998std::set<dof_id_type>
999getBoundaryNodes(const MeshBase & mesh, const BoundaryID boundary_id)
1000{
1001 std::set<dof_id_type> boundary_node_ids;
1002 const BoundaryInfo & boundary_info = mesh.get_boundary_info();
1003
1004 // Get all nodes from the sideset with ID of boundary_id
1005 const auto & bc_sides =
1006 boundary_info.build_side_list(libMesh::BoundaryInfo::BCTupleSortBy::BOUNDARY_ID);
1007 for (const auto & [elem_id, side, bc_id] : bc_sides)
1008 {
1009 if (bc_id == boundary_id)
1010 {
1011 const auto elem = mesh.elem_ptr(elem_id);
1012 for (const auto ni : elem->nodes_on_side(side))
1013 boundary_node_ids.insert(elem->node_id(ni));
1014 }
1015 }
1016
1017 // Get all nodes from nodeset with ID of boundary_id
1018 const auto & bc_nodes = boundary_info.build_node_list();
1019 for (const auto & [n_id, bc_id] : bc_nodes)
1020 if (bc_id == boundary_id)
1021 boundary_node_ids.insert(n_id);
1022
1023 return boundary_node_ids;
1024}
1025
1026void
1028 std::vector<BoundaryName> boundary_names,
1029 const SubdomainID new_subdomain_id,
1030 const SubdomainName & new_subdomain_name)
1031{
1032 // Generate a new block id if one isn't supplied.
1033 SubdomainID new_block_id = new_subdomain_id;
1034
1035 // Make sure our boundary info and parallel counts are setup
1036 if (!mesh.is_prepared())
1037 {
1038 const bool allow_remote_element_removal = mesh.allow_remote_element_removal();
1039 // We want all of our boundary elements available, so avoid removing them if they haven't
1040 // already been so
1041 mesh.allow_remote_element_removal(false);
1042 mesh.complete_preparation();
1043 mesh.allow_remote_element_removal(allow_remote_element_removal);
1044 }
1045
1046 // Check that the sidesets are present in the mesh
1047 for (const auto & sideset : boundary_names)
1049 mooseException("The sideset '", sideset, "' was not found within the mesh");
1050
1051 auto sideset_ids = MooseMeshUtils::getBoundaryIDs(mesh, boundary_names, true);
1052 std::set<boundary_id_type> sidesets(sideset_ids.begin(), sideset_ids.end());
1053 auto side_list = mesh.get_boundary_info().build_side_list();
1054 if (!mesh.is_serial() && mesh.comm().size() > 1)
1055 {
1056 std::vector<Elem *> elements_to_send;
1057 unsigned short i_need_boundary_elems = 0;
1058 for (const auto & [elem_id, side, bc_id] : side_list)
1059 {
1060 libmesh_ignore(side);
1061 if (sidesets.count(bc_id))
1062 {
1063 // Whether we have this boundary information through our locally owned element or a ghosted
1064 // element, we'll need the boundary elements for parallel consistent addition
1065 i_need_boundary_elems = 1;
1066 auto * elem = mesh.elem_ptr(elem_id);
1067 if (elem->processor_id() == mesh.processor_id())
1068 elements_to_send.push_back(elem);
1069 }
1070 }
1071
1072 std::set<const Elem *, libMesh::CompareElemIdsByLevel> connected_elements(
1073 elements_to_send.begin(), elements_to_send.end());
1074 std::set<const Node *> connected_nodes;
1075 reconnect_nodes(connected_elements, connected_nodes);
1076 std::set<dof_id_type> connected_node_ids;
1077 for (auto * nd : connected_nodes)
1078 connected_node_ids.insert(nd->id());
1079
1080 std::vector<unsigned short> need_boundary_elems(mesh.comm().size());
1081 mesh.comm().allgather(i_need_boundary_elems, need_boundary_elems);
1082 std::unordered_map<processor_id_type, decltype(elements_to_send)> push_element_data;
1083 std::unordered_map<processor_id_type, decltype(connected_nodes)> push_node_data;
1084
1085 for (const auto pid : index_range(mesh.comm()))
1086 // Don't need to send to self
1087 if (pid != mesh.processor_id() && need_boundary_elems[pid])
1088 {
1089 if (elements_to_send.size())
1090 push_element_data[pid] = elements_to_send;
1091 if (connected_nodes.size())
1092 push_node_data[pid] = connected_nodes;
1093 }
1094
1095 auto node_action_functor = [](processor_id_type, const auto &)
1096 {
1097 // Node packing specialization already has unpacked node into mesh, so nothing to do
1098 };
1099 Parallel::push_parallel_packed_range(mesh.comm(), push_node_data, &mesh, node_action_functor);
1100 auto elem_action_functor = [](processor_id_type, const auto &)
1101 {
1102 // Elem packing specialization already has unpacked elem into mesh, so nothing to do
1103 };
1104 TIMPI::push_parallel_packed_range(mesh.comm(), push_element_data, &mesh, elem_action_functor);
1105
1106 // now that we've gathered everything, we need to rebuild the side list
1107 side_list = mesh.get_boundary_info().build_side_list();
1108 }
1109
1110 // Use libmesh's BoundaryInfo::add_elements to create lower-d elements with interior_parent
1111 // pointers and subdomain IDs set in one pass.
1112 auto & unstr_mesh = libMesh::cast_ref<UnstructuredMesh &>(mesh);
1113 unstr_mesh.get_boundary_info().add_elements(sidesets, unstr_mesh, false, {new_block_id});
1114
1115 // Assign block name, if provided
1116 if (new_subdomain_name.size())
1117 mesh.set_subdomain_name(new_block_id, new_subdomain_name);
1118
1119 const bool skip_partitioning_old = mesh.skip_partitioning();
1120 mesh.skip_partitioning(true);
1121 mesh.prepare_for_use();
1122 mesh.skip_partitioning(skip_partitioning_old);
1123}
1124
1125void
1126convertBlockToMesh(MeshBase & source_mesh,
1127 MeshBase & target_mesh,
1128 const std::vector<SubdomainName> & target_blocks)
1129{
1130 if (!source_mesh.is_replicated())
1131 mooseError("This generator does not support distributed meshes.");
1132
1133 const auto target_block_ids = MooseMeshUtils::getSubdomainIDs(source_mesh, target_blocks);
1134
1135 // Check that the block ids/names exist in the mesh
1136 std::set<SubdomainID> mesh_blocks;
1137 source_mesh.subdomain_ids(mesh_blocks);
1138
1139 for (const auto i : index_range(target_block_ids))
1140 if (target_block_ids[i] == Moose::INVALID_BLOCK_ID || !mesh_blocks.count(target_block_ids[i]))
1141 {
1142 mooseException("The target_block '", target_blocks[i], "' was not found within the mesh.");
1143 }
1144
1145 // know which nodes have already been inserted, by tracking the old mesh's node's ids'
1146 std::unordered_map<dof_id_type, dof_id_type> old_new_node_map;
1147
1148 for (const auto target_block_id : target_block_ids)
1149 {
1150
1151 for (auto elem : source_mesh.active_subdomain_elements_ptr_range(target_block_id))
1152 {
1153 if (elem->level() != 0)
1154 mooseError("Refined blocks are not supported by this generator. "
1155 "Can you re-organize mesh generators to refine after converting the block?");
1156
1157 // make a deep copy so that mutiple meshes' destructors don't segfault at program termination
1158 auto copy = elem->build(elem->type());
1159
1160 // Keep the subdomain id
1161 copy->subdomain_id() = elem->subdomain_id();
1162
1163 // index of node in the copy element must be managed manually as there is no intelligent
1164 // insert method
1165 dof_id_type copy_n_index = 0;
1166
1167 // correctly assign new copies of nodes, loop over nodes
1168 for (dof_id_type i : elem->node_index_range())
1169 {
1170 auto & n = elem->node_ref(i);
1171
1172 if (old_new_node_map.count(n.id()))
1173 {
1174 // case where we have already inserted this particular point before
1175 // then we need to find the already-inserted one and hook it up right
1176 // to it's respective element
1177 copy->set_node(copy_n_index++, target_mesh.node_ptr(old_new_node_map[n.id()]));
1178 }
1179 else
1180 {
1181 // case where we've NEVER inserted this particular point before
1182 // add them both to the element and the mesh
1183
1184 // Nodes' IDs are their indexes in the nodes' respective mesh
1185 // If we set them as invalid they are automatically assigned
1186 // Add to mesh, auto-assigning a new id.
1187 Node * node = target_mesh.add_point(elem->point(i));
1188
1189 // Add to element copy (manually)
1190 copy->set_node(copy_n_index++, node);
1191
1192 // remember the (old) ID
1193 old_new_node_map[n.id()] = node->id();
1194 }
1195 }
1196
1197 // it is ok to release the copy element into the mesh because derived meshes class
1198 // (ReplicatedMesh, DistributedMesh) manage their own elements, will delete them
1199 target_mesh.add_elem(copy.release());
1200 }
1201 }
1202
1203 // Move subdomain names
1204 for (const auto sbd_id : target_block_ids)
1205 target_mesh.set_subdomain_name(sbd_id, source_mesh.subdomain_name(sbd_id));
1206}
1207
1208void
1210 UnstructuredMesh & destination,
1211 const UnstructuredMesh & source,
1212 const bool avoid_merging_subdomains,
1213 const bool avoid_merging_boundaries,
1214 const Parallel::Communicator & communicator)
1215{
1216 dof_id_type node_delta = destination.max_node_id();
1217 dof_id_type elem_delta = destination.max_elem_id();
1218
1219 unique_id_type unique_delta =
1220#ifdef LIBMESH_ENABLE_UNIQUE_ID
1221 destination.parallel_max_unique_id();
1222#else
1223 0;
1224#endif
1225
1226 // Prevent overlaps by offsetting the subdomains in
1227 std::unordered_map<subdomain_id_type, subdomain_id_type> id_remapping;
1228 unsigned int block_offset = 0;
1229 if (avoid_merging_subdomains)
1230 {
1231 // Note: if performance becomes an issue, this is overkill for just getting the max node id
1232 std::set<subdomain_id_type> source_ids;
1233 std::set<subdomain_id_type> dest_ids;
1234
1235 // We need source subdomain ids already cached; libMesh will
1236 // scream otherwise
1237 source.subdomain_ids(source_ids, true);
1238
1239 // Our destination is non-const, so we can fix any missing caches
1240 if (!destination.preparation().has_cached_elem_data)
1241 destination.cache_elem_data();
1242
1243 destination.subdomain_ids(dest_ids, true);
1244
1245 mooseAssert(source_ids.size(), "Should have a subdomain");
1246 mooseAssert(dest_ids.size(), "Should have a subdomain");
1247 unsigned int max_dest_bid = *dest_ids.rbegin();
1248 unsigned int min_source_bid = *source_ids.begin();
1249 communicator.max(max_dest_bid);
1250 communicator.min(min_source_bid);
1251 block_offset = 1 + max_dest_bid - min_source_bid;
1252 for (const auto bid : source_ids)
1253 id_remapping[bid] = block_offset + bid;
1254 }
1255
1256 // Copy mesh data over from the other mesh
1257 destination.copy_nodes_and_elements(source,
1258 // Skipping this should cause the neighbors
1259 // to simply be copied from the other mesh
1260 // (which makes sense and is way faster)
1261 /*skip_find_neighbors = */ true,
1262 elem_delta,
1263 node_delta,
1264 unique_delta,
1265 avoid_merging_subdomains ? &id_remapping : nullptr);
1266
1267 // Get an offset to prevent overlaps / wild merging between boundaries
1268 BoundaryInfo & boundary = destination.get_boundary_info();
1269 const BoundaryInfo & other_boundary = source.get_boundary_info();
1270
1271 unsigned int bid_offset = 0;
1272 if (avoid_merging_boundaries)
1273 {
1274 const auto boundary_ids = boundary.get_boundary_ids();
1275 const auto other_boundary_ids = other_boundary.get_boundary_ids();
1276 unsigned int max_dest_bid = boundary_ids.size() ? *boundary_ids.rbegin() : 0;
1277 unsigned int min_source_bid = other_boundary_ids.size() ? *other_boundary_ids.begin() : 0;
1278 communicator.max(max_dest_bid);
1279 communicator.min(min_source_bid);
1280 bid_offset = 1 + max_dest_bid - min_source_bid;
1281 }
1282
1283 // Note: the code below originally came from ReplicatedMesh::stitch_mesh_helper()
1284 // in libMesh replicated_mesh.C around line 1203
1285
1286 // Copy BoundaryInfo from other_mesh too. We do this via the
1287 // list APIs rather than element-by-element for speed.
1288 for (const auto & t : other_boundary.build_node_list())
1289 boundary.add_node(std::get<0>(t) + node_delta, bid_offset + std::get<1>(t));
1290
1291 for (const auto & t : other_boundary.build_side_list())
1292 boundary.add_side(std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
1293
1294 for (const auto & t : other_boundary.build_edge_list())
1295 boundary.add_edge(std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
1296
1297 for (const auto & t : other_boundary.build_shellface_list())
1298 boundary.add_shellface(
1299 std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
1300
1301 // Check for the case with two block ids sharing the same name
1302 if (avoid_merging_subdomains)
1303 {
1304 mooseAssert(mg.parameters().isParamDefined("avoid_merging_subdomains"),
1305 "Missing parameter in the mesh generator calling this function: "
1306 "avoid_merging_subdomains. Considering setting avoid_merging_subdomains to true.");
1307 for (const auto & [block_id, block_name] : destination.get_subdomain_name_map())
1308 for (const auto & [source_id, source_name] : source.get_subdomain_name_map())
1309 if (block_name == source_name)
1310 mg.paramWarning(
1311 "avoid_merging_subdomains",
1312 "Not merging subdomains is creating two subdomains with the same name '" +
1313 block_name + "' but different ids: " + std::to_string(source_id) + " & " +
1314 std::to_string(block_id + block_offset) +
1315 ".\n We recommend using a RenameBlockGenerator to prevent this as you "
1316 "will get errors reading the Exodus output later.");
1317 }
1318
1319 for (const auto & [block_id, block_name] : source.get_subdomain_name_map())
1320 destination.set_subdomain_name_map().insert(
1321 std::make_pair<SubdomainID, SubdomainName>(block_id + block_offset, block_name));
1322
1323 // Check for the case with two boundary ids sharing the same name
1324 if (avoid_merging_boundaries)
1325 {
1326 mooseAssert(mg.parameters().isParamDefined("avoid_merging_boundaries"),
1327 "Missing parameter in the mesh generator calling this function: "
1328 "avoid_merging_boundaries. Considering setting avoid_merging_boundaries to true.");
1329 for (const auto & [b_id, b_name] : other_boundary.get_sideset_name_map())
1330 for (const auto & [source_id, source_name] : boundary.get_sideset_name_map())
1331 if (b_name == source_name)
1332 mg.paramWarning(
1333 "avoid_merging_boundaries",
1334 "Not merging boundaries is creating two sidesets with the same name '" + b_name +
1335 "' but different ids: " + std::to_string(source_id) + " & " +
1336 std::to_string(b_id + bid_offset) +
1337 ".\n We recommend using a RenameBoundaryGenerator to prevent this as you "
1338 "will get errors reading the Exodus output later.");
1339 for (const auto & [b_id, b_name] : other_boundary.get_nodeset_name_map())
1340 for (const auto & [source_id, source_name] : boundary.get_nodeset_name_map())
1341 if (b_name == source_name)
1342 mg.paramWarning(
1343 "avoid_merging_boundaries",
1344 "Not merging boundaries is creating two nodesets with the same name '" + b_name +
1345 "' but different ids: " + std::to_string(source_id) + " & " +
1346 std::to_string(b_id + bid_offset) +
1347 ".\n We recommend using a RenameBoundaryGenerator to prevent this as you "
1348 "will get errors reading the Exodus output later.");
1349 }
1350
1351 for (const auto & [nodeset_id, nodeset_name] : other_boundary.get_nodeset_name_map())
1352 boundary.set_nodeset_name_map().insert(
1353 std::make_pair<BoundaryID, BoundaryName>(nodeset_id + bid_offset, nodeset_name));
1354
1355 for (const auto & [sideset_id, sideset_name] : other_boundary.get_sideset_name_map())
1356 boundary.set_sideset_name_map().insert(
1357 std::make_pair<BoundaryID, BoundaryName>(sideset_id + bid_offset, sideset_name));
1358
1359 for (const auto & [edgeset_id, edgeset_name] : other_boundary.get_edgeset_name_map())
1360 boundary.set_edgeset_name_map().insert(
1361 std::make_pair<BoundaryID, BoundaryName>(edgeset_id + bid_offset, edgeset_name));
1362}
1363
1364void
1365buildPolyLineMesh(MeshBase & mesh,
1366 const std::vector<Point> & points,
1367 const std::vector<Point> & mid_points,
1368 const bool loop,
1369 const BoundaryName & start_boundary,
1370 const BoundaryName & end_boundary,
1371 const std::vector<unsigned int> & nums_edges_between_points)
1372{
1373 mooseAssert(nums_edges_between_points.size() == 1 ||
1374 nums_edges_between_points.size() == points.size() - 1 + loop,
1375 "nums_edges_between_points must be either a single value or have the same number of "
1376 "entries as segments defined by the points.");
1377 mooseAssert(
1378 mid_points.size() == 0 || mid_points.size() == points.size() - (loop ? 0 : 1),
1379 "mid_points must be either empty or have the consistent number of entries as points.");
1380 mooseAssert(
1381 mid_points.size() == 0 ||
1382 (nums_edges_between_points.size() == 1 && nums_edges_between_points.front() == 1) ||
1383 (nums_edges_between_points.size() == points.size() - 1 + loop &&
1384 std::all_of(nums_edges_between_points.begin(),
1385 nums_edges_between_points.end(),
1386 [](unsigned int n) { return n == 1; })),
1387 "mid_points can only be provided if each segment has exactly one edge.");
1388
1389 const auto n_points = points.size();
1390 for (auto i : make_range(n_points))
1391 {
1392 const auto & num_edges_between_points =
1393 (nums_edges_between_points.size() == 1)
1394 ? nums_edges_between_points[0]
1395 : (i == nums_edges_between_points.size() ? 0 : nums_edges_between_points[i]);
1396
1397 Point p = points[i];
1398 const auto pt_counter = (nums_edges_between_points.size() == 1)
1399 ? i
1400 : std::accumulate(nums_edges_between_points.begin(),
1401 nums_edges_between_points.begin() + i,
1402 0);
1403 mesh.add_point(
1404 p, nums_edges_between_points.size() == 1 ? (i * num_edges_between_points) : pt_counter);
1405
1406 if (num_edges_between_points > 1)
1407 {
1408 if (!loop && (i + 1) == n_points)
1409 break;
1410
1411 const auto ip1 = (i + 1) % n_points;
1412 const Point pvec = (points[ip1] - p) / num_edges_between_points;
1413
1414 for (auto j : make_range(1u, num_edges_between_points))
1415 {
1416 p += pvec;
1417 mesh.add_point(
1418 p,
1419 (nums_edges_between_points.size() == 1 ? (i * num_edges_between_points) : pt_counter) +
1420 j);
1421 }
1422 }
1423 }
1424 // Add mid points if applicable. When mid points are provided, each segment has exactly one edge,
1425 // so the midpoint node ids follow the vertex node ids.
1426 for (const auto & i : make_range(mid_points.size()))
1427 mesh.add_point(mid_points[i], n_points + i);
1428
1429 const auto n_segments = loop ? n_points : (n_points - 1);
1430 const auto n_elem =
1431 nums_edges_between_points.size() == 1
1432 ? n_segments * nums_edges_between_points[0]
1433 : std::accumulate(nums_edges_between_points.begin(), nums_edges_between_points.end(), 0);
1434 const auto max_nodes =
1435 (nums_edges_between_points.size() == 1 ? n_segments * nums_edges_between_points[0]
1436 : std::accumulate(nums_edges_between_points.begin(),
1437 nums_edges_between_points.end(),
1438 0)) +
1439 (loop ? 0 : 1);
1440 for (auto i : make_range(n_elem))
1441 {
1442 std::unique_ptr<Elem> elem;
1443 if (mid_points.size())
1444 {
1445 elem = std::make_unique<Edge3>();
1446 elem->set_node(2, mesh.node_ptr(n_points + i));
1447 }
1448 else
1449 elem = Elem::build(EDGE2);
1450 const auto ip1 = (i + 1) % max_nodes;
1451 elem->set_node(0, mesh.node_ptr(i));
1452 elem->set_node(1, mesh.node_ptr(ip1));
1453 elem->set_id() = i;
1454 mesh.add_elem(std::move(elem));
1455 }
1456
1457 if (!loop)
1458 {
1459 BoundaryInfo & bi = mesh.get_boundary_info();
1460 std::vector<BoundaryName> bdy_names{start_boundary, end_boundary};
1461 std::vector<boundary_id_type> ids = MooseMeshUtils::getBoundaryIDs(mesh, bdy_names, true);
1462 bi.add_side(mesh.elem_ptr(0), 0, ids[0]);
1463 bi.add_side(mesh.elem_ptr(n_elem - 1), 1, ids[1]);
1464 }
1465 else
1466 mooseAssert(start_boundary.empty() && end_boundary.empty(),
1467 "Cannot assign start/end boundaries on a looped polyline.");
1468
1469 mesh.prepare_for_use();
1470}
1471
1472void
1473buildPolyLineMesh(MeshBase & mesh,
1474 const std::vector<Point> & points,
1475 const bool loop,
1476 const BoundaryName & start_boundary,
1477 const BoundaryName & end_boundary,
1478 const std::vector<unsigned int> & nums_edges_between_points)
1479{
1481 mesh, points, {}, loop, start_boundary, end_boundary, nums_edges_between_points);
1482}
1483
1484void
1485buildPolyLineMesh(MeshBase & mesh,
1486 const std::vector<Point> & points,
1487 const bool loop,
1488 const BoundaryName & start_boundary,
1489 const BoundaryName & end_boundary,
1490 const Real max_elem_size)
1491{
1492 std::vector<unsigned int> nums_edges_between_points;
1493 const auto n_points = points.size();
1494 for (auto i : make_range(n_points))
1495 {
1496 if (!loop && (i + 1) == n_points)
1497 break;
1498
1499 const auto ip1 = (i + 1) % n_points;
1500 const Real length = (points[ip1] - points[i]).norm();
1501 const unsigned int n_elems = std::max(
1502 static_cast<unsigned int>(std::ceil(length / max_elem_size)), static_cast<unsigned int>(1));
1503 nums_edges_between_points.push_back(n_elems);
1504 }
1505
1507 mesh, points, {}, loop, start_boundary, end_boundary, nums_edges_between_points);
1508}
1509
1510void
1511addExternalBoundary(MeshBase & mesh, const BoundaryID extern_bid, bool & has_external_bid)
1512{
1513 auto & binfo = mesh.get_boundary_info();
1514 for (const auto & elem : mesh.active_element_ptr_range())
1515 for (const auto & i_side : elem->side_index_range())
1516 if (elem->neighbor_ptr(i_side) == nullptr)
1517 {
1518 has_external_bid = true;
1519 binfo.add_side(elem, i_side, extern_bid);
1520 }
1521}
1522}
boundary_id_type BoundaryID
subdomain_id_type SubdomainID
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition MooseError.h:345
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
std::set< std::string > sideset
bool isParamDefined(const std::string &name) const
Method returns true if the parameter is defined for any type.
MeshGenerators are objects that can modify or add to an existing mesh.
const InputParameters & parameters() const
Get the parameters of the object.
Definition MooseBase.h:131
Provides a way for users to bail out of the current solve.
virtual const char * what() const
Get out the error message.
void paramWarning(const std::string &param, Args... args) const
MeshBase & mesh
void copyIntoMesh(MeshGenerator &mg, UnstructuredMesh &destination, const UnstructuredMesh &source, const bool avoid_merging_subdomains, const bool avoid_merging_boundaries, const Parallel::Communicator &communicator)
Helper function for copying one mesh into another.
bool hasBoundaryID(const MeshBase &input_mesh, const BoundaryID id)
Whether a particular boundary ID exists in the mesh.
Point boundaryCentroidCalculator(const BoundaryName &boundary, MeshBase &mesh)
Calculates the centroid of a boundary on a mesh.
bool hasBoundaryName(const MeshBase &mesh, const BoundaryName &name)
Whether a particular boundary name exists in the mesh.
void extraElemIntegerSwapParametersProcessor(const std::string &class_name, const unsigned int num_sections, const unsigned int num_integers, const std::vector< std::vector< std::vector< dof_id_type > > > &elem_integers_swaps, std::vector< std::unordered_map< dof_id_type, dof_id_type > > &elem_integers_swap_pairs)
Reprocess the elem_integers_swaps into maps so they are easier to use.
bool hasSubdomainName(const MeshBase &input_mesh, const SubdomainName &name)
Whether a particular subdomain name exists in the mesh.
void buildPolyLineMesh(MeshBase &mesh, const std::vector< Point > &points, const bool loop, const BoundaryName &start_boundary, const BoundaryName &end_boundary, const std::vector< unsigned int > &nums_edges_between_points)
Generates meshes from edges connecting a list of points.
void changeBoundaryId(MeshBase &mesh, const libMesh::boundary_id_type old_id, const libMesh::boundary_id_type new_id, bool delete_prev)
Changes the old boundary ID to a new ID in the mesh.
bool hasSubdomainID(const MeshBase &input_mesh, const SubdomainID &id)
Whether a particular subdomain ID exists in the mesh.
std::vector< subdomain_id_type > getSubdomainIDs(const libMesh::MeshBase &mesh, const std::vector< SubdomainName > &subdomain_name)
Get the associated subdomainIDs for the subdomain names that are passed in.
std::unique_ptr< ReplicatedMesh > buildBoundaryMesh(const MeshBase &input_mesh, const boundary_id_type boundary_id)
Build a lower-dimensional mesh from a boundary of an input mesh Note: The lower-dimensional mesh will...
std::vector< BoundaryID > getBoundaryIDs(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown, const std::set< BoundaryID > &mesh_boundary_ids)
Gets the boundary IDs with their names.
std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > buildBoundaryNodeToElemMap(const MeshBase &input_mesh, const boundary_id_type boundary_id)
Build a map from the node ids to all element ids they are part of for the nodes on a particular nodes...
void changeSubdomainId(MeshBase &mesh, const subdomain_id_type old_id, const subdomain_id_type new_id)
Changes the old subdomain ID to a new ID in the mesh.
void createSubdomainFromSidesets(MeshBase &mesh, std::vector< BoundaryName > boundary_names, const SubdomainID new_subdomain_id, const SubdomainName &new_subdomain_name)
Create a new subdomain by generating new side elements from a list of sidesets in a given mesh.
std::set< dof_id_type > getBoundaryNodes(const MeshBase &mesh, const BoundaryID boundary_id)
Get all the nodes on that particular boundary, whether a nodeset or a sideset.
BoundaryID getNextFreeBoundaryID(MeshBase &input_mesh)
Checks input mesh and returns the largest boundary ID in the mesh plus one, which is a boundary ID in...
void addExternalBoundary(MeshBase &mesh, const BoundaryID extern_bid, bool &has_external_bid)
Adds a sideset for the external boundary of the mesh (e.g.
void swapNodesInElem(Elem &elem, const unsigned int nd1, const unsigned int nd2)
Swap two nodes within an element.
void convertBlockToMesh(MeshBase &source_mesh, MeshBase &target_mesh, const std::vector< SubdomainName > &target_blocks)
Convert a list of blocks in a given mesh to a standalone new mesh.
std::set< BoundaryID > getBoundaryIDSet(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown)
Gets the boundary IDs into a set with their names.
std::unordered_map< dof_id_type, dof_id_type > getExtraIDUniqueCombinationMap(const MeshBase &mesh, const std::set< SubdomainID > &block_ids, std::vector< ExtraElementIDName > extra_ids)
Create a new set of element-wise IDs by finding unique combinations of existing extra ID values.
void idSwapParametersProcessor(const std::string &class_name, const std::string &id_name, const std::vector< std::vector< T > > &id_swaps, std::vector< std::unordered_map< T, T > > &id_swap_pairs, const unsigned int row_index_shift=0)
Reprocess the swap related input parameters to make pairs out of them to ease further processing.
void mergeBoundaryIDsWithSameName(MeshBase &mesh)
Merges the boundary IDs of boundaries that have the same names but different IDs.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
Gets the boundary ID associated with the given BoundaryName.
bool isCoPlanar(const std::vector< Point > &vec_pts, const Point plane_nvec, const Point fixed_pt)
Decides whether all the Points of a vector of Points are in a plane that is defined by a normal vecto...
std::unique_ptr< ReplicatedMesh > buildLoopBoundaryOf2DMesh(const MeshBase &input_mesh, const boundary_id_type boundary_id)
Build a loop mesh of edges from the contiguous 2D boundary of 2D input mesh Note: The lower-dimension...
RealVectorValue boundaryWeightedNormal(const BoundaryName &boundary, MeshBase &mesh)
Calculates the side-volume weighted (side-vertex) average normal of a boundary on a mesh.
void makeOrderedNodeList(std::vector< std::pair< dof_id_type, dof_id_type > > &node_assm, std::vector< dof_id_type > &elem_id_list, std::vector< dof_id_type > &midpoint_node_list, std::vector< dof_id_type > &ordered_node_list, std::vector< dof_id_type > &ordered_elem_id_list)
Convert a list of sides in the form of a vector of pairs of node ids into a list of ordered nodes bas...
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
Point meshCentroidCalculator(const MeshBase &mesh)
Calculates the centroid of a MeshBase.
bool hasBoundaryNameOrID(const MeshBase &mesh, const BoundaryName &name_or_id)
Whether a particular boundary name or ID exists in the mesh.
Real computeMaxDistanceToAxis(const MeshBase &mesh, const Point &origin, const RealVectorValue &direction)
Computes the maximum distance from all nodes of a mesh to a general axis.
std::string stringify(const T &t)
conversion to string
Definition Conversion.h:65
const BoundaryID INVALID_BOUNDARY_ID
Definition MooseTypes.C:22
const SubdomainID INVALID_BLOCK_ID
Definition MooseTypes.C:20
void push_parallel_packed_range(const Communicator &comm, MapToContainers &&data, Context *context, const ActionFunctor &act_on_data)
const unsigned int invalid_uint
Real distance(const Point &p)