libMesh
mesh_stitch.C
Go to the documentation of this file.
1 // libmesh includes
2 #include <libmesh/boundary_info.h>
3 #include <libmesh/distributed_mesh.h>
4 #include <libmesh/elem.h>
5 #include <libmesh/mesh.h>
6 #include <libmesh/mesh_generation.h>
7 #include <libmesh/mesh_modification.h>
8 #include <libmesh/parallel_implementation.h>
9 #include <libmesh/node.h>
10 #include <libmesh/replicated_mesh.h>
11 #include <libmesh/utility.h>
12 
13 // cppunit includes
14 #include "test_comm.h"
15 #include "libmesh_cppunit.h"
16 
17 #include <algorithm>
18 #include <regex>
19 
20 using namespace libMesh;
21 
22 
23 class MeshStitchTest : public CppUnit::TestCase {
24 public:
25  LIBMESH_CPPUNIT_TEST_SUITE( MeshStitchTest );
26 
27 #if LIBMESH_DIM > 2
28  CPPUNIT_TEST( testReplicatedMeshStitch );
29  CPPUNIT_TEST( testDistributedMeshStitch );
30  CPPUNIT_TEST( testReplicatedBoundaryInfo );
31  CPPUNIT_TEST( testDistributedBoundaryInfo );
32  CPPUNIT_TEST( testReplicatedMeshStitchElemsets );
33  CPPUNIT_TEST( testRemappingStitch );
34  CPPUNIT_TEST( testAmbiguousRemappingStitch );
35 #endif // LIBMESH_DIM > 2
36 
37  CPPUNIT_TEST_SUITE_END();
38 
39 private:
40 
41 public:
42  void setUp()
43  {}
44 
45  void tearDown()
46  {}
47 
49  const boundary_id_type boundary_id_offset,
50  const std::string & boundary_name_prefix)
51  {
52  BoundaryInfo & boundary_info = mesh.get_boundary_info();
53  const auto mesh_boundary_ids = boundary_info.get_global_boundary_ids();
54  for (auto rit = mesh_boundary_ids.rbegin(); rit != mesh_boundary_ids.rend(); ++rit)
55  {
56  const auto old_sideset_name = boundary_info.sideset_name(*rit);
57  const auto old_nodeset_name = boundary_info.nodeset_name(*rit);
58 
59  MeshTools::Modification::change_boundary_id(mesh, *rit, *rit + boundary_id_offset);
60 
61  boundary_info.sideset_name(*rit + boundary_id_offset) =
62  boundary_name_prefix + old_sideset_name;
63  boundary_info.nodeset_name(*rit + boundary_id_offset) =
64  boundary_name_prefix + old_nodeset_name;
65  }
66  }
67 
68 
69  template <typename MeshType>
71  {
72  LOG_UNIT_TEST;
73 
74  MeshType mesh0(*TestCommWorld), mesh1(*TestCommWorld);
75 
76  int ps = 2;
77  MeshTools::Generation::build_cube(mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX8);
78  MeshTools::Generation::build_cube(mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX8);
79 
80  // rename and shift boundaries
81  renameAndShift(mesh0, 0, "zero_");
82  renameAndShift(mesh1, 6, "one_");
83 
84  mesh0.stitch_meshes(mesh1, 2, 10, TOLERANCE, true, true, false, false);
85 
86  CPPUNIT_ASSERT_EQUAL(mesh0.n_elem(), static_cast<dof_id_type>(16));
87  CPPUNIT_ASSERT_EQUAL(mesh0.n_nodes(), static_cast<dof_id_type>(45));
88 
89  const BoundaryInfo & bi = mesh0.get_boundary_info();
90  std::set<boundary_id_type> sbi = bi.get_side_boundary_ids();
92 
93  typename std::decay<decltype(sbi.size())>::type expected_size = 10;
94  CPPUNIT_ASSERT_EQUAL(expected_size, sbi.size());
95 
96  std::set<boundary_id_type> nbi = bi.get_node_boundary_ids();
98  CPPUNIT_ASSERT_EQUAL(expected_size, nbi.size());
99 
100  // We expect that the "zero_right" and "one_left" boundaries have
101  // disappeared after being stitched together.
102  std::set<std::string> expected_names = {{"zero_left",
103  "zero_top",
104  "zero_front",
105  "zero_back",
106  "zero_bottom",
107  "one_right",
108  "one_top",
109  "one_front",
110  "one_back",
111  "one_bottom"}};
112  std::set<std::string> ss_names;
113  for (const auto & pr : bi.get_sideset_name_map())
114  ss_names.insert(pr.second);
115  CPPUNIT_ASSERT(ss_names == expected_names);
116 
117  std::set<std::string> ns_names;
118  for (const auto & pr : bi.get_nodeset_name_map())
119  ns_names.insert(pr.second);
120  CPPUNIT_ASSERT(ns_names == expected_names);
121  }
122 
123 
125  {
126  testBoundaryInfo<ReplicatedMesh>();
127  }
128 
129 
131  {
132  testBoundaryInfo<DistributedMesh>();
133  }
134 
135 
136  template <typename MeshType>
138  {
139  LOG_UNIT_TEST;
140 
141  // Generate four meshes to be stitched together
142  MeshType mesh0(*TestCommWorld),
143  mesh1(*TestCommWorld),
144  mesh2(*TestCommWorld),
145  mesh3(*TestCommWorld);
146 
147  // Give the meshes different extra integers to make sure those
148  // merge. Reuse names between nodes and elements to make sure
149  // those don't mix. Add some integers before and others after
150  // generation to test flexibility there.
151 
152  std::vector<std::string> names2 {"bar", "baz"};
153  mesh2.add_elem_integers(names2);
154 
155  std::vector<std::string> names3 {"bar", "foo"};
156  mesh3.add_elem_integers(names3);
157 
158  int ps = 2;
159  MeshTools::Generation::build_cube (mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX27);
160  MeshTools::Generation::build_cube (mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX27);
161  MeshTools::Generation::build_cube (mesh2, ps, ps, ps, -1, 0, -1, 0, 0, 1, HEX27);
162  MeshTools::Generation::build_cube (mesh3, ps, ps, ps, 0, 1, -1, 0, 0, 1, HEX27);
163 
164  struct trivially_copyable_pair // std::pair triggers -Wclass-memaccess
165  {
166  dof_id_type first, second;
167  };
168 
169  mesh0.add_node_integer("baz");
170  unsigned int foo1e_idx = mesh1.add_elem_integer("foo");
171  mesh2.template add_elem_datum<trivially_copyable_pair>("qux");
172  unsigned int qux2n_idx = mesh2.template add_node_datum<trivially_copyable_pair>("qux");
173  mesh3.add_node_integers(names3);
174 
175  for (const auto & elem : mesh1.element_ptr_range())
176  elem->set_extra_integer(foo1e_idx, 2);
177 
178  for (const auto & node : mesh2.node_ptr_range())
179  node->template set_extra_datum<trivially_copyable_pair>
180  (qux2n_idx, {3, 4});
181 
182  // We stitch the meshes in a hierarchical way.
183  mesh0.stitch_meshes(mesh1, 2, 4, TOLERANCE, true, true, false, false);
184  mesh2.stitch_meshes(mesh3, 2, 4, TOLERANCE, true, true, false, false);
185  mesh0.stitch_meshes(mesh2, 1, 3, TOLERANCE, true, true, false, false);
186 
187  CPPUNIT_ASSERT_EQUAL(mesh0.n_elem(), static_cast<dof_id_type>(32));
188  CPPUNIT_ASSERT_EQUAL(mesh0.n_nodes(), static_cast<dof_id_type>(405));
189  CPPUNIT_ASSERT_EQUAL(mesh0.n_elem_integers(), 5u); // that pair counts 2x
190  CPPUNIT_ASSERT_EQUAL(mesh0.n_node_integers(), 5u);
191  std::vector<std::string> all_names {"foo", "bar", "baz", "qux"};
192  std::vector<unsigned int> node_name_indices {4, 3, 0, 1};
193  for (unsigned int i=0; i != 4; ++i)
194  {
195  CPPUNIT_ASSERT(mesh0.has_elem_integer(all_names[i]));
196  CPPUNIT_ASSERT_EQUAL(mesh0.get_elem_integer_index(all_names[i]), i);
197  CPPUNIT_ASSERT(mesh0.has_node_integer(all_names[i]));
198  CPPUNIT_ASSERT_EQUAL(mesh0.get_node_integer_index(all_names[i]), node_name_indices[i]);
199  }
200 
201  unsigned int foo0e_idx = mesh0.get_elem_integer_index("foo");
202  for (const auto & elem : mesh0.element_ptr_range())
203  {
204  CPPUNIT_ASSERT_EQUAL(elem->n_extra_integers(), 5u);
205  const Point c = elem->vertex_average();
206  if (c(0) > 0 && c(1) > 0) // this came from mesh1
207  CPPUNIT_ASSERT_EQUAL(elem->get_extra_integer(foo0e_idx), static_cast<dof_id_type>(2));
208  else
209  CPPUNIT_ASSERT_EQUAL(elem->get_extra_integer(foo0e_idx), DofObject::invalid_id);
210  }
211 
212  unsigned int qux0n_idx = mesh0.get_node_integer_index("qux");
213  for (const auto & node : mesh0.node_ptr_range())
214  {
215  CPPUNIT_ASSERT_EQUAL(node->n_extra_integers(), 5u);
216  trivially_copyable_pair datum =
217  node->template get_extra_datum<trivially_copyable_pair>(qux0n_idx);
218  if ((*node)(0) <= 0 && (*node)(1) < 0) // this came from mesh2
219  {
220  CPPUNIT_ASSERT_EQUAL(datum.first, static_cast<dof_id_type>(3));
221  CPPUNIT_ASSERT_EQUAL(datum.second, static_cast<dof_id_type>(4));
222  }
223  else
224  {
225  CPPUNIT_ASSERT_EQUAL(datum.first, DofObject::invalid_id);
226  CPPUNIT_ASSERT_EQUAL(datum.second, DofObject::invalid_id);
227  }
228  }
229  }
230 
232  {
233  testMeshStitch<ReplicatedMesh>();
234  }
235 
237  {
238  testMeshStitch<DistributedMesh>();
239  }
240 
241  template <typename MeshType>
242  void testMeshStitchElemsets (unsigned int ps)
243  {
244  LOG_UNIT_TEST;
245 
246  // Generate meshes to be stitched together. We are going to clone
247  // these so work with unique_ptrs directly.
248  auto mesh0 = std::make_unique<MeshType>(*TestCommWorld);
249 
250  // If the user tries to stitch meshes with overlapping codes, we
251  // allow this as long as the codes refer to the same underlying
252  // set ids.
253 
254  // Build a mesh on the unit cube
255  MeshTools::Generation::build_cube (*mesh0, ps, ps, ps,
256  /*xmin=*/0., /*xmax=*/1.,
257  /*ymin=*/0., /*ymax=*/1.,
258  /*zmin=*/0., /*zmax=*/1.,
259  HEX27);
260 
261  // Make a copy
262  auto mesh1 = mesh0->clone();
263 
264  // Shift copy one unit to the right
265  MeshTools::Modification::translate(*mesh1, /*x-dir*/1.0);
266 
267  // For both meshes:
268  // .) Put odd-numbered Elems in elmset 1
269  // .) Put even-numbered Elems in elemset 2
270  // We use the trivial encoding: elemset id == elemset code for simplicity
271  auto place_elems = [](MeshBase & mesh)
272  {
273  unsigned int elemset_index =
274  mesh.add_elem_integer("elemset_code", /*allocate_data=*/true);
275 
276  mesh.add_elemset_code(/*code=*/1, /*set_ids*/{1});
277  mesh.add_elemset_code(/*code=*/2, /*set_ids*/{2});
278 
279  for (const auto & elem : mesh.element_ptr_range())
280  {
281  if (elem->id() % 2) // id odd
282  elem->set_extra_integer(elemset_index, 1);
283  else // id even
284  elem->set_extra_integer(elemset_index, 2);
285  }
286  };
287 
288  place_elems(*mesh0);
289  place_elems(*mesh1);
290 
291  // Before stitching, change the elemset codes on mesh1 so they
292  // don't overlap with the codes on mesh0.
293  mesh1->change_elemset_code(/*old*/1, /*new*/3); // 1 -> 3
294  mesh1->change_elemset_code(/*old*/2, /*new*/4); // 2 -> 4
295 
296  // Before stitching, change the elemset ids on mesh1 so they
297  // don't overlap with the elemset ids on mesh0.
298  mesh1->change_elemset_id(/*old*/1, /*new*/100); // 1 -> 100
299  mesh1->change_elemset_id(/*old*/2, /*new*/200); // 2 -> 200
300 
301  // Stitch the meshes together at the indicated boundary ids
302  mesh0->stitch_meshes(dynamic_cast<UnstructuredMesh &>(*mesh1),
303  /*this boundary=*/2,
304  /*other boundary=*/4,
305  TOLERANCE,
306  /*clear_stitched_boundary_ids=*/true,
307  /*verbose=*/true,
308  /*use_binary_search=*/false,
309  /*enforce_all_nodes_match_on_boundaries=*/false);
310 
311  // Number of elements in each mesh pre-stitch
312  dof_id_type n_elem_prestitch = Utility::pow<3>(ps);
313 
314  // mesh0 should contain 2 * ps**3 total elements after stitching
315  CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(2 * n_elem_prestitch), mesh0->n_elem());
316 
317  // Check that the stitched mesh still stores "elemset_code" in the
318  // same index (0) as it was before the meshes were stitched.
319  unsigned int elemset_index = mesh0->get_elem_integer_index("elemset_code");
320  CPPUNIT_ASSERT_EQUAL(0u, elemset_index);
321 
322  // Check that the stitched mesh has merged elemset codes and ids as expected
323  MeshBase::elemset_type id_set_to_fill;
324  const elemset_id_type code_to_type[] = {0,1,2,100,200};
325  for (dof_id_type elemset_code=1; elemset_code<5; ++elemset_code)
326  {
327  mesh0->get_elemsets(elemset_code, id_set_to_fill);
328 
329  // Assert one elemset id in each set, and that set contains the correct id
330  CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), id_set_to_fill.size());
331  CPPUNIT_ASSERT(id_set_to_fill.count(code_to_type[elemset_code]));
332  }
333 
334  bool ps_odd = ps % 2;
335 
336  for (const auto & elem : mesh0->element_ptr_range())
337  {
338  dof_id_type elemset_code = elem->get_extra_integer(elemset_index);
339  bool elem_id_odd = elem->id() % 2;
340 
341  // Debugging
342  // libMesh::out << "Elem " << elem->id() << " in stitched mesh has elemset_code = " << elemset_code << std::endl;
343 
344  // Verify that the stitched mesh elemset codes match their pre-stitched values
345  if (elem->id() < n_elem_prestitch) // lower half id
346  {
347  if (elem_id_odd)
348  CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(1), elemset_code);
349  else
350  CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(2), elemset_code);
351  }
352  else // upper half id
353  {
354  // i.) If ps == odd, then n_elem_prestitch == odd, and even mesh1
355  // elem ids will become odd, and odd mesh1 elem ids will become
356  // even..
357  // ii.) If ps == even, then n_elem_prestitch == even, and even mesh1
358  // elem ids will remain even, odd mesh1 elem ids will remain odd.
359  if (elem_id_odd)
360  CPPUNIT_ASSERT_EQUAL(ps_odd ? static_cast<dof_id_type>(4) : static_cast<dof_id_type>(3), elemset_code);
361  else
362  CPPUNIT_ASSERT_EQUAL(ps_odd ? static_cast<dof_id_type>(3) : static_cast<dof_id_type>(4), elemset_code);
363  }
364  }
365  }
366 
368  {
369  testMeshStitchElemsets<ReplicatedMesh>(/*ps=*/2);
370  testMeshStitchElemsets<ReplicatedMesh>(/*ps=*/3);
371  }
372 
373 
375  {
376  LOG_UNIT_TEST;
377 
378  Mesh mesh0(*TestCommWorld), mesh1(*TestCommWorld);
379 
380  int ps = 2;
381  MeshTools::Generation::build_cube(mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX8);
382  MeshTools::Generation::build_cube(mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX8);
383 
384  // rename and shift boundaries
385  renameAndShift(mesh0, 0, "zero_");
386  renameAndShift(mesh1, 6, "one_");
387 
388  // Create "auto" generated subdomain ids
389  for (const auto & elem : mesh0.element_ptr_range())
390  elem->subdomain_id() = 123;
391 
392  for (const auto & elem : mesh1.element_ptr_range())
393  elem->subdomain_id() = 456;
394 
395  // Resolve them to the same name
396  mesh0.subdomain_name(123) = "OneTwoThree";
397  mesh1.subdomain_name(456) = "OneTwoThree"; // silly autogen
398 
399  mesh0.stitch_meshes(mesh1, 2, 10, TOLERANCE, true, false, false,
400  false, false, /* remap_subdomain_ids = */ true);
401 
402  CPPUNIT_ASSERT_EQUAL(mesh0.n_elem(), static_cast<dof_id_type>(16));
403  CPPUNIT_ASSERT_EQUAL(mesh0.n_nodes(), static_cast<dof_id_type>(45));
404 
405  // Ensure they still map to the same name but now with the same id
406  for (const auto & elem : mesh0.element_ptr_range())
407  CPPUNIT_ASSERT_EQUAL(elem->subdomain_id(), subdomain_id_type(123));
408  }
409 
410 
412  {
413  LOG_UNIT_TEST;
414 
415  Mesh mesh0(*TestCommWorld), mesh1(*TestCommWorld);
416 
417  int ps = 2;
418  MeshTools::Generation::build_cube(mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX8);
419  MeshTools::Generation::build_cube(mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX8);
420 
421  // rename and shift boundaries
422  renameAndShift(mesh0, 0, "zero_");
423  renameAndShift(mesh1, 6, "one_");
424 
425  // Create matching subdomain ids
426  for (const auto & elem : mesh0.element_ptr_range())
427  elem->subdomain_id() = 123;
428 
429  for (const auto & elem : mesh1.element_ptr_range())
430  elem->subdomain_id() = 123;
431 
432  // Create a conflict when only one is named
433  mesh1.subdomain_name(123) = "OneTwoThree";
434 
435 #ifdef LIBMESH_ENABLE_EXCEPTIONS
436  bool threw_error = false;
437  try
438  {
439  mesh0.stitch_meshes(mesh1, 2, 10, TOLERANCE, true, false, false,
440  false, false, /* remap_subdomain_ids = */ true);
441  }
442  catch (libMesh::LogicError & e)
443  {
444  std::regex msg_regex("safely stitch with a mesh");
445  CPPUNIT_ASSERT(std::regex_search(e.what(), msg_regex));
446  threw_error = true;
447  }
448 
449  CPPUNIT_ASSERT(threw_error);
450 #endif // LIBMESH_ENABLE_EXCEPTIONS
451  }
452 
453 };
454 
void tearDown()
Definition: mesh_stitch.C:45
const std::set< boundary_id_type > & get_side_boundary_ids() const
CPPUNIT_TEST_SUITE_REGISTRATION(MeshStitchTest)
void testAmbiguousRemappingStitch()
Definition: mesh_stitch.C:411
std::string & nodeset_name(boundary_id_type id)
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:171
static constexpr Real TOLERANCE
TestClass subdomain_id_type
Based on the 4-byte comment warning above, this probably doesn&#39;t work with exodusII at all...
Definition: id_types.h:43
void testDistributedMeshStitch()
Definition: mesh_stitch.C:236
void testMeshStitchElemsets(unsigned int ps)
Definition: mesh_stitch.C:242
unsigned int add_elem_integer(std::string name, bool allocate_data=true, dof_id_type default_value=DofObject::invalid_id)
Register an integer datum (of type dof_id_type) to be added to each element in the mesh...
Definition: mesh_base.C:560
void add_elemset_code(dof_id_type code, MeshBase::elemset_type id_set)
Tabulate a user-defined "code" for elements which belong to the element sets specified in id_set...
Definition: mesh_base.C:398
const std::map< boundary_id_type, std::string > & get_sideset_name_map() const
MeshBase & mesh
The libMesh namespace provides an interface to certain functionality in the library.
void renameAndShift(UnstructuredMesh &mesh, const boundary_id_type boundary_id_offset, const std::string &boundary_name_prefix)
Definition: mesh_stitch.C:48
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:165
This is the MeshBase class.
Definition: mesh_base.h:75
void testMeshStitch()
Definition: mesh_stitch.C:137
void testReplicatedMeshStitch()
Definition: mesh_stitch.C:231
const std::set< boundary_id_type > & get_node_boundary_ids() const
void testRemappingStitch()
Definition: mesh_stitch.C:374
const std::map< boundary_id_type, std::string > & get_nodeset_name_map() const
int8_t boundary_id_type
Definition: id_types.h:51
The UnstructuredMesh class is derived from the MeshBase class.
void change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
Finds any boundary ids that are currently old_id, changes them to new_id.
The BoundaryInfo class contains information relevant to boundary conditions including storing faces...
Definition: boundary_info.h:57
static const dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:482
void testReplicatedBoundaryInfo()
Definition: mesh_stitch.C:124
std::string & subdomain_name(subdomain_id_type id)
Definition: mesh_base.C:1692
void testDistributedBoundaryInfo()
Definition: mesh_stitch.C:130
void translate(MeshBase &mesh, const Real xt=0., const Real yt=0., const Real zt=0.)
Translates the mesh.
std::string & sideset_name(boundary_id_type id)
void testReplicatedMeshStitchElemsets()
Definition: mesh_stitch.C:367
std::set< elemset_id_type > elemset_type
Typedef for the "set" container used to store elemset ids.
Definition: mesh_base.h:318
A class to represent the internal "this should never happen" errors, to be thrown by "libmesh_error()...
const std::set< boundary_id_type > & get_global_boundary_ids() const
void testBoundaryInfo()
Definition: mesh_stitch.C:70
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition: mesh.h:50
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
void build_cube(UnstructuredMesh &mesh, const unsigned int nx=0, const unsigned int ny=0, const unsigned int nz=0, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const Real zmin=0., const Real zmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
Builds a (elements) cube.
uint8_t dof_id_type
Definition: id_types.h:67
void set_union(T &data, const unsigned int root_id) const