https://mooseframework.inl.gov
Loading...
Searching...
No Matches
Functions
BoundaryLayerUtils Namespace Reference

Functions

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 (or a 1D loop).
 
std::vector< Point > generateOffsetPolyline (MeshGenerator *mg, std::unique_ptr< libMesh::UnstructuredMesh > &ply_mesh_u, std::vector< Point > &points, std::vector< Point > &mid_points, const bool outward, const Real thickness)
 Generates a list of points offset from the input boundary polyline by a specified thickness in either outward/inward direction.
 
void collectExteriorVertexPointsFromMesh (libMesh::TriangulatorInterface::MeshedHole &bdry_mh, std::vector< Point > &points, std::vector< Point > &mid_points, const bool skip_node_reduction=false)
 Collects key vertex points (and optional midpoints) from a meshed hole, optionally discarding colinear vertices.
 
Point getKeyNormal (const Elem *elem, const unsigned int s, const unsigned int node_index)
 Extracts the normal vector of the EDGE3 side of a quadratic element at a given node index.
 

Function Documentation

◆ buildBoundaryLayerRing()

std::unique_ptr< MeshBase > BoundaryLayerUtils::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 (or a 1D loop).

Generates num_layers + 1 parallel polylines (geometric progression of thicknesses with the given bias), triangulates each annulus with triangulateWithDelaunay, and sequentially stitches them. The output mesh carries 2 * num_layers boundary ids, with the innermost being id 1 and the outermost being id (num_layers - 1) * 2.

Parameters
mgThe calling mesh generator (for paramError reporting + buildMeshBaseObject)
input_meshThe 2D-XY input mesh or 1D closed loop providing the seed boundary
boundary_namesSubset of boundary names on input_mesh defining the seed boundary; if empty, the external boundary of input_mesh is auto-detected via MeshedHole
num_layersNumber of element layers to generate
thicknessTotal boundary-layer thickness
layer_biasGeometric growth factor between successive layer thicknesses (1.0 = uniform)
outwardIf true, the layer grows outward from the seed boundary; else inward
tri_elem_typeTriangle element type ("TRI3", "TRI6", "TRI7", or "DEFAULT")
output_subdomain_idSubdomain id assigned to all generated triangles (0 = default)
output_subdomain_nameSubdomain name assigned to output_subdomain_id (empty = unnamed)
Returns
The stitched boundary-layer ring mesh

Definition at line 30 of file BoundaryLayerUtils.C.

