libMesh
Loading...
Searching...
No Matches
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
19using namespace libMesh;
20
21class WriteElemsetData : public CppUnit::TestCase
22{
23public:
25
26#if LIBMESH_DIM > 1
27#ifdef LIBMESH_HAVE_EXODUS_API
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
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.
231 TestCommWorld->barrier();
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.
298 TestCommWorld->barrier();
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
CPPUNIT_TEST(testWriteExodus)
void checkByCentroid(const PointLocatorBase &pl, const Point &centroid, unsigned int elemset_index, dof_id_type expected_elemset_code)
void testWriteImpl(const std::string &filename)
LIBMESH_CPPUNIT_TEST_SUITE(WriteElemsetData)
void checkElemsetCodes(const MeshBase &mesh)
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...
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition dof_object.h:473
This is the base class from which all geometric element types are derived.
Definition elem.h:96
This is the MeshBase class.
Definition mesh_base.h:81
unsigned int get_elem_integer_index(std::string_view name) const
Definition mesh_base.C:689
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:1355
dof_id_type get_elemset_code(const MeshBase::elemset_type &id_set) const
Definition mesh_base.C:497
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:623
bool has_elem_integer(std::string_view name) const
Definition mesh_base.C:701
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:456
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:487
std::unique_ptr< PointLocatorBase > sub_point_locator() const
Definition mesh_base.C:1833
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition mesh.h:51
This is the base class for point locators.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
virtual void read(const std::string &name, void *mesh_data=nullptr, bool skip_renumber_nodes_and_elements=false, bool skip_find_neighbors=false, bool skip_detect_interior_parents=false) override
Reads the file specified by name.
Communicator * TestCommWorld
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.
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:153
uint8_t dof_id_type
Definition id_types.h:67
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
CPPUNIT_TEST_SUITE_REGISTRATION(WriteElemsetData)