https://mooseframework.inl.gov
Loading...
Searching...
No Matches
XYDelaunayGenerator.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#include "XYDelaunayGenerator.h"
11
12#include "CastUniquePointer.h"
13#include "MooseMeshUtils.h"
15#include "BoundaryLayerUtils.h"
16#include "MooseUtils.h"
17
18#include "libmesh/int_range.h"
19#include "libmesh/mesh_modification.h"
20#include "libmesh/mesh_serializer.h"
21#include "libmesh/unstructured_mesh.h"
22#include "DelimitedFileReader.h"
23
25
28{
31
32 MooseEnum algorithm("BINARY EXHAUSTIVE", "BINARY");
33 MooseEnum tri_elem_type("TRI3 TRI6 TRI7 DEFAULT", "DEFAULT");
34
35 params.addParam<SubdomainID>("output_subdomain_id", "Subdomain id to set on new triangles.");
36
37 params.addParam<bool>("smooth_triangulation",
38 false,
39 "Whether to do Laplacian mesh smoothing on the generated triangles.");
40
41 params.addParam<MooseEnum>(
42 "algorithm",
43 algorithm,
44 "Control the use of binary search for the nodes of the stitched surfaces.");
45 params.addParam<MooseEnum>(
46 "tri_element_type", tri_elem_type, "Type of the triangular elements to be generated.");
47 params.addParam<bool>(
48 "verbose_stitching", false, "Whether mesh stitching should have verbose output.");
49 params.addParam<std::vector<Point>>("interior_points",
50 {},
51 "Interior node locations, if no smoothing is used. Any point "
52 "outside the surface will not be meshed.");
53 params.addParam<std::vector<FileName>>(
54 "interior_point_files", {}, "Text file(s) with the interior points, one per line");
55 params.addClassDescription("Triangulates meshes within boundaries defined by input meshes.");
56
57 params.addRangeCheckedParam<Real>("outer_boundary_layer_thickness",
58 0,
59 "outer_boundary_layer_thickness>=0",
60 "Thickness of the outer boundary layer to be added.");
61 params.addParam<unsigned int>(
62 "outer_boundary_layer_num", 0, "Number of layers for the outer boundary layer.");
63 params.addRangeCheckedParam<Real>(
64 "outer_boundary_layer_bias",
65 1.0,
66 "outer_boundary_layer_bias>0",
67 "Bias factor for the thickness of each layer in the outer boundary layer.");
68
69 params.addRangeCheckedParam<std::vector<Real>>(
70 "holes_boundary_layer_thickness",
71 "holes_boundary_layer_thickness>=0",
72 "Thickness of the hole boundary layers to be added.");
73 params.addParam<std::vector<unsigned int>>("holes_boundary_layer_num",
74 "Numbers of layers for the hole boundary layers.");
75 params.addRangeCheckedParam<std::vector<Real>>(
76 "holes_boundary_layer_bias",
77 "holes_boundary_layer_bias>0",
78 "Bias factors for the thickness of each layer in the hole boundary layers.");
79
80 params.addParamNamesToGroup("interior_points interior_point_files",
81 "Mandatory mesh interior nodes");
82
83 params.addParamNamesToGroup("outer_boundary_layer_thickness outer_boundary_layer_num "
84 "outer_boundary_layer_bias holes_boundary_layer_thickness "
85 "holes_boundary_layer_num holes_boundary_layer_bias",
86 "Boundary layer");
87
88 return params;
89}
90
92 : SurfaceDelaunayGeneratorBase(parameters),
93 _bdy_ptr(getMesh("boundary")),
94 _add_nodes_per_boundary_segment(getParam<unsigned int>("add_nodes_per_boundary_segment")),
95 _refine_bdy(getParam<bool>("refine_boundary")),
96 _output_subdomain_id(0),
97 _smooth_tri(getParam<bool>("smooth_triangulation")),
98 _hole_ptrs(getMeshes("holes")),
99 _stitch_holes(getParam<std::vector<bool>>("stitch_holes")),
100 _refine_holes(getParam<std::vector<bool>>("refine_holes")),
101 _algorithm(parameters.get<MooseEnum>("algorithm")),
102 _tri_elem_type(parameters.get<MooseEnum>("tri_element_type")),
103 _verbose_stitching(parameters.get<bool>("verbose_stitching")),
104 _interior_points(getParam<std::vector<Point>>("interior_points")),
105 _outer_boundary_layer_thickness(getParam<Real>("outer_boundary_layer_thickness")),
106 _outer_boundary_layer_num(getParam<unsigned int>("outer_boundary_layer_num")),
107 _outer_boundary_layer_bias(getParam<Real>("outer_boundary_layer_bias")),
108 _holes_boundary_layer_thickness(
109 isParamValid("holes_boundary_layer_thickness")
110 ? getParam<std::vector<Real>>("holes_boundary_layer_thickness")
111 : std::vector<Real>()),
112 _holes_boundary_layer_num(isParamValid("holes_boundary_layer_num")
113 ? getParam<std::vector<unsigned int>>("holes_boundary_layer_num")
114 : std::vector<unsigned int>()),
115 _holes_boundary_layer_bias(isParamValid("holes_boundary_layer_bias")
116 ? getParam<std::vector<Real>>("holes_boundary_layer_bias")
117 : std::vector<Real>())
118{
120
121 // Copied from MultiApp.C
122 const auto & positions_files = getParam<std::vector<FileName>>("interior_point_files");
123 for (const auto p_file_it : index_range(positions_files))
124 {
125 const std::string positions_file = positions_files[p_file_it];
126 MooseUtils::DelimitedFileReader file(positions_file, &_communicator);
128 file.read();
129
130 const std::vector<Point> & data = file.getDataAsPoints();
131 for (const auto & d : data)
132 _interior_points.push_back(d);
133 }
135
139 "outer_boundary_layer_thickness",
140 "this parameter must be set as non-zero along with a non-zero outer_boundary_layer_num.");
141
143 {
145 paramError("add_nodes_per_boundary_segment",
146 "Cannot add nodes per boundary segment when using an outer boundary layer.");
147 if (_refine_bdy)
148 paramError("refine_boundary", "Cannot refine boundary when using an outer boundary layer.");
149 }
150
155 paramError("holes_boundary_layer_num",
156 "holes_boundary_layer_thickness, holes_boundary_layer_bias and this parameter must "
157 "be specified or not specified together.");
160 paramError("holes_boundary_layer_thickness",
161 "If specified, this parameter must have the same length as 'holes'.");
163 paramError("holes_boundary_layer_num",
164 "If specified, this parameter must have the same length as 'holes'.");
166 paramError("holes_boundary_layer_bias",
167 "If specified, this parameter must have the same length as 'holes'.");
168 for (const auto & i : index_range(_holes_boundary_layer_thickness))
169 {
173 "holes_boundary_layer_thickness",
174 "entry " + std::to_string(i) +
175 " must be set as non-zero along with a non-zero holes_boundary_layer_num entry.");
177 {
178 if ((_refine_holes.size() > i && _refine_holes[i]) || _refine_holes.empty())
179 paramError("refine_holes", "Cannot refine hole boundary when using a hole boundary layer.");
180 }
181 }
182}
183
184std::unique_ptr<MeshBase>
186{
188 fillDelaunayOptions(xyd_opts);
189 xyd_opts.smooth_tri = _smooth_tri;
191 xyd_opts.tri_elem_type = std::string(_tri_elem_type);
192 xyd_opts.use_binary_search = (_algorithm == "BINARY");
194 if (isParamValid("output_subdomain_id"))
195 {
196 xyd_opts.has_output_subdomain_id = true;
197 xyd_opts.output_subdomain_id = getParam<SubdomainID>("output_subdomain_id");
198 }
199
200 std::vector<std::unique_ptr<MeshBase>> hole_meshes(_hole_ptrs.size());
201 for (auto hole_i : index_range(_hole_ptrs))
202 hole_meshes[hole_i] = std::move(*_hole_ptrs[hole_i]);
203
204 std::unique_ptr<MeshBase> boundary_mesh = std::move(_bdy_ptr);
205
206 // Preserve the user-facing output_boundary so we can restore it after the outer-ring stitch-back
207 // (we'll temporarily replace it with a sentinel name to locate the seam).
208 const bool user_has_output_boundary = xyd_opts.has_output_boundary;
209 const BoundaryName user_output_boundary = xyd_opts.output_boundary;
210 const BoundaryName tmp_outer_name("__xyd_bdry_layer_tmp_outer__");
211
212 const bool using_outer_layer = (_outer_boundary_layer_num > 0);
213 const SubdomainID layer_sd_id =
215 const SubdomainName layer_sd_name =
216 xyd_opts.has_output_subdomain_name ? xyd_opts.output_subdomain_name : SubdomainName();
217
218 // Build outer boundary-layer ring if requested. The ring's innermost side (bcid 1) becomes the
219 // outer constraint for the interior triangulation; we then stitch a clone of the ring back onto
220 // the result at that seam.
221 std::unique_ptr<MeshBase> outer_ring_clone;
222 if (using_outer_layer)
223 {
224 auto outer_ring = BoundaryLayerUtils::buildBoundaryLayerRing(*this,
225 *boundary_mesh,
226 xyd_opts.input_boundary_names,
230 /*outward=*/false,
232 layer_sd_id,
233 layer_sd_name);
234 outer_ring_clone = outer_ring->clone();
235 boundary_mesh = std::move(outer_ring);
236 xyd_opts.input_boundary_names = {BoundaryName("1")};
237 xyd_opts.has_output_boundary = true;
238 xyd_opts.output_boundary = tmp_outer_name;
239 }
240
241 // Build hole boundary-layer rings if requested. Each ring is stitched into the result by the
242 // standard hole-stitching path in triangulateWithDelaunay (with stitch_holes forced true).
243 for (auto hole_i : index_range(_hole_ptrs))
244 {
245 if (hole_i < _holes_boundary_layer_num.size() && _holes_boundary_layer_num[hole_i] > 0)
246 {
247 const bool keep_input = (hole_i < _stitch_holes.size() && _stitch_holes[hole_i]);
248 auto hole_ring =
250 *hole_meshes[hole_i],
251 std::vector<BoundaryName>(),
255 /*outward=*/true,
257 layer_sd_id,
258 layer_sd_name);
259
260 if (keep_input)
261 {
262 // Stitch the original hole mesh into the ring at ring's innermost side (bcid 1).
263 auto & ring_u = dynamic_cast<UnstructuredMesh &>(*hole_ring);
264 auto & inp_u = dynamic_cast<UnstructuredMesh &>(*hole_meshes[hole_i]);
265 libMesh::MeshSerializer s1(ring_u), s2(inp_u);
266 if (!ring_u.is_prepared())
267 ring_u.prepare_for_use();
268 if (!inp_u.is_prepared())
269 inp_u.prepare_for_use();
270 // Renumber input mesh boundary ids so they don't overlap with the ring's, mirroring the
271 // approach in StitchMeshGenerator. This avoids degenerate stitching when both meshes
272 // contain the same bcids on different sides.
273 const auto & ring_bids = ring_u.get_boundary_info().get_global_boundary_ids();
274 const auto inp_bids = inp_u.get_boundary_info().get_global_boundary_ids();
275 const auto max_bid = std::max(*ring_bids.rbegin(),
276 inp_bids.empty() ? boundary_id_type(0) : *inp_bids.rbegin());
277 BoundaryID ext_id = 1;
278 bool overlap = false;
279 for (auto b : inp_bids)
280 if (ring_bids.count(b))
281 overlap = true;
282 if (overlap)
283 {
284 BoundaryID idx = 1;
285 for (auto b : inp_bids)
286 {
287 const auto new_b = max_bid + (idx++);
288 inp_u.get_boundary_info().renumber_id(b, new_b);
289 }
290 ext_id = max_bid + idx;
291 }
292 else
293 ext_id = max_bid + 1;
294 inp_u.comm().max(ext_id);
295 bool has_ext = false;
296 MooseMeshUtils::addExternalBoundary(inp_u, ext_id, has_ext);
297 mooseAssert(has_ext, "A 2D-XY mesh should have an external boundary.");
298 const auto ring_u_ext_id =
299 std::max(MooseMeshUtils::getNextFreeBoundaryID(ring_u), BoundaryID(ext_id + 1));
300 MooseMeshUtils::changeBoundaryId(ring_u, 1, ring_u_ext_id, false);
301 if (xyd_opts.hole_boundary_inner_id_defaults.size() <= hole_i)
302 xyd_opts.hole_boundary_inner_id_defaults.resize(_hole_ptrs.size());
303 xyd_opts.hole_boundary_inner_id_defaults[hole_i] = {ring_u_ext_id};
304 // we want to keep the ring's original inner bcid (1) for later use.
305 ring_u.stitch_meshes(inp_u,
306 1,
307 ext_id,
308 TOLERANCE,
309 /*clear_stitched_bcids=*/true,
311 _algorithm == "BINARY",
312 /*enforce_all_nodes_match_on_boundaries=*/false,
313 /*merge_boundary_nodes_all_or_nothing=*/false,
314 /*remap_subdomain_ids=*/false);
315 }
316
317 hole_meshes[hole_i] = std::move(hole_ring);
318 if (xyd_opts.stitch_holes.size() <= hole_i)
319 xyd_opts.stitch_holes.resize(_hole_ptrs.size(), false);
320 xyd_opts.stitch_holes[hole_i] = true;
321 // The ring presents multiple external manifolds; tell triangulateWithDelaunay to use the
322 // outermost (bcid (num_layers - 1) * 2) as the hole's outer boundary.
323 if (xyd_opts.hole_boundary_id_filters.size() <= hole_i)
324 xyd_opts.hole_boundary_id_filters.resize(_hole_ptrs.size());
325 xyd_opts.hole_boundary_id_filters[hole_i] = {
326 std::size_t((_holes_boundary_layer_num[hole_i] - 1) * 2)};
327 }
328 }
329
331 *this, std::move(boundary_mesh), std::move(hole_meshes), xyd_opts);
332
333 // Stitch the outer ring clone back onto the interior triangulation at the recorded seam.
334 if (outer_ring_clone)
335 {
336 auto sentinel_ids = MooseMeshUtils::getBoundaryIDs(*result, {tmp_outer_name}, false);
337 const boundary_id_type sentinel_id = sentinel_ids[0];
338
339 // Preserve the ring's outermost bcid (= (num_layers - 1) * 2) by renaming it to a high temp
340 // value so the post-stitch rename can recover it as the final outer bcid.
341 const boundary_id_type ring_outermost_orig =
342 boundary_id_type((_outer_boundary_layer_num - 1) * 2);
343 // The maximum boundary ID in the outer ring is _outer_boundary_layer_num * 2 - 1, so
344 // _outer_boundary_layer_num * 2 is safe for itself. We need the maximum boundary ID of the
345 // result mesh too.
346 const boundary_id_type ring_outermost_temp =
347 std::max(boundary_id_type(_outer_boundary_layer_num * 2),
350 *outer_ring_clone, ring_outermost_orig, ring_outermost_temp);
351
352 auto & result_u = dynamic_cast<UnstructuredMesh &>(*result);
353 auto & clone_u = dynamic_cast<UnstructuredMesh &>(*outer_ring_clone);
354 libMesh::MeshSerializer s1(result_u), s2(clone_u);
355 result_u.stitch_meshes(clone_u,
356 sentinel_id,
357 1,
358 TOLERANCE,
359 /*clear_stitched_bcids=*/true,
361 _algorithm == "BINARY");
362
363 libMesh::MeshTools::Modification::change_boundary_id(*result, ring_outermost_temp, sentinel_id);
364
365 if (user_has_output_boundary)
366 {
367 auto user_id = MooseMeshUtils::getBoundaryIDs(*result, {user_output_boundary}, true).front();
368 if (user_id != sentinel_id)
369 libMesh::MeshTools::Modification::change_boundary_id(*result, sentinel_id, user_id);
370 result->get_boundary_info().sideset_name(user_id) = user_output_boundary;
371 }
372
373 result->unset_is_prepared();
374 }
375
376 return result;
377}
boundary_id_type BoundaryID
subdomain_id_type SubdomainID
registerMooseObject("MooseApp", XYDelaunayGenerator)
void ErrorVector unsigned int
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
This method takes a space delimited list of parameter names and adds them to the specified group name...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object.
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump.
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
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
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition MooseBase.h:199
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition MooseEnum.h:55
Utility class for reading delimited data (e.g., CSV data).
const std::vector< Point > getDataAsPoints() const
Get the data in Point format.
void read()
Perform the actual data reading.
Base class for Delaunay mesh generators applied to a surface.
void checkInteriorPoints(const std::vector< Point > &interior_points) const
Errors if a point was given twice as an interior point, which the triangulation cannot honor.
void fillDelaunayOptions(MeshTriangulationUtils::XYDelaunayOptions &opts) const
Fills the triangulation options that follow from the parameters boundaryAndHolesParams() adds,...
static InputParameters boundaryAndHolesParams()
The parameters that select the outer boundary to triangulate within and the holes to leave out of the...
void checkBoundaryAndHolesParams(const std::vector< std::unique_ptr< MeshBase > * > &hole_ptrs) const
Errors if the parameters boundaryAndHolesParams() adds contradict each other or the holes they refer ...
Generates a triangulation in the XY plane, based on an input mesh defining the outer boundary (as wel...
const bool _smooth_tri
Whether to do Laplacian mesh smoothing on the generated triangles.
const std::vector< unsigned int > _holes_boundary_layer_num
Per-hole boundary-layer ring layer counts.
std::unique_ptr< MeshBase > & _bdy_ptr
Input mesh defining the boundary to triangulate within.
const std::vector< Real > _holes_boundary_layer_bias
Per-hole boundary-layer ring bias factors.
const bool _refine_bdy
Whether to allow automatically refining the outer boundary.
const unsigned int _outer_boundary_layer_num
Number of element layers in the outer boundary-layer ring.
const std::vector< std::unique_ptr< MeshBase > * > _hole_ptrs
Holds pointers to the pointers to input meshes defining holes.
const std::vector< bool > _stitch_holes
Whether to stitch to the mesh defining each hole.
XYDelaunayGenerator(const InputParameters &parameters)
const Real _outer_boundary_layer_thickness
Thickness of an optional boundary-layer ring grown inward from the outer boundary.
const MooseEnum _tri_elem_type
Type of triangular elements to be generated.
const MooseEnum _algorithm
Type of algorithm used to find matching nodes (binary or exhaustive)
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
const std::vector< bool > _refine_holes
Whether to allow automatically refining each hole boundary.
static InputParameters validParams()
std::vector< Point > _interior_points
Desired interior node locations.
const unsigned int _add_nodes_per_boundary_segment
How many more nodes to add in each outer boundary segment.
const Real _outer_boundary_layer_bias
Bias factor for the layer thicknesses in the outer boundary-layer ring.
const std::vector< Real > _holes_boundary_layer_thickness
Per-hole boundary-layer ring thicknesses (grown outward from each hole)
const bool _verbose_stitching
Whether mesh stitching should have verbose output.
const Parallel::Communicator & _communicator
std::unique_ptr< MeshBase > buildBoundaryLayerRing(MeshGenerator &mg, MeshBase &input_mesh, const std::vector< BoundaryName > &boundary_names, unsigned int num_layers, Real thickness, Real layer_bias, bool outward, const MooseEnum &tri_elem_type, SubdomainID output_subdomain_id, const SubdomainName &output_subdomain_name)
Builds a conformal boundary-layer ring of triangulated annuli along a boundary of an input 2D mesh (o...
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...
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< 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...
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 change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
Bundle of inputs for triangulateWithDelaunay.
std::vector< std::set< std::size_t > > hole_boundary_id_filters
std::vector< std::set< BoundaryID > > hole_boundary_inner_id_defaults