40{
41 // Extract seed boundary polyline points (vertices + optional midpoints) from input_mesh.
42 std::set<std::size_t> bdry_id_set;
43 if (!boundary_names.empty())
44 {
45 auto ids =
46 MooseMeshUtils::getBoundaryIDs(input_mesh, boundary_names, /*generate unknown*/ false);
47 bdry_id_set.insert(ids.begin(), ids.end());
48 }
49 TriangulatorInterface::MeshedHole bdry_mh(input_mesh, bdry_id_set);
50 std::vector<Point> cur_pts;
51 std::vector<Point> cur_mids;
52 // Preserve every input boundary node (including colinear interior nodes on straight edges) so
53 // the ring's innermost polyline can be stitched back to the input mesh's boundary exactly when
54 // keep_input is requested by a downstream caller.
55 collectExteriorVertexPointsFromMesh(bdry_mh,
56 cur_pts,
57 cur_mids,
58 /*skip_node_reduction=*/true);
59
60 // Geometric progression of incremental thicknesses. layer_thicknesses[i] is the offset
61 // distance from polyline i-1 to polyline i (for i >= 1); layer_thicknesses[0] is unused.
62 std::vector<Real> layer_thicknesses(num_layers + 1);
63 const Real unit_thickness =
64 (layer_bias == 1.0)
65 ? (thickness / num_layers)
66 : (thickness / (std::pow(layer_bias, num_layers) - 1.0) * (layer_bias - 1.0));
67 layer_thicknesses[0] = 0.0;
68 for (auto i : make_range(std::vector<Real>::size_type(1), layer_thicknesses.size()))
69 layer_thicknesses[i] = unit_thickness * std::pow(layer_bias, i - 1);
70
71 // Generate the N+1 polyline meshes. Polylines are indexed so that polyline 0 is geometrically
72 // innermost (smallest) and polyline N is outermost (largest). Iteration order depends on
73 // direction: for outward, build polyline 0 first (= input boundary); for inward, build polyline
74 // N first.
75 std::vector<std::unique_ptr<MeshBase>> polylines(num_layers + 1);
76 for (auto layer_i : make_range(num_layers + 1))
77 {
78 const unsigned int layer_index = outward ? layer_i : (num_layers - layer_i);
79
80 // Build the polyline mesh for storage as polylines[layer_index].
81 auto ply = std::make_unique<ReplicatedMesh>(mg.comm());
83 cur_pts,
84 cur_mids,
85 /*loop=*/true,
86 BoundaryName(),
87 BoundaryName(),
88 std::vector<unsigned int>({1}));
89 polylines[layer_index] = std::move(ply);
90
91 if (layer_i + 1 < num_layers + 1)
92 {
93 // Build a sacrificial polyline mesh to feed generateOffsetPolyline (it triangulates the
94 // input mesh in place to compute side normals; we discard it afterwards).
95 auto ply_for_offset = std::make_unique<ReplicatedMesh>(mg.comm());
97 cur_pts,
98 cur_mids,
99 /*loop=*/true,
100 BoundaryName(),
101 BoundaryName(),
102 std::vector<unsigned int>({1}));
103 std::unique_ptr<UnstructuredMesh> ply_for_offset_u =
104 dynamic_pointer_cast<UnstructuredMesh>(std::move(ply_for_offset));
105
106 std::vector<Point> next_combined = generateOffsetPolyline(
107 &mg, ply_for_offset_u, cur_pts, cur_mids, outward, layer_thicknesses[layer_i + 1]);
108 if (cur_mids.empty())
109 cur_pts = std::move(next_combined);
110 else
111 {
112 const auto n_vert = cur_pts.size();
113 cur_pts.assign(next_combined.begin(), next_combined.begin() + n_vert);
114 cur_mids.assign(next_combined.begin() + n_vert, next_combined.end());
115 }
116 }
117 }
118
119 // Triangulate each annulus (between polylines[i] and polylines[i+1]). Stitch each annulus to
120 // the accumulating ring (except the first one).
121 std::unique_ptr<MeshBase> ring;
122 for (auto i : make_range(num_layers))
123 {
125 xyd_opts.refine_bdy = false;
126 xyd_opts.verify_holes = false;
127 xyd_opts.stitch_holes = {i > 0};
128 xyd_opts.refine_holes = {false};
129 xyd_opts.tri_elem_type = std::string(tri_elem_type);
130 if (output_subdomain_id != 0)
131 {
132 xyd_opts.has_output_subdomain_id = true;
133 xyd_opts.output_subdomain_id = output_subdomain_id;
134 }
135 if (output_subdomain_name.size())
136 {
137 xyd_opts.has_output_subdomain_name = true;
138 xyd_opts.output_subdomain_name = output_subdomain_name;
139 }
140
141 std::vector<std::unique_ptr<MeshBase>> holes;
142 holes.reserve(1);
143 if (i == 0)
144 holes.push_back(std::move(polylines[0]));
145 else
146 holes.push_back(std::move(ring));
147
149 mg, std::move(polylines[i + 1]), std::move(holes), xyd_opts);
150 }
151 // We now have 2 * num_layers boundaries
152 // Let's only keep the innermost (1) and outermost (2 * num_layers) boundaries, and remove all
153 // intermediate ring bcids
154 std::vector<BoundaryID> bids_to_delete;
155 for (const auto b : make_range(2 * num_layers))
156 {
157 if (b == 1 || b == (num_layers - 1) * 2)
158 continue;
159 bids_to_delete.push_back(b);
160 }
161 auto & bi = ring->get_boundary_info();
162 for (auto b : bids_to_delete)
163 bi.remove_id(b);
164
165 return ring;
166}
const Parallel::Communicator & comm() const
std::vector< Point > generateOffsetPolyline(MeshGenerator *mg, std::unique_ptr< libMesh::UnstructuredMesh > &ply_mesh_u, std::vector< Point > &points, std::vector< Point > &mid_points, const bool outward, const Real thickness)
Generates a list of points offset from the input boundary polyline by a specified thickness in either...
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 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.
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.
IntRange< T > make_range(T beg, T end)
MooseUnits pow(const MooseUnits &, int)
Definition Units.C:537
Bundle of inputs for triangulateWithDelaunay.
const unsigned int n_vert

