https://mooseframework.inl.gov
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 
10 #include "PatchSidesetGenerator.h"
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 
30 registerMooseObject("HeatTransferApp", PatchSidesetGenerator);
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 
58  params.addClassDescription(
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 
73 std::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 
89  // check if the provided sideset name is actually a sideset id
90  // if _sideset_name can be converted to integer it's interpreted
91  // as sideset id
92  std::stringstream ss;
93  ss << _sideset_name;
94  ss >> _sideset;
95  if (ss.fail())
96  _sideset = boundary_info.get_id_by_name(_sideset_name);
97 
98  // make sure that _sideset exists
99  if (_sideset == BoundaryInfo::invalid_id)
100  paramError("sideset_name", "Not a valid boundary");
101 
102  // create a dim - 1 dimensional mesh
103  auto boundary_mesh = buildReplicatedMesh(mesh->mesh_dimension() - 1);
104  boundary_mesh->set_mesh_dimension(mesh->mesh_dimension() - 1);
105  boundary_mesh->set_spatial_dimension(mesh->mesh_dimension());
106 
107  // nodes in the new mesh by boundary_node_id (index)
108  std::vector<Node *> boundary_nodes;
109  // a map from the node numbering on the volumetric mesh to the numbering
110  // on the boundary_mesh
111  std::map<dof_id_type, dof_id_type> mesh_node_id_to_boundary_node_id;
112  // a local counter keeping track of how many entries have been added to boundary_nodes
113  dof_id_type boundary_node_id = 0;
114  // a map from new element id in the boundary mesh to the element id/side/sideset
115  // tuple it came from
116  std::map<dof_id_type, std::tuple<dof_id_type, unsigned short int, boundary_id_type>>
117  boundary_elem_to_mesh_elem;
118  for (auto & side : side_list)
119  {
120  if (std::get<2>(side) == _sideset)
121  {
122  // the original volumetric mesh element
123  const Elem * elem = mesh->elem_ptr(std::get<0>(side));
124 
125  // the boundary element
126  std::unique_ptr<const Elem> boundary_elem = elem->side_ptr(std::get<1>(side));
127 
128  // an array that saves the boundary node ids of this elem in the right order
129  std::vector<dof_id_type> bnd_elem_node_ids(boundary_elem->n_nodes());
130 
131  // loop through the nodes in boundary_elem
132  for (MooseIndex(boundary_elem->n_nodes()) j = 0; j < boundary_elem->n_nodes(); ++j)
133  {
134  const Node * node = boundary_elem->node_ptr(j);
135 
136  // Is this node a new node?
137  if (mesh_node_id_to_boundary_node_id.find(node->id()) ==
138  mesh_node_id_to_boundary_node_id.end())
139  {
140  // yes, it is new, need to add it to the mesh_node_id_to_boundary_node_id map
141  mesh_node_id_to_boundary_node_id.insert(
142  std::pair<dof_id_type, dof_id_type>(node->id(), boundary_node_id));
143 
144  // this adds this node to the boundary mesh and puts it at the right position
145  // in the boundary_nodes array
146  Point pt(*node);
147  boundary_nodes.push_back(boundary_mesh->add_point(pt, boundary_node_id));
148 
149  // keep track of the boundary node for setting up the element
150  bnd_elem_node_ids[j] = boundary_node_id;
151 
152  // increment the boundary_node_id counter
153  ++boundary_node_id;
154  }
155  else
156  bnd_elem_node_ids[j] = mesh_node_id_to_boundary_node_id.find(node->id())->second;
157  }
158 
159  // all nodes for this element have been added, so we can add the element to the
160  // boundary mesh
161  Elem * new_bnd_elem = boundaryElementHelper(*boundary_mesh, boundary_elem->type());
162 
163  // keep track of these new boundary elements in boundary_elem_to_mesh_elem
164  boundary_elem_to_mesh_elem.insert(
165  std::pair<dof_id_type, std::tuple<dof_id_type, unsigned short int, boundary_id_type>>(
166  new_bnd_elem->id(), side));
167 
168  // set the nodes & subdomain_id of the new element by looping over the
169  // boundary_elem and then inserting its nodes into new_bnd_elem in the
170  // same order
171  for (MooseIndex(boundary_elem->n_nodes()) j = 0; j < boundary_elem->n_nodes(); ++j)
172  {
173  dof_id_type old_node_id = boundary_elem->node_ptr(j)->id();
174  if (mesh_node_id_to_boundary_node_id.find(old_node_id) ==
175  mesh_node_id_to_boundary_node_id.end())
176  mooseError("Node id", old_node_id, " not linked to new node id.");
177  dof_id_type new_node_id = mesh_node_id_to_boundary_node_id.find(old_node_id)->second;
178  new_bnd_elem->set_node(j, boundary_nodes[new_node_id]);
179  }
180  }
181  }
182 
183  // partition the boundary mesh
184  boundary_mesh->prepare_for_use();
185  _n_boundary_mesh_elems = boundary_mesh->n_elem();
186  if (_partitioner_name == "grid")
187  partition(*boundary_mesh);
188  else
189  {
190  auto partitioner_enum = getParam<MooseEnum>("partitioner");
191  MooseMesh::setPartitioner(*boundary_mesh, partitioner_enum, false, _pars, *this);
192  boundary_mesh->partition(_n_patches);
193  }
194 
195  // make sure every partition has at least one element; if not rename and adjust _n_patches
196  checkPartitionAndCompress(*boundary_mesh);
197 
198  // prepare sideset names and boundary_ids added to mesh
199  std::vector<BoundaryName> sideset_names =
200  sidesetNameHelper(boundary_info.get_sideset_name(_sideset));
201 
202  std::vector<boundary_id_type> boundary_ids =
203  MooseMeshUtils::getBoundaryIDs(*mesh, sideset_names, true);
204 
205  mooseAssert(sideset_names.size() == _n_patches,
206  "sideset_names must have as many entries as user-requested number of patches.");
207  mooseAssert(boundary_ids.size() == _n_patches,
208  "boundary_ids must have as many entries as user-requested number of patches.");
209 
210  // loop through all elements in the boundary mesh and assign the side of
211  // the _original_ element to the new sideset
212  for (const auto & elem : boundary_mesh->active_element_ptr_range())
213  {
214  if (boundary_elem_to_mesh_elem.find(elem->id()) == boundary_elem_to_mesh_elem.end())
215  mooseError("Element in the boundary mesh with id ",
216  elem->id(),
217  " not found in boundary_elem_to_mesh_elem.");
218 
219  auto side = boundary_elem_to_mesh_elem.find(elem->id())->second;
220 
221  mooseAssert(elem->processor_id() < boundary_ids.size(),
222  "Processor id larger than number of patches.");
223  boundary_info.add_side(
224  std::get<0>(side), std::get<1>(side), boundary_ids[elem->processor_id()]);
225  }
226 
227  // make sure new boundary names are set
228  for (MooseIndex(boundary_ids.size()) j = 0; j < boundary_ids.size(); ++j)
229  {
230  boundary_info.sideset_name(boundary_ids[j]) = sideset_names[j];
231  boundary_info.nodeset_name(boundary_ids[j]) = sideset_names[j];
232  }
233 
234  return mesh;
235 }
236 
237 void
239 {
240  if (_partitioner_name == "grid")
241  {
242  // Figure out the physical bounds of the given mesh
243  auto bounding_box = MeshTools::create_bounding_box(mesh);
244  const auto & min = bounding_box.min();
245  const auto & max = bounding_box.max();
246  const auto & delta = max - min;
247 
248  // set number of elements
249  std::vector<unsigned int> nelems(3);
250  if (_dim == 1)
251  {
252  // find the largest component in delta
253  unsigned int largest_id = 0;
254  Real largest = delta(0);
255  for (unsigned int j = 1; j < 3; ++j)
256  if (largest < delta(j))
257  {
258  largest = delta(j);
259  largest_id = j;
260  }
261 
262  // set nelems now
263  nelems = {1, 1, 1};
264  nelems[largest_id] = _n_patches;
265  }
266  else
267  {
268  // find the smallest component in delta
269  unsigned int smallest_id = 0;
270  Real smallest = delta(0);
271  for (unsigned int j = 1; j < 3; ++j)
272  if (smallest > delta(j))
273  {
274  smallest = delta(j);
275  smallest_id = j;
276  }
277 
278  // store the ids for the two larger dimensions
279  unsigned int id1 = 1, id2 = 2;
280  if (smallest_id == 1)
281  id1 = 0;
282  else if (smallest_id == 2)
283  id2 = 0;
284 
285  // set number of elements
286  nelems[smallest_id] = 1;
287  nelems[id1] = std::round(std::sqrt(delta(id1) / delta(id2) * _n_patches));
288  nelems[id2] = std::round(std::sqrt(delta(id2) / delta(id1) * _n_patches));
289  unsigned int final_n_patches = nelems[id1] * nelems[id2];
290  // We need to check if the number of requested patches and the number of
291  // actually created patches matches. If the two do not match, then a warning
292  // is printed.
293  if (_n_patches != final_n_patches)
294  {
295  _console << "Note: For creating radiation patches for boundary " << _sideset
296  << " using grid partitioner number of patches was changed from " << _n_patches
297  << " to " << final_n_patches << std::endl;
298  _n_patches = final_n_patches;
299  }
300  }
301 
302  const Real dx = delta(0) / nelems[0];
303  const Real dy = delta(1) / nelems[1];
304  const Real dz = delta(2) / nelems[2];
305  for (auto & elem_ptr : mesh.active_element_ptr_range())
306  {
307  const Point centroid = elem_ptr->vertex_average();
308  processor_id_type proc_id;
309  const unsigned int ix = std::floor((centroid(0) - min(0)) / dx);
310  const unsigned int iy = std::floor((centroid(1) - min(1)) / dy);
311  const unsigned int iz = std::floor((centroid(2) - min(2)) / dz);
312  proc_id = ix + iy * nelems[0] + iz * nelems[0] * nelems[1];
313  elem_ptr->processor_id() = proc_id;
314  }
315  }
316  else
317  mooseError("Partitioner ", _partitioner_name, " not recognized.");
318 }
319 
320 void
322 {
323  std::set<processor_id_type> processor_ids;
324  for (auto & elem_ptr : mesh.active_element_ptr_range())
325  processor_ids.insert(elem_ptr->processor_id());
326 
327  if (processor_ids.size() == _n_patches)
328  return;
329 
330  // at least one partition does not have an elem assigned to it
331  // adjust _n_patches
332  _console << "Some partitions for side set " << _sideset
333  << " are empty. Adjusting number of patches from " << _n_patches << " to "
334  << processor_ids.size() << std::endl;
335  _n_patches = processor_ids.size();
336 
337  // create a vector and sort it
338  std::vector<processor_id_type> processor_ids_vec;
339  for (auto & p : processor_ids)
340  processor_ids_vec.push_back(p);
341  std::sort(processor_ids_vec.begin(), processor_ids_vec.end());
342 
343  // now remap the processor ids
344  std::map<processor_id_type, processor_id_type> processor_id_remap;
345  for (MooseIndex(processor_ids_vec.size()) j = 0; j < processor_ids_vec.size(); ++j)
346  processor_id_remap[processor_ids_vec[j]] = j;
347 
348  for (auto & elem_ptr : mesh.active_element_ptr_range())
349  {
350  processor_id_type p = elem_ptr->processor_id();
351  const auto & it = processor_id_remap.find(p);
352  if (it == processor_id_remap.end())
353  mooseError("Parition id ", p, " not in processor_id_remap.");
354  elem_ptr->processor_id() = it->second;
355  }
356 }
357 
358 std::vector<BoundaryName>
359 PatchSidesetGenerator::sidesetNameHelper(const std::string & base_name) const
360 {
361  std::vector<BoundaryName> rv;
362  for (unsigned int j = 0; j < _n_patches; ++j)
363  {
364  std::stringstream ss;
365  ss << base_name << "_" << j;
366  rv.push_back(ss.str());
367  }
368  return rv;
369 }
370 
371 Elem *
373 {
374  std::unique_ptr<Elem> elem = libMesh::Elem::build(type);
375  if (elem->dim() < 3)
376  return mesh.add_elem(std::move(elem));
377 
378  mooseError("Unsupported element type (libMesh elem_type enum): ", type);
379 }
unsigned int _dim
dimensionality of the sidesets to partition
void addRequiredRangeCheckedParam(const std::string &name, const std::string &parsed_function, const std::string &doc_string)
std::unique_ptr< ReplicatedMesh > buildReplicatedMesh(unsigned int dim=libMesh::invalid_uint)
T & getMesh(MooseMesh &mesh)
function to cast mesh
Definition: SCM.h:35
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
static void setPartitioner(MeshBase &mesh_base, MooseEnum &partitioner, bool use_distributed_mesh, const InputParameters &params, MooseObject &context_obj)
std::vector< BoundaryName > sidesetNameHelper(const std::string &base_name) const
returns the name of the _n_patches subdivisions derived from _sideset
Subdivides a sidesets into smaller patches each of which is going to be a new patch.
static InputParameters validParams()
registerMooseObject("HeatTransferApp", PatchSidesetGenerator)
MeshBase & mesh
int delta(unsigned int i, unsigned int j)
Delta function, which returns zero if $i j$ and unity if $i=j$.
void addRequiredParam(const std::string &name, const std::string &doc_string)
auto max(const L &left, const R &right)
MooseMesh *const _mesh
dof_id_type _n_boundary_mesh_elems
number of elements of the boundary mesh
Elem * boundaryElementHelper(MeshBase &mesh, libMesh::ElemType type) const
void errorIfDistributedMesh(std::string name) const
uint8_t processor_id_type
std::unique_ptr< MeshBase > & _input
MooseEnum _partitioner_name
the name of the partitioner being used
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
const std::string & type() const
PatchSidesetGenerator(const InputParameters &parameters)
std::vector< BoundaryID > getBoundaryIDs(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown, const std::set< BoundaryID > &mesh_boundary_ids)
void paramError(const std::string &param, Args... args) const
static InputParameters validParams()
const BoundaryName & _sideset_name
The sideset that will be subdivided.
subdomain_id_type _sideset
The sideset that will be subdivided.
static MooseEnum partitioning()
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
unsigned int _n_patches
the number of patches that this sideset generator divides _sideset into
void checkPartitionAndCompress(MeshBase &mesh)
Checks partitions and makes sure every partition has at least one elem.
void mooseError(Args &&... args) const
const InputParameters & _pars
void addClassDescription(const std::string &doc_string)
void partition(MeshBase &mesh)
a function for implementing custom partitioning
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
const ConsoleStream _console
std::unique_ptr< MeshBase > generate() override
auto min(const L &left, const R &right)
void ErrorVector unsigned int
uint8_t dof_id_type
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)