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 "PatternedMeshGenerator.h"
11 :
12 : #include "CastUniquePointer.h"
13 :
14 : #include "libmesh/replicated_mesh.h"
15 : #include "libmesh/distributed_mesh.h"
16 : #include "libmesh/boundary_info.h"
17 : #include "libmesh/mesh_modification.h"
18 : #include "libmesh/mesh_tools.h"
19 : #include "MooseMeshUtils.h"
20 :
21 : registerMooseObject("MooseApp", PatternedMeshGenerator);
22 :
23 : InputParameters
24 14321 : PatternedMeshGenerator::validParams()
25 : {
26 14321 : InputParameters params = MeshGenerator::validParams();
27 :
28 14321 : params.addRequiredParam<std::vector<MeshGeneratorName>>("inputs", "The input MeshGenerators.");
29 14321 : params.addRangeCheckedParam<Real>(
30 : "x_width", 0, "x_width>=0", "The tile width in the x direction");
31 14321 : params.addRangeCheckedParam<Real>(
32 : "y_width", 0, "y_width>=0", "The tile width in the y direction");
33 14321 : params.addRangeCheckedParam<Real>(
34 : "z_width", 0, "z_width>=0", "The tile width in the z direction");
35 :
36 : // Boundaries : user has to provide id or name for each boundary
37 :
38 : // x boundary names
39 14321 : params.addParam<BoundaryName>("left_boundary", "left", "name of the left (x) boundary");
40 14321 : params.addParam<BoundaryName>("right_boundary", "right", "name of the right (x) boundary");
41 :
42 : // y boundary names
43 14321 : params.addParam<BoundaryName>("top_boundary", "top", "name of the top (y) boundary");
44 14321 : params.addParam<BoundaryName>("bottom_boundary", "bottom", "name of the bottom (y) boundary");
45 :
46 14321 : params.addRequiredParam<std::vector<std::vector<unsigned int>>>(
47 : "pattern", "A double-indexed array starting with the upper-left corner");
48 :
49 14321 : params.addClassDescription("Creates a 2D mesh from a specified set of unique 'tiles' meshes and "
50 : "a two-dimensional pattern.");
51 :
52 14321 : return params;
53 0 : }
54 :
55 28 : PatternedMeshGenerator::PatternedMeshGenerator(const InputParameters & parameters)
56 : : MeshGenerator(parameters),
57 28 : _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
58 28 : _mesh_ptrs(getMeshes("inputs")),
59 28 : _pattern(getParam<std::vector<std::vector<unsigned int>>>("pattern")),
60 28 : _x_width(getParam<Real>("x_width")),
61 28 : _y_width(getParam<Real>("y_width")),
62 84 : _z_width(getParam<Real>("z_width"))
63 : {
64 204 : for (MooseIndex(_pattern) i = 0; i < _pattern.size(); ++i)
65 2272 : for (MooseIndex(_pattern[i]) j = 0; j < _pattern[i].size(); ++j)
66 2096 : if (_pattern[i][j] >= _input_names.size())
67 0 : paramError("pattern",
68 0 : "Index " + Moose::stringify(_pattern[i][j]) +
69 : " is larger than the the maximum possible index, which is determined by the "
70 : "number of MeshGenerators provided in inputs");
71 28 : }
72 :
73 : std::unique_ptr<MeshBase>
74 28 : PatternedMeshGenerator::generate()
75 : {
76 : // Reserve spaces for all the meshes
77 28 : _meshes.reserve(_input_names.size());
78 :
79 : // Getting the boundaries provided by the user
80 : const std::vector<BoundaryName> boundary_names = {getParam<BoundaryName>("left_boundary"),
81 : getParam<BoundaryName>("right_boundary"),
82 : getParam<BoundaryName>("top_boundary"),
83 140 : getParam<BoundaryName>("bottom_boundary")};
84 : const std::vector<std::string> boundary_param_names = {
85 140 : "left_boundary", "right_boundary", "top_boundary", "bottom_boundary"};
86 :
87 : // IDs for each input (indexed by input mesh and then left/right/top/bottom)
88 : std::vector<std::vector<boundary_id_type>> input_bids(
89 28 : _input_names.size(), std::vector<boundary_id_type>(4, Moose::INVALID_BOUNDARY_ID));
90 28 : bool have_common_ids = true;
91 :
92 : // Using a vector of vectors instead of vector of sets to preserve insertion order
93 28 : std::vector<std::vector<boundary_id_type>> input_bids_unique(_input_names.size());
94 :
95 : // For an error check on the number of uniquely named boundaries to stitch
96 28 : size_t set_length = 0;
97 :
98 : // Given a boundary name (i.e., 'left_boundary'), this will give the correct
99 : // index to get the correct boundary id to later use for boundary stitching
100 28 : std::map<std::string, size_t> boundary_name_to_index_map;
101 :
102 : // Keep track of used boundary ids to generate new, unused ones later (if needed)
103 28 : std::set<boundary_id_type> all_boundary_ids;
104 :
105 : // Read in all of the meshes
106 28 : _meshes.resize(_input_names.size());
107 76 : for (const auto i : index_range(_input_names))
108 : {
109 52 : std::unique_ptr<ReplicatedMesh> mesh = dynamic_pointer_cast<ReplicatedMesh>(*_mesh_ptrs[i]);
110 52 : if (!mesh)
111 0 : paramError("inputs",
112 : "The input mesh '",
113 0 : _input_names[i],
114 : "' is not a replicated mesh.\n\n",
115 0 : type(),
116 : " only works with inputs that are replicated.\n\n",
117 : "Try running without distributed mesh.");
118 52 : _meshes[i] = dynamic_pointer_cast<ReplicatedMesh>(mesh);
119 :
120 : // List of boundary ids corresponsind to left/right/top/bottom boundary names
121 52 : const auto ids = MooseMeshUtils::getBoundaryIDs(*_meshes[i], boundary_names, false);
122 : mooseAssert(ids.size() == boundary_names.size(),
123 : "Unexpected number of ids returned for MooseMeshUtils::getBoundaryIDs");
124 :
125 : // Keep track of indices of first instance of each unique boundary id
126 52 : std::map<boundary_id_type, size_t> seen_bid_to_index_map;
127 :
128 52 : size_t index = 0;
129 248 : for (const auto side : make_range(4))
130 : {
131 : // Check if the boundary has been initialized
132 200 : if (ids[side] == Moose::INVALID_BOUNDARY_ID)
133 12 : paramError("inputs",
134 : "The '",
135 4 : boundary_param_names[side],
136 : "' parameter with value '",
137 4 : boundary_names[side],
138 : "' does not exist in input mesh '",
139 4 : _input_names[i],
140 : "'");
141 :
142 196 : input_bids[i][side] = ids[side];
143 :
144 : // We only do this when i == 0 because all input meshes should have the
145 : // same index map. Allowing different index maps for different input
146 : // meshes results in undefined behaviour when stitching
147 196 : if (i == 0)
148 : {
149 100 : if (std::count(input_bids_unique[i].begin(), input_bids_unique[i].end(), ids[side]) == 0)
150 : {
151 100 : input_bids_unique[i].push_back(ids[side]);
152 100 : seen_bid_to_index_map[ids[side]] = index;
153 100 : boundary_name_to_index_map[boundary_param_names[side]] = index++;
154 : }
155 : else
156 0 : boundary_name_to_index_map[boundary_param_names[side]] = seen_bid_to_index_map[ids[side]];
157 : }
158 :
159 : else // i > 0
160 : {
161 96 : if (ids[side] != input_bids[i - 1][side])
162 32 : have_common_ids = false;
163 :
164 96 : if (std::count(input_bids_unique[i].begin(), input_bids_unique[i].end(), ids[side]) == 0)
165 96 : input_bids_unique[i].push_back(ids[side]);
166 : }
167 : }
168 :
169 : // Error check on lengths of input_bids_unique
170 48 : if (i > 0 && set_length != input_bids_unique.size())
171 0 : mooseError(
172 : "Input meshes have incompatible boundary ids. This can occur when input meshes have "
173 : "the same boundary id for multiple boundaries, but in a way that is different "
174 : "between the meshes. Try assigning each left/right/top/bottom to its own boundary id.");
175 :
176 48 : set_length = input_bids_unique.size();
177 :
178 : // List of all boundary ids used in _meshes[i] so we don't reuse any existing boundary ids.
179 48 : const auto all_ids = _meshes[i]->get_boundary_info().get_boundary_ids();
180 :
181 : // Keep track of used IDs so we can later find IDs that are unused across all meshes
182 48 : all_boundary_ids.insert(all_ids.begin(), all_ids.end());
183 48 : }
184 :
185 : // Check if the user has provided the x, y and z widths.
186 : // If not (their value is 0 by default), compute them
187 24 : auto bbox = MeshTools::create_bounding_box(*_meshes[0]);
188 24 : if (_x_width == 0)
189 24 : _x_width = bbox.max()(0) - bbox.min()(0);
190 24 : if (_y_width == 0)
191 24 : _y_width = bbox.max()(1) - bbox.min()(1);
192 24 : if (_z_width == 0)
193 24 : _z_width = bbox.max()(2) - bbox.min()(2);
194 :
195 : // stitch_bids will hold boundary ids passed to mesh stitcher
196 24 : std::vector<boundary_id_type> stitch_bids;
197 :
198 24 : if (have_common_ids) // No need to change existing boundary ids
199 16 : stitch_bids = input_bids_unique[0];
200 :
201 : else // Need to make boundary ids common accross all inputs
202 : {
203 : // Generate previously unused boundary ids
204 72 : for (boundary_id_type id = 0; id != Moose::INVALID_BOUNDARY_ID; ++id)
205 72 : if (!all_boundary_ids.count(id))
206 : {
207 32 : stitch_bids.push_back(id);
208 : // It is okay to only use the 0th index here, since we ensure all entries in
209 : // input_bids_unique have the same size through the above error check.
210 32 : if (stitch_bids.size() == input_bids_unique[0].size())
211 8 : break;
212 : }
213 :
214 : // Make all inputs have common boundary ids
215 24 : for (const auto i : index_range(_meshes))
216 80 : for (const auto side : index_range(stitch_bids))
217 64 : MeshTools::Modification::change_boundary_id(
218 64 : *_meshes[i], input_bids_unique[i][side], stitch_bids[side]);
219 : }
220 :
221 : // Data structure that holds each row
222 24 : _row_meshes.resize(_pattern.size());
223 :
224 : // Aliases
225 24 : const boundary_id_type &left_bid(stitch_bids[boundary_name_to_index_map["left_boundary"]]),
226 24 : right_bid(stitch_bids[boundary_name_to_index_map["right_boundary"]]),
227 24 : top_bid(stitch_bids[boundary_name_to_index_map["top_boundary"]]),
228 24 : bottom_bid(stitch_bids[boundary_name_to_index_map["bottom_boundary"]]);
229 :
230 : // Build each row mesh
231 152 : for (MooseIndex(_pattern) i = 0; i < _pattern.size(); ++i)
232 1552 : for (MooseIndex(_pattern[i]) j = 0; j < _pattern[i].size(); ++j)
233 : {
234 1424 : Real deltax = j * _x_width, deltay = i * _y_width;
235 :
236 : // If this is the first cell of the row initialize the row mesh
237 1424 : if (j == 0)
238 : {
239 128 : auto clone = _meshes[_pattern[i][j]]->clone();
240 128 : _row_meshes[i] = dynamic_pointer_cast<ReplicatedMesh>(clone);
241 :
242 128 : MeshTools::Modification::translate(*_row_meshes[i], deltax, -deltay, 0);
243 :
244 128 : continue;
245 128 : }
246 :
247 1296 : ReplicatedMesh & cell_mesh = *_meshes[_pattern[i][j]];
248 :
249 : // Move the mesh into the right spot. -i because we are starting at the top
250 1296 : MeshTools::Modification::translate(cell_mesh, deltax, -deltay, 0);
251 :
252 : // Subdomain map is aggregated on each row first. This retrieves a writable reference
253 1296 : auto & main_subdomain_map = _row_meshes[i]->set_subdomain_name_map();
254 : // Retrieve subdomain name map from the mesh to be stitched and merge into the row's
255 : // subdomain map
256 1296 : const auto & increment_subdomain_map = cell_mesh.get_subdomain_name_map();
257 1296 : mergeSubdomainNameMaps(main_subdomain_map, increment_subdomain_map);
258 :
259 1296 : _row_meshes[i]->stitch_meshes(cell_mesh,
260 : right_bid,
261 1296 : left_bid,
262 : TOLERANCE,
263 : /*clear_stitched_boundary_ids=*/true,
264 : /*verbose=*/false);
265 :
266 : // Undo the translation
267 1296 : MeshTools::Modification::translate(cell_mesh, -deltax, deltay, 0);
268 : }
269 :
270 : // Now stitch together the rows
271 : // We're going to stitch them all to row 0 (which is the real mesh)
272 128 : for (MooseIndex(_pattern) i = 1; i < _pattern.size(); i++)
273 : {
274 : // Get a writeable reference subdomain-name map for the main mesh to which the other rows are
275 : // stitched
276 104 : auto & main_subdomain_map = _row_meshes[0]->set_subdomain_name_map();
277 : // Retrieve subdomain name map from the mesh to be stitched and merge into the main
278 : // subdomain map
279 104 : const auto & increment_subdomain_map = _row_meshes[i]->get_subdomain_name_map();
280 104 : mergeSubdomainNameMaps(main_subdomain_map, increment_subdomain_map);
281 :
282 104 : _row_meshes[0]->stitch_meshes(*_row_meshes[i],
283 : bottom_bid,
284 : top_bid,
285 : TOLERANCE,
286 : /*clear_stitched_boundary_ids=*/true,
287 : /*verbose=*/false);
288 : }
289 :
290 : // Change boundary ids back to those of meshes[0] to not surprise user
291 24 : if (!have_common_ids)
292 40 : for (const auto side : index_range(stitch_bids))
293 32 : MeshTools::Modification::change_boundary_id(
294 32 : *_row_meshes[0], stitch_bids[side], input_bids_unique[0][side]);
295 :
296 24 : _row_meshes[0]->set_isnt_prepared();
297 48 : return dynamic_pointer_cast<MeshBase>(_row_meshes[0]);
298 108 : }
299 :
300 : void
301 1400 : PatternedMeshGenerator::mergeSubdomainNameMaps(
302 : std::map<subdomain_id_type, std::string> & main_subdomain_map,
303 : const std::map<subdomain_id_type, std::string> & increment_subdomain_map)
304 : {
305 : // Insert secondary subdomain map into main subdomain map
306 1400 : main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
307 : // Check if one SubdomainName is shared by more than one subdomain ids
308 1400 : std::set<SubdomainName> main_subdomain_map_name_list;
309 1512 : for (auto const & id_name_pair : main_subdomain_map)
310 : {
311 112 : const auto name_to_insert = id_name_pair.second;
312 112 : if (main_subdomain_map_name_list.find(name_to_insert) != main_subdomain_map_name_list.end())
313 0 : paramError("inputs",
314 0 : "Two of the input meshes contain a subdomain with the name '" + name_to_insert +
315 : "' which corresponds to two conflicting subdomain ids.");
316 112 : main_subdomain_map_name_list.emplace(name_to_insert);
317 112 : }
318 1400 : }
|