Referenced by XYDelaunayGenerator::generate(), and XYTriangleBoundaryLayerGenerator::generate().

◆ collectExteriorVertexPointsFromMesh()

void BoundaryLayerUtils::collectExteriorVertexPointsFromMesh ( libMesh::TriangulatorInterface::MeshedHole bdry_mh,
std::vector< Point > &  points,
std::vector< Point > &  mid_points,
const bool  skip_node_reduction = false 
)

Collects key vertex points (and optional midpoints) from a meshed hole, optionally discarding colinear vertices.

Parameters
bdry_mhThe 2D MeshedHole object from which to collect key points
pointsThe vector to which collected vertex points are appended (in order)
mid_pointsThe vector to which collected midpoints are appended; only populated when the MeshedHole reports a midpoint per side AND skip_node_reduction is true
skip_node_reductionIf true, retain every vertex and midpoint; if false, drop vertices that are colinear with their two neighbors and do not collect midpoints

Definition at line 321 of file BoundaryLayerUtils.C.

325{
326 for (const auto i : make_range(bdry_mh.n_points()))
327 {
328 if (skip_node_reduction || !geom_utils::arePointsColinear(
329 bdry_mh.point((i - 1 + bdry_mh.n_points()) % bdry_mh.n_points()),
330 bdry_mh.point(i),
331 bdry_mh.point((i + 1) % bdry_mh.n_points())))
332 {
333 points.push_back(bdry_mh.point(i));
334 if (bdry_mh.n_midpoints() == 1 && skip_node_reduction)
335 mid_points.push_back(bdry_mh.midpoint(0, i));
336 }
337 }
338}
virtual Point point(const unsigned int n) const override
virtual Point midpoint(const unsigned int m, const unsigned int n) const override
virtual unsigned int n_points() const override
virtual unsigned int n_midpoints() const override
bool arePointsColinear(const Point &p1, const Point &p2, const Point &p3)
Check if three points are colinear.

Referenced by buildBoundaryLayerRing(), and generateOffsetPolyline().

◆ generateOffsetPolyline()

std::vector< Point > BoundaryLayerUtils::generateOffsetPolyline ( MeshGenerator mg,
std::unique_ptr< libMesh::UnstructuredMesh > &  ply_mesh_u,
std::vector< Point > &  points,
std::vector< Point > &  mid_points,
const bool  outward,
const Real  thickness 
)

Generates a list of points offset from the input boundary polyline by a specified thickness in either outward/inward direction.

Parameters
mgThe mesh generator calling this function, used for paramError reporting
ply_mesh_uThe 1D loop polyline mesh of the original boundary; the volume it encloses will be triangulated in-place to compute the normal and define the inward/outward directions
pointsThe vertex points of the original polyline. If empty, populated from the input mesh via collectExteriorVertexPointsFromMesh.
mid_pointsThe midpoints of the original polyline (optional to enable quadratic elements). If both this and points are empty, populated from the input mesh.
outwardWhether to offset in the outward (true) or inward (false) direction
thicknessThe offset distance (>= 0)
Returns
Offset points: vertices first, then midpoints (length = points.size() + mid_points.size())

Definition at line 169 of file BoundaryLayerUtils.C.

