https://mooseframework.inl.gov
CombinerGenerator.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 "CombinerGenerator.h"
11 
12 #include "CastUniquePointer.h"
13 #include "MooseUtils.h"
14 #include "DelimitedFileReader.h"
15 
16 #include "libmesh/replicated_mesh.h"
17 #include "libmesh/unstructured_mesh.h"
18 #include "libmesh/mesh_modification.h"
19 #include "libmesh/point.h"
20 #include "libmesh/node.h"
21 
23 
26 {
28 
29  params.addClassDescription(
30  "Combine multiple meshes (or copies of one mesh) together into one (disjoint) mesh. Can "
31  "optionally translate those meshes before combining them.");
32 
33  params.addRequiredParam<std::vector<MeshGeneratorName>>(
34  "inputs",
35  "The input MeshGenerators. This can either be N generators or 1 generator. If only 1 is "
36  "given then 'positions' must also be given.");
37 
38  params.addParam<std::vector<Point>>(
39  "positions",
40  "The (optional) position of each given mesh. If N 'inputs' were given then this must either "
41  "be left blank or N positions must be given. If 1 input was given then this MUST be "
42  "provided.");
43 
44  params.addParam<std::vector<FileName>>(
45  "positions_file", "Alternative way to provide the position of each given mesh.");
46 
47  params.addParam<bool>("avoid_merging_subdomains",
48  false,
49  "Whether to prevent merging subdomains by offsetting ids. The first mesh "
50  "in the input will keep the same subdomains ids, the others will have "
51  "offsets. All subdomain names will remain valid");
52  params.addParam<bool>("avoid_merging_boundaries",
53  false,
54  "Whether to prevent merging sidesets by offsetting ids. The first mesh "
55  "in the input will keep the same boundary ids, the others will have "
56  "offsets. All boundary names will remain valid");
57 
58  return params;
59 }
60 
62  : MeshGenerator(parameters),
63  _meshes(getMeshes("inputs")),
64  _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
65  _avoid_merging_subdomains(getParam<bool>("avoid_merging_subdomains")),
66  _avoid_merging_boundaries(getParam<bool>("avoid_merging_boundaries"))
67 {
68  if (_input_names.empty())
69  paramError("input_names", "You need to specify at least one MeshGenerator as an input.");
70 
71  if (isParamValid("positions") && isParamValid("positions_file"))
72  mooseError("Both 'positions' and 'positions_file' cannot be specified simultaneously in "
73  "CombinerGenerator ",
74  _name);
75 
76  if (_input_names.size() == 1)
77  if (!isParamValid("positions") && !isParamValid("positions_file"))
78  paramError("positions",
79  "If only one input mesh is given, then 'positions' or 'positions_file' must also "
80  "be supplied");
81 }
82 
83 void
85 {
86  if (isParamValid("positions"))
87  {
88  _positions = getParam<std::vector<Point>>("positions");
89 
90  // the check in the constructor wont catch error where the user sets positions = ''
91  if ((_input_names.size() == 1) && _positions.empty())
92  paramError("positions", "If only one input mesh is given, then 'positions' cannot be empty.");
93 
94  if (_input_names.size() != 1)
95  if (_positions.size() && (_input_names.size() != _positions.size()))
96  paramError(
97  "positions",
98  "If more than one input mesh is provided then the number of positions provided must "
99  "exactly match the number of input meshes.");
100  }
101  else if (isParamValid("positions_file"))
102  {
103  std::vector<FileName> positions_file = getParam<std::vector<FileName>>("positions_file");
104 
105  // the check in the constructor wont catch error where the user sets positions_file = ''
106  if ((_input_names.size() == 1) && positions_file.empty())
107  paramError("positions_file",
108  "If only one input mesh is given, then 'positions_file' cannot be empty.");
109 
110  for (const auto & f : positions_file)
111  {
114  file.read();
115 
116  const std::vector<Point> & data = file.getDataAsPoints();
117 
118  if (_input_names.size() != 1)
119  if (data.size() && (_input_names.size() != data.size()))
120  paramError("positions_file",
121  "If more than one input mesh is provided then the number of positions must "
122  "exactly match the number of input meshes.");
123 
124  for (const auto & d : data)
125  _positions.push_back(d);
126  }
127  }
128 }
129 
130 std::unique_ptr<MeshBase>
132 {
133  // Two cases:
134  // 1. Multiple input meshes and optional positions
135  // 2. One input mesh and multiple positions
136  fillPositions();
137 
138  // Case 1
139  if (_meshes.size() != 1)
140  {
141  // merge all meshes into the first one
142  auto mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
143 
144  if (!mesh)
145  paramError("inputs", _input_names[0], " is not a valid unstructured mesh");
146 
147  // Move the first input mesh if applicable
148  if (_positions.size())
149  {
150  MeshTools::Modification::translate(
151  *mesh, _positions[0](0), _positions[0](1), _positions[0](2));
152  }
153 
154  // Read in all of the other meshes
155  for (MooseIndex(_meshes) i = 1; i < _meshes.size(); ++i)
156  {
157  auto other_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[i]);
158 
159  if (!other_mesh)
160  paramError("inputs", _input_names[i], " is not a valid unstructured mesh");
161 
162  // Move It
163  if (_positions.size())
164  {
165  MeshTools::Modification::translate(
166  *other_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
167  }
168 
169  copyIntoMesh(*mesh, *other_mesh);
170  }
171 
172  mesh->set_isnt_prepared();
173  return dynamic_pointer_cast<MeshBase>(mesh);
174  }
175  else // Case 2
176  {
177  auto input_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
178 
179  if (!input_mesh)
180  paramError("inputs", _input_names[0], " is not a valid unstructured mesh");
181 
182  // Make a copy and displace it in order to get the final mesh started
183  auto copy =
184  input_mesh->clone(); // This is required because dynamic_pointer_cast() requires an l-value
185  auto final_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
186 
187  if (!final_mesh)
188  mooseError("Unable to copy mesh!");
189 
190  MeshTools::Modification::translate(
191  *final_mesh, _positions[0](0), _positions[0](1), _positions[0](2));
192 
193  // Here's the way this is going to work:
194  // I'm going to make one more copy of the input_mesh so that I can move it and copy it in
195  // Then, after it's copied in I'm going to reset its coordinates by looping over the input_mesh
196  // and resetting the nodal positions.
197  // This could be done without the copy - you would translate the mesh then translate it back...
198  // However, I'm worried about floating point roundoff. If you were doing this 100,000 times or
199  // more then the mesh could "drift" away from its original position. I really want the
200  // translations to be exact each time.
201  // I suppose that it is technically possible to just save off a datastructure (map, etc.) that
202  // could hold the nodal positions only (instead of a copy of the mesh) but I'm not sure that
203  // would really save much... we'll see if it shows up in profiling somewhere
204  copy = input_mesh->clone();
205  auto translated_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
206 
207  if (!translated_mesh)
208  mooseError("Unable to copy mesh!");
209 
210  for (MooseIndex(_meshes) i = 1; i < _positions.size(); ++i)
211  {
212  // Move
213  MeshTools::Modification::translate(
214  *translated_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
215 
216  // Copy into final mesh
217  copyIntoMesh(*final_mesh, *translated_mesh);
218 
219  // Reset nodal coordinates
220  for (auto translated_node_ptr : translated_mesh->node_ptr_range())
221  {
222  auto & translated_node = *translated_node_ptr;
223  auto & input_node = input_mesh->node_ref(translated_node_ptr->id());
224 
225  for (MooseIndex(LIBMESH_DIM) i = 0; i < LIBMESH_DIM; i++)
226  translated_node(i) = input_node(i);
227  }
228  }
229 
230  final_mesh->set_isnt_prepared();
231  return dynamic_pointer_cast<MeshBase>(final_mesh);
232  }
233 }
234 
235 void
236 CombinerGenerator::copyIntoMesh(UnstructuredMesh & destination, const UnstructuredMesh & source)
237 {
238  dof_id_type node_delta = destination.max_node_id();
239  dof_id_type elem_delta = destination.max_elem_id();
240 
241  unique_id_type unique_delta =
242 #ifdef LIBMESH_ENABLE_UNIQUE_ID
243  destination.parallel_max_unique_id();
244 #else
245  0;
246 #endif
247 
248  // Prevent overlaps by offsetting the subdomains in
249  std::unordered_map<subdomain_id_type, subdomain_id_type> id_remapping;
250  unsigned int block_offset = 0;
252  {
253  // Note: if performance becomes an issue, this is overkill for just getting the max node id
254  std::set<subdomain_id_type> source_ids;
255  std::set<subdomain_id_type> dest_ids;
256  source.subdomain_ids(source_ids, true);
257  destination.subdomain_ids(dest_ids, true);
258  mooseAssert(source_ids.size(), "Should have a subdomain");
259  mooseAssert(dest_ids.size(), "Should have a subdomain");
260  unsigned int max_dest_bid = *dest_ids.rbegin();
261  unsigned int min_source_bid = *source_ids.begin();
262  _communicator.max(max_dest_bid);
263  _communicator.min(min_source_bid);
264  block_offset = 1 + max_dest_bid - min_source_bid;
265  for (const auto bid : source_ids)
266  id_remapping[bid] = block_offset + bid;
267  }
268 
269  // Copy mesh data over from the other mesh
270  destination.copy_nodes_and_elements(source,
271  // Skipping this should cause the neighbors
272  // to simply be copied from the other mesh
273  // (which makes sense and is way faster)
274  /*skip_find_neighbors = */ true,
275  elem_delta,
276  node_delta,
277  unique_delta,
278  _avoid_merging_subdomains ? &id_remapping : nullptr);
279 
280  // Get an offset to prevent overlaps / wild merging between boundaries
281  BoundaryInfo & boundary = destination.get_boundary_info();
282  const BoundaryInfo & other_boundary = source.get_boundary_info();
283 
284  unsigned int bid_offset = 0;
286  {
287  const auto boundary_ids = boundary.get_boundary_ids();
288  const auto other_boundary_ids = other_boundary.get_boundary_ids();
289  unsigned int max_dest_bid = boundary_ids.size() ? *boundary_ids.rbegin() : 0;
290  unsigned int min_source_bid = other_boundary_ids.size() ? *other_boundary_ids.begin() : 0;
291  _communicator.max(max_dest_bid);
292  _communicator.min(min_source_bid);
293  bid_offset = 1 + max_dest_bid - min_source_bid;
294  }
295 
296  // Note: the code below originally came from ReplicatedMesh::stitch_mesh_helper()
297  // in libMesh replicated_mesh.C around line 1203
298 
299  // Copy BoundaryInfo from other_mesh too. We do this via the
300  // list APIs rather than element-by-element for speed.
301  for (const auto & t : other_boundary.build_node_list())
302  boundary.add_node(std::get<0>(t) + node_delta, bid_offset + std::get<1>(t));
303 
304  for (const auto & t : other_boundary.build_side_list())
305  boundary.add_side(std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
306 
307  for (const auto & t : other_boundary.build_edge_list())
308  boundary.add_edge(std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
309 
310  for (const auto & t : other_boundary.build_shellface_list())
311  boundary.add_shellface(
312  std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
313 
314  // Check for the case with two block ids sharing the same name
316  {
317  for (const auto & [block_id, block_name] : destination.get_subdomain_name_map())
318  for (const auto & [source_id, source_name] : source.get_subdomain_name_map())
319  if (block_name == source_name)
320  paramWarning("avoid_merging_subdomains",
321  "Not merging subdomains is creating two subdomains with the same name '" +
322  block_name + "' but different ids: " + std::to_string(source_id) +
323  " & " + std::to_string(block_id + block_offset) +
324  ".\n We recommend using a RenameBlockGenerator to prevent this as you "
325  "will get errors reading the Exodus output later.");
326  }
327 
328  for (const auto & [block_id, block_name] : source.get_subdomain_name_map())
329  destination.set_subdomain_name_map().insert(
330  std::make_pair<SubdomainID, SubdomainName>(block_id + block_offset, block_name));
331 
332  // Check for the case with two boundary ids sharing the same name
334  {
335  for (const auto & [b_id, b_name] : other_boundary.get_sideset_name_map())
336  for (const auto & [source_id, source_name] : boundary.get_sideset_name_map())
337  if (b_name == source_name)
338  paramWarning(
339  "avoid_merging_boundaries",
340  "Not merging boundaries is creating two sidesets with the same name '" + b_name +
341  "' but different ids: " + std::to_string(source_id) + " & " +
342  std::to_string(b_id + bid_offset) +
343  ".\n We recommend using a RenameBoundaryGenerator to prevent this as you "
344  "will get errors reading the Exodus output later.");
345  for (const auto & [b_id, b_name] : other_boundary.get_nodeset_name_map())
346  for (const auto & [source_id, source_name] : boundary.get_nodeset_name_map())
347  if (b_name == source_name)
348  paramWarning(
349  "avoid_merging_boundaries",
350  "Not merging boundaries is creating two nodesets with the same name '" + b_name +
351  "' but different ids: " + std::to_string(source_id) + " & " +
352  std::to_string(b_id + bid_offset) +
353  ".\n We recommend using a RenameBoundaryGenerator to prevent this as you "
354  "will get errors reading the Exodus output later.");
355  }
356 
357  for (const auto & [nodeset_id, nodeset_name] : other_boundary.get_nodeset_name_map())
358  boundary.set_nodeset_name_map().insert(
359  std::make_pair<BoundaryID, BoundaryName>(nodeset_id + bid_offset, nodeset_name));
360 
361  for (const auto & [sideset_id, sideset_name] : other_boundary.get_sideset_name_map())
362  boundary.set_sideset_name_map().insert(
363  std::make_pair<BoundaryID, BoundaryName>(sideset_id + bid_offset, sideset_name));
364 
365  for (const auto & [edgeset_id, edgeset_name] : other_boundary.get_edgeset_name_map())
366  boundary.set_edgeset_name_map().insert(
367  std::make_pair<BoundaryID, BoundaryName>(edgeset_id + bid_offset, edgeset_name));
368 }
const bool _avoid_merging_boundaries
Boolean to control whether to prevent merging boundaries.
Collects multiple meshes into a single (unconnected) mesh.
const std::vector< Point > getDataAsPoints() const
Get the data in Point format.
void fillPositions()
Fill the offset positions for each mesh.
MeshBase & mesh
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
const Parallel::Communicator & _communicator
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
void min(const T &r, T &o, Request &req) const
const bool _avoid_merging_subdomains
Boolean to control whether to prevent merging subdomains.
registerMooseObject("MooseApp", CombinerGenerator)
const std::vector< MeshGeneratorName > & _input_names
The mesh generators to use.
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
void read()
Perform the actual data reading.
static InputParameters validParams()
Definition: MeshGenerator.C:23
const std::string _name
The name of this class.
Definition: MooseBase.h:90
static InputParameters validParams()
void copyIntoMesh(UnstructuredMesh &destination, const UnstructuredMesh &source)
Helper funciton for copying one mesh into another.
Utility class for reading delimited data (e.g., CSV data).
void max(const T &r, T &o, Request &req) const
std::vector< Point > _positions
The (offsets) positions for each mesh.
const std::vector< std::unique_ptr< MeshBase > * > _meshes
CombinerGenerator(const InputParameters &parameters)
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type.
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
void paramWarning(const std::string &param, Args... args) const
Emits a warning prefixed with the file and line number of the given param (from the input file) along...
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:32
uint8_t unique_id_type
uint8_t dof_id_type