Line data Source code
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 "XYTriangleBoundaryLayerGenerator.h"
11 :
12 : #include "CastUniquePointer.h"
13 : #include "MooseMeshUtils.h"
14 : #include "BoundaryLayerUtils.h"
15 :
16 : #include "libmesh/mesh_modification.h"
17 : #include "libmesh/mesh_serializer.h"
18 : #include "libmesh/unstructured_mesh.h"
19 :
20 : registerMooseObject("MooseApp", XYTriangleBoundaryLayerGenerator);
21 :
22 : InputParameters
23 3261 : XYTriangleBoundaryLayerGenerator::validParams()
24 : {
25 3261 : InputParameters params = MeshGenerator::validParams();
26 :
27 13044 : params.addRequiredParam<MeshGeneratorName>(
28 : "input", "The input mesh based on which to create the conformal boundary layer.");
29 13044 : params.addRequiredParam<Real>("thickness",
30 : "The total thickness of the boundary layer to be created.");
31 16305 : params.addRangeCheckedParam<unsigned int>(
32 6522 : "num_layers", 1, "num_layers>0", "The total number of boundary layers to be created.");
33 9783 : params.addParam<Real>(
34 : "layer_bias",
35 6522 : 1.0,
36 : "The bias factor for the thickness of each layer of elements. A value > 1 leads to thicker "
37 : "layers away from the input mesh, while a value < 1 leads to thicker layers close to the "
38 : "input mesh. The default value of 1 leads to uniform layer thickness.");
39 :
40 13044 : MooseEnum boundary_layer_direction("OUTWARD INWARD", "OUTWARD");
41 13044 : params.addParam<MooseEnum>(
42 : "boundary_layer_direction",
43 : boundary_layer_direction,
44 : "In which direction the boundary layer is created with respect to the side normal of the "
45 : "elements along the boundary of the input mesh.");
46 :
47 9783 : params.addParam<std::vector<BoundaryName>>(
48 : "boundary_names",
49 6522 : std::vector<BoundaryName>(),
50 : "The names of the boundaries around which the boundary layer will be created.");
51 :
52 13044 : params.addParam<BoundaryName>("interface_name",
53 : "The optional boundary name to be assigned to the interface "
54 : "between the generated boundary layer and the input mesh.");
55 13044 : params.addParam<BoundaryName>(
56 : "surface_name",
57 : "The optional boundary name to be assigned to the surface of the generated boundary layer.");
58 :
59 13044 : params.addParam<SubdomainName>("subdomain_name",
60 : "Subdomain name to set for the boundary layer mesh.");
61 13044 : params.addParam<SubdomainID>("subdomain_id", "Subdomain id to set for the boundary layer mesh.");
62 :
63 13044 : MooseEnum tri_elem_type("TRI3 TRI6 TRI7", "TRI3");
64 13044 : params.addParam<MooseEnum>("tri_elem_type",
65 : tri_elem_type,
66 : "The type of triangular elements to use for the boundary layer.");
67 :
68 9783 : params.addParam<bool>("keep_input",
69 6522 : false,
70 : "Whether to keep the input mesh in the final output. If false, only the "
71 : "boundary layers will be included in the output mesh.");
72 :
73 3261 : params.addClassDescription(
74 : "Generate a 2D layered mesh that represents a conformal boundary layer along "
75 : "the boundary of an input 2D mesh or a 1D loop mesh.");
76 :
77 6522 : return params;
78 3261 : }
79 :
80 100 : XYTriangleBoundaryLayerGenerator::XYTriangleBoundaryLayerGenerator(
81 100 : const InputParameters & parameters)
82 : : MeshGenerator(parameters),
83 100 : _input(getMesh("input")),
84 200 : _thickness(getParam<Real>("thickness")),
85 200 : _num_layers(getParam<unsigned int>("num_layers")),
86 200 : _layer_bias(getParam<Real>("layer_bias")),
87 100 : _boundary_layer_direction(
88 300 : getParam<MooseEnum>("boundary_layer_direction").template getEnum<BoundaryLayerDirection>()),
89 200 : _boundary_names(getParam<std::vector<BoundaryName>>("boundary_names")),
90 220 : _interface_name(isParamValid("interface_name") ? getParam<BoundaryName>("interface_name")
91 : : BoundaryName()),
92 220 : _surface_name(isParamValid("surface_name") ? getParam<BoundaryName>("surface_name")
93 : : BoundaryName()),
94 360 : _subdomain_name(isParamValid("subdomain_name") ? getParam<SubdomainName>("subdomain_name")
95 : : SubdomainName()),
96 360 : _output_subdomain_id(isParamValid("subdomain_id") ? getParam<SubdomainID>("subdomain_id") : 0),
97 100 : _tri_elem_type(parameters.get<MooseEnum>("tri_elem_type")),
98 300 : _keep_input(getParam<bool>("keep_input"))
99 : {
100 100 : }
101 :
102 : std::unique_ptr<MeshBase>
103 100 : XYTriangleBoundaryLayerGenerator::generate()
104 : {
105 100 : const bool outward = (_boundary_layer_direction == BoundaryLayerDirection::OUTWARD);
106 :
107 : // The MeshGenerator system requires that the input mesh be moved out of its unique_ptr during
108 : // generate(). Move it now; when keep_input is false the local unique_ptr is simply dropped at
109 : // the end, when keep_input is true we stitch from this local pointer.
110 100 : std::unique_ptr<MeshBase> input_mesh = std::move(_input);
111 :
112 : auto ring = BoundaryLayerUtils::buildBoundaryLayerRing(*this,
113 100 : *input_mesh,
114 100 : _boundary_names,
115 100 : _num_layers,
116 100 : _thickness,
117 100 : _layer_bias,
118 : outward,
119 100 : _tri_elem_type,
120 100 : _output_subdomain_id,
121 100 : _subdomain_name);
122 :
123 : // The ring carries boundary ids 0 ... 2*num_layers - 1; the innermost is bcid 1 and the
124 : // outermost is bcid (num_layers - 1) * 2. The "interface" side (touching the input mesh) is the
125 : // innermost for outward layers and the outermost for inward layers; the "surface" side (away
126 : // from the input mesh) is the opposite.
127 100 : const boundary_id_type ring_innermost = 1;
128 100 : const boundary_id_type ring_outermost = boundary_id_type((_num_layers - 1) * 2);
129 100 : const boundary_id_type interface_bid = outward ? ring_innermost : ring_outermost;
130 100 : const boundary_id_type surface_bid = outward ? ring_outermost : ring_innermost;
131 :
132 100 : if (_keep_input)
133 : {
134 20 : auto & ring_u = dynamic_cast<libMesh::UnstructuredMesh &>(*ring);
135 20 : auto & inp_u = dynamic_cast<libMesh::UnstructuredMesh &>(*input_mesh);
136 20 : if (!ring_u.is_prepared())
137 20 : ring_u.prepare_for_use();
138 20 : if (!inp_u.is_prepared())
139 0 : inp_u.prepare_for_use();
140 : // Avoid bcid overlap between ring and input; renumber input's bcids out of the ring's range.
141 20 : const auto ring_bids = ring_u.get_boundary_info().get_global_boundary_ids();
142 20 : const auto inp_bids = inp_u.get_boundary_info().get_global_boundary_ids();
143 20 : const auto max_bid = std::max(ring_bids.empty() ? boundary_id_type(0) : *ring_bids.rbegin(),
144 20 : inp_bids.empty() ? boundary_id_type(0) : *inp_bids.rbegin());
145 20 : BoundaryID ext_id = 1;
146 20 : bool overlap = false;
147 60 : for (auto b : inp_bids)
148 40 : if (ring_bids.count(b))
149 20 : overlap = true;
150 20 : if (overlap)
151 : {
152 20 : BoundaryID idx = 1;
153 60 : for (auto b : inp_bids)
154 40 : inp_u.get_boundary_info().renumber_id(b, max_bid + (idx++));
155 20 : ext_id = max_bid + idx;
156 : }
157 : else
158 0 : ext_id = max_bid + 1;
159 20 : inp_u.comm().max(ext_id);
160 20 : bool has_ext = false;
161 20 : MooseMeshUtils::addExternalBoundary(inp_u, ext_id, has_ext);
162 : mooseAssert(has_ext, "A 2D-XY mesh should have an external boundary.");
163 20 : libMesh::MeshSerializer s1(ring_u), s2(inp_u);
164 : // Stitch at the side of the ring that physically touches the input boundary. Keep the
165 : // interface_bid tag so the interface boundary survives for downstream naming.
166 20 : ring_u.stitch_meshes(inp_u,
167 : interface_bid,
168 : ext_id,
169 : TOLERANCE,
170 : /*clear_stitched_bcids=*/false,
171 : /*verbose=*/false,
172 : /*use_binary_search=*/true,
173 : /*enforce_all_nodes_match_on_boundaries=*/false,
174 : /*merge_boundary_nodes_all_or_nothing=*/false,
175 : /*remap_subdomain_ids=*/false);
176 20 : }
177 :
178 100 : auto & bi = ring->get_boundary_info();
179 : // Delete the interface/surface if not requested to retain
180 100 : if (_interface_name.empty())
181 90 : bi.remove_id(interface_bid);
182 100 : if (_surface_name.empty())
183 90 : bi.remove_id(surface_bid);
184 :
185 : // Apply interface_name / surface_name. The interface and surface bcids may collide with the
186 : // user-requested ids; do a two-stage shift to avoid clobbering.
187 100 : bool has_conflict = false;
188 100 : if (!_interface_name.empty())
189 : {
190 : const auto interface_user_bcid =
191 30 : MooseMeshUtils::getBoundaryIDs(*ring, {_interface_name}, true).front();
192 10 : if (interface_user_bcid == surface_bid && !_surface_name.empty())
193 : {
194 0 : libMesh::MeshTools::Modification::change_boundary_id(*ring, surface_bid, 10001);
195 0 : has_conflict = true;
196 : }
197 10 : libMesh::MeshTools::Modification::change_boundary_id(*ring, interface_bid, interface_user_bcid);
198 10 : bi.sideset_name(interface_user_bcid) = _interface_name;
199 : }
200 100 : if (!_surface_name.empty())
201 : {
202 : const auto surface_user_bcid =
203 30 : MooseMeshUtils::getBoundaryIDs(*ring, {_surface_name}, true).front();
204 20 : libMesh::MeshTools::Modification::change_boundary_id(
205 10 : *ring, has_conflict ? boundary_id_type(10001) : surface_bid, surface_user_bcid);
206 10 : bi.sideset_name(surface_user_bcid) = _surface_name;
207 : }
208 :
209 : // Match the parent MooseMesh's remote-element-removal setting so SetupMeshAction's sync check
210 : // passes when the user uses the mesh in contexts (e.g. boundary postprocessors) that flip this
211 : // flag on MooseMesh before the mesh is set.
212 100 : ring->allow_remote_element_removal(_mesh->allowRemoteElementRemoval());
213 100 : ring->unset_is_prepared();
214 200 : return ring;
215 120 : }
|