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 std::string & _name
The name of this class.
Definition: MooseBase.h:359
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 ...
Definition: MooseBase.h:435
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...
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 read()
Perform the actual data reading.
static InputParameters validParams()
Definition: MeshGenerator.C:23
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
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:267
CombinerGenerator(const InputParameters &parameters)
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...
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:195
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...
Definition: MooseBase.h:442
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