https://mooseframework.inl.gov
Loading...
Searching...
No Matches
MeshTriangulationUtils.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
11#include "MooseMeshUtils.h"
12#include "CastUniquePointer.h"
13
14#include "libmesh/elem.h"
15#include "libmesh/boundary_info.h"
16#include "libmesh/int_range.h"
17#include "libmesh/enum_to_string.h"
18#include "libmesh/mesh_modification.h"
19#include "libmesh/mesh_serializer.h"
20#include "libmesh/mesh_triangle_holes.h"
21#include "libmesh/parsed_function.h"
22#include "libmesh/poly2tri_triangulator.h"
23#include "libmesh/unstructured_mesh.h"
24
26{
27
28std::set<std::size_t>
29outerBoundaryIds(MeshGenerator & mg, MeshBase & boundary_mesh, const XYDelaunayOptions & opts)
30{
31 std::set<std::size_t> bdy_ids;
32
33 if (!opts.input_boundary_names.empty())
34 {
35 if (!opts.input_subdomain_names.empty())
36 mg.paramError(
37 "input_subdomain_names",
38 "input_boundary_names and input_subdomain_names cannot both specify an outer boundary.");
39
40 for (const auto & name : opts.input_boundary_names)
41 {
42 auto bcid = MooseMeshUtils::getBoundaryID(name, boundary_mesh);
43 if (bcid == BoundaryInfo::invalid_id)
44 mg.paramError("input_boundary_names", name, " is not a boundary name in the input mesh");
45
46 bdy_ids.insert(bcid);
47 }
48 }
49
50 if (!opts.input_subdomain_names.empty())
51 {
52 // Make sure subdomain info caches are up to date
53 if (!boundary_mesh.preparation().has_cached_elem_data)
54 boundary_mesh.cache_elem_data();
55
56 const auto subdomain_ids =
58
59 // Check that the requested subdomains exist in the mesh
60 std::set<SubdomainID> subdomains;
61 boundary_mesh.subdomain_ids(subdomains);
62
63 for (auto i : index_range(subdomain_ids))
64 {
65 if (subdomain_ids[i] == Moose::INVALID_BLOCK_ID || !subdomains.count(subdomain_ids[i]))
66 mg.paramError("input_subdomain_names",
68 " was not found in the boundary mesh");
69
70 bdy_ids.insert(subdomain_ids[i]);
71 }
72 }
73
74 return bdy_ids;
75}
76
77std::unique_ptr<MeshBase>
79 std::unique_ptr<MeshBase> boundary_mesh,
80 std::vector<std::unique_ptr<MeshBase>> hole_meshes,
81 const XYDelaunayOptions & xyd_opts)
82{
83 // Put the boundary mesh in a local pointer
84 std::unique_ptr<UnstructuredMesh> mesh =
85 dynamic_pointer_cast<UnstructuredMesh>(std::move(boundary_mesh));
86
87 // Get ready to triangulate the line segments we extract from it
89 poly2tri.triangulation_type() = libMesh::TriangulatorInterface::PSLG;
90
91 // If we're using a user-requested subset of boundaries on that
92 // mesh, get their ids.
93 const std::set<std::size_t> bdy_ids = outerBoundaryIds(mg, *mesh, xyd_opts);
94
95 if (!bdy_ids.empty())
96 poly2tri.set_outer_boundary_ids(bdy_ids);
97
99 poly2tri.set_refine_boundary_allowed(xyd_opts.refine_bdy);
100 poly2tri.set_verify_hole_boundaries(xyd_opts.verify_holes);
101
102 poly2tri.desired_area() = xyd_opts.desired_area;
103 poly2tri.minimum_angle() = 0; // Not yet supported
104 poly2tri.smooth_after_generating() = xyd_opts.smooth_tri;
105
106 std::vector<libMesh::TriangulatorInterface::MeshedHole> meshed_holes;
107 std::vector<libMesh::TriangulatorInterface::Hole *> triangulator_hole_ptrs(hole_meshes.size());
108 // This tells us the element orders of the hole meshes
109 // For the boundary meshes, it can be access through poly2tri.segment_midpoints.
110 std::vector<bool> holes_with_midpoints(hole_meshes.size());
111 bool stitch_second_order_holes(false);
112
113 // Make sure pointers here aren't invalidated by a resize
114 meshed_holes.reserve(hole_meshes.size());
115 for (auto hole_i : index_range(hole_meshes))
116 {
117 if (!hole_meshes[hole_i]->is_prepared())
118 hole_meshes[hole_i]->prepare_for_use();
119 if (hole_i < xyd_opts.hole_boundary_id_filters.size() &&
120 !xyd_opts.hole_boundary_id_filters[hole_i].empty())
121 meshed_holes.emplace_back(*hole_meshes[hole_i], xyd_opts.hole_boundary_id_filters[hole_i]);
122 else
123 meshed_holes.emplace_back(*hole_meshes[hole_i]);
124 holes_with_midpoints[hole_i] = meshed_holes.back().n_midpoints();
125 stitch_second_order_holes =
126 xyd_opts.stitch_holes.empty()
127 ? false
128 : ((holes_with_midpoints[hole_i] && xyd_opts.stitch_holes[hole_i]) ||
129 stitch_second_order_holes);
130 if (hole_i < xyd_opts.refine_holes.size())
131 meshed_holes.back().set_refine_boundary_allowed(xyd_opts.refine_holes[hole_i]);
132
133 triangulator_hole_ptrs[hole_i] = &meshed_holes.back();
134 }
135 if (stitch_second_order_holes &&
136 (xyd_opts.tri_elem_type == "TRI3" || xyd_opts.tri_elem_type == "DEFAULT"))
137 mg.paramError(
138 "tri_element_type",
139 "Cannot use first order elements with stitched quadratic element holes. Please try "
140 "to specify a higher-order tri_element_type or reduce the order of the hole inputs.");
141
142 if (!triangulator_hole_ptrs.empty())
143 poly2tri.attach_hole_list(&triangulator_hole_ptrs);
144
145 if (xyd_opts.desired_area_func != "")
146 {
147 // poly2tri will clone this so it's fine going out of scope
149 poly2tri.set_desired_area_function(&area_func);
150 }
151 else if (xyd_opts.use_auto_area_func)
152 {
153 poly2tri.set_auto_area_function(
154 mg.comm(),
157 xyd_opts.auto_area_func_default_size > 0.0 ? xyd_opts.auto_area_func_default_size : 0.0,
159 : -1.0);
160 }
161
162 if (xyd_opts.tri_elem_type == "TRI6")
163 poly2tri.elem_type() = libMesh::ElemType::TRI6;
164 else if (xyd_opts.tri_elem_type == "TRI7")
165 {
166 poly2tri.elem_type() = libMesh::ElemType::TRI7;
167 // Snapping boundary mid-edge nodes onto a curved input boundary leaves the
168 // TRI7 interior node at the stale straight-edge centroid. Ask libMesh to
169 // move it to the curved-mapping centroid; this is also what makes
170 // libMesh's Tri6-based tangling check valid for TRI7.
171 poly2tri.set_fixup_tri7_center_nodes(true);
172 }
173 // Add interior points before triangulating. Only points inside the boundaries
174 // will be meshed.
175 for (const auto & point : xyd_opts.interior_points)
176 mesh->add_point(point);
177
178 poly2tri.triangulate();
179
180 finalizeTriangulation(mg, *mesh, hole_meshes, holes_with_midpoints, xyd_opts);
181
182 return mesh;
183}
184
185void
187 UnstructuredMesh & mesh,
188 std::vector<std::unique_ptr<MeshBase>> & holes,
189 const std::vector<bool> & holes_with_midpoints,
190 const XYDelaunayOptions & opts)
191{
192 SubdomainID output_subdomain_id = opts.has_output_subdomain_id ? opts.output_subdomain_id : 0;
193
195 {
197
198 if (id == Elem::invalid_subdomain_id)
199 {
200 if (!opts.has_output_subdomain_id)
201 {
202 // We'll probably need to make a new ID, then
203 output_subdomain_id = MooseMeshUtils::getNextFreeSubdomainID(mesh);
204
205 // But check the hole meshes for our output subdomain name too
206 for (auto & hole_ptr : holes)
207 {
208 auto possible_sbdid =
210 // Huh, it was in one of them
211 if (possible_sbdid != Elem::invalid_subdomain_id)
212 {
213 output_subdomain_id = possible_sbdid;
214 break;
215 }
216 output_subdomain_id =
217 std::max(output_subdomain_id, MooseMeshUtils::getNextFreeSubdomainID(*hole_ptr));
218 }
219 }
220 }
221 else
222 {
224 {
225 if (id != output_subdomain_id)
226 mg.paramError("output_subdomain_name",
227 "name has been used by the input meshes and the corresponding id is not "
228 "equal to 'output_subdomain_id'");
229 }
230 else
231 output_subdomain_id = id;
232 }
233 // We do not want to set an empty subdomain name
234 if (opts.output_subdomain_name.size())
235 mesh.set_subdomain_name(output_subdomain_id, opts.output_subdomain_name);
236 }
237
238 if (opts.smooth_tri || output_subdomain_id)
239 for (auto elem : mesh.element_ptr_range())
240 {
241 mooseAssert(
242 elem->type() ==
243 (opts.tri_elem_type == "TRI6" ? TRI6 : (opts.tri_elem_type == "TRI7" ? TRI7 : TRI3)),
244 "Unexpected element type " << libMesh::Utility::enum_to_string(elem->type())
245 << " found in triangulation");
246
247 elem->subdomain_id() = output_subdomain_id;
248
249 // I do not trust Laplacian mesh smoothing not to invert
250 // elements near reentrant corners. Eventually we'll add better
251 // smoothing options, but even those might have failure cases.
252 // Better to always do extra tests here than to ever let users
253 // try to run on a degenerate mesh.
254 if (opts.smooth_tri)
255 {
256 auto cross_prod = (elem->point(1) - elem->point(0)).cross(elem->point(2) - elem->point(0));
257
258 if (cross_prod(2) <= 0)
259 mooseError("Inverted element found in triangulation.\n"
260 "Laplacian smoothing can create these at reentrant corners; disable it?");
261 }
262 }
263
264 // The hole meshes are specified by the user, so they could have any
265 // BCID or no BCID or any combination of BCIDs on their outer
266 // boundary, so we'll have to set our own BCID to use for stitching
267 // there. We'll need to check all the holes for used BCIDs, if we
268 // want to pick a new ID on hole N that doesn't conflict with any
269 // IDs on hole M < N (or with the IDs on the new triangulation)
270
271 // The new triangulation by default assigns BCID i+1 to hole i ...
272 // but we can't even use this for mesh stitching, because we can't
273 // be sure it isn't also already in use on the hole's mesh and so we
274 // won't be able to safely clear it afterwards.
275 const boundary_id_type end_bcid = holes.size() + 1;
276
277 // If a hole has its boundary layer mesh, we need to move the hole bcid to the "real" hole
278 // boundary in the boundary layer mesh. So we need to record them here.
279 std::vector<BoundaryID> hole_boundary_rec(holes.size());
280 std::iota(hole_boundary_rec.begin(), hole_boundary_rec.end(), 1);
281
282 // For the hole meshes that need to be stitched, we would like to make sure the hole boundary ids
283 // and output boundary id are not conflicting with the existing boundary ids of the hole meshes to
284 // be stitched.
285 BoundaryID free_boundary_id = 0;
286 if (opts.stitch_holes.size())
287 {
288 for (auto hole_i : index_range(holes))
289 {
290 if (opts.stitch_holes[hole_i])
291 {
292 free_boundary_id =
293 std::max(free_boundary_id, MooseMeshUtils::getNextFreeBoundaryID(*holes[hole_i]));
294 holes[hole_i]->comm().max(free_boundary_id);
295 }
296 }
297 for (auto h : index_range(holes))
298 {
299 libMesh::MeshTools::Modification::change_boundary_id(mesh, h + 1, h + 1 + free_boundary_id);
300 hole_boundary_rec[h] = h + 1 + free_boundary_id;
301 }
302 }
303 boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
304
305 // We might be overriding the default bcid numbers. We have to be
306 // careful about how we renumber, though. We pick unused temporary
307 // numbers because e.g. "0->2, 2->0" is impossible to do
308 // sequentially, but "0->N, 2->N+2, N->2, N+2->0" works.
310 mesh, 0, (opts.has_output_boundary ? end_bcid : 0) + free_boundary_id);
311
312 if (!opts.hole_boundaries.empty())
313 {
314 auto hole_boundary_ids = MooseMeshUtils::getBoundaryIDs(mesh, opts.hole_boundaries, true);
315
316 for (auto h : index_range(holes))
318 mesh, h + 1 + free_boundary_id, h + 1 + free_boundary_id + end_bcid);
319
320 for (auto h : index_range(holes))
321 {
323 mesh, h + 1 + free_boundary_id + end_bcid, hole_boundary_ids[h]);
324 hole_boundary_rec[h] = hole_boundary_ids[h];
325 mesh.get_boundary_info().sideset_name(hole_boundary_ids[h]) = opts.hole_boundaries[h];
326 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(hole_boundary_ids[h] + 1));
327 }
328 }
329
330 if (opts.has_output_boundary)
331 {
332 const std::vector<BoundaryID> output_boundary_id =
334
336 mesh, end_bcid + free_boundary_id, output_boundary_id[0]);
337 mesh.get_boundary_info().sideset_name(output_boundary_id[0]) = opts.output_boundary;
338
339 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(output_boundary_id[0] + 1));
340 }
341
342 bool doing_stitching = false;
343
344 for (auto hole_i : index_range(holes))
345 {
346 const MeshBase & hole_mesh = *holes[hole_i];
347 auto & hole_boundary_info = hole_mesh.get_boundary_info();
348 const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
349
350 if (!local_hole_bcids.empty())
351 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(*local_hole_bcids.rbegin() + 1));
352 hole_mesh.comm().max(new_hole_bcid);
353
354 if (hole_i < opts.stitch_holes.size() && opts.stitch_holes[hole_i])
355 doing_stitching = true;
356 }
357
358 const boundary_id_type inner_bcid = new_hole_bcid + 1;
359
360 // libMesh mesh stitching still requires a serialized mesh, and it's
361 // cheaper to do that once than to do it once-per-hole
362 libMesh::MeshSerializer serial(mesh, doing_stitching);
363
364 // Define a reference map variable for subdomain map
365 auto & main_subdomain_map = mesh.set_subdomain_name_map();
366 for (auto hole_i : index_range(holes))
367 {
368 if (hole_i < opts.stitch_holes.size() && opts.stitch_holes[hole_i])
369 {
370 UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(*holes[hole_i]);
371 // increase hole mesh order if the triangulation mesh has higher order
372 if (!holes_with_midpoints[hole_i])
373 {
374 if (opts.tri_elem_type == "TRI6")
375 hole_mesh.all_second_order();
376 else if (opts.tri_elem_type == "TRI7")
377 hole_mesh.all_complete_order();
378 }
379 auto & hole_boundary_info = hole_mesh.get_boundary_info();
380
381 // Our algorithm here requires a serialized Mesh. To avoid
382 // redundant serialization and deserialization (libMesh
383 // MeshedHole and stitch_meshes still also require
384 // serialization) we'll do the serialization up front.
385 libMesh::MeshSerializer serial_hole(hole_mesh);
386
387 // It would have been nicer for MeshedHole to add the BCID
388 // itself, but we want MeshedHole to work with a const mesh.
389 // We'll still use MeshedHole, for its code distinguishing
390 // outer boundaries from inner boundaries on a
391 // hole-with-holes.
392 const auto & hole_bdy_id_filter = (hole_i < opts.hole_boundary_id_filters.size())
393 ? opts.hole_boundary_id_filters[hole_i]
394 : std::set<std::size_t>();
395 libMesh::TriangulatorInterface::MeshedHole mh{hole_mesh, hole_bdy_id_filter};
396
397 // We have to translate from MeshedHole points to mesh
398 // sides.
399 std::unordered_map<Point, Point> next_hole_boundary_point;
400 const int np = mh.n_points();
401 for (auto pi : make_range(1, np))
402 next_hole_boundary_point[mh.point(pi - 1)] = mh.point(pi);
403 next_hole_boundary_point[mh.point(np - 1)] = mh.point(0);
404
405#ifndef NDEBUG
406 int found_hole_sides = 0;
407#endif
408 for (auto elem : hole_mesh.element_ptr_range())
409 {
410 if (elem->dim() != 2)
411 mooseError("Non 2-D element found in hole; stitching is not supported.");
412
413 auto ns = elem->n_sides();
414 for (auto s : make_range(ns))
415 {
416 auto it_s = next_hole_boundary_point.find(elem->point(s));
417 if (it_s != next_hole_boundary_point.end())
418 if (it_s->second == elem->point((s + 1) % ns))
419 {
420 hole_boundary_info.add_side(elem, s, new_hole_bcid);
421#ifndef NDEBUG
422 ++found_hole_sides;
423#endif
424 }
425 }
426 }
427 mooseAssert(found_hole_sides == np, "Failed to find full outer boundary of meshed hole");
428
429 auto & mesh_boundary_info = mesh.get_boundary_info();
430#ifndef NDEBUG
431 int found_inner_sides = 0;
432#endif
433 for (auto elem : mesh.element_ptr_range())
434 {
435 auto ns = elem->n_sides();
436 for (auto s : make_range(ns))
437 {
438 auto it_s = next_hole_boundary_point.find(elem->point((s + 1) % ns));
439 if (it_s != next_hole_boundary_point.end())
440 if (it_s->second == elem->point(s))
441 {
442 mesh_boundary_info.add_side(elem, s, inner_bcid);
443#ifndef NDEBUG
444 ++found_inner_sides;
445#endif
446 }
447 }
448 }
449 mooseAssert(found_inner_sides == np, "Failed to find full boundary around meshed hole");
450
451 // Retrieve subdomain name map from the mesh to be stitched and insert it into the main
452 // subdomain map
453 const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
454 main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
455
456 // We do not need the hole_bdy_id_filter anymore
457 for (const auto & bcid : hole_bdy_id_filter)
458 hole_boundary_info.remove_id(bcid);
459 // If we are stitching a hole boundary layer mesh, we need to reassign the bcid
460 if (hole_bdy_id_filter.size())
461 {
463 hole_mesh,
464 opts.hole_boundary_inner_id_defaults[hole_i].empty()
465 ? 1
466 : *opts.hole_boundary_inner_id_defaults[hole_i].begin(),
467 hole_boundary_rec[hole_i],
468 true);
469 hole_mesh.get_boundary_info().sideset_name(hole_boundary_rec[hole_i]) =
470 mesh.get_boundary_info().sideset_name(hole_boundary_rec[hole_i]);
471 mesh.get_boundary_info().remove_id(hole_boundary_rec[hole_i]);
472 }
473
474 mesh.stitch_meshes(hole_mesh,
475 inner_bcid,
476 new_hole_bcid,
477 TOLERANCE,
478 /*clear_stitched_bcids*/ true,
480 opts.use_binary_search);
481 }
482 }
483 // Check if one SubdomainName is shared by more than one subdomain ids
484 std::set<SubdomainName> main_subdomain_map_name_list;
485 for (auto const & id_name_pair : main_subdomain_map)
486 main_subdomain_map_name_list.emplace(id_name_pair.second);
487 if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
488 mg.paramError("holes", "The hole meshes contain subdomain name maps with conflicts.");
489
490 mesh.unset_is_prepared();
491}
492}
boundary_id_type BoundaryID
subdomain_id_type SubdomainID
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
MeshGenerators are objects that can modify or add to an existing mesh.
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition MooseBase.h:457
const Parallel::Communicator & comm() const
virtual void set_refine_boundary_allowed(bool refine_bdy_allowed) override
virtual void set_desired_area_function(FunctionBase< Real > *desired) override
virtual void triangulate() override
virtual unsigned int n_points() const override
void set_outer_boundary_ids(std::set< std::size_t > bdy_ids)
TriangulationType & triangulation_type()
void set_verify_hole_boundaries(bool v)
void attach_hole_list(const std::vector< Hole * > *holes)
void set_fixup_tri7_center_nodes(bool v)
void set_auto_area_function(const Parallel::Communicator &comm, const unsigned int num_nearest_pts, const unsigned int power, const Real background_value, const Real background_eff_dist)
void set_interpolate_boundary_points(int n_points)
MeshBase & mesh
void finalizeTriangulation(MeshGenerator &mg, UnstructuredMesh &mesh, std::vector< std::unique_ptr< MeshBase > > &holes, const std::vector< bool > &holes_with_midpoints, const XYDelaunayOptions &opts)
Performs the subdomain and boundary naming, the boundary id remapping (outer boundary to 0 and hole i...
std::unique_ptr< MeshBase > triangulateWithDelaunay(MeshGenerator &mg, std::unique_ptr< MeshBase > boundary_mesh, std::vector< std::unique_ptr< MeshBase > > hole_meshes, const XYDelaunayOptions &xyd_opts)
Performs a 2D Delaunay triangulation (via libMesh::Poly2TriTriangulator) inside a closed boundary mes...
std::set< std::size_t > outerBoundaryIds(MeshGenerator &mg, MeshBase &boundary_mesh, const XYDelaunayOptions &opts)
Resolves the outer-boundary selection of the options into the set of ids that define it: the ids of '...
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.
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::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.
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...
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.
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
const SubdomainID INVALID_BLOCK_ID
Definition MooseTypes.C:20
void change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
std::string enum_to_string(const T e)
Bundle of inputs for triangulateWithDelaunay.
std::vector< SubdomainName > input_subdomain_names
std::vector< std::set< std::size_t > > hole_boundary_id_filters
std::vector< std::set< BoundaryID > > hole_boundary_inner_id_defaults