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 
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  if (boundary_elem_to_mesh_elem.find(elem->id()) == boundary_elem_to_mesh_elem.end())
209  mooseError("Element in the boundary mesh with id ",
210  elem->id(),
211  " not found in boundary_elem_to_mesh_elem.");
212 
213  auto side = boundary_elem_to_mesh_elem.find(elem->id())->second;
214 
215  mooseAssert(elem->processor_id() < boundary_ids.size(),
216  "Processor id larger than number of patches.");
217  boundary_info.add_side(
218  std::get<0>(side), std::get<1>(side), boundary_ids[elem->processor_id()]);
219  }
220 
221  // make sure new boundary names are set
222  for (MooseIndex(boundary_ids.size()) j = 0; j < boundary_ids.size(); ++j)
223  {
224  boundary_info.sideset_name(boundary_ids[j]) = sideset_names[j];
225  boundary_info.nodeset_name(boundary_ids[j]) = sideset_names[j];
226  }
227 
228  return mesh;
229 }
230 
231 void
233 {
234  if (_partitioner_name == "grid")
235  {
236  // Figure out the physical bounds of the given mesh
237  auto bounding_box = MeshTools::create_bounding_box(mesh);
238  const auto & min = bounding_box.min();
239  const auto & max = bounding_box.max();
240  const auto & delta = max - min;
241 
242  // set number of elements
243  std::vector<unsigned int> nelems(3);
244  if (_dim == 1)
245  {
246  // find the largest component in delta
247  unsigned int largest_id = 0;
248  Real largest = delta(0);
249  for (unsigned int j = 1; j < 3; ++j)
250  if (largest < delta(j))
251  {
252  largest = delta(j);
253  largest_id = j;
254  }
255 
256  // set nelems now
257  nelems = {1, 1, 1};
258  nelems[largest_id] = _n_patches;
259  }
260  else
261  {
262  // find the smallest component in delta
263  unsigned int smallest_id = 0;
264  Real smallest = delta(0);
265  for (unsigned int j = 1; j < 3; ++j)
266  if (smallest > delta(j))
267  {
268  smallest = delta(j);
269  smallest_id = j;
270  }
271 
272  // store the ids for the two larger dimensions
273  unsigned int id1 = 1, id2 = 2;
274  if (smallest_id == 1)
275  id1 = 0;
276  else if (smallest_id == 2)
277  id2 = 0;
278 
279  // set number of elements
280  nelems[smallest_id] = 1;
281  nelems[id1] = std::round(std::sqrt(delta(id1) / delta(id2) * _n_patches));
282  nelems[id2] = std::round(std::sqrt(delta(id2) / delta(id1) * _n_patches));
283  unsigned int final_n_patches = nelems[id1] * nelems[id2];
284  // We need to check if the number of requested patches and the number of
285  // actually created patches matches. If the two do not match, then a warning
286  // is printed.
287  if (_n_patches != final_n_patches)
288  {
289  _console << "Note: For creating radiation patches for boundary " << _sideset
290  << " using grid partitioner number of patches was changed from " << _n_patches
291  << " to " << final_n_patches << std::endl;
292  _n_patches = final_n_patches;
293  }
294  }
295 
296  const Real dx = delta(0) / nelems[0];
297  const Real dy = delta(1) / nelems[1];
298  const Real dz = delta(2) / nelems[2];
299  for (auto & elem_ptr : mesh.active_element_ptr_range())
300  {
301  const Point centroid = elem_ptr->vertex_average();
302  processor_id_type proc_id;
303  const unsigned int ix = std::floor((centroid(0) - min(0)) / dx);
304  const unsigned int iy = std::floor((centroid(1) - min(1)) / dy);
305  const unsigned int iz = std::floor((centroid(2) - min(2)) / dz);
306  proc_id = ix + iy * nelems[0] + iz * nelems[0] * nelems[1];
307  elem_ptr->processor_id() = proc_id;
308  }
309  }
310  else
311  mooseError("Partitioner ", _partitioner_name, " not recognized.");
312 }
313 
314 void
316 {
317  std::set<processor_id_type> processor_ids;
318  for (auto & elem_ptr : mesh.active_element_ptr_range())
319  processor_ids.insert(elem_ptr->processor_id());
320 
321  if (processor_ids.size() == _n_patches)
322  return;
323 
324  // at least one partition does not have an elem assigned to it
325  // adjust _n_patches
326  _console << "Some partitions for side set " << _sideset
327  << " are empty. Adjusting number of patches from " << _n_patches << " to "
328  << processor_ids.size() << std::endl;
329  _n_patches = processor_ids.size();
330 
331  // create a vector and sort it
332  std::vector<processor_id_type> processor_ids_vec;
333  for (auto & p : processor_ids)
334  processor_ids_vec.push_back(p);
335  std::sort(processor_ids_vec.begin(), processor_ids_vec.end());
336 
337  // now remap the processor ids
338  std::map<processor_id_type, processor_id_type> processor_id_remap;
339  for (MooseIndex(processor_ids_vec.size()) j = 0; j < processor_ids_vec.size(); ++j)
340  processor_id_remap[processor_ids_vec[j]] = j;
341 
342  for (auto & elem_ptr : mesh.active_element_ptr_range())
343  {
344  processor_id_type p = elem_ptr->processor_id();
345  const auto & it = processor_id_remap.find(p);
346  if (it == processor_id_remap.end())
347  mooseError("Parition id ", p, " not in processor_id_remap.");
348  elem_ptr->processor_id() = it->second;
349  }
350 }
351 
352 std::vector<BoundaryName>
353 PatchSidesetGenerator::sidesetNameHelper(const std::string & base_name) const
354 {
355  std::vector<BoundaryName> rv;
356  for (unsigned int j = 0; j < _n_patches; ++j)
357  {
358  std::stringstream ss;
359  ss << base_name << "_" << j;
360  rv.push_back(ss.str());
361  }
362  return rv;
363 }
364 
365 Elem *
367 {
368  std::unique_ptr<Elem> elem = libMesh::Elem::build(type);
369  if (elem->dim() < 3)
370  return mesh.add_elem(std::move(elem));
371 
372  mooseError("Unsupported element type (libMesh elem_type enum): ", type);
373 }
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)
const InputParameters & _pars
std::unique_ptr< ReplicatedMesh > buildReplicatedMesh(unsigned int dim=libMesh::invalid_uint)
void paramError(const std::string &param, Args... args) const
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$.
BoundaryID _sideset
The sideset that will be subdivided.
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
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
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)
bool hasBoundaryNameOrID(const MeshBase &mesh, const BoundaryName &name_or_id)
static InputParameters validParams()
const BoundaryName & _sideset_name
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
const Real p
void checkPartitionAndCompress(MeshBase &mesh)
Checks partitions and makes sure every partition has at least one elem.
void mooseError(Args &&... args) const
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)