Line data Source code
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 : #include "MooseMeshUtils.h"
16 :
17 : #include "libmesh/replicated_mesh.h"
18 : #include "libmesh/unstructured_mesh.h"
19 : #include "libmesh/mesh_modification.h"
20 : #include "libmesh/point.h"
21 : #include "libmesh/node.h"
22 :
23 : registerMooseObject("MooseApp", CombinerGenerator);
24 :
25 : InputParameters
26 4475 : CombinerGenerator::validParams()
27 : {
28 4475 : InputParameters params = MeshGenerator::validParams();
29 :
30 8950 : params.addClassDescription(
31 : "Combine multiple meshes (or copies of one mesh) together into one (disjoint) mesh. Can "
32 : "optionally translate those meshes before combining them.");
33 :
34 17900 : params.addRequiredParam<std::vector<MeshGeneratorName>>(
35 : "inputs",
36 : "The input MeshGenerators. This can either be N generators or 1 generator. If only 1 is "
37 : "given then 'positions' must also be given.");
38 :
39 17900 : params.addParam<std::vector<Point>>(
40 : "positions",
41 : "The (optional) position of each given mesh. If N 'inputs' were given then this must either "
42 : "be left blank or N positions must be given. If 1 input was given then this MUST be "
43 : "provided.");
44 :
45 17900 : params.addParam<std::vector<FileName>>(
46 : "positions_file", "Alternative way to provide the position of each given mesh.");
47 :
48 13425 : params.addParam<bool>("avoid_merging_subdomains",
49 8950 : false,
50 : "Whether to prevent merging subdomains by offsetting ids. The first mesh "
51 : "in the input will keep the same subdomains ids, the others will have "
52 : "offsets. All subdomain names will remain valid");
53 8950 : params.addParam<bool>("avoid_merging_boundaries",
54 8950 : false,
55 : "Whether to prevent merging sidesets by offsetting ids. The first mesh "
56 : "in the input will keep the same boundary ids, the others will have "
57 : "offsets. All boundary names will remain valid");
58 :
59 4475 : return params;
60 0 : }
61 :
62 707 : CombinerGenerator::CombinerGenerator(const InputParameters & parameters)
63 : : MeshGenerator(parameters),
64 707 : _meshes(getMeshes("inputs")),
65 1414 : _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
66 1414 : _avoid_merging_subdomains(getParam<bool>("avoid_merging_subdomains")),
67 2828 : _avoid_merging_boundaries(getParam<bool>("avoid_merging_boundaries"))
68 : {
69 707 : if (_input_names.empty())
70 0 : paramError("input_names", "You need to specify at least one MeshGenerator as an input.");
71 :
72 2753 : if (isParamValid("positions") && isParamValid("positions_file"))
73 0 : mooseError("Both 'positions' and 'positions_file' cannot be specified simultaneously in "
74 : "CombinerGenerator ",
75 0 : _name);
76 :
77 707 : if (_input_names.size() == 1)
78 392 : if (!isParamValid("positions") && !isParamValid("positions_file"))
79 0 : paramError("positions",
80 : "If only one input mesh is given, then 'positions' or 'positions_file' must also "
81 : "be supplied");
82 707 : }
83 :
84 : void
85 689 : CombinerGenerator::fillPositions()
86 : {
87 2067 : if (isParamValid("positions"))
88 : {
89 630 : _positions = getParam<std::vector<Point>>("positions");
90 :
91 : // the check in the constructor wont catch error where the user sets positions = ''
92 315 : if ((_input_names.size() == 1) && _positions.empty())
93 6 : paramError("positions", "If only one input mesh is given, then 'positions' cannot be empty.");
94 :
95 312 : if (_input_names.size() != 1)
96 207 : if (_positions.size() && (_input_names.size() != _positions.size()))
97 6 : paramError(
98 : "positions",
99 : "If more than one input mesh is provided then the number of positions provided must "
100 : "exactly match the number of input meshes.");
101 : }
102 1122 : else if (isParamValid("positions_file"))
103 : {
104 52 : std::vector<FileName> positions_file = getParam<std::vector<FileName>>("positions_file");
105 :
106 : // the check in the constructor wont catch error where the user sets positions_file = ''
107 26 : if ((_input_names.size() == 1) && positions_file.empty())
108 6 : paramError("positions_file",
109 : "If only one input mesh is given, then 'positions_file' cannot be empty.");
110 :
111 43 : for (const auto & f : positions_file)
112 : {
113 23 : MooseUtils::DelimitedFileReader file(f, &_communicator);
114 23 : file.setFormatFlag(MooseUtils::DelimitedFileReader::FormatFlag::ROWS);
115 23 : file.read();
116 :
117 23 : const std::vector<Point> & data = file.getDataAsPoints();
118 :
119 23 : if (_input_names.size() != 1)
120 13 : if (data.size() && (_input_names.size() != data.size()))
121 6 : paramError("positions_file",
122 : "If more than one input mesh is provided then the number of positions must "
123 : "exactly match the number of input meshes.");
124 :
125 80 : for (const auto & d : data)
126 60 : _positions.push_back(d);
127 20 : }
128 20 : }
129 677 : }
130 :
131 : std::unique_ptr<MeshBase>
132 689 : CombinerGenerator::generate()
133 : {
134 : // Two cases:
135 : // 1. Multiple input meshes and optional positions
136 : // 2. One input mesh and multiple positions
137 689 : fillPositions();
138 :
139 : // Case 1
140 677 : if (_meshes.size() != 1)
141 : {
142 : // merge all meshes into the first one
143 562 : auto mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
144 :
145 562 : if (!mesh)
146 0 : paramError("inputs", _input_names[0], " is not a valid unstructured mesh");
147 :
148 : // Move the first input mesh if applicable
149 562 : if (_positions.size())
150 : {
151 214 : MeshTools::Modification::translate(
152 214 : *mesh, _positions[0](0), _positions[0](1), _positions[0](2));
153 : }
154 :
155 : // Read in all of the other meshes
156 2450 : for (MooseIndex(_meshes) i = 1; i < _meshes.size(); ++i)
157 : {
158 1888 : auto other_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[i]);
159 :
160 1888 : if (!other_mesh)
161 0 : paramError("inputs", _input_names[i], " is not a valid unstructured mesh");
162 :
163 : // We'll be using cached subdomain sets in copyIntoMesh, so our
164 : // const inputs need caches ready.
165 1888 : if (!other_mesh->preparation().has_cached_elem_data)
166 1719 : other_mesh->cache_elem_data();
167 :
168 : // Move It
169 1888 : if (_positions.size())
170 : {
171 698 : MeshTools::Modification::translate(
172 698 : *other_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
173 : }
174 :
175 1888 : MooseMeshUtils::copyIntoMesh(*this,
176 1888 : *mesh,
177 1888 : *other_mesh,
178 1888 : _avoid_merging_subdomains,
179 1888 : _avoid_merging_boundaries,
180 : _communicator);
181 1888 : }
182 :
183 562 : mesh->unset_is_prepared();
184 562 : return dynamic_pointer_cast<MeshBase>(mesh);
185 562 : }
186 : else // Case 2
187 : {
188 115 : auto input_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
189 :
190 : // We'll be using cached subdomain sets in copyIntoMesh
191 115 : if (!input_mesh->preparation().has_cached_elem_data)
192 115 : input_mesh->cache_elem_data();
193 :
194 115 : if (!input_mesh)
195 0 : paramError("inputs", _input_names[0], " is not a valid unstructured mesh");
196 :
197 : // Make a copy and displace it in order to get the final mesh started
198 : auto copy =
199 115 : input_mesh->clone(); // This is required because dynamic_pointer_cast() requires an l-value
200 115 : auto final_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
201 :
202 115 : if (!final_mesh)
203 0 : mooseError("Unable to copy mesh!");
204 :
205 115 : MeshTools::Modification::translate(
206 115 : *final_mesh, _positions[0](0), _positions[0](1), _positions[0](2));
207 :
208 : // Here's the way this is going to work:
209 : // I'm going to make one more copy of the input_mesh so that I can move it and copy it in
210 : // Then, after it's copied in I'm going to reset its coordinates by looping over the input_mesh
211 : // and resetting the nodal positions.
212 : // This could be done without the copy - you would translate the mesh then translate it back...
213 : // However, I'm worried about floating point roundoff. If you were doing this 100,000 times or
214 : // more then the mesh could "drift" away from its original position. I really want the
215 : // translations to be exact each time.
216 : // I suppose that it is technically possible to just save off a datastructure (map, etc.) that
217 : // could hold the nodal positions only (instead of a copy of the mesh) but I'm not sure that
218 : // would really save much... we'll see if it shows up in profiling somewhere
219 115 : copy = input_mesh->clone();
220 115 : auto translated_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
221 :
222 115 : if (!translated_mesh)
223 0 : mooseError("Unable to copy mesh!");
224 :
225 163 : for (MooseIndex(_meshes) i = 1; i < _positions.size(); ++i)
226 : {
227 : // Move
228 48 : MeshTools::Modification::translate(
229 48 : *translated_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
230 :
231 : // Copy into final mesh
232 48 : MooseMeshUtils::copyIntoMesh(*this,
233 48 : *final_mesh,
234 48 : *translated_mesh,
235 48 : _avoid_merging_subdomains,
236 48 : _avoid_merging_boundaries,
237 : _communicator);
238 :
239 : // Reset nodal coordinates
240 4744 : for (auto translated_node_ptr : translated_mesh->node_ptr_range())
241 : {
242 4696 : auto & translated_node = *translated_node_ptr;
243 4696 : auto & input_node = input_mesh->node_ref(translated_node_ptr->id());
244 :
245 18784 : for (MooseIndex(LIBMESH_DIM) i = 0; i < LIBMESH_DIM; i++)
246 14088 : translated_node(i) = input_node(i);
247 48 : }
248 : }
249 :
250 115 : final_mesh->unset_is_prepared();
251 115 : return dynamic_pointer_cast<MeshBase>(final_mesh);
252 115 : }
253 : }
|