https://mooseframework.inl.gov
Loading...
Searching...
No Matches
PatchSidesetGenerator.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 "InputParameters.h"
12#include "MooseTypes.h"
13#include "CastUniquePointer.h"
14#include "MooseUtils.h"
15#include "MooseMeshUtils.h"
16
17#include "libmesh/distributed_mesh.h"
18#include "libmesh/elem.h"
19#include "libmesh/linear_partitioner.h"
20#include "libmesh/centroid_partitioner.h"
21#include "libmesh/parmetis_partitioner.h"
22#include "libmesh/hilbert_sfc_partitioner.h"
23#include "libmesh/morton_sfc_partitioner.h"
24#include "libmesh/enum_elem_type.h"
25
26#include <set>
27#include <limits>
28#include "libmesh/mesh_tools.h"
29
31
34{
36
37 params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
38 params.addRequiredParam<BoundaryName>("boundary",
39 "The boundary that will be divided into patches");
40 params.addRequiredRangeCheckedParam<unsigned int>(
41 "n_patches", "n_patches>0", "Number of patches");
42
43 MooseEnum partitioning = MooseMesh::partitioning(); // default MOOSE partitioning
44 partitioning += "grid"; // ...but also add our own
45 params.addParam<MooseEnum>(
46 "partitioner",
47 partitioning,
48 "Specifies a mesh partitioner to use when splitting the mesh for a parallel computation.");
49
50 MooseEnum direction("x y z radial");
51 params.addParam<MooseEnum>("centroid_partitioner_direction",
52 direction,
53 "Specifies the sort direction if using the centroid partitioner. "
54 "Available options: x, y, z, radial");
55
56 params.addParamNamesToGroup("partitioner centroid_partitioner_direction", "Partitioning");
57
59 "Divides the given sideset into smaller patches of roughly equal size.");
60
61 return params;
62}
63
65 : MeshGenerator(parameters),
66 _input(getMesh("input")),
67 _n_patches(getParam<unsigned int>("n_patches")),
68 _sideset_name(getParam<BoundaryName>("boundary")),
69 _partitioner_name(getParam<MooseEnum>("partitioner"))
70{
71}
72
73std::unique_ptr<MeshBase>
75{
76 std::unique_ptr<MeshBase> mesh = std::move(_input);
77
78 _mesh->errorIfDistributedMesh("PatchSidesetGenerator");
79
80 // Get a reference to our BoundaryInfo object for later use
81 BoundaryInfo & boundary_info = mesh->get_boundary_info();
82
83 // get dimensionality
84 _dim = mesh->mesh_dimension() - 1;
85
86 // get a list of all sides; vector of tuples (elem, loc_side, side_set)
87 auto side_list = boundary_info.build_active_side_list();
88
90 paramError("boundary",
91 "The provided boundary name or ID (" + _sideset_name +
92 ") does not exist in the mesh.");
93
95
96 // create a dim - 1 dimensional mesh
97 auto boundary_mesh = buildReplicatedMesh(mesh->mesh_dimension() - 1);
98 boundary_mesh->set_mesh_dimension(mesh->mesh_dimension() - 1);
99 boundary_mesh->set_spatial_dimension(mesh->mesh_dimension());
100
101 // nodes in the new mesh by boundary_node_id (index)
102 std::vector<Node *> boundary_nodes;
103 // a map from the node numbering on the volumetric mesh to the numbering
104 // on the boundary_mesh
105 std::map<dof_id_type, dof_id_type> mesh_node_id_to_boundary_node_id;
106 // a local counter keeping track of how many entries have been added to boundary_nodes
107 dof_id_type boundary_node_id = 0;
108 // a map from new element id in the boundary mesh to the element id/side/sideset
109 // tuple it came from
110 std::map<dof_id_type, std::tuple<dof_id_type, unsigned short int, boundary_id_type>>
111 boundary_elem_to_mesh_elem;
112 for (auto & side : side_list)
113 {
114 if (std::get<2>(side) == _sideset)
115 {
116 // the original volumetric mesh element
117 const Elem * elem = mesh->elem_ptr(std::get<0>(side));
118
119 // the boundary element
120 std::unique_ptr<const Elem> boundary_elem = elem->side_ptr(std::get<1>(side));
121
122 // an array that saves the boundary node ids of this elem in the right order
123 std::vector<dof_id_type> bnd_elem_node_ids(boundary_elem->n_nodes());
124
125 // loop through the nodes in boundary_elem
126 for (MooseIndex(boundary_elem->n_nodes()) j = 0; j < boundary_elem->n_nodes(); ++j)
127 {
128 const Node * node = boundary_elem->node_ptr(j);
129
130 // Is this node a new node?
131 if (mesh_node_id_to_boundary_node_id.find(node->id()) ==
132 mesh_node_id_to_boundary_node_id.end())
133 {
134 // yes, it is new, need to add it to the mesh_node_id_to_boundary_node_id map
135 mesh_node_id_to_boundary_node_id.insert(
136 std::pair<dof_id_type, dof_id_type>(node->id(), boundary_node_id));
137
138 // this adds this node to the boundary mesh and puts it at the right position
139 // in the boundary_nodes array
140 Point pt(*node);
141 boundary_nodes.push_back(boundary_mesh->add_point(pt, boundary_node_id));
142
143 // keep track of the boundary node for setting up the element
144 bnd_elem_node_ids[j] = boundary_node_id;
145
146 // increment the boundary_node_id counter
147 ++boundary_node_id;
148 }
149 else
150 bnd_elem_node_ids[j] = mesh_node_id_to_boundary_node_id.find(node->id())->second;
151 }
152
153 // all nodes for this element have been added, so we can add the element to the
154 // boundary mesh
155 Elem * new_bnd_elem = boundaryElementHelper(*boundary_mesh, boundary_elem->type());
156
157 // keep track of these new boundary elements in boundary_elem_to_mesh_elem
158 boundary_elem_to_mesh_elem.insert(
159 std::pair<dof_id_type, std::tuple<dof_id_type, unsigned short int, boundary_id_type>>(
160 new_bnd_elem->id(), side));
161
162 // set the nodes & subdomain_id of the new element by looping over the
163 // boundary_elem and then inserting its nodes into new_bnd_elem in the
164 // same order
165 for (MooseIndex(boundary_elem->n_nodes()) j = 0; j < boundary_elem->n_nodes(); ++j)
166 {
167 dof_id_type old_node_id = boundary_elem->node_ptr(j)->id();
168 if (mesh_node_id_to_boundary_node_id.find(old_node_id) ==
169 mesh_node_id_to_boundary_node_id.end())
170 mooseError("Node id", old_node_id, " not linked to new node id.");
171 dof_id_type new_node_id = mesh_node_id_to_boundary_node_id.find(old_node_id)->second;
172 new_bnd_elem->set_node(j, boundary_nodes[new_node_id]);
173 }
174 }
175 }
176
177 // partition the boundary mesh
178 boundary_mesh->prepare_for_use();
179 _n_boundary_mesh_elems = boundary_mesh->n_elem();
180 if (_partitioner_name == "grid")
181 partition(*boundary_mesh);
182 else
183 {
184 auto partitioner_enum = getParam<MooseEnum>("partitioner");
185 MooseMesh::setPartitioner(*boundary_mesh, partitioner_enum, false, _pars, *this);
186 boundary_mesh->partition(_n_patches);
187 }
188
189 // make sure every partition has at least one element; if not rename and adjust _n_patches
190 checkPartitionAndCompress(*boundary_mesh);
191
192 // prepare sideset names and boundary_ids added to mesh
193 std::vector<BoundaryName> sideset_names =
194 sidesetNameHelper(boundary_info.get_sideset_name(_sideset));
195
196 std::vector<boundary_id_type> boundary_ids =
197 MooseMeshUtils::getBoundaryIDs(*mesh, sideset_names, true);
198
199 mooseAssert(sideset_names.size() == _n_patches,
200 "sideset_names must have as many entries as user-requested number of patches.");
201 mooseAssert(boundary_ids.size() == _n_patches,
202 "boundary_ids must have as many entries as user-requested number of patches.");
203
204 // loop through all elements in the boundary mesh and assign the side of
205 // the _original_ element to the new sideset
206 for (const auto & elem : boundary_mesh->active_element_ptr_range())
207 {
208 const auto it = boundary_elem_to_mesh_elem.find(elem->id());
209 if (it == boundary_elem_to_mesh_elem.end())
210 mooseError("Element in the boundary mesh with id ",
211 elem->id(),
212 " not found in boundary_elem_to_mesh_elem.");
213
214 auto side = boundary_elem_to_mesh_elem.find(elem->id())->second;
215
216 mooseAssert(elem->processor_id() < boundary_ids.size(),
217 "Processor id larger than number of patches.");
218 boundary_info.add_side(
219 std::get<0>(side), std::get<1>(side), boundary_ids[elem->processor_id()]);
220 }
221
222 // make sure new boundary names are set
223 for (MooseIndex(boundary_ids.size()) j = 0; j < boundary_ids.size(); ++j)
224 {
225 boundary_info.sideset_name(boundary_ids[j]) = sideset_names[j];
226 boundary_info.nodeset_name(boundary_ids[j]) = sideset_names[j];
227 }
228
229 return mesh;
230}
231
232void
234{
235 if (_partitioner_name == "grid")
236 {
237 // Figure out the physical bounds of the given mesh
238 auto bounding_box = MeshTools::create_bounding_box(mesh);
239 const auto & min = bounding_box.min();
240 const auto & max = bounding_box.max();
241 const auto & delta = max - min;
242
243 // set number of elements
244 std::vector<unsigned int> nelems(3);
245 if (_dim == 1)
246 {
247 // find the largest component in delta
248 unsigned int largest_id = 0;
249 Real largest = delta(0);
250 for (unsigned int j = 1; j < 3; ++j)
251 if (largest < delta(j))
252 {
253 largest = delta(j);
254 largest_id = j;
255 }
256
257 // set nelems now
258 nelems = {1, 1, 1};
259 nelems[largest_id] = _n_patches;
260 }
261 else
262 {
263 // find the smallest component in delta
264 unsigned int smallest_id = 0;
265 Real smallest = delta(0);
266 for (unsigned int j = 1; j < 3; ++j)
267 if (smallest > delta(j))
268 {
269 smallest = delta(j);
270 smallest_id = j;
271 }
272
273 // store the ids for the two larger dimensions
274 unsigned int id1 = 1, id2 = 2;
275 if (smallest_id == 1)
276 id1 = 0;
277 else if (smallest_id == 2)
278 id2 = 0;
279
280 // set number of elements
281 nelems[smallest_id] = 1;
282 nelems[id1] = std::round(std::sqrt(delta(id1) / delta(id2) * _n_patches));
283 nelems[id2] = std::round(std::sqrt(delta(id2) / delta(id1) * _n_patches));
284 const std::size_t final_n_patches = nelems[id1] * nelems[id2];
285 // We need to check if the number of requested patches and the number of
286 // actually created patches matches. If the two do not match, then a warning
287 // is printed.
288 if (_n_patches != final_n_patches)
289 {
290 _console << "Note: For creating radiation patches for boundary " << _sideset
291 << " using grid partitioner number of patches was changed from " << _n_patches
292 << " to " << final_n_patches << std::endl;
293 _n_patches = final_n_patches;
294 }
295 }
296
297 const Point delem(delta(0) / nelems[0], delta(1) / nelems[1], delta(2) / nelems[2]);
298 for (auto & elem_ptr : mesh.active_element_ptr_range())
299 {
300 const Point centroid = elem_ptr->vertex_average();
301 std::array<unsigned int, 3> ielem;
302 for (const auto i : make_range(LIBMESH_DIM))
303 {
304 const auto dist = centroid(i) - min(i);
305 ielem[i] = (std::abs(dist) < (libMesh::TOLERANCE * libMesh::TOLERANCE))
306 ? 0
307 : std::floor(dist / delem(i));
308 }
309 elem_ptr->processor_id() = ielem[0] + nelems[0] * (ielem[1] + ielem[2] * nelems[1]);
310 mooseAssert(elem_ptr->processor_id() < _n_patches, "Invalid processor id");
311 }
312 }
313 else
314 mooseError("Partitioner ", _partitioner_name, " not recognized.");
315}
316
317void
319{
320 std::set<processor_id_type> processor_ids;
321 for (auto & elem_ptr : mesh.active_element_ptr_range())
322 processor_ids.insert(elem_ptr->processor_id());
323
324 if (processor_ids.size() == _n_patches)
325 return;
326
327 // at least one partition does not have an elem assigned to it
328 // adjust _n_patches
329 _console << "Some partitions for side set " << _sideset
330 << " are empty. Adjusting number of patches from " << _n_patches << " to "
331 << processor_ids.size() << std::endl;
332 _n_patches = processor_ids.size();
333
334 // create a vector and sort it
335 std::vector<processor_id_type> processor_ids_vec;
336 for (auto & p : processor_ids)
337 processor_ids_vec.push_back(p);
338 std::sort(processor_ids_vec.begin(), processor_ids_vec.end());
339
340 // now remap the processor ids
341 std::map<processor_id_type, processor_id_type> processor_id_remap;
342 for (MooseIndex(processor_ids_vec.size()) j = 0; j < processor_ids_vec.size(); ++j)
343 processor_id_remap[processor_ids_vec[j]] = j;
344
345 for (auto & elem_ptr : mesh.active_element_ptr_range())
346 {
347 processor_id_type p = elem_ptr->processor_id();
348 const auto & it = processor_id_remap.find(p);
349 if (it == processor_id_remap.end())
350 mooseError("Parition id ", p, " not in processor_id_remap.");
351 elem_ptr->processor_id() = it->second;
352 }
353}
354
355std::vector<BoundaryName>
356PatchSidesetGenerator::sidesetNameHelper(const std::string & base_name) const
357{
358 std::vector<BoundaryName> rv;
359 for (unsigned int j = 0; j < _n_patches; ++j)
360 {
361 std::stringstream ss;
362 ss << base_name << "_" << j;
363 rv.push_back(ss.str());
364 }
365 return rv;
366}
367
368Elem *
370{
371 std::unique_ptr<Elem> elem = libMesh::Elem::build(type);
372 if (elem->dim() < 3)
373 return mesh.add_elem(std::move(elem));
374
375 mooseError("Unsupported element type (libMesh elem_type enum): ", type);
376}
const Real p
registerMooseObject("HeatTransferApp", PatchSidesetGenerator)
void ErrorVector unsigned int
const ConsoleStream _console
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
void addRequiredRangeCheckedParam(const std::string &name, const std::string &parsed_function, const std::string &doc_string)
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
MooseMesh *const _mesh
std::unique_ptr< ReplicatedMesh > buildReplicatedMesh(unsigned int dim=libMesh::invalid_uint)
static InputParameters validParams()
const std::string & type() const
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
const InputParameters & _pars
void errorIfDistributedMesh(std::string name) const
Subdivides a sidesets into smaller patches each of which is going to be a new patch.
MooseEnum _partitioner_name
the name of the partitioner being used
unsigned int _dim
dimensionality of the sidesets to partition
unsigned int _n_patches
the number of patches that this sideset generator divides _sideset into
void partition(MeshBase &mesh)
a function for implementing custom partitioning
PatchSidesetGenerator(const InputParameters &parameters)
const BoundaryName & _sideset_name
The sideset that will be subdivided.
static InputParameters validParams()
std::unique_ptr< MeshBase > generate() override
BoundaryID _sideset
The sideset that will be subdivided.
std::unique_ptr< MeshBase > & _input
Elem * boundaryElementHelper(MeshBase &mesh, libMesh::ElemType type) const
void checkPartitionAndCompress(MeshBase &mesh)
Checks partitions and makes sure every partition has at least one elem.
dof_id_type _n_boundary_mesh_elems
number of elements of the boundary mesh
std::vector< BoundaryName > sidesetNameHelper(const std::string &base_name) const
returns the name of the _n_patches subdivisions derived from _sideset
MeshBase & 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)
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
bool hasBoundaryNameOrID(const MeshBase &mesh, const BoundaryName &name_or_id)
static constexpr Real TOLERANCE