175{
176 // If the input points are empty, we will extract them from the input 1D mesh
177 if (points.empty())
178 {
179 mooseAssert(mid_points.empty(),
180 "If the input points are empty, the input mid_points must be also empty.");
181
182 TriangulatorInterface::MeshedHole bdry_mh(*ply_mesh_u);
183 collectExteriorVertexPointsFromMesh(bdry_mh, points, mid_points);
184 }
185 // Generate a very simple triangulation mesh so that we can get the outward normal vectors
186 libMesh::Poly2TriTriangulator poly2tri(*ply_mesh_u);
187 poly2tri.triangulation_type() = libMesh::TriangulatorInterface::PSLG;
188
189 poly2tri.set_interpolate_boundary_points(0);
190 poly2tri.set_refine_boundary_allowed(false);
191 poly2tri.set_verify_hole_boundaries(false);
192 poly2tri.desired_area() = 0;
193 poly2tri.minimum_angle() = 0; // Not yet supported
194 poly2tri.smooth_after_generating() = false;
195 if (mid_points.size())
196 poly2tri.elem_type() = libMesh::ElemType::TRI6;
197 poly2tri.triangulate();
198
199 // We need to serialize the mesh for next steps
200 libMesh::MeshSerializer serial(*ply_mesh_u);
201 // The mesh now only contains one side set that corresponds to the outer boundary with an ID of 0
202 auto bdry_list(ply_mesh_u->get_boundary_info().build_side_list());
203
204 // For each vertex, the shifting direction to form the offset is defined by the normal vectors of
205 // the two sides that contain the vertex.
206 // We gather the normals for each node on the boundary here, which are all vertices because of
207 // the pre-selection of nodes.
208 std::map<dof_id_type, std::vector<Point>> node_normal_map;
209 std::map<dof_id_type, Point> mid_node_normal_map;
210 for (const auto & bside : bdry_list)
211 {
212 const auto & side = ply_mesh_u->elem_ptr(std::get<0>(bside))->side_ptr(std::get<1>(bside));
213 // For linear elements, the side normal is constant and can be obtained straightforwardly;
214 // For quadratic elements, we need the normal at the shared vertices
215 const Point side_normal_0 =
216 mid_points.size()
217 ? getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 0)
218 : ply_mesh_u->elem_ptr(std::get<0>(bside))
219 ->side_vertex_average_normal(std::get<1>(bside));
220 const Point side_normal_1 =
221 mid_points.size()
222 ? getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 1)
223 : side_normal_0;
224
225 if (node_normal_map.count(side->node_ptr(0)->id()))
226 node_normal_map[side->node_ptr(0)->id()].push_back(side_normal_0);
227 else
228 node_normal_map[side->node_ptr(0)->id()] = {side_normal_0};
229 if (node_normal_map.count(side->node_ptr(1)->id()))
230 node_normal_map[side->node_ptr(1)->id()].push_back(side_normal_1);
231 else
232 node_normal_map[side->node_ptr(1)->id()] = {side_normal_1};
233
234 if (mid_points.size())
235 {
236 const Point mid_node_normal =
237 getKeyNormal(ply_mesh_u->elem_ptr(std::get<0>(bside)), std::get<1>(bside), 2);
238 mid_node_normal_map
239 [ply_mesh_u->elem_ptr(std::get<0>(bside))->node_ptr(std::get<1>(bside) + 3)->id()] =
240 mid_node_normal;
241 }
242 }
243
244 std::vector<Point> mod_reduced_pts_list(points);
245 for (const auto & [node_id, normal_vecs] : node_normal_map)
246 {
247 mooseAssert(normal_vecs.size() == 2,
248 "Each vertex should be connected to exactly two sides in a polygon.");
249
250 const Point original_pt = *(ply_mesh_u->node_ptr(node_id));
251 // Form an average normal at the vertex from the two connected sides' normals
252 const Point move_dir =
253 (normal_vecs.front() + normal_vecs.back()).unit() * (outward ? 1.0 : -1.0);
254 // Consider four points of interest to determine the moving distance
255 // 1. the vertex point
256 // 2. point along normal_vecs.front() from the vertex with a distance thickness
257 // 3. point along normal_vecs.back() from the vertex with a distance thickness
258 // 4. point along move_dir from the vertex with a distance mov_dist
259 // The four points form a kite shape with its symmetry axis along 1-4 direction
260 // Angle 1-2-4 and angle 1-3-4 are right angles
261 // Angle 2-1-3 (i.e., theta) can be calculated using the interior product of
262 // v1 = normal_vecs.front() and v2 = normal_vecs.back():
263 // cos(theta) = (v1 . v2) / (|v1| * |v2|)
264 // Because of the right angles, the distance between 1 and 4 can be calculated as:
265 // mov_dist = thickness / cos(theta/2)
266 // and cos(theta/2) = sqrt((1 + cos(theta)) / 2)
267 const Real mov_dist =
268 thickness / std::sqrt((1.0 + (normal_vecs.front() * normal_vecs.back()) /
269 (normal_vecs.front().norm() * normal_vecs.back().norm())) /
270 2.0);
271 mooseAssert(std::count(points.begin(), points.end(), original_pt) == 1,
272 "The original point should be found exactly once in the reduced points list.");
273 mod_reduced_pts_list[std::distance(points.begin(),
274 std::find(points.begin(), points.end(), original_pt))] =
275 original_pt + move_dir * mov_dist;
276 }
277
278 // To ensure no overlapping, we need to check set of four points
279 // p1 and p2 should be a pair of points before and after shifting
280 // p3 and p4 should be a pair of adjacent shifted points that are neither not p2
281 for (const auto & i_node_1 : make_range(mod_reduced_pts_list.size()))
282 {
283 const Point & p1 = points[i_node_1];
284 const Point & p2 = mod_reduced_pts_list[i_node_1];
285 for (const auto & i_node_2 : make_range(mod_reduced_pts_list.size()))
286 {
287 if (i_node_2 == i_node_1 || (i_node_2 + 1) % mod_reduced_pts_list.size() == i_node_1)
288 continue;
289 const Point & p3 = mod_reduced_pts_list[i_node_2];
290 const Point & p4 = mod_reduced_pts_list[(i_node_2 + 1) % mod_reduced_pts_list.size()];
291 if (thickness > 0)
292 if (geom_utils::segmentsIntersect(p1, p2, p3, p4))
293 mg->paramError(
294 "thickness",
295 "The thickness is so large that the mesh is tangled because the offset nodes "
296 "are no longer in the same order when following the original boundary. Please "
297 "reduce the thickness value.");
298 }
299 }
300
301 std::vector<Point> mid_mod_reduced_pts_list(mid_points.size());
302 for (const auto & [node_id, normal_vec] : mid_node_normal_map)
303 {
304 const Point original_pt = *(ply_mesh_u->node_ptr(node_id));
305 const Point move_dir = normal_vec.unit() * (outward ? 1.0 : -1.0);
306 mid_mod_reduced_pts_list[std::distance(
307 mid_points.begin(), std::find(mid_points.begin(), mid_points.end(), original_pt))] =
308 original_pt + move_dir * thickness;
309 }
310
311 // combine mod_reduced_pts_list and mid_mod_reduced_pts_list to get the final list of points for
312 // the layer mesh
313 std::vector<Point> layer_pts_list(mod_reduced_pts_list);
314 layer_pts_list.insert(
315 layer_pts_list.end(), mid_mod_reduced_pts_list.begin(), mid_mod_reduced_pts_list.end());
316
317 return layer_pts_list;
318}
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
Point getKeyNormal(const Elem *elem, const unsigned int s, const unsigned int node_index)
Extracts the normal vector of the EDGE3 side of a quadratic element at a given node index.
void collectExteriorVertexPointsFromMesh(libMesh::TriangulatorInterface::MeshedHole &bdry_mh, std::vector< Point > &points, std::vector< Point > &mid_points, const bool skip_node_reduction=false)
Collects key vertex points (and optional midpoints) from a meshed hole, optionally discarding colinea...
bool segmentsIntersect(const Point &p1, const Point &p2, const Point &p3, const Point &p4)
Check if the line segment p1-p2 intersects with line segment p3-p4 (only working in 2D (x-y plane)).
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

