libMesh
write_elemset_data.C
Go to the documentation of this file.
1 // Basic include files
2 #include "libmesh/equation_systems.h"
3 #include "libmesh/exodusII_io.h"
4 #include "libmesh/nemesis_io.h"
5 #include "libmesh/mesh.h"
6 #include "libmesh/mesh_generation.h"
7 #include "libmesh/parallel.h" // set_union
8 #include "libmesh/replicated_mesh.h"
9 #include "libmesh/string_to_enum.h"
10 #include "libmesh/boundary_info.h"
11 #include "libmesh/utility.h" // libmesh_map_find
12 #include "libmesh/elem.h"
13 
14 #include "test_comm.h"
15 #include "libmesh_cppunit.h"
16 
17 
18 // Bring in everything from the libMesh namespace
19 using namespace libMesh;
20 
21 class WriteElemsetData : public CppUnit::TestCase
22 {
23 public:
24  LIBMESH_CPPUNIT_TEST_SUITE(WriteElemsetData);
25 
26 #if LIBMESH_DIM > 1
27 #ifdef LIBMESH_HAVE_EXODUS_API
28  CPPUNIT_TEST(testWriteExodus);
29 #endif // #ifdef LIBMESH_HAVE_EXODUS_API
30 #ifdef LIBMESH_HAVE_NEMESIS_API
31  // CPPUNIT_TEST(testWriteNemesis); // Not yet implemented
32 #endif
33 #endif
34 
35  CPPUNIT_TEST_SUITE_END();
36 
38  const Point & centroid,
39  unsigned int elemset_index,
40  dof_id_type expected_elemset_code)
41  {
42  const Elem * elem = pl(centroid);
43 
44  // For ReplicatedMesh, this Elem should be found on all procs, but
45  // in case this test is ever run with a DistributedMesh, the Elem
46  // won't be found on all procs, so we only test it on procs where
47  // it is found.
48  if (elem)
49  CPPUNIT_ASSERT_EQUAL(/*expected=*/expected_elemset_code, /*actual=*/elem->get_extra_integer(elemset_index));
50  }
51 
53  {
54  // Make sure that the mesh actually has an extra_integer for "elemset_code"
55  CPPUNIT_ASSERT(mesh.has_elem_integer("elemset_code"));
56 
57  // Check that the elements in mesh are in the correct elemsets.
58  // The elemset_codes will not in general match because they are
59  // created by a generic algorithm in the Exodus reader while above
60  // they were hard-coded.
61  unsigned int elemset_index = mesh.get_elem_integer_index("elemset_code");
62 
63  // Make sure the elemset_codes match what we are expecting.
64  // The Exodus reader assigns the codes based on operator<
65  // for std::sets, which gives us the ordering {1}, {1,2}, {2}
66  CPPUNIT_ASSERT_EQUAL(/*expected=*/static_cast<dof_id_type>(0), /*actual=*/mesh.get_elemset_code({1}));
67  CPPUNIT_ASSERT_EQUAL(/*expected=*/static_cast<dof_id_type>(1), /*actual=*/mesh.get_elemset_code({1,2}));
68  CPPUNIT_ASSERT_EQUAL(/*expected=*/static_cast<dof_id_type>(2), /*actual=*/mesh.get_elemset_code({2}));
69 
70  // Debugging: print vertex_average() for some elements. Perhaps we can use this to uniquely identify
71  // elements for the test...
72  // for (auto id : {8,14,3,24,9,15})
73  // libMesh::out << "Elem " << id
74  // << ", vertex average = " << mesh.elem_ptr(id)->vertex_average()
75  // << std::endl;
76 
77  // We'll use a PointLocator to quickly find elements by centroid
78  auto pl = mesh.sub_point_locator();
79 
80  // Return nullptr when Points are not located in any element
81  // rather than crashing. When running in parallel, this happens
82  // quite often.
83  pl->enable_out_of_mesh_mode();
84 
85  // Test that elements have the same elemset codes they did prior to being written to file.
86  checkByCentroid(*pl, Point(0.4, -0.4, 0), elemset_index, /*expected_elemset_code=*/0); // original Elem 8
87  checkByCentroid(*pl, Point(0.8, 0, 0), elemset_index, /*expected_elemset_code=*/0); // original Elem 14
88  checkByCentroid(*pl, Point(0.4, -0.8, 0), elemset_index, /*expected_elemset_code=*/1); // original Elem 3
89  checkByCentroid(*pl, Point(0.8, 0.8, 0), elemset_index, /*expected_elemset_code=*/1); // original Elem 24
90  checkByCentroid(*pl, Point(0.8, -0.4, 0), elemset_index, /*expected_elemset_code=*/2); // original Elem 9
91  checkByCentroid(*pl, Point(-0.8, 0.4, 0), elemset_index, /*expected_elemset_code=*/2); // original Elem 15
92  }
93 
94  template <typename IOClass>
95  void testWriteImpl(const std::string & filename)
96  {
97  // TODO: Currently this test only works for ReplicatedMesh. It
98  // should be updated so that it works for DistributedMesh as well,
99  // and then we can just set MeshType == Mesh.
100  typedef ReplicatedMesh MeshType;
101 
102  // Construct mesh for writing
103  MeshType mesh(*TestCommWorld);
104 
105  // Allocate space for an extra integer on each element to store a "code" which
106  // determines which sets an Elem belongs to. We do this before building the Mesh.
107  //
108  // extra_integer val & sets elem belongs to
109  // DofObject::invalid_id (default) & elem belongs to no sets
110  // 1 & elem belongs to set A only
111  // 2 & elem belongs to set B only
112  // 3 & elem belongs to sets A and B
113  unsigned int elemset_index =
114  mesh.add_elem_integer("elemset_code",
115  /*allocate_data=*/true);
116 
117  // We are generating this mesh, so it should not be renumbered.
118  // No harm in being explicit about it, however.
119  mesh.allow_renumbering(false);
120 
122  /*nx=*/5, /*ny=*/5,
123  -1., 1.,
124  -1., 1.,
125  QUAD4);
126 
127  // Set ids for elements in elemsets 1, 2
128  std::set<dof_id_type> set1 = {3, 8, 14, 24};
129  std::set<dof_id_type> set2 = {3, 9, 15, 24};
130 
131  // Loop over Elems and set "elemset_code" values
132  for (const auto & elem : mesh.element_ptr_range())
133  {
134  bool
135  in1 = set1.count(elem->id()),
136  in2 = set2.count(elem->id());
137 
139  if (in1)
140  val = 1;
141  if (in2)
142  val = 2;
143  if (in1 && in2)
144  val = 3;
145 
146  elem->set_extra_integer(elemset_index, val);
147  }
148 
149  // Tell the Mesh about these elemsets
150  mesh.add_elemset_code(1, {1});
151  mesh.add_elemset_code(2, {2});
152  mesh.add_elemset_code(3, {1,2});
153 
154  // Debugging: print valid elemset_codes values
155  // for (const auto & elem : mesh.element_ptr_range())
156  // {
157  // dof_id_type elemset_code =
158  // elem->get_extra_integer(elemset_index);
159  //
160  // if (elemset_code != DofObject::invalid_id)
161  // libMesh::out << "Elem " << elem->id() << ", elemset_code = " << elemset_code << std::endl;
162  // }
163 
164  // Set up variables defined on these elemsets
165  std::vector<std::string> var_names = {"var1", "var2", "var3"};
166  std::vector<std::set<elemset_id_type>> elemset_ids =
167  {
168  {1}, // var1 is defined on elemset 1
169  {2}, // var2 is defined on elemset 2
170  {1,2} // var3 is defined on elemsets 1 and 2
171  };
172  std::vector<std::map<std::pair<dof_id_type, elemset_id_type>, Real>> elemset_vals(var_names.size());
173 
174  // To catch values handed back by MeshBase::get_elemsets()
175  std::set<elemset_id_type> id_set_to_fill;
176 
177  for (const auto & elem : mesh.element_ptr_range())
178  {
179  // Get list of elemset ids to which this element belongs
180  mesh.get_elemsets(elem->get_extra_integer(elemset_index), id_set_to_fill);
181 
182  bool
183  in1 = id_set_to_fill.count(1),
184  in2 = id_set_to_fill.count(2);
185 
186  // Set the value for var1 == 1.0 on all elements in elemset 1
187  if (in1)
188  elemset_vals[/*var1 index=*/0].emplace( std::make_pair(elem->id(), /*elemset_id=*/1), 1.0);
189 
190  // Set the value of var2 == 2.0 on all elements in elemset 2
191  if (in2)
192  elemset_vals[/*var2 index=*/1].emplace( std::make_pair(elem->id(), /*elemset_id=*/2), 2.0);
193 
194  // Set the value of var3 == 3.0 on elements in the union of sets 1 and 2
195  if (in1 || in2)
196  for (const auto & id : id_set_to_fill)
197  elemset_vals[/*var3 index=*/2].emplace( std::make_pair(elem->id(), /*elemset_id=*/id), 3.0);
198  }
199 
200  // Sanity check: we should have 8 total elements in set1 and set2 combined
201  CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(8), elemset_vals[/*var3 index=*/2].size());
202 
203  // Lambda to help with debugging
204  // auto print_map = [](const std::vector<std::map<std::pair<dof_id_type, elemset_id_type>, Real>> & input)
205  // {
206  // for (auto i : index_range(input))
207  // {
208  // libMesh::out << "Map " << i << " = " << std::endl;
209  // for (const auto & [pr, val] : input[i])
210  // {
211  // const auto & elem_id = pr.first;
212  // const auto & elemset_id = pr.second;
213  // libMesh::out << "(" << elem_id << ", " << elemset_id << ") = " << val << std::endl;
214  // }
215  // }
216  // };
217 
218  // Debugging: print the elemset_vals struct we just built
219  // print_map(elemset_vals);
220 
221  // Write the file in the ExodusII format, including the element set information.
222  // Note: elemsets should eventually be written during ExodusII_IO::write(), this
223  // would match the behavior of sidesets and nodesets.
224  {
225  IOClass writer(mesh);
226  writer.write(filename);
227  writer.write_elemset_data(/*timestep=*/1, var_names, elemset_ids, elemset_vals);
228  }
229 
230  // Make sure that the writing is done before the reading starts.
232 
233  // Now read it back in
234  MeshType read_mesh(*TestCommWorld);
235 
236  // Do not allow renumbering on this mesh either.
237  read_mesh.allow_renumbering(false);
238 
239  IOClass reader(read_mesh);
240  // reader.verbose(true); // additional messages while debugging
241  reader.read(filename);
242 
243  // When reading in a Mesh using an "IOClass" object, it is not
244  // automatically prepared for use, so do that now.
245  read_mesh.prepare_for_use();
246 
247  // Do generic checks that are independent of IOClass
248  checkElemsetCodes(read_mesh);
249 
250  // Read in the elemset variables from file. This is currently a
251  // feature that is only supported by the Exodus IOClass, so it is
252  // not part of the checkElemsetCodes() function.
253  std::vector<std::string> read_in_var_names;
254  std::vector<std::set<elemset_id_type>> read_in_elemset_ids;
255  std::vector<std::map<std::pair<dof_id_type, elemset_id_type>, Real>> read_in_elemset_vals;
256  reader.read_elemset_data(/*timestep=*/1, read_in_var_names, read_in_elemset_ids, read_in_elemset_vals);
257 
258  // Debugging
259  // print_map(read_in_elemset_vals);
260 
261  // Assert that the data we read in matches what we wrote out
262  CPPUNIT_ASSERT(read_in_var_names == var_names);
263  CPPUNIT_ASSERT(read_in_elemset_ids == elemset_ids);
264  CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(8), read_in_elemset_vals[/*var3 index=*/2].size());
265  CPPUNIT_ASSERT(read_in_elemset_vals == elemset_vals);
266 
267  // Also check that the flat indices match those in the file
268  std::map<std::pair<dof_id_type, elemset_id_type>, unsigned int> elemset_array_indices;
269  reader.get_elemset_data_indices(elemset_array_indices);
270 
271  // Verify that we have the following (Exodus-based) elem ids in the following indices.
272  // These indices were copied from an ncdump of the exo file.
273  std::vector<dof_id_type> elem_els1 = {4, 9, 15, 25};
274  std::vector<dof_id_type> elem_els2 = {4, 10, 16, 25};
275 
276  for (auto i : index_range(elem_els1))
277  CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(i),
278  elemset_array_indices[std::make_pair(/*elem id=*/elem_els1[i] - 1, // convert to libmesh id
279  /*set id*/1)]);
280  for (auto i : index_range(elem_els2))
281  CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(i),
282  elemset_array_indices[std::make_pair(/*elem id=*/elem_els2[i] - 1, // convert to libmesh id
283  /*set id*/2)]);
284 
285 #ifdef LIBMESH_HAVE_XDR
286  // Also test that we can successfully write elemset codes to
287  // XDR/XDA files. Only do this if XDR is enabled. In theory, we
288  // could still test that the ASCII (xda) file writing capability
289  // still works even when the binary (xdr) file writing capability
290  // is disabled; in practice this is probably not worth the extra
291  // hassle.
292 
293  // Now write an xda file so that we can test that elemset codes
294  // are preserved when reading the Mesh back in.
295  read_mesh.write("write_elemset_data.xda");
296 
297  // Make sure that the writing is done before the reading starts.
299 
300  // Now read it back in and do generic checks that are independent of IOClass.
301  Mesh read_mesh2(*TestCommWorld);
302  // XDR files implicitly renumber mesh files in parallel, so setting this flag
303  // does not have the desired effect of preventing renumbering in that case.
304  read_mesh2.allow_renumbering(false);
305  read_mesh2.read("write_elemset_data.xda");
306  checkElemsetCodes(read_mesh2);
307 
308 #endif // LIBMESH_HAVE_XDR
309  }
310 
312  {
313  LOG_UNIT_TEST;
314 
315  testWriteImpl<ExodusII_IO>("write_elemset_data.e");
316  }
317 
319  {
320  // LOG_UNIT_TEST;
321 
322  // FIXME: Not yet implemented
323  // testWriteImpl<Nemesis_IO>("write_elemset_data.n");
324  }
325 };
326 
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
std::unique_ptr< PointLocatorBase > sub_point_locator() const
Definition: mesh_base.C:1638
void allow_renumbering(bool allow)
If false is passed in then this mesh will no longer be renumbered when being prepared for use...
Definition: mesh_base.h:1196
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:171
CPPUNIT_TEST_SUITE_REGISTRATION(WriteElemsetData)
void checkElemsetCodes(const MeshBase &mesh)
void checkByCentroid(const PointLocatorBase &pl, const Point &centroid, unsigned int elemset_index, dof_id_type expected_elemset_code)
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
bool has_elem_integer(std::string_view name) const
Definition: mesh_base.C:638
void barrier() const
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
void build_square(UnstructuredMesh &mesh, const unsigned int nx, const unsigned int ny, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 2D meshes.
The libMesh namespace provides an interface to certain functionality in the library.
dof_id_type get_elemset_code(const MeshBase::elemset_type &id_set) const
Definition: mesh_base.C:439
This is the MeshBase class.
Definition: mesh_base.h:75
void get_elemsets(dof_id_type elemset_code, MeshBase::elemset_type &id_set_to_fill) const
Look up the element sets for a given elemset code and vice-versa.
Definition: mesh_base.C:429
void testWriteImpl(const std::string &filename)
unsigned int get_elem_integer_index(std::string_view name) const
Definition: mesh_base.C:626
static const dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:482
This is the base class for point locators.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual void read(const std::string &name, void *mesh_data=nullptr, bool skip_renumber_nodes_and_elements=false, bool skip_find_neighbors=false) override
Reads the file specified by name.
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
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:117
dof_id_type get_extra_integer(const unsigned int index) const
Gets the value on this object of the extra integer associated with index, which should have been obta...
Definition: dof_object.h:1102
uint8_t dof_id_type
Definition: id_types.h:67