Referenced by buildBoundaryLayerRing().

◆ getKeyNormal()

Point BoundaryLayerUtils::getKeyNormal ( const Elem *  elem,
const unsigned int  s,
const unsigned int  node_index 
)

Extracts the normal vector of the EDGE3 side of a quadratic element at a given node index.

Parameters
elemThe element containing the side of interest
sThe side index of interest
node_indexThe index of the node on the side at which to extract the normal vector
Returns
The normal vector at the node index on the side of interest

Definition at line 341 of file BoundaryLayerUtils.C.

342{
343 const std::unique_ptr<const Elem> face = elem->build_side_ptr(s);
344 mooseAssert(face->type() == ElemType::EDGE3,
345 "Only elements with EDGE3 sides are supported in this function.");
346 mooseAssert(node_index < 3,
347 "The node index for an EDGE3 side should be 0, 1, or 2 (for the two vertices and the "
348 "midpoint).");
349 std::unique_ptr<libMesh::FEBase> fe(
350 libMesh::FEBase::build(2, libMesh::FEType(elem->default_order())));
351 const std::vector<Point> & normals = fe->get_normals();
352 std::vector<Point> ref_pts = {face->reference_elem()->point(node_index)};
353 fe->reinit(elem, s, TOLERANCE, &ref_pts);
354 return normals[0];
355}
std::unique_ptr< FEGenericBase< Real > > build(const unsigned int dim, const FEType &fet)

Referenced by generateOffsetPolyline().