libMesh
Loading...
Searching...
No Matches
exodusII_io_helper.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19#include "libmesh/exodusII_io_helper.h"
20
21
22#ifdef LIBMESH_HAVE_EXODUS_API
23
24// libMesh includes
25#include "libmesh/boundary_info.h"
26#include "libmesh/enum_elem_type.h"
27#include "libmesh/elem.h"
28#include "libmesh/equation_systems.h"
29#include "libmesh/fpe_disabler.h"
30#include "libmesh/remote_elem.h"
31#include "libmesh/system.h"
32#include "libmesh/numeric_vector.h"
33#include "libmesh/enum_to_string.h"
34#include "libmesh/enum_elem_type.h"
35#include "libmesh/int_range.h"
36#include "libmesh/utility.h"
37#include "libmesh/libmesh_logging.h"
38
39#ifdef DEBUG
40#include "libmesh/mesh_tools.h" // for elem_types warning
41#endif
42
43#include <libmesh/ignore_warnings.h>
44namespace exII {
45extern "C" {
46#include "exodusII.h" // defines MAX_LINE_LENGTH, MAX_STR_LENGTH used later
47}
48}
49#include <libmesh/restore_warnings.h>
50
51// C++ includes
52#include <algorithm>
53#include <cfenv> // workaround for HDF5 bug
54#include <cstdlib> // std::strtol
55#include <sstream>
56#include <string>
57#include <unordered_map>
58
59// Anonymous namespace for file local data and helper functions
60namespace
61{
62
63// ExodusII defaults to 32 bytes names, but we've had user complaints
64// about truncation with those.
65// It looks like the maximum they'll support is 80 byte names.
66static constexpr int libmesh_max_str_length = MAX_LINE_LENGTH;
67
68using namespace libMesh;
69
70// File scope constant node/edge/face mapping arrays.
71// 2D inverse face map definitions.
72// These take a libMesh ID and turn it into an Exodus ID
73const std::vector<int> trishell3_inverse_edge_map = {3, 4, 5};
74const std::vector<int> quadshell4_inverse_edge_map = {3, 4, 5, 6};
75
76// 3D node map definitions
77// The hex27, prism20-21, and tet14 appear to be the only elements
78// with a non-identity mapping between Exodus' node numbering and
79// libmesh's. Exodus doesn't even number prisms hierarchically!
80const std::vector<int> hex27_node_map = {
81 // Vertex and mid-edge nodes
82 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
83 // Mid-face nodes and center node
84 21, 25, 24, 26, 23, 22, 20};
85//20 21 22 23 24 25 26 // LibMesh indices
86
87const std::vector<int> hex27_inverse_node_map = {
88 // Vertex and mid-edge nodes
89 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
90 // Mid-face nodes and center node
91 26, 20, 25, 24, 22, 21, 23};
92//20 21 22 23 24 25 26
93
94const std::vector<int> prism20_node_map = {
95 // Vertices
96 0, 1, 2, 3, 4, 5,
97 // Matching mid-edge nodes
98 6, 7, 8, 9, 10, 11, 12, 13, 14,
99 // Non-matching nodes
100 19, 17, 18, 15, 16};
101//15 16 17 18 19 // LibMesh indices
102
103const std::vector<int> prism20_inverse_node_map = {
104 // Vertices
105 0, 1, 2, 3, 4, 5,
106 // Matching mid-edge nodes
107 6, 7, 8, 9, 10, 11, 12, 13, 14,
108 // Non-matching nodes
109 18, 19, 16, 17, 15};
110//15 16 17 18 19
111
112const std::vector<int> prism21_node_map = {
113 // Vertices
114 0, 1, 2, 3, 4, 5,
115 // Matching mid-edge nodes
116 6, 7, 8, 9, 10, 11, 12, 13, 14,
117 // Non-matching nodes
118 20, 18, 19, 16, 17, 15};
119//15 16 17 18 19 20 // LibMesh indices
120
121const std::vector<int> prism21_inverse_node_map = {
122 // Vertices
123 0, 1, 2, 3, 4, 5,
124 // Matching mid-edge nodes
125 6, 7, 8, 9, 10, 11, 12, 13, 14,
126 // Non-matching nodes
127 20, 18, 19, 16, 17, 15};
128//15 16 17 18 19 20
129
130const std::vector<int> tet14_node_map = {
131 // Vertex and mid-edge nodes
132 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
133 // Mid-face nodes
134 10, 13, 11, 12};
135//10 11 12 13 // LibMesh indices
136
137const std::vector<int> tet14_inverse_node_map = {
138 // Vertex and mid-edge nodes
139 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
140 // Mid-face nodes
141 10, 12, 13, 11};
142//10 11 12 13
143
144
145// 3D face map definitions
146const std::vector<int> tet_face_map = {1, 2, 3, 0};
147const std::vector<int> hex_face_map = {1, 2, 3, 4, 0, 5};
148const std::vector<int> prism_face_map = {1, 2, 3, 0, 4};
149
150// These take a libMesh ID and turn it into an Exodus ID
151const std::vector<int> tet_inverse_face_map = {4, 1, 2, 3};
152const std::vector<int> hex_inverse_face_map = {5, 1, 2, 3, 4, 6};
153const std::vector<int> prism_inverse_face_map = {4, 1, 2, 3, 5};
154
155// 3D element edge maps. Map 0-based Exodus id -> libMesh id.
156// Commented out until we have code that needs it, to keep compiler
157// warnings happy.
158// const std::vector<int> hex_edge_map =
159 // {0,1,2,3,8,9,10,11,4,5,7,6};
160
161// 3D inverse element edge maps. Map libmesh edge ids to 1-based Exodus edge ids.
162// Commented out until we have code that needs it, to keep compiler
163// warnings happy.
164// const std::vector<int> hex_inverse_edge_map =
165 // {1,2,3,4,9,10,12,11,5,6,7,8};
166
170 int inquire(libMesh::ExodusII_IO_Helper & e2h, exII::ex_inquiry req_info_in, std::string error_msg="")
171 {
172 int ret_int = 0;
173 char ret_char = 0;
174 float ret_float = 0.;
175
176 e2h.ex_err = exII::ex_inquire(e2h.ex_id,
177 req_info_in,
178 &ret_int,
179 &ret_float,
180 &ret_char);
181
182 EX_CHECK_ERR(e2h.ex_err, error_msg);
183
184 return ret_int;
185 }
186
187 // Bezier Extraction test: if we see BEx data we had better be in a
188 // Bezier element block
189 inline bool is_bezier_elem(const char * elem_type_str)
190 {
191 // Reading Bezier Extraction from Exodus files requires ExodusII v8
192#if EX_API_VERS_NODOT < 800
193 libMesh::libmesh_ignore(elem_type_str);
194 return false;
195#else
196 if (strlen(elem_type_str) <= 4)
197 return false;
198 return (std::string(elem_type_str, elem_type_str+4) == "BEX_");
199#endif
200 }
201
202
203 std::map<subdomain_id_type, std::vector<unsigned int>>
204 build_subdomain_map(const MeshBase & mesh,
205 bool add_sides,
206 bool reduce,
207 subdomain_id_type & subdomain_id_end)
208 {
209 // Without a reduction across ranks, the subdomain map is only
210 // complete if the whole mesh is available here.
212 if (reduce)
213 libmesh_parallel_only(mesh.comm());
214
215 // When reducing, each rank only contributes its local elements, so
216 // that no element is counted twice on a replicated mesh.
217 auto for_each_elem = [&mesh, reduce](auto && f)
218 {
219 if (reduce)
220 for (const auto & elem : mesh.active_local_element_ptr_range())
221 f(*elem);
222 else
223 for (const auto & elem : mesh.active_element_ptr_range())
224 f(*elem);
225 };
226
227 // Exodus requires every element block to contain a single element
228 // type. Group elements first by subdomain id and then by element
229 // type, so that a subdomain containing more than one element type
230 // (for example a mix of C0POLYHEDRON and HEX8 cells) is split across
231 // multiple blocks rather than written as a single, invalid block.
232 std::map<subdomain_id_type,
233 std::map<ElemType, std::vector<unsigned int>>> elems_by_subdomain_type;
234
235 // We skip writing infinite elements to the Exodus file, so don't
236 // put them in the map. That way the number of blocks should be
237 // correct.
238 if (reduce)
239 {
240 std::vector<dof_id_type> elem_ids;
241 std::vector<subdomain_id_type> elem_sbd_ids;
242 std::vector<int> elem_types;
243 for_each_elem([&](const Elem & elem)
244 {
245 if (elem.infinite())
246 return;
247 elem_ids.push_back(elem.id());
248 elem_sbd_ids.push_back(elem.subdomain_id());
249 elem_types.push_back(elem.type());
250 });
251
252 // Every rank contributes the same number of entries to each
253 // vector, so the gathered vectors stay aligned.
254 mesh.comm().allgather(elem_ids);
255 mesh.comm().allgather(elem_sbd_ids);
256 mesh.comm().allgather(elem_types);
257
258 for (auto i : index_range(elem_ids))
259 elems_by_subdomain_type[elem_sbd_ids[i]][static_cast<ElemType>(elem_types[i])]
260 .push_back(cast_int<unsigned int>(elem_ids[i]));
261
262 // Gathered ids arrive grouped by rank; sort them to match the
263 // id ordering of a serial traversal.
264 for (auto & [sbd_id, type_map] : elems_by_subdomain_type)
265 {
266 libmesh_ignore(sbd_id);
267 for (auto & [elem_t, ids] : type_map)
268 {
269 libmesh_ignore(elem_t);
270 std::sort(ids.begin(), ids.end());
271 }
272 }
273 }
274 else
275 for_each_elem([&](const Elem & elem)
276 {
277 if (elem.infinite())
278 return;
279 elems_by_subdomain_type[elem.subdomain_id()][elem.type()].push_back(elem.id());
280 });
281
282 // Assign a block id to each (subdomain, element type) group. The
283 // first element type in each subdomain keeps the subdomain id as its
284 // block id, so single-type subdomains (the common case) are written
285 // exactly as before. Any additional element types in the same
286 // subdomain get synthesized block ids allocated above all existing
287 // subdomain ids.
288 std::map<subdomain_id_type, std::vector<unsigned int>> subdomain_map;
289 subdomain_id_type next_synth_block_id = elems_by_subdomain_type.empty() ?
290 0 : elems_by_subdomain_type.rbegin()->first + 1;
291
292 for (auto & [sbd_id, type_map] : elems_by_subdomain_type)
293 {
294 bool first_type = true;
295 for (auto & [elem_t, elem_ids] : type_map)
296 {
297 libmesh_ignore(elem_t);
298 const subdomain_id_type block_id =
299 first_type ? sbd_id : next_synth_block_id++;
300
301 first_type = false;
302 subdomain_map[block_id] = std::move(elem_ids);
303 }
304 }
305
306 // Real element blocks occupy the ids below subdomain_id_end; any
307 // blocks synthesized for visualization sides are numbered after them.
308 if (!subdomain_map.empty())
309 subdomain_id_end = subdomain_map.rbegin()->first + 1;
310
311 // If we've been asked to add side elements, those go in their own
312 // blocks. We don't have any ids to list for elements that don't
313 // explicitly exist in the mesh, but we add an entry to keep track of
314 // the number of elements we'll add in each new block.
315 if (add_sides)
316 {
317 std::map<subdomain_id_type, unsigned int> side_block_counts;
318 for_each_elem([&](const Elem & elem)
319 {
320 if (elem.infinite())
321 return;
322
323 for (auto s : elem.side_index_range())
324 {
326 continue;
327
328 const subdomain_id_type side_block_id =
329 cast_int<subdomain_id_type>(subdomain_id_end + elem.side_type(s));
330
331 // Guard the invariant above (also catches subdomain_id_type
332 // overflow wrapping a side block id back down onto an
333 // element block id).
334 libmesh_assert_greater_equal(side_block_id, subdomain_id_end);
335
336 ++side_block_counts[side_block_id];
337 }
338 });
339
340 if (reduce)
341 mesh.comm().sum(side_block_counts);
342
343 for (const auto & [side_block_id, count] : side_block_counts)
344 subdomain_map[side_block_id] = {count};
345 }
346
347 return subdomain_map;
348 }
349} // end anonymous namespace
350
351
352
353namespace libMesh
354{
355
356// ExodusII_IO_Helper::Conversion static data
357const int ExodusII_IO_Helper::Conversion::invalid_id = std::numeric_limits<int>::max();
358
360 bool v,
361 bool run_only_on_proc0,
362 bool single_precision) :
363 ParallelObject(parent),
364 ex_id(0),
365 ex_err(0),
366 header_info(), // zero-initialize
367 title(header_info.title),
368 num_dim(header_info.num_dim),
369 num_nodes(header_info.num_nodes),
370 num_elem(header_info.num_elem),
371 num_elem_blk(header_info.num_elem_blk),
372 num_edge(header_info.num_edge),
373 num_edge_blk(header_info.num_edge_blk),
374 num_face(header_info.num_face),
375 num_face_blk(header_info.num_face_blk),
376 num_node_sets(header_info.num_node_sets),
377 num_side_sets(header_info.num_side_sets),
378 num_elem_sets(header_info.num_elem_sets),
379 num_global_vars(0),
380 num_sideset_vars(0),
381 num_nodeset_vars(0),
382 num_elemset_vars(0),
383 num_elem_this_blk(0),
384 num_nodes_per_elem(0),
385 num_attr(0),
386 num_elem_all_sidesets(0),
387 num_elem_all_elemsets(0),
388 bex_num_elem_cvs(0),
389 num_time_steps(0),
390 num_nodal_vars(0),
391 num_elem_vars(0),
392 verbose(v),
393 set_unique_ids_from_maps(false),
394 opened_for_writing(false),
395 opened_for_reading(false),
396 _run_only_on_proc0(run_only_on_proc0),
397 _opened_by_create(false),
398 _elem_vars_initialized(false),
399 _global_vars_initialized(false),
400 _nodal_vars_initialized(false),
401 _use_mesh_dimension_instead_of_spatial_dimension(false),
402 _write_hdf5(true),
403 _max_name_length(32),
404 _end_elem_id(0),
405 _write_as_dimension(0),
406 _single_precision(single_precision)
407{
408 title.resize(MAX_LINE_LENGTH+1);
409 elem_type.resize(libmesh_max_str_length);
412}
413
414
415
417
418
419
421{
422 return EX_API_VERS_NODOT;
423}
424
425
426
427// Initialization function for conversion_map object
429{
430 auto convert_type = [this](ElemType type,
431 std::string_view exodus_type,
432 const std::vector<int> * node_map = nullptr,
433 const std::vector<int> * inverse_node_map = nullptr,
434 const std::vector<int> * side_map = nullptr,
435 const std::vector<int> * inverse_side_map = nullptr,
436 const std::vector<int> * shellface_map = nullptr,
437 const std::vector<int> * inverse_shellface_map = nullptr,
438 size_t shellface_index_offset = 0)
439 {
440 std::unique_ptr<Elem> elem = Elem::build(type);
441 auto & conv = conversion_map[elem->dim()][type];
442 conv.libmesh_type = type;
443 conv.exodus_type = exodus_type;
444 conv.node_map = node_map;
445 conv.inverse_node_map = inverse_node_map;
446 conv.side_map = side_map;
447 conv.inverse_side_map = inverse_side_map;
448 conv.shellface_map = shellface_map;
449 conv.inverse_shellface_map = inverse_shellface_map;
450 conv.shellface_index_offset = shellface_index_offset;
451 conv.n_nodes = elem->n_nodes();
452 for (int d = elem->dim()+1; d <= 3; ++d)
453 conversion_map[d][type] = conv;
454 };
455
456 convert_type(NODEELEM, "SPHERE");
457 convert_type(EDGE2, "EDGE2");
458 convert_type(EDGE3, "EDGE3");
459 convert_type(EDGE4, "EDGE4");
460 convert_type(QUAD4, "QUAD4");
461 convert_type(QUAD8, "QUAD8");
462 convert_type(QUAD9, "QUAD9");
463 convert_type(C0POLYGON, "NSIDED");
464 {
465 auto & conv = conversion_map[3][C0POLYHEDRON];
466 conv.libmesh_type = C0POLYHEDRON;
467 conv.exodus_type = "NFACED";
468 conv.dim = 3;
469 conv.n_nodes = 0;
470 }
471 convert_type(QUADSHELL4, "SHELL4", nullptr, nullptr, nullptr,
472 /* inverse_side_map = */ &quadshell4_inverse_edge_map,
473 nullptr, nullptr, /* shellface_index_offset = */ 2);
474 convert_type(QUADSHELL8, "SHELL8", nullptr, nullptr, nullptr,
475 /* inverse_side_map = */ &quadshell4_inverse_edge_map,
476 nullptr, nullptr, /* shellface_index_offset = */ 2);
477 convert_type(QUADSHELL9, "SHELL9", nullptr, nullptr, nullptr,
478 /* inverse_side_map = */ &quadshell4_inverse_edge_map,
479 nullptr, nullptr, /* shellface_index_offset = */ 2);
480
481 convert_type(TRI3, "TRI3");
482 convert_type(TRI6, "TRI6");
483 convert_type(TRI7, "TRI7");
484 // Exodus does weird things to triangle side mapping in 3D. See
485 // https://sandialabs.github.io/seacas-docs/html/element_types.html#tri
486 conversion_map[3][TRI3].inverse_side_map = &trishell3_inverse_edge_map;
487 conversion_map[3][TRI3].shellface_index_offset = 2;
488 conversion_map[3][TRI6].inverse_side_map = &trishell3_inverse_edge_map;
489 conversion_map[3][TRI6].shellface_index_offset = 2;
490 conversion_map[3][TRI7].inverse_side_map = &trishell3_inverse_edge_map;
491 conversion_map[3][TRI7].shellface_index_offset = 2;
492
493 convert_type(TRISHELL3, "TRISHELL3", nullptr, nullptr, nullptr,
494 /* inverse_side_map = */ &trishell3_inverse_edge_map,
495 nullptr, nullptr, /* shellface_index_offset = */ 2);
496 convert_type(TRI3SUBDIVISION, "TRI3");
497 convert_type(HEX8, "HEX8", nullptr, nullptr,
498 &hex_face_map, &hex_inverse_face_map);
499 convert_type(HEX20, "HEX20", nullptr, nullptr,
500 &hex_face_map, &hex_inverse_face_map);
501 convert_type(HEX27, "HEX27", &hex27_node_map,
502 &hex27_inverse_node_map,
503 &hex_face_map, &hex_inverse_face_map);
504 convert_type(TET4, "TETRA4", nullptr, nullptr,
505 &tet_face_map, &tet_inverse_face_map);
506 convert_type(TET10, "TETRA10", nullptr, nullptr,
507 &tet_face_map, &tet_inverse_face_map);
508 convert_type(TET14, "TETRA14", &tet14_node_map,
509 &tet14_inverse_node_map,
510 &tet_face_map, &tet_inverse_face_map);
511 convert_type(PRISM6, "WEDGE", nullptr, nullptr,
512 &prism_face_map, &prism_inverse_face_map);
513 convert_type(PRISM15, "WEDGE15", nullptr, nullptr,
514 &prism_face_map, &prism_inverse_face_map);
515 convert_type(PRISM18, "WEDGE18", nullptr, nullptr,
516 &prism_face_map, &prism_inverse_face_map);
517 convert_type(PRISM20, "WEDGE20", &prism20_node_map,
518 &prism20_inverse_node_map,
519 &prism_face_map, &prism_inverse_face_map);
520 convert_type(PRISM21, "WEDGE21", &prism21_node_map,
521 &prism21_inverse_node_map,
522 &prism_face_map, &prism_inverse_face_map);
523 convert_type(PYRAMID5, "PYRAMID5");
524 convert_type(PYRAMID13, "PYRAMID13");
525 convert_type(PYRAMID14, "PYRAMID14");
526 convert_type(PYRAMID18, "PYRAMID18");
527}
528
529
530
531// This function initializes the element_equivalence_map the first time it
532// is called, and returns early all other times.
534{
535 // We use an ExodusII SPHERE element to represent a NodeElem
537
538 // EDGE2 equivalences
544 element_equivalence_map["TRUSS2"] = EDGE2;
547
548 // EDGE3 equivalences
550 element_equivalence_map["TRUSS3"] = EDGE3;
553
554 // EDGE4 equivalences
556 element_equivalence_map["TRUSS4"] = EDGE4;
559
560 // This whole design is going to need to be refactored whenever we
561 // support higher-order IGA, with one element type having variable
562 // polynomiaal degree...
563 element_equivalence_map["BEX_CURVE"] = EDGE3;
564
565 // QUAD4 equivalences
568
569 // QUADSHELL4 equivalences
572
573 // QUAD8 equivalences
575
576 // QUADSHELL8 equivalences
578
579 // QUAD9 equivalences
581 // This only supports p==2 IGA:
582 element_equivalence_map["BEX_QUAD"] = QUAD9;
583
584 // QUADSHELL9 equivalences
586
587 // Runtime-topology polytope equivalences
590
591 // TRI3 equivalences
594 element_equivalence_map["TRIANGLE"] = TRI3;
595
596 // TRISHELL3 equivalences
598 element_equivalence_map["TRISHELL3"] = TRISHELL3;
599
600 // TRI6 equivalences
602 // element_equivalence_map["TRISHELL6"] = TRI6;
603 // This only supports p==2 IGA:
604 element_equivalence_map["BEX_TRIANGLE"] = TRI6;
605
606 // TRI7 equivalences
608
609 // HEX8 equivalences
612
613 // HEX20 equivalences
615
616 // HEX27 equivalences
618 // This only supports p==2 IGA:
619 element_equivalence_map["BEX_HEX"] = HEX27;
620
621 // TET4 equivalences
622 element_equivalence_map["TETRA"] = TET4;
623 element_equivalence_map["TETRA4"] = TET4;
624
625 // TET10 equivalences
626 element_equivalence_map["TETRA10"] = TET10;
627 // This only supports p==2 IGA:
628 element_equivalence_map["BEX_TETRA"] = TET10;
629
630 // TET14 (in Exodus 8) equivalence
631 element_equivalence_map["TETRA14"] = TET14;
632
633 // PRISM6 equivalences
636
637 // PRISM15 equivalences
638 element_equivalence_map["WEDGE15"] = PRISM15;
639
640 // PRISM18 equivalences
641 element_equivalence_map["WEDGE18"] = PRISM18;
642 // This only supports p==2 IGA:
643 element_equivalence_map["BEX_WEDGE"] = PRISM18;
644
645 // PRISM20 equivalences
646 element_equivalence_map["WEDGE20"] = PRISM20;
647
648 // PRISM21 equivalences
649 element_equivalence_map["WEDGE21"] = PRISM21;
650
651 // PYRAMID equivalences
653 element_equivalence_map["PYRAMID5"] = PYRAMID5;
654 element_equivalence_map["PYRAMID13"] = PYRAMID13;
655 element_equivalence_map["PYRAMID14"] = PYRAMID14;
656 element_equivalence_map["PYRAMID18"] = PYRAMID18;
657}
658
661{
662 auto & maps_for_dim = libmesh_map_find(conversion_map, this->num_dim);
663 return libmesh_map_find(maps_for_dim, type);
664}
665
667ExodusII_IO_Helper::get_conversion(std::string type_str) const
668{
669 // Do only upper-case comparisons
670 std::transform(type_str.begin(), type_str.end(), type_str.begin(), ::toupper);
671 return get_conversion (libmesh_map_find(element_equivalence_map, type_str));
672}
673
675{
676 return elem_type.data();
677}
678
679
680
681void ExodusII_IO_Helper::message(std::string_view msg)
682{
683 if (verbose) libMesh::out << msg << std::endl;
684}
685
686
687
688void ExodusII_IO_Helper::message(std::string_view msg, int i)
689{
690 if (verbose) libMesh::out << msg << i << "." << std::endl;
691}
692
693
695MappedOutputVector(const std::vector<Real> & our_data_in,
696 bool single_precision_in)
697 : our_data(our_data_in),
698 single_precision(single_precision_in)
699{
701 {
702 if (sizeof(Real) != sizeof(float))
703 {
704 float_vec.resize(our_data.size());
705 // boost float128 demands explicit downconversions
706 for (std::size_t i : index_range(our_data))
707 float_vec[i] = float(our_data[i]);
708 }
709 }
710
711 else if (sizeof(Real) != sizeof(double))
712 {
713 double_vec.resize(our_data.size());
714 // boost float128 demands explicit downconversions
715 for (std::size_t i : index_range(our_data))
716 double_vec[i] = double(our_data[i]);
717 }
718}
719
720void *
722{
723 if (single_precision)
724 {
725 if (sizeof(Real) != sizeof(float))
726 return static_cast<void*>(float_vec.data());
727 }
728
729 else if (sizeof(Real) != sizeof(double))
730 return static_cast<void*>(double_vec.data());
731
732 // Otherwise return a (suitably casted) pointer to the original underlying data.
733 return const_cast<void *>(static_cast<const void *>(our_data.data()));
734}
735
737MappedInputVector(std::vector<Real> & our_data_in,
738 bool single_precision_in)
739 : our_data(our_data_in),
740 single_precision(single_precision_in)
741{
742 // Allocate temporary space to store enough floats/doubles, if required.
744 {
745 if (sizeof(Real) != sizeof(float))
746 float_vec.resize(our_data.size());
747 }
748 else if (sizeof(Real) != sizeof(double))
749 double_vec.resize(our_data.size());
750}
751
754{
755 if (single_precision)
756 {
757 if (sizeof(Real) != sizeof(float))
758 our_data.assign(float_vec.begin(), float_vec.end());
759 }
760 else if (sizeof(Real) != sizeof(double))
761 our_data.assign(double_vec.begin(), double_vec.end());
762}
763
764void *
766{
767 if (single_precision)
768 {
769 if (sizeof(Real) != sizeof(float))
770 return static_cast<void*>(float_vec.data());
771 }
772
773 else if (sizeof(Real) != sizeof(double))
774 return static_cast<void*>(double_vec.data());
775
776 // Otherwise return a (suitably casted) pointer to the original underlying data.
777 return static_cast<void *>(our_data.data());
778}
779
780void ExodusII_IO_Helper::open(const char * filename, bool read_only)
781{
782 // Version of Exodus you are using
783 float ex_version = 0.;
784
785 int comp_ws = 0;
786
788 comp_ws = cast_int<int>(sizeof(float));
789
790 // Fall back on double precision when necessary since ExodusII
791 // doesn't seem to support long double
792 else
793 comp_ws = cast_int<int>(std::min(sizeof(Real), sizeof(double)));
794
795 // Word size in bytes of the floating point data as they are stored
796 // in the ExodusII file. "If this argument is 0, the word size of the
797 // floating point data already stored in the file is returned"
798 int io_ws = 0;
799
800 {
801 FPEDisabler disable_fpes;
802 ex_id = exII::ex_open(filename,
803 read_only ? EX_READ : EX_WRITE,
804 &comp_ws,
805 &io_ws,
806 &ex_version);
807 }
808
809 std::string err_msg = std::string("Error opening ExodusII mesh file: ") + std::string(filename);
810 EX_CHECK_ERR(ex_id, err_msg);
811 if (verbose) libMesh::out << "File opened successfully." << std::endl;
812
813 // If we're writing then we'll want to use the specified length;
814 // if we're reading then we'll override this by what's in the file.
815 int max_name_length_to_set = _max_name_length;
816
817 if (read_only)
818 {
819 opened_for_reading = true;
820 elem_node_counts.clear();
821 elem_face_counts.clear();
823
824 // ExodusII reads truncate to 32-char strings by default; we'd
825 // like to support whatever's in the file, so as early as possible
826 // let's find out what that is.
827 int max_name_length = exII::ex_inquire_int(ex_id, exII::EX_INQ_DB_MAX_USED_NAME_LENGTH);
828
829 libmesh_error_msg_if(max_name_length > MAX_LINE_LENGTH,
830 "Unexpected maximum name length of " <<
831 max_name_length << " in file " << filename <<
832 " exceeds expected " << MAX_LINE_LENGTH);
833
834 // I don't think the 32 here should be necessary, but let's make
835 // sure we don't accidentally make things *worse* for anyone.
836 max_name_length_to_set = std::max(max_name_length, 32);
837 }
838 else
839 opened_for_writing = true;
840
841 ex_err = exII::ex_set_max_name_length(ex_id, max_name_length_to_set);
842 EX_CHECK_ERR(ex_err, "Error setting max ExodusII name length.");
843
844 current_filename = std::string(filename);
845}
846
847
848
851{
852 // Read init params using newer API that reads into a struct. For
853 // backwards compatibility, assign local member values from struct
854 // afterwards. Note: using the new API allows us to automatically
855 // read edge and face block/set information if it's present in the
856 // file.
857 exII::ex_init_params params = {};
858 int err_flag = exII::ex_get_init_ext(ex_id, &params);
859 EX_CHECK_ERR(err_flag, "Error retrieving header info.");
860
861 // Extract required data into our struct
863 h.title.assign(params.title, params.title + MAX_LINE_LENGTH);
864 h.num_dim = params.num_dim;
865 h.num_nodes = params.num_nodes;
866 h.num_elem = params.num_elem;
867 h.num_elem_blk = params.num_elem_blk;
868 h.num_node_sets = params.num_node_sets;
869 h.num_side_sets = params.num_side_sets;
870 h.num_elem_sets = params.num_elem_sets;
871 h.num_edge_blk = params.num_edge_blk;
872 h.num_edge = params.num_edge;
873 h.num_face_blk = params.num_face_blk;
874 h.num_face = params.num_face;
875
876 // And return it
877 return h;
878}
879
880
881
883{
884 // Read header params from file, storing them in this class's
885 // ExodusHeaderInfo struct. This automatically updates the local
886 // num_dim, num_elem, etc. references.
887 this->header_info = this->read_header();
888
889 // Read the number of timesteps which are present in the file
890 this->read_num_time_steps();
891
892 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_NODAL, &num_nodal_vars);
893 EX_CHECK_ERR(ex_err, "Error reading number of nodal variables.");
894
895 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_ELEM_BLOCK, &num_elem_vars);
896 EX_CHECK_ERR(ex_err, "Error reading number of elemental variables.");
897
898 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_GLOBAL, &num_global_vars);
899 EX_CHECK_ERR(ex_err, "Error reading number of global variables.");
900
901 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_SIDE_SET, &num_sideset_vars);
902 EX_CHECK_ERR(ex_err, "Error reading number of sideset variables.");
903
904 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_NODE_SET, &num_nodeset_vars);
905 EX_CHECK_ERR(ex_err, "Error reading number of nodeset variables.");
906
907 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_ELEM_SET, &num_elemset_vars);
908 EX_CHECK_ERR(ex_err, "Error reading number of elemset variables.");
909
910 message("Exodus header info retrieved successfully.");
911}
912
913
914
915
917{
918 // The QA records are four MAX_STR_LENGTH-byte character strings.
919 int num_qa_rec =
920 inquire(*this, exII::EX_INQ_QA, "Error retrieving number of QA records");
921
922 if (verbose)
923 libMesh::out << "Found "
924 << num_qa_rec
925 << " QA record(s) in the Exodus file."
926 << std::endl;
927
928 if (num_qa_rec > 0)
929 {
930 // Actual (num_qa_rec x 4) storage for strings. The object we
931 // pass to the Exodus API will just contain pointers into the
932 // qa_storage object, which will have all automatic memory
933 // management.
934 std::vector<std::vector<std::vector<char>>> qa_storage(num_qa_rec);
935 for (auto i : make_range(num_qa_rec))
936 {
937 qa_storage[i].resize(4);
938 for (auto j : make_range(4))
939 qa_storage[i][j].resize(libmesh_max_str_length+1);
940 }
941
942 // inner_array_t is a fixed-size array of 4 strings
943 typedef char * inner_array_t[4];
944
945 // There is at least one compiler (Clang 12.0.1) that complains about
946 // "a non-scalar type used in a pseudo-destructor expression" when
947 // we try to instantiate a std::vector of inner_array_t objects as in:
948 // std::vector<inner_array_t> qa_record(num_qa_rec);
949 // So, we instead attempt to achieve the same effect with a std::unique_ptr.
950 auto qa_record = std::make_unique<inner_array_t[]>(num_qa_rec);
951
952 // Create data structure to be passed to Exodus API by setting
953 // pointers to the actual strings which are in qa_storage.
954 for (auto i : make_range(num_qa_rec))
955 for (auto j : make_range(4))
956 qa_record[i][j] = qa_storage[i][j].data();
957
958 ex_err = exII::ex_get_qa (ex_id, qa_record.get());
959 EX_CHECK_ERR(ex_err, "Error reading the QA records.");
960
961 // Print the QA records
962 if (verbose)
963 {
964 for (auto i : make_range(num_qa_rec))
965 {
966 libMesh::out << "QA Record: " << i << std::endl;
967 for (auto j : make_range(4))
968 libMesh::out << qa_record[i][j] << std::endl;
969 }
970 }
971 }
972}
973
974
975
976
978{
979 if (verbose)
980 libMesh::out << "Title: \t" << title.data() << std::endl
981 << "Mesh Dimension: \t" << num_dim << std::endl
982 << "Number of Nodes: \t" << num_nodes << std::endl
983 << "Number of elements: \t" << num_elem << std::endl
984 << "Number of elt blocks: \t" << num_elem_blk << std::endl
985 << "Number of node sets: \t" << num_node_sets << std::endl
986 << "Number of side sets: \t" << num_side_sets << std::endl
987 << "Number of elem sets: \t" << num_elem_sets << std::endl;
988}
989
990
991
993{
994 LOG_SCOPE("read_nodes()", "ExodusII_IO_Helper");
995
996 x.resize(num_nodes);
997 y.resize(num_nodes);
998 z.resize(num_nodes);
999
1000 if (num_nodes)
1001 {
1002 ex_err = exII::ex_get_coord
1003 (ex_id,
1007
1008 EX_CHECK_ERR(ex_err, "Error retrieving nodal data.");
1009 message("Nodal data retrieved successfully.");
1010 }
1011
1012 // If a nodal attribute bex_weight exists, we get spline weights
1013 // from it
1014 int n_nodal_attr = 0;
1015 ex_err = exII::ex_get_attr_param(ex_id, exII::EX_NODAL, 0, & n_nodal_attr);
1016 EX_CHECK_ERR(ex_err, "Error getting number of nodal attributes.");
1017
1018 if (n_nodal_attr > 0)
1019 {
1020 std::vector<std::vector<char>> attr_name_data
1021 (n_nodal_attr, std::vector<char>(libmesh_max_str_length + 1));
1022 std::vector<char *> attr_names(n_nodal_attr);
1023 for (auto i : index_range(attr_names))
1024 attr_names[i] = attr_name_data[i].data();
1025
1026 ex_err = exII::ex_get_attr_names(ex_id, exII::EX_NODAL, 0, attr_names.data());
1027 EX_CHECK_ERR(ex_err, "Error getting nodal attribute names.");
1028
1029 for (auto i : index_range(attr_names))
1030 if (std::string("bex_weight") == attr_names[i])
1031 {
1032 w.resize(num_nodes);
1033 ex_err =
1034 exII::ex_get_one_attr (ex_id, exII::EX_NODAL, 0, i+1,
1036 EX_CHECK_ERR(ex_err, "Error getting Bezier Extraction nodal weights");
1037 }
1038 }
1039}
1040
1041
1042
1044{
1045 node_num_map.resize(num_nodes);
1046
1047 // Note: we cannot use the exII::ex_get_num_map() here because it
1048 // (apparently) does not behave like ex_get_node_num_map() when
1049 // there is no node number map in the file: it throws an error
1050 // instead of returning a default identity array (1,2,3,...).
1051 ex_err = exII::ex_get_node_num_map
1052 (ex_id, node_num_map.empty() ? nullptr : node_num_map.data());
1053
1054 EX_CHECK_ERR(ex_err, "Error retrieving nodal number map.");
1055 message("Nodal numbering map retrieved successfully.");
1056
1057 if (verbose)
1058 {
1059 libMesh::out << "[" << this->processor_id() << "] node_num_map[i] = ";
1060 for (unsigned int i=0; i<static_cast<unsigned int>(std::min(10, num_nodes-1)); ++i)
1061 libMesh::out << node_num_map[i] << ", ";
1062 libMesh::out << "... " << node_num_map.back() << std::endl;
1063 }
1064}
1065
1066
1068{
1069 // If a bex blob exists, we look for Bezier Extraction coefficient
1070 // data there.
1071
1072 // These APIs require newer Exodus than 5.22
1073#if EX_API_VERS_NODOT >= 800
1074 int n_blobs = exII::ex_inquire_int(ex_id, exII::EX_INQ_BLOB);
1075
1076 if (n_blobs > 0)
1077 {
1078 std::vector<exII::ex_blob> blobs(n_blobs);
1079 std::vector<std::vector<char>> blob_names(n_blobs);
1080 for (auto i : make_range(n_blobs))
1081 {
1082 blob_names[i].resize(libmesh_max_str_length+1);
1083 blobs[i].name = blob_names[i].data();
1084 }
1085
1086 ex_err = exII::ex_get_blobs(ex_id, blobs.data());
1087 EX_CHECK_ERR(ex_err, "Error getting blobs.");
1088
1089 bool found_blob = false;
1090 const exII::ex_blob * my_blob = &blobs[0];
1091 for (const auto & blob : blobs)
1092 {
1093 if (std::string("bex_cv_blob") == blob.name)
1094 {
1095 found_blob = true;
1096 my_blob = &blob;
1097 }
1098 }
1099
1100 if (!found_blob)
1101 libmesh_error_msg("Found no bex_cv_blob for bezier elements");
1102
1103 const int n_blob_attr =
1104 exII::ex_get_attribute_count(ex_id, exII::EX_BLOB,
1105 my_blob->id);
1106
1107 std::vector<exII::ex_attribute> attributes(n_blob_attr);
1108 ex_err = exII::ex_get_attribute_param(ex_id, exII::EX_BLOB,
1109 my_blob->id,
1110 attributes.data());
1111 EX_CHECK_ERR(ex_err, "Error getting bex blob attribute parameters.");
1112
1113 int bex_num_dense_cv_blocks = 0;
1114 std::vector<int> bex_dense_cv_info;
1115 for (auto & attr : attributes)
1116 {
1117 if (std::string("bex_dense_cv_info") == attr.name)
1118 {
1119 const std::size_t value_count = attr.value_count;
1120 if (value_count % 2)
1121 libmesh_error_msg("Found odd number of bex_dense_cv_info");
1122
1123 bex_dense_cv_info.resize(value_count);
1124 attr.values = bex_dense_cv_info.data();
1125 exII::ex_get_attribute(ex_id, &attr);
1126
1127 bex_num_dense_cv_blocks = value_count / 2;
1128
1129 libmesh_error_msg_if(bex_num_dense_cv_blocks > 1,
1130 "Found more than 1 dense bex CV block; unsure how to handle that");
1131 }
1132 }
1133
1134 if (bex_dense_cv_info.empty())
1135 libmesh_error_msg("No bex_dense_cv_info found");
1136
1137 int n_blob_vars;
1138 exII::ex_get_variable_param(ex_id, exII::EX_BLOB, &n_blob_vars);
1139 std::vector<char> var_name (libmesh_max_str_length + 1);
1140 for (auto v_id : make_range(1,n_blob_vars+1))
1141 {
1142 ex_err = exII::ex_get_variable_name(ex_id, exII::EX_BLOB, v_id, var_name.data());
1143 EX_CHECK_ERR(ex_err, "Error reading bex blob var name.");
1144
1145 if (std::string("bex_dense_cv_blocks") == var_name.data())
1146 {
1147 std::vector<double> bex_dense_cv_blocks(my_blob->num_entry);
1148
1149 ex_err = exII::ex_get_var(ex_id, 1, exII::EX_BLOB, v_id,
1150 my_blob->id, my_blob->num_entry,
1151 bex_dense_cv_blocks.data());
1152 EX_CHECK_ERR(ex_err, "Error reading bex_dense_cv_blocks.");
1153
1155 bex_dense_constraint_vecs.resize(bex_num_dense_cv_blocks);
1156
1157 std::size_t offset = 0;
1158 for (auto i : IntRange<std::size_t>(0, bex_num_dense_cv_blocks))
1159 {
1160 bex_dense_constraint_vecs[i].resize(bex_dense_cv_info[2*i]);
1161 const int vecsize = bex_dense_cv_info[2*i+1];
1162 for (auto & vec : bex_dense_constraint_vecs[i])
1163 {
1164 vec.resize(vecsize);
1165 std::copy(std::next(bex_dense_cv_blocks.begin(), offset),
1166 std::next(bex_dense_cv_blocks.begin(), offset + vecsize),
1167 vec.begin());
1168 offset += vecsize;
1169 }
1170 }
1171 libmesh_assert(offset == bex_dense_cv_blocks.size());
1172 }
1173 }
1174 }
1175#endif // EX_API_VERS_NODOT >= 800
1176}
1177
1178
1179void ExodusII_IO_Helper::print_nodes(std::ostream & out_stream)
1180{
1181 for (int i=0; i<num_nodes; i++)
1182 out_stream << "(" << x[i] << ", " << y[i] << ", " << z[i] << ")" << std::endl;
1183}
1184
1185
1186
1188{
1189 if (num_elem_blk)
1190 {
1191 // Read all element block IDs.
1192 block_ids.resize(num_elem_blk);
1193 ex_err = exII::ex_get_ids(ex_id,
1194 exII::EX_ELEM_BLOCK,
1195 block_ids.data());
1196
1197 EX_CHECK_ERR(ex_err, "Error getting block IDs.");
1198 message("All block IDs retrieved successfully.");
1199
1200 char name_buffer[libmesh_max_str_length+1];
1201 for (int i=0; i<num_elem_blk; ++i)
1202 {
1203 ex_err = exII::ex_get_name(ex_id, exII::EX_ELEM_BLOCK,
1204 block_ids[i], name_buffer);
1205 EX_CHECK_ERR(ex_err, "Error getting block name.");
1206 id_to_block_names[block_ids[i]] = name_buffer;
1207 }
1208 message("All block names retrieved successfully.");
1209 }
1210
1211 if (num_edge_blk)
1212 {
1213 // Read all edge block IDs.
1215 ex_err = exII::ex_get_ids(ex_id,
1216 exII::EX_EDGE_BLOCK,
1217 edge_block_ids.data());
1218
1219 EX_CHECK_ERR(ex_err, "Error getting edge block IDs.");
1220 message("All edge block IDs retrieved successfully.");
1221
1222 // Read in edge block names
1223 char name_buffer[libmesh_max_str_length+1];
1224 for (int i=0; i<num_edge_blk; ++i)
1225 {
1226 ex_err = exII::ex_get_name(ex_id, exII::EX_EDGE_BLOCK,
1227 edge_block_ids[i], name_buffer);
1228 EX_CHECK_ERR(ex_err, "Error getting block name.");
1229 id_to_edge_block_names[edge_block_ids[i]] = name_buffer;
1230 }
1231 message("All edge block names retrieved successfully.");
1232 }
1233}
1234
1235
1236
1238{
1239 libmesh_assert_less (index, block_ids.size());
1240
1241 return block_ids[index];
1242}
1243
1244
1245
1247{
1248 libmesh_assert_less (index, block_ids.size());
1249
1250 return id_to_block_names[block_ids[index]];
1251}
1252
1253
1254
1256{
1257 libmesh_assert_less (index, ss_ids.size());
1258
1259 return ss_ids[index];
1260}
1261
1262
1263
1265{
1266 libmesh_assert_less (index, ss_ids.size());
1267
1268 return id_to_ss_names[ss_ids[index]];
1269}
1270
1271
1272
1274{
1275 libmesh_assert_less (index, nodeset_ids.size());
1276
1277 return nodeset_ids[index];
1278}
1279
1280
1281
1283{
1284 libmesh_assert_less (index, nodeset_ids.size());
1285
1286 return id_to_ns_names[nodeset_ids[index]];
1287}
1288
1289
1291{
1292 LOG_SCOPE("read_face_blocks()", "ExodusII_IO_Helper");
1293
1294 if (!c0polyhedron_face_connect.empty())
1295 return;
1296
1297 libmesh_error_msg_if(num_face_blk == 0,
1298 "Error: Exodus NFACED element block found, "
1299 "but the file has no face blocks.");
1300
1301 std::vector<int> face_block_ids(num_face_blk);
1302 ex_err = exII::ex_get_ids(ex_id,
1303 exII::EX_FACE_BLOCK,
1304 face_block_ids.data());
1305 EX_CHECK_ERR(ex_err, "Error getting face block IDs.");
1306
1309
1310 for (auto block : index_range(face_block_ids))
1311 {
1312 std::vector<char> face_type(libmesh_max_str_length+1);
1313 int num_face_this_blk = 0;
1314 int num_node_data_this_blk = 0;
1315 int num_edges_per_face = 0;
1316 int num_faces_per_face = 0;
1317 int num_attr_face = 0;
1318
1319 ex_err = exII::ex_get_block(ex_id,
1320 exII::EX_FACE_BLOCK,
1321 face_block_ids[block],
1322 face_type.data(),
1323 &num_face_this_blk,
1324 &num_node_data_this_blk,
1325 &num_edges_per_face,
1326 &num_faces_per_face,
1327 &num_attr_face);
1328 EX_CHECK_ERR(ex_err, "Error getting face block info.");
1329
1330 const auto & conv = get_conversion(std::string(face_type.data()));
1331 libmesh_error_msg_if(conv.libmesh_elem_type() != C0POLYGON,
1332 "Error: NFACED polyhedron input currently expects "
1333 "NSIDED face blocks, but face block "
1334 << face_block_ids[block] << " has Exodus type "
1335 << face_type.data() << ".");
1336
1337 libmesh_error_msg_if
1338 (!(num_edges_per_face == 0) && !(num_edges_per_face == -1),
1339 "Error: Exodus NSIDED face block "
1340 << face_block_ids[block]
1341 << " has edge connectivity, which NFACED polyhedron input "
1342 << "does not currently support.");
1343 libmesh_error_msg_if
1344 (!(num_faces_per_face == 0) && !(num_faces_per_face == -1),
1345 "Error: Exodus NSIDED face block "
1346 << face_block_ids[block]
1347 << " has face-in-face connectivity, which NFACED polyhedron "
1348 << "input does not currently support.");
1349
1350 std::vector<int> face_node_counts(num_face_this_blk);
1351 if (!face_node_counts.empty())
1352 {
1353 ex_err = exII::ex_get_entity_count_per_polyhedra
1354 (ex_id,
1355 exII::EX_FACE_BLOCK,
1356 face_block_ids[block],
1357 face_node_counts.data());
1358 EX_CHECK_ERR(ex_err, "Error reading polyhedron face node counts");
1359 }
1360
1361 int counted_nodes = 0;
1362 for (const auto count : face_node_counts)
1363 counted_nodes += count;
1364
1365 libmesh_error_msg_if(counted_nodes != num_node_data_this_blk,
1366 "Error: Exodus NSIDED face block "
1367 << face_block_ids[block]
1368 << " says it has " << num_node_data_this_blk
1369 << " total node entries, but its per-face "
1370 << "node counts sum to " << counted_nodes << ".");
1371
1372 std::vector<int> face_connect(num_node_data_this_blk);
1373 if (!face_connect.empty())
1374 {
1375 ex_err = exII::ex_get_conn(ex_id,
1376 exII::EX_FACE_BLOCK,
1377 face_block_ids[block],
1378 face_connect.data(),
1379 nullptr,
1380 nullptr);
1381 EX_CHECK_ERR(ex_err, "Error reading polyhedron face connectivity.");
1382 }
1383
1384 std::size_t offset = 0;
1385 for (const auto count : face_node_counts)
1386 {
1387 libmesh_error_msg_if(count < 3,
1388 "Error: Exodus NSIDED face block "
1389 << face_block_ids[block]
1390 << " has a face with only "
1391 << count << " nodes.");
1392
1393 c0polyhedron_face_connect.emplace_back
1394 (face_connect.begin() + offset,
1395 face_connect.begin() + offset + count);
1396 offset += count;
1397 }
1398 }
1399
1400 libmesh_error_msg_if(c0polyhedron_face_connect.size() !=
1401 cast_int<std::size_t>(num_face),
1402 "Error: Exodus file says it has "
1403 << num_face << " faces, but its face blocks contain "
1404 << c0polyhedron_face_connect.size() << " faces.");
1405}
1406
1407
1408
1409
1411{
1412 LOG_SCOPE("read_elem_in_block()", "ExodusII_IO_Helper");
1413
1414 libmesh_assert_less (block, block_ids.size());
1415 elem_node_counts.clear();
1416 elem_face_counts.clear();
1417
1418 // Unlike the other "extended" APIs, this one does not use a parameter struct.
1419 int num_edges_per_elem = 0;
1420 int num_faces_per_elem = 0;
1421 int num_node_data_per_elem = 0;
1422 ex_err = exII::ex_get_block(ex_id,
1423 exII::EX_ELEM_BLOCK,
1424 block_ids[block],
1425 elem_type.data(),
1427 &num_node_data_per_elem,
1428 &num_edges_per_elem, // 0 or -1 if no "extended" block info
1429 &num_faces_per_elem, // 0 or -1 if no "extended" block info
1430 &num_attr);
1431
1432 EX_CHECK_ERR(ex_err, "Error getting block info.");
1433 message("Info retrieved successfully for block: ", block);
1434
1435 // Nemesis uses "Empty" as the element type for blocks with no
1436 // elements on the current processor. There is no element conversion
1437 // for that sentinel type, nor is one needed for an empty block.
1438 const bool is_bezier = is_bezier_elem(elem_type.data());
1439 const Conversion * conversion = nullptr;
1440 if (num_elem_this_blk || is_bezier)
1441 conversion = &get_conversion(std::string(elem_type.data()));
1442
1443 const bool is_c0polygon =
1444 conversion && conversion->libmesh_elem_type() == C0POLYGON;
1445 const bool is_c0polyhedron =
1446 conversion && conversion->libmesh_elem_type() == C0POLYHEDRON;
1447
1448 // Warn or error when we don't currently support reading blocks with extended info.
1449 // Note: the docs say -1 will be returned for this but I found that it was
1450 // actually 0, so not sure which it will be in general.
1451 if (is_c0polyhedron && !(num_edges_per_elem == 0) && !(num_edges_per_elem == -1))
1452 libmesh_error_msg("Error: Exodus NFACED element blocks with edge "
1453 "connectivity are not currently supported.");
1454 else if (!(num_edges_per_elem == 0) && !(num_edges_per_elem == -1))
1455 libmesh_warning("Exodus files with extended edge connectivity not currently supported.");
1456 if (!is_c0polyhedron && !(num_faces_per_elem == 0) && !(num_faces_per_elem == -1))
1457 libmesh_warning("Exodus files with extended face connectivity not currently supported.");
1458
1459 // If we have a Bezier element here, then we've packed constraint
1460 // vector connectivity at the end of the nodal connectivity, and
1461 // num_nodes_per_elem reflected both.
1462 if (is_bezier)
1463 {
1464 libmesh_assert(conversion);
1465 num_nodes_per_elem = conversion->n_nodes;
1466 }
1467 else if (is_c0polygon)
1468 {
1470
1471 if (!elem_node_counts.empty())
1472 {
1473 ex_err = exII::ex_get_entity_count_per_polyhedra
1474 (ex_id,
1475 exII::EX_ELEM_BLOCK,
1476 block_ids[block],
1477 elem_node_counts.data());
1478 EX_CHECK_ERR(ex_err, "Error reading polygon node counts");
1479 }
1480
1481 int counted_nodes = 0;
1482 for (const auto count : elem_node_counts)
1483 counted_nodes += count;
1484
1485 libmesh_error_msg_if(counted_nodes != num_node_data_per_elem,
1486 "Error: Exodus NSIDED block "
1487 << block_ids[block]
1488 << " says it has " << num_node_data_per_elem
1489 << " total node entries, but its per-element "
1490 << "node counts sum to " << counted_nodes << ".");
1491
1493 }
1494 else if (is_c0polyhedron)
1495 {
1496 if (c0polyhedron_face_connect.empty())
1497 this->read_face_blocks();
1498
1500
1501 if (!elem_face_counts.empty())
1502 {
1503 ex_err = exII::ex_get_entity_count_per_polyhedra
1504 (ex_id,
1505 exII::EX_ELEM_BLOCK,
1506 block_ids[block],
1507 elem_face_counts.data());
1508 EX_CHECK_ERR(ex_err, "Error reading polyhedron face counts");
1509 }
1510
1511 int counted_faces = 0;
1512 for (const auto count : elem_face_counts)
1513 counted_faces += count;
1514
1515 libmesh_error_msg_if(counted_faces != num_faces_per_elem,
1516 "Error: Exodus NFACED block "
1517 << block_ids[block]
1518 << " says it has " << num_faces_per_elem
1519 << " total face entries, but its per-element "
1520 << "face counts sum to " << counted_faces << ".");
1521
1523 }
1524 else
1525 num_nodes_per_elem = num_node_data_per_elem;
1526
1527 if (verbose)
1528 {
1529 libMesh::out << "Read a block of " << num_elem_this_blk
1530 << " " << elem_type.data() << "(s)";
1531 if (is_c0polygon)
1532 libMesh::out << " having " << num_node_data_per_elem
1533 << " total node entries.";
1534 else if (is_c0polyhedron)
1535 libMesh::out << " having " << num_faces_per_elem
1536 << " total face entries.";
1537 else
1538 libMesh::out << " having " << num_nodes_per_elem
1539 << " nodes per element.";
1540 libMesh::out << std::endl;
1541 }
1542
1543 // Read in the connectivity of the elements of this block,
1544 // watching out for the case where we actually have no
1545 // elements in this block (possible with parallel files)
1546 connect.resize(is_c0polygon ?
1547 num_node_data_per_elem :
1548 is_c0polyhedron ?
1549 num_faces_per_elem :
1550 num_node_data_per_elem*num_elem_this_blk);
1551
1552 if (!connect.empty())
1553 {
1554 ex_err = exII::ex_get_conn(ex_id,
1555 exII::EX_ELEM_BLOCK,
1556 block_ids[block],
1557 is_c0polyhedron ? nullptr : connect.data(),
1558 nullptr, // elem_edge_conn (unused)
1559 is_c0polyhedron ? connect.data() : nullptr);
1560
1561 EX_CHECK_ERR(ex_err, "Error reading block connectivity.");
1562 message("Connectivity retrieved successfully for block: ", block);
1563 }
1564
1565 // If we had any attributes for this block, check to see if some of
1566 // them were Bezier-extension attributes.
1567
1568 // num_attr above is zero, not actually the number of block attributes?
1569 // ex_get_attr_param *also* gives me zero? Really, Exodus?
1570#if EX_API_VERS_NODOT >= 800
1571 int real_n_attr = exII::ex_get_attribute_count(ex_id, exII::EX_ELEM_BLOCK, block_ids[block]);
1572 EX_CHECK_ERR(real_n_attr, "Error getting number of element block attributes.");
1573
1574 if (real_n_attr > 0)
1575 {
1576 std::vector<exII::ex_attribute> attributes(real_n_attr);
1577
1578 ex_err = exII::ex_get_attribute_param(ex_id, exII::EX_ELEM_BLOCK, block_ids[block], attributes.data());
1579 EX_CHECK_ERR(ex_err, "Error getting element block attribute parameters.");
1580
1581 ex_err = exII::ex_get_attributes(ex_id, real_n_attr, attributes.data());
1582 EX_CHECK_ERR(ex_err, "Error getting element block attribute values.");
1583
1584 for (auto attr : attributes)
1585 {
1586 if (std::string("bex_elem_degrees") == attr.name)
1587 {
1588 if (attr.type != exII::EX_INTEGER)
1589 libmesh_error_msg("Found non-integer bex_elem_degrees");
1590
1591 if (attr.value_count > 3)
1592 libmesh_error_msg("Looking for at most 3 bex_elem_degrees; found " << attr.value_count);
1593
1594 libmesh_assert(is_bezier);
1595
1596 std::vector<int> bex_elem_degrees(3); // max dim
1597
1598 const int * as_int = static_cast<int *>(attr.values);
1599 std::copy(as_int, as_int+attr.value_count, bex_elem_degrees.begin());
1600
1601
1602 // Right now Bezier extraction elements aren't possible
1603 // for p>2 and aren't useful for p<2, and we don't
1604 // support anisotropic p...
1605#ifndef NDEBUG
1606 libmesh_assert(conversion);
1607 for (auto d : IntRange<int>(0, conversion->dim))
1608 libmesh_assert_equal_to(bex_elem_degrees[d], 2);
1609#endif
1610 }
1611 // ex_get_attributes did a values=calloc(); free() is our job.
1612 if (attr.values)
1613 free(attr.values);
1614 }
1615 }
1616
1617 if (is_bezier)
1618 {
1619 // We'd better have the number of cvs we expect
1620 if( num_node_data_per_elem > num_nodes_per_elem )
1621 bex_num_elem_cvs = num_node_data_per_elem / 2;
1622 else
1624 libmesh_assert_greater_equal(bex_num_elem_cvs, 0);
1625
1626 // The old connect vector is currently a mix of the expected
1627 // connectivity and any Bezier extraction connectivity;
1628 // disentangle that, if necessary.
1630 if (num_node_data_per_elem > num_nodes_per_elem)
1631 {
1632 std::vector<int> old_connect(bex_num_elem_cvs * num_elem_this_blk);
1633 old_connect.swap(connect);
1634 auto src = old_connect.data();
1635 auto dst = connect.data();
1636 for (auto e : IntRange<std::size_t>(0, num_elem_this_blk))
1637 {
1638 std::copy(src, src + bex_num_elem_cvs, dst);
1639 src += bex_num_elem_cvs;
1640 dst += bex_num_elem_cvs;
1641
1642 bex_cv_conn[e].resize(bex_num_elem_cvs);
1643 std::copy(src, src + bex_num_elem_cvs,
1644 bex_cv_conn[e].begin());
1645 src += bex_num_elem_cvs;
1646 }
1647 }
1648 }
1649
1650#endif // EX_API_VERS_NODOT >= 800
1651}
1652
1653
1654
1656{
1657 LOG_SCOPE("read_edge_blocks()", "ExodusII_IO_Helper");
1658
1659 // Check for quick return if there are no edge blocks.
1660 if (num_edge_blk == 0)
1661 return;
1662
1663 // Build data structure that we can quickly search for edges
1664 // and then add required BoundaryInfo information. This is a
1665 // map from edge->key() to a list of (elem_id, edge_id) pairs
1666 // for the Edge in question. Since edge->key() is edge orientation
1667 // invariant, this map does not distinguish different orientations
1668 // of the same Edge. Since edge->key() is also not guaranteed to be
1669 // unique (though it is very unlikely for two distinct edges to have
1670 // the same key()), when we later look up an (elem_id, edge_id) pair
1671 // in the edge_map, we need to verify that the edge indeed matches
1672 // the searched edge by doing some further checks.
1673 typedef std::pair<dof_id_type, unsigned int> ElemEdgePair;
1674 std::unordered_map<dof_id_type, std::vector<ElemEdgePair>> edge_map;
1675 std::unique_ptr<Elem> edge_ptr;
1676 for (const auto & elem : mesh.element_ptr_range())
1677 for (auto e : elem->edge_index_range())
1678 {
1679 elem->build_edge_ptr(edge_ptr, e);
1680 dof_id_type edge_key = edge_ptr->key();
1681
1682 // Creates vector if not already there
1683 auto & vec = edge_map[edge_key];
1684 vec.emplace_back(elem->id(), e);
1685
1686 // If edge_ptr is a higher-order Elem (EDGE3 or higher) then also add
1687 // a map entry for the lower-order (EDGE2) element which has matching
1688 // vertices. This allows us to match lower-order edge blocks to edges
1689 // of higher-order 3D elems (e.g. HEX20, TET10) and simplifies the
1690 // definition of edge blocks.
1691 if (edge_ptr->default_order() != FIRST)
1692 {
1693 // Construct a temporary low-order edge so that we can compute its key()
1694 auto low_order_edge =
1696
1697 // Assign node pointers to low-order edge
1698 for (unsigned int v=0; v<edge_ptr->n_vertices(); ++v)
1699 low_order_edge->set_node(v, edge_ptr->node_ptr(v));
1700
1701 // Compute the key for the temporary low-order edge we just built
1702 dof_id_type low_order_edge_key = low_order_edge->key();
1703
1704 // Add this key to the map associated with the same (elem,
1705 // edge) pair as the higher-order edge
1706 auto & low_order_vec = edge_map[low_order_edge_key];
1707 low_order_vec.emplace_back(elem->id(), e);
1708 }
1709 }
1710
1711 // Get reference to the mesh's BoundaryInfo object, as we will be
1712 // adding edges to this below.
1714
1715 for (const auto & edge_block_id : edge_block_ids)
1716 {
1717 // exII::ex_get_block() output parameters. Unlike the other
1718 // "extended" APIs, exII::ex_get_block() does not use a
1719 // parameter struct.
1720 int num_edge_this_blk = 0;
1721 int num_nodes_per_edge = 0;
1722 int num_edges_per_edge = 0;
1723 int num_faces_per_edge = 0;
1724 int num_attr_per_edge = 0;
1725 ex_err = exII::ex_get_block(ex_id,
1726 exII::EX_EDGE_BLOCK,
1727 edge_block_id,
1728 elem_type.data(),
1729 &num_edge_this_blk,
1730 &num_nodes_per_edge,
1731 &num_edges_per_edge, // 0 or -1 for edge blocks
1732 &num_faces_per_edge, // 0 or -1 for edge blocks
1733 &num_attr_per_edge);
1734
1735 EX_CHECK_ERR(ex_err, "Error getting edge block info.");
1736 message("Info retrieved successfully for block: ", edge_block_id);
1737
1738 // Read in the connectivity of the edges of this block,
1739 // watching out for the case where we actually have no
1740 // elements in this block (possible with parallel files)
1741 connect.resize(num_nodes_per_edge * num_edge_this_blk);
1742
1743 if (!connect.empty())
1744 {
1745 ex_err = exII::ex_get_conn(ex_id,
1746 exII::EX_EDGE_BLOCK,
1747 edge_block_id,
1748 connect.data(), // node_conn
1749 nullptr, // elem_edge_conn (unused)
1750 nullptr); // elem_face_conn (unused)
1751
1752 EX_CHECK_ERR(ex_err, "Error reading block connectivity.");
1753 message("Connectivity retrieved successfully for block: ", edge_block_id);
1754
1755 // All edge types have an identity mapping from the corresponding
1756 // Exodus type, so we don't need to bother with mapping ids, but
1757 // we do need to know what kind of elements to build.
1758 const auto & conv = get_conversion(std::string(elem_type.data()));
1759
1760 // Loop over indices in connectivity array, build edge elements,
1761 // look them up in the edge_map.
1762 for (auto [i, sz] = std::make_tuple(0u, connect.size()); i<sz; i+=num_nodes_per_edge)
1763 {
1764 auto edge = Elem::build(conv.libmesh_elem_type());
1765 for (int n=0; n<num_nodes_per_edge; ++n)
1766 {
1767 auto exodus_node_id = this->connect[i+n];
1768 dof_id_type libmesh_node_id = this->get_libmesh_node_id(exodus_node_id);
1769 edge->set_node(n, mesh.node_ptr(libmesh_node_id));
1770 }
1771
1772 // Compute key for the edge Elem we just built.
1773 dof_id_type edge_key = edge->key();
1774
1775 // If this key is not found in the edge_map, which is
1776 // supposed to include every edge in the Mesh, then we
1777 // will throw an error now.
1778 auto & elem_edge_pair_vec =
1779 libmesh_map_find(edge_map, edge_key);
1780
1781 for (const auto & elem_edge_pair : elem_edge_pair_vec)
1782 {
1783 // We only want to match edges which have the same
1784 // nodes (possibly with different orientation) to the one in the
1785 // Exodus file, otherwise we ignore this elem_edge_pair.
1786 //
1787 // Note: this also handles the situation where two
1788 // edges have the same key (hash collision) as then
1789 // this check avoids a false positive.
1790
1791 // Build edge indicated by elem_edge_pair
1792 mesh.elem_ptr(elem_edge_pair.first)->
1793 build_edge_ptr(edge_ptr, elem_edge_pair.second);
1794
1795 // Determine whether this candidate edge is a "real" match,
1796 // i.e. has the same nodes with a possibly different
1797 // orientation. Note that here we only check that
1798 // the vertices match regardless of how many nodes
1799 // the edge has, which allows us to match a
1800 // lower-order edge to a higher-order Elem.
1801 bool is_match =
1802 ((edge_ptr->node_id(0) == edge->node_id(0)) && (edge_ptr->node_id(1) == edge->node_id(1))) ||
1803 ((edge_ptr->node_id(0) == edge->node_id(1)) && (edge_ptr->node_id(1) == edge->node_id(0)));
1804
1805 if (is_match)
1806 {
1807 // Add this (elem, edge, id) combo to the BoundaryInfo object.
1808 bi.add_edge(elem_edge_pair.first,
1809 elem_edge_pair.second,
1810 edge_block_id);
1811 }
1812 } // end loop over elem_edge_pairs
1813 } // end loop over connectivity array
1814
1815 // Set edgeset name in the BoundaryInfo object.
1816 bi.edgeset_name(edge_block_id) = id_to_edge_block_names[edge_block_id];
1817 } // end if !connect.empty()
1818 } // end for edge_block_id : edge_block_ids
1819}
1820
1821
1822
1824{
1825 elem_num_map.resize(num_elem);
1826
1827 // Note: we cannot use the exII::ex_get_num_map() here because it
1828 // (apparently) does not behave like ex_get_elem_num_map() when
1829 // there is no elem number map in the file: it throws an error
1830 // instead of returning a default identity array (1,2,3,...).
1831 ex_err = exII::ex_get_elem_num_map
1832 (ex_id, elem_num_map.empty() ? nullptr : elem_num_map.data());
1833
1834 EX_CHECK_ERR(ex_err, "Error retrieving element number map.");
1835 message("Element numbering map retrieved successfully.");
1836
1837 if (num_elem)
1838 {
1839 // The elem_num_map may contain ids larger than num_elem. In
1840 // other words, the elem_num_map is not necessarily just a
1841 // permutation of the "trivial" 1,2,3,... mapping, it can
1842 // contain effectively "any" numbers. Therefore, to get
1843 // "_end_elem_id", we need to check what the max entry in the
1844 // elem_num_map is.
1845 auto it = std::max_element(elem_num_map.begin(), elem_num_map.end());
1846 _end_elem_id = *it;
1847 }
1848 else
1849 _end_elem_id = 0;
1850
1851 if (verbose)
1852 {
1853 libMesh::out << "[" << this->processor_id() << "] elem_num_map[i] = ";
1854 for (unsigned int i=0; i<static_cast<unsigned int>(std::min(10, num_elem-1)); ++i)
1855 libMesh::out << elem_num_map[i] << ", ";
1856 libMesh::out << "... " << elem_num_map.back() << std::endl;
1857 }
1858}
1859
1860
1861
1863{
1864 ss_ids.resize(num_side_sets);
1865 if (num_side_sets > 0)
1866 {
1867 ex_err = exII::ex_get_ids(ex_id,
1868 exII::EX_SIDE_SET,
1869 ss_ids.data());
1870 EX_CHECK_ERR(ex_err, "Error retrieving sideset information.");
1871 message("All sideset information retrieved successfully.");
1872
1873 // Resize appropriate data structures -- only do this once outside the loop
1876
1877 // Inquire about the length of the concatenated side sets element list
1878 num_elem_all_sidesets = inquire(*this, exII::EX_INQ_SS_ELEM_LEN, "Error retrieving length of the concatenated side sets element list!");
1879
1883 }
1884
1885 char name_buffer[libmesh_max_str_length+1];
1886 for (int i=0; i<num_side_sets; ++i)
1887 {
1888 ex_err = exII::ex_get_name(ex_id, exII::EX_SIDE_SET,
1889 ss_ids[i], name_buffer);
1890 EX_CHECK_ERR(ex_err, "Error getting side set name.");
1891 id_to_ss_names[ss_ids[i]] = name_buffer;
1892 }
1893 message("All side set names retrieved successfully.");
1894}
1895
1896
1898{
1899 nodeset_ids.resize(num_node_sets);
1900 if (num_node_sets > 0)
1901 {
1902 ex_err = exII::ex_get_ids(ex_id,
1903 exII::EX_NODE_SET,
1904 nodeset_ids.data());
1905 EX_CHECK_ERR(ex_err, "Error retrieving nodeset information.");
1906 message("All nodeset information retrieved successfully.");
1907
1908 // Resize appropriate data structures -- only do this once outside the loop
1911 }
1912
1913 char name_buffer[libmesh_max_str_length+1];
1914 for (int i=0; i<num_node_sets; ++i)
1915 {
1916 ex_err = exII::ex_get_name(ex_id, exII::EX_NODE_SET,
1917 nodeset_ids[i], name_buffer);
1918 EX_CHECK_ERR(ex_err, "Error getting node set name.");
1919 id_to_ns_names[nodeset_ids[i]] = name_buffer;
1920 }
1921 message("All node set names retrieved successfully.");
1922}
1923
1924
1925
1927{
1928 elemset_ids.resize(num_elem_sets);
1929 if (num_elem_sets > 0)
1930 {
1931 ex_err = exII::ex_get_ids(ex_id,
1932 exII::EX_ELEM_SET,
1933 elemset_ids.data());
1934 EX_CHECK_ERR(ex_err, "Error retrieving elemset information.");
1935 message("All elemset information retrieved successfully.");
1936
1937 // Resize appropriate data structures -- only do this once outside the loop
1940
1941 // Inquire about the length of the concatenated elemset list
1943 inquire(*this, exII::EX_INQ_ELS_LEN,
1944 "Error retrieving length of the concatenated elem sets element list!");
1945
1948
1949 // Debugging
1950 // libMesh::out << "num_elem_all_elemsets = " << num_elem_all_elemsets << std::endl;
1951 }
1952
1953 char name_buffer[libmesh_max_str_length+1];
1954 for (int i=0; i<num_elem_sets; ++i)
1955 {
1956 ex_err = exII::ex_get_name(ex_id, exII::EX_ELEM_SET,
1957 elemset_ids[i], name_buffer);
1958 EX_CHECK_ERR(ex_err, "Error getting node set name.");
1959 id_to_elemset_names[elemset_ids[i]] = name_buffer;
1960 }
1961 message("All elem set names retrieved successfully.");
1962}
1963
1964
1965
1966void ExodusII_IO_Helper::read_sideset(int id, int offset)
1967{
1968 LOG_SCOPE("read_sideset()", "ExodusII_IO_Helper");
1969
1970 libmesh_assert_less (id, ss_ids.size());
1971 libmesh_assert_less (id, num_sides_per_set.size());
1972 libmesh_assert_less (id, num_df_per_set.size());
1973 libmesh_assert_less_equal (offset, elem_list.size());
1974 libmesh_assert_less_equal (offset, side_list.size());
1975
1976 ex_err = exII::ex_get_set_param(ex_id,
1977 exII::EX_SIDE_SET,
1978 ss_ids[id],
1979 &num_sides_per_set[id],
1980 &num_df_per_set[id]);
1981 EX_CHECK_ERR(ex_err, "Error retrieving sideset parameters.");
1982 message("Parameters retrieved successfully for sideset: ", id);
1983
1984
1985 // It's OK for offset==elem_list.size() as long as num_sides_per_set[id]==0
1986 // because in that case we don't actually read anything...
1987#ifdef DEBUG
1988 if (static_cast<unsigned int>(offset) == elem_list.size() ||
1989 static_cast<unsigned int>(offset) == side_list.size() )
1990 libmesh_assert_equal_to (num_sides_per_set[id], 0);
1991#endif
1992
1993
1994 // Don't call ex_get_set unless there are actually sides there to get.
1995 // Exodus prints an annoying warning in DEBUG mode otherwise...
1996 if (num_sides_per_set[id] > 0)
1997 {
1998 ex_err = exII::ex_get_set(ex_id,
1999 exII::EX_SIDE_SET,
2000 ss_ids[id],
2001 &elem_list[offset],
2002 &side_list[offset]);
2003 EX_CHECK_ERR(ex_err, "Error retrieving sideset data.");
2004 message("Data retrieved successfully for sideset: ", id);
2005
2006 for (int i=0; i<num_sides_per_set[id]; i++)
2007 id_list[i+offset] = ss_ids[id];
2008 }
2009}
2010
2011
2012
2013void ExodusII_IO_Helper::read_elemset(int id, int offset)
2014{
2015 LOG_SCOPE("read_elemset()", "ExodusII_IO_Helper");
2016
2017 libmesh_assert_less (id, elemset_ids.size());
2018 libmesh_assert_less (id, num_elems_per_set.size());
2019 libmesh_assert_less (id, num_elem_df_per_set.size());
2020 libmesh_assert_less_equal (offset, elemset_list.size());
2021
2022 ex_err = exII::ex_get_set_param(ex_id,
2023 exII::EX_ELEM_SET,
2024 elemset_ids[id],
2025 &num_elems_per_set[id],
2026 &num_elem_df_per_set[id]);
2027 EX_CHECK_ERR(ex_err, "Error retrieving elemset parameters.");
2028 message("Parameters retrieved successfully for elemset: ", id);
2029
2030
2031 // It's OK for offset==elemset_list.size() as long as num_elems_per_set[id]==0
2032 // because in that case we don't actually read anything...
2033 #ifdef DEBUG
2034 if (static_cast<unsigned int>(offset) == elemset_list.size())
2035 libmesh_assert_equal_to (num_elems_per_set[id], 0);
2036 #endif
2037
2038 // Don't call ex_get_set() unless there are actually elems there to get.
2039 // Exodus prints an annoying warning in DEBUG mode otherwise...
2040 if (num_elems_per_set[id] > 0)
2041 {
2042 ex_err = exII::ex_get_set(ex_id,
2043 exII::EX_ELEM_SET,
2044 elemset_ids[id],
2045 &elemset_list[offset],
2046 /*set_extra_list=*/nullptr);
2047 EX_CHECK_ERR(ex_err, "Error retrieving elemset data.");
2048 message("Data retrieved successfully for elemset: ", id);
2049
2050 // Create vector containing elemset ids for each element in the set
2051 for (int i=0; i<num_elems_per_set[id]; i++)
2052 elemset_id_list[i+offset] = elemset_ids[id];
2053 }
2054}
2055
2056
2057
2059{
2060 LOG_SCOPE("read_all_nodesets()", "ExodusII_IO_Helper");
2061
2062 // Figure out how many nodesets there are in the file so we can
2063 // properly resize storage as necessary.
2065 inquire
2066 (*this, exII::EX_INQ_NODE_SETS,
2067 "Error retrieving number of node sets");
2068
2069 // Figure out how many nodes there are in all the nodesets.
2070 int total_nodes_in_all_sets =
2071 inquire
2072 (*this, exII::EX_INQ_NS_NODE_LEN,
2073 "Error retrieving number of nodes in all node sets.");
2074
2075 // Figure out how many distribution factors there are in all the nodesets.
2076 int total_df_in_all_sets =
2077 inquire
2078 (*this, exII::EX_INQ_NS_DF_LEN,
2079 "Error retrieving number of distribution factors in all node sets.");
2080
2081 // If there are no nodesets, there's nothing to read in.
2082 if (num_node_sets == 0)
2083 return;
2084
2085 // Allocate space to read all the nodeset data.
2086 // Use existing class members where possible to avoid shadowing
2087 nodeset_ids.clear(); nodeset_ids.resize(num_node_sets);
2092 node_sets_node_list.clear(); node_sets_node_list.resize(total_nodes_in_all_sets);
2093 node_sets_dist_fact.clear(); node_sets_dist_fact.resize(total_df_in_all_sets);
2094
2095 // Handle single-precision files
2096 MappedInputVector mapped_node_sets_dist_fact(node_sets_dist_fact, _single_precision);
2097
2098 // Build exII::ex_set_spec struct
2099 exII::ex_set_specs set_specs = {};
2100 set_specs.sets_ids = nodeset_ids.data();
2101 set_specs.num_entries_per_set = num_nodes_per_set.data();
2102 set_specs.num_dist_per_set = num_node_df_per_set.data();
2103 set_specs.sets_entry_index = node_sets_node_index.data();
2104 set_specs.sets_dist_index = node_sets_dist_index.data();
2105 set_specs.sets_entry_list = node_sets_node_list.data();
2106 set_specs.sets_extra_list = nullptr;
2107 set_specs.sets_dist_fact = total_df_in_all_sets ? mapped_node_sets_dist_fact.data() : nullptr;
2108
2109 ex_err = exII::ex_get_concat_sets(ex_id, exII::EX_NODE_SET, &set_specs);
2110 EX_CHECK_ERR(ex_err, "Error reading concatenated nodesets");
2111
2112 // Read the nodeset names from file!
2113 char name_buffer[libmesh_max_str_length+1];
2114 for (int i=0; i<num_node_sets; ++i)
2115 {
2116 ex_err = exII::ex_get_name
2117 (ex_id,
2118 exII::EX_NODE_SET,
2119 nodeset_ids[i],
2120 name_buffer);
2121 EX_CHECK_ERR(ex_err, "Error getting node set name.");
2122 id_to_ns_names[nodeset_ids[i]] = name_buffer;
2123 }
2124}
2125
2126
2127
2129{
2130 // Call ex_close on every processor that did ex_open or ex_create;
2131 // newer Exodus versions error if we try to reopen a file that
2132 // hasn't been officially closed. Don't close the file if we didn't
2133 // open it; this also raises an Exodus error.
2134
2135 // We currently do read-only ex_open on every proc (to do read
2136 // operations on every proc), but we do ex_open and ex_create for
2137 // writes on every proc only with Nemesis files.
2139 (this->processor_id() == 0) ||
2141 {
2143 {
2144 ex_err = exII::ex_close(ex_id);
2145 // close() is called from the destructor, so it may be called e.g.
2146 // during stack unwinding while processing an exception. In that case
2147 // we don't want to throw another exception or immediately terminate
2148 // the code, since that would prevent any possible recovery from the
2149 // exception in question. So we just log the error closing the file
2150 // and continue.
2151 if (ex_err < 0)
2152 message("Error closing Exodus file.");
2153 else
2154 message("Exodus file closed successfully.");
2155 }
2156 }
2157
2158 // Now that the file is closed, it's no longer opened for
2159 // reading or writing.
2160 opened_for_writing = false;
2161 opened_for_reading = false;
2162 _opened_by_create = false;
2163}
2164
2165
2166
2168{
2169 // Make sure we have an up-to-date count of the number of time steps in the file.
2170 this->read_num_time_steps();
2171
2172 if (num_time_steps > 0)
2173 {
2174 time_steps.resize(num_time_steps);
2175 ex_err = exII::ex_get_all_times
2176 (ex_id,
2178 EX_CHECK_ERR(ex_err, "Error reading timesteps!");
2179 }
2180}
2181
2182
2183
2185{
2187 inquire(*this, exII::EX_INQ_TIME, "Error retrieving number of time steps");
2188}
2189
2190
2191
2192void ExodusII_IO_Helper::read_nodal_var_values(std::string nodal_var_name, int time_step)
2193{
2194 LOG_SCOPE("read_nodal_var_values()", "ExodusII_IO_Helper");
2195
2196 // Read the nodal variable names from file, so we can see if we have the one we're looking for
2197 this->read_var_names(NODAL);
2198
2199 // See if we can find the variable we are looking for
2200 unsigned int var_index = 0;
2201 bool found = false;
2202
2203 // Do a linear search for nodal_var_name in nodal_var_names
2204 for (; var_index<nodal_var_names.size(); ++var_index)
2205 {
2206 found = (nodal_var_names[var_index] == nodal_var_name);
2207 if (found)
2208 break;
2209 }
2210
2211 if (!found)
2212 {
2213 libMesh::err << "Available variables: " << std::endl;
2214 for (const auto & var_name : nodal_var_names)
2215 libMesh::err << var_name << std::endl;
2216
2217 libmesh_error_msg("Unable to locate variable named: " << nodal_var_name);
2218 }
2219
2220 // Clear out any previously read nodal variable values
2221 this->nodal_var_values.clear();
2222
2223 std::vector<Real> unmapped_nodal_var_values(num_nodes);
2224
2225 // Call the Exodus API to read the nodal variable values
2226 ex_err = exII::ex_get_var
2227 (ex_id,
2228 time_step,
2229 exII::EX_NODAL,
2230 var_index+1,
2231 1, // exII::ex_entity_id, not sure exactly what this is but in the ex_get_nodal_var.c shim, they pass 1
2232 num_nodes,
2233 MappedInputVector(unmapped_nodal_var_values, _single_precision).data());
2234 EX_CHECK_ERR(ex_err, "Error reading nodal variable values!");
2235
2236 for (auto i : make_range(num_nodes))
2237 {
2238 // Determine the libmesh node id implied by "i". The
2239 // get_libmesh_node_id() helper function expects a 1-based
2240 // Exodus node id, so we construct the "implied" Exodus node id
2241 // from "i" by adding 1.
2242 //
2243 // If the user has set the "set_unique_ids_from_maps" flag to
2244 // true, then calling get_libmesh_node_id(i+1) will just return
2245 // i, otherwise it will determine the value (with error
2246 // checking) using this->node_num_map.
2247 auto libmesh_node_id = this->get_libmesh_node_id(/*exodus_node_id=*/i+1);
2248
2249 // Store the nodal value in the map.
2250 this->nodal_var_values[libmesh_node_id] = unmapped_nodal_var_values[i];
2251 }
2252}
2253
2254
2255
2257{
2258 switch (type)
2259 {
2260 case NODAL:
2262 break;
2263 case ELEMENTAL:
2265 break;
2266 case GLOBAL:
2268 break;
2269 case SIDESET:
2271 break;
2272 case NODESET:
2274 break;
2275 case ELEMSET:
2277 break;
2278 default:
2279 libmesh_error_msg("Unrecognized ExodusVarType " << type);
2280 }
2281}
2282
2283
2284
2286 int & count,
2287 std::vector<std::string> & result)
2288{
2289 // First read and store the number of names we have
2290 ex_err = exII::ex_get_var_param(ex_id, var_type, &count);
2291 EX_CHECK_ERR(ex_err, "Error reading number of variables.");
2292
2293 // Do nothing if no variables are detected
2294 if (count == 0)
2295 return;
2296
2297 // Second read the actual names and convert them into a format we can use
2298 NamesData names_table(count, libmesh_max_str_length);
2299
2300 ex_err = exII::ex_get_var_names(ex_id,
2301 var_type,
2302 count,
2303 names_table.get_char_star_star()
2304 );
2305 EX_CHECK_ERR(ex_err, "Error reading variable names!");
2306
2307 if (verbose)
2308 {
2309 libMesh::out << "Read the variable(s) from the file:" << std::endl;
2310 for (int i=0; i<count; i++)
2311 libMesh::out << names_table.get_char_star(i) << std::endl;
2312 }
2313
2314 // Allocate enough space for our variable name strings.
2315 result.resize(count);
2316
2317 // Copy the char buffers into strings.
2318 for (int i=0; i<count; i++)
2319 result[i] = names_table.get_char_star(i); // calls string::op=(const char *)
2320}
2321
2322
2323
2324
2325void
2327 const std::vector<std::string> & names)
2328{
2329 switch (type)
2330 {
2331 case NODAL:
2332 this->write_var_names_impl("n", num_nodal_vars, names);
2333 break;
2334 case ELEMENTAL:
2335 this->write_var_names_impl("e", num_elem_vars, names);
2336 break;
2337 case GLOBAL:
2338 this->write_var_names_impl("g", num_global_vars, names);
2339 break;
2340 case SIDESET:
2341 {
2342 // Note: calling this function *sets* num_sideset_vars to the
2343 // number of entries in the 'names' vector, num_sideset_vars
2344 // does not already need to be set before calling this.
2345 this->write_var_names_impl("s", num_sideset_vars, names);
2346 break;
2347 }
2348 case NODESET:
2349 {
2350 this->write_var_names_impl("m", num_nodeset_vars, names);
2351 break;
2352 }
2353 case ELEMSET:
2354 {
2355 this->write_var_names_impl("t", num_elemset_vars, names);
2356 break;
2357 }
2358 default:
2359 libmesh_error_msg("Unrecognized ExodusVarType " << type);
2360 }
2361}
2362
2363
2364
2365void
2367 int & count,
2368 const std::vector<std::string> & names)
2369{
2370 // Update the count variable so that it's available to other parts of the class.
2371 count = cast_int<int>(names.size());
2372
2373 // Write that number of variables to the file.
2374 ex_err = exII::ex_put_var_param(ex_id, var_type, count);
2375 EX_CHECK_ERR(ex_err, "Error setting number of vars.");
2376
2377 // Nemesis doesn't like trying to write nodal variable names in
2378 // files with no nodes.
2379 if (!this->num_nodes)
2380 return;
2381
2382 if (count > 0)
2383 {
2384 NamesData names_table(count, _max_name_length);
2385
2386 // Store the input names in the format required by Exodus.
2387 for (int i=0; i != count; ++i)
2388 {
2389 if(names[i].length() > _max_name_length)
2390 libmesh_warning(
2391 "*** Warning, Exodus variable name \"" <<
2392 names[i] << "\" too long (current max " <<
2393 _max_name_length << "/" << libmesh_max_str_length <<
2394 " characters). Name will be truncated. ");
2395 names_table.push_back_entry(names[i]);
2396 }
2397
2398 if (verbose)
2399 {
2400 libMesh::out << "Writing variable name(s) to file: " << std::endl;
2401 for (int i=0; i != count; ++i)
2402 libMesh::out << names_table.get_char_star(i) << std::endl;
2403 }
2404
2405 ex_err = exII::ex_put_var_names(ex_id,
2406 var_type,
2407 count,
2408 names_table.get_char_star_star()
2409 );
2410
2411 EX_CHECK_ERR(ex_err, "Error writing variable names.");
2412 }
2413}
2414
2415
2416
2417void ExodusII_IO_Helper::read_elemental_var_values(std::string elemental_var_name,
2418 int time_step,
2419 std::map<dof_id_type, Real> & elem_var_value_map)
2420{
2421 LOG_SCOPE("read_elemental_var_values()", "ExodusII_IO_Helper");
2422
2424
2425 // See if we can find the variable we are looking for
2426 unsigned int var_index = 0;
2427 bool found = false;
2428
2429 // Do a linear search for elem_var_name in elemental_var_names
2430 for (; var_index != elem_var_names.size(); ++var_index)
2431 if (elem_var_names[var_index] == elemental_var_name)
2432 {
2433 found = true;
2434 break;
2435 }
2436
2437 if (!found)
2438 {
2439 libMesh::err << "Available variables: " << std::endl;
2440 for (const auto & var_name : elem_var_names)
2441 libMesh::err << var_name << std::endl;
2442
2443 libmesh_error_msg("Unable to locate variable named: " << elemental_var_name);
2444 }
2445
2446 // Sequential index which we can use to look up the element ID in the elem_num_map.
2447 unsigned ex_el_num = 0;
2448
2449 // Element variable truth table
2450 std::vector<int> var_table(block_ids.size() * elem_var_names.size());
2451 exII::ex_get_truth_table(ex_id, exII::EX_ELEM_BLOCK, block_ids.size(), elem_var_names.size(), var_table.data());
2452
2453 for (unsigned i=0; i<static_cast<unsigned>(num_elem_blk); i++)
2454 {
2455 ex_err = exII::ex_get_block(ex_id,
2456 exII::EX_ELEM_BLOCK,
2457 block_ids[i],
2458 /*elem_type=*/nullptr,
2460 /*num_nodes_per_entry=*/nullptr,
2461 /*num_edges_per_entry=*/nullptr,
2462 /*num_faces_per_entry=*/nullptr,
2463 /*num_attr=*/nullptr);
2464 EX_CHECK_ERR(ex_err, "Error getting number of elements in block.");
2465
2466 // If the current variable isn't active on this subdomain, advance
2467 // the index by the number of elements on this block and go to the
2468 // next loop iteration.
2469 if (!var_table[elem_var_names.size()*i + var_index])
2470 {
2471 ex_el_num += num_elem_this_blk;
2472 continue;
2473 }
2474
2475 std::vector<Real> block_elem_var_values(num_elem_this_blk);
2476
2477 ex_err = exII::ex_get_var
2478 (ex_id,
2479 time_step,
2480 exII::EX_ELEM_BLOCK,
2481 var_index+1,
2482 block_ids[i],
2484 MappedInputVector(block_elem_var_values, _single_precision).data());
2485 EX_CHECK_ERR(ex_err, "Error getting elemental values.");
2486
2487 for (unsigned j=0; j<static_cast<unsigned>(num_elem_this_blk); j++)
2488 {
2489 // Determine the libmesh id of the element with zero-based
2490 // index "ex_el_num". This function expects a one-based
2491 // index, so we add 1 to ex_el_num when we pass it in.
2492 auto libmesh_elem_id =
2493 this->get_libmesh_elem_id(ex_el_num + 1);
2494
2495 // Store the elemental value in the map.
2496 elem_var_value_map[libmesh_elem_id] = block_elem_var_values[j];
2497
2498 // Go to the next sequential element ID.
2499 ex_el_num++;
2500 }
2501 }
2502}
2503
2504
2505
2507{
2508 return this->get_libmesh_id(exodus_node_id, this->node_num_map);
2509}
2510
2512{
2513 return this->get_libmesh_id(exodus_elem_id, this->elem_num_map);
2514}
2515
2518 const std::vector<int> & num_map)
2519{
2520 // The input exodus_id is assumed to be a (1-based) index into
2521 // the {node,elem}_num_map, so in order to use exodus_id as an index
2522 // in C++, we need to first make it zero-based.
2523 auto exodus_id_zero_based =
2524 cast_int<dof_id_type>(exodus_id - 1);
2525
2526 // Throw an informative error message rather than accessing past the
2527 // end of the provided num_map. If we are setting Elem unique_ids
2528 // based on the num_map, we don't need to do this check.
2529 if (!this->set_unique_ids_from_maps)
2530 libmesh_error_msg_if(exodus_id_zero_based >= num_map.size(),
2531 "Cannot get LibMesh id for Exodus id: " << exodus_id);
2532
2533 // If the user set the flag which stores Exodus node/elem ids as
2534 // unique_ids instead of regular ids, then the libmesh id we are
2535 // looking for is actually just "exodus_id_zero_based". Otherwise,
2536 // we need to look up the Node/Elem's id in the provided num_map,
2537 // *and* then subtract 1 from that because the entries in the
2538 // num_map are also 1-based.
2539 dof_id_type libmesh_id =
2541 cast_int<dof_id_type>(exodus_id_zero_based) :
2542 cast_int<dof_id_type>(num_map[exodus_id_zero_based] - 1);
2543
2544 return libmesh_id;
2545}
2546
2547
2548
2549void
2551conditionally_set_node_unique_id(MeshBase & mesh, Node * node, int zero_based_node_num_map_index)
2552{
2553 this->set_dof_object_unique_id(mesh, node, libmesh_vector_at(this->node_num_map, zero_based_node_num_map_index));
2554}
2555
2556void
2558conditionally_set_elem_unique_id(MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index)
2559{
2560 this->set_dof_object_unique_id(mesh, elem, libmesh_vector_at(this->elem_num_map, zero_based_elem_num_map_index));
2561}
2562
2563void
2565 MeshBase & mesh,
2566 DofObject * dof_object,
2567 int exodus_mapped_id)
2568{
2569 if (this->set_unique_ids_from_maps)
2570 {
2571 // Exodus ids are always 1-based while libmesh ids are always
2572 // 0-based, so to make a libmesh unique_id here, we subtract 1
2573 // from the exodus_mapped_id to make it 0-based.
2574 auto exodus_mapped_id_zero_based =
2575 cast_int<dof_id_type>(exodus_mapped_id - 1);
2576
2577 // Set added_node's unique_id to "exodus_mapped_id_zero_based".
2578 dof_object->set_unique_id(cast_int<unique_id_type>(exodus_mapped_id_zero_based));
2579
2580 // Normally the Mesh is responsible for setting the unique_ids
2581 // of Nodes/Elems in a consistent manner, so when we set the unique_id
2582 // of a Node/Elem manually based on the {node,elem}_num_map, we need to
2583 // make sure that the "next" unique id assigned by the Mesh
2584 // will still be valid. We do this by making sure that the
2585 // next_unique_id is greater than the one we set manually. The
2586 // APIs for doing this are only defined when unique ids are
2587 // enabled.
2588#ifdef LIBMESH_ENABLE_UNIQUE_ID
2589 unique_id_type next_unique_id = mesh.next_unique_id();
2590 mesh.set_next_unique_id(std::max(next_unique_id, static_cast<unique_id_type>(exodus_mapped_id_zero_based + 1)));
2591#else
2592 // Avoid compiler warnings about the unused variable
2594#endif
2595 }
2596}
2597
2598
2599// For Writing Solutions
2600
2601void ExodusII_IO_Helper::create(std::string filename)
2602{
2603 // If we're processor 0, always create the file.
2604 // If we running on all procs, e.g. as one of several Nemesis files, also
2605 // call create there.
2606 if ((this->processor_id() == 0) || (!_run_only_on_proc0))
2607 {
2608 int
2609 comp_ws = 0,
2610 io_ws = 0;
2611
2613 {
2614 comp_ws = cast_int<int>(sizeof(float));
2615 io_ws = cast_int<int>(sizeof(float));
2616 }
2617 // Fall back on double precision when necessary since ExodusII
2618 // doesn't seem to support long double
2619 else
2620 {
2621 comp_ws = cast_int<int>
2622 (std::min(sizeof(Real), sizeof(double)));
2623 io_ws = cast_int<int>
2624 (std::min(sizeof(Real), sizeof(double)));
2625 }
2626
2627 // By default we just open the Exodus file in "EX_CLOBBER" mode,
2628 // which, according to "ncdump -k", writes the file in "64-bit
2629 // offset" mode, which is a NETCDF3 file format.
2630 int mode = EX_CLOBBER;
2631
2632 // If HDF5 is available, by default we will write Exodus files
2633 // in a more modern NETCDF4-compatible format. For this file
2634 // type, "ncdump -k" will report "netCDF-4".
2635#ifdef LIBMESH_HAVE_HDF5
2636 if (this->_write_hdf5)
2637 {
2638 mode |= EX_NETCDF4;
2639 mode |= EX_NOCLASSIC;
2640 }
2641#endif
2642
2643 {
2644 FPEDisabler disable_fpes;
2645 ex_id = exII::ex_create(filename.c_str(), mode, &comp_ws, &io_ws);
2646 }
2647
2648 EX_CHECK_ERR(ex_id, "Error creating ExodusII/Nemesis mesh file.");
2649
2650 // We don't have access to the names we might be writing until we
2651 // write them, so we can't set a guaranteed max name length here.
2652 // But it looks like the most ExodusII can support is 80, so we'll
2653 // just waste 48 bytes here and there.
2654 ex_err = exII::ex_set_max_name_length(ex_id, _max_name_length);
2655 EX_CHECK_ERR(ex_err, "Error setting max ExodusII name length.");
2656
2657 if (verbose)
2658 libMesh::out << "File created successfully." << std::endl;
2659 }
2660
2661 opened_for_writing = true;
2662 _opened_by_create = true;
2663 current_filename = filename;
2664}
2665
2666
2667
2668void ExodusII_IO_Helper::initialize(std::string str_title, const MeshBase & mesh, bool use_discontinuous)
2669{
2670 // The majority of this function only executes on processor 0, so any functions
2671 // which are collective, like n_active_elem() or n_edge_conds() must be called
2672 // before the processors' execution paths diverge.
2673 libmesh_parallel_only(mesh.comm());
2674
2675 unsigned int n_active_elem = mesh.n_active_elem();
2676 const BoundaryInfo & bi = mesh.get_boundary_info();
2677 num_edge = bi.n_edge_conds();
2678
2679 num_elem = n_active_elem;
2680 num_nodes = 0;
2681 num_face = 0;
2682 num_face_blk = 0;
2683
2684 dof_id_type local_num_c0polyhedron_faces = 0;
2685 bool has_c0polyhedron = false;
2686 for (const auto & elem : mesh.active_local_element_ptr_range())
2687 if (elem->type() == C0POLYHEDRON)
2688 {
2689 has_c0polyhedron = true;
2690 local_num_c0polyhedron_faces += elem->n_sides();
2691 }
2692
2693 mesh.comm().sum(local_num_c0polyhedron_faces);
2694 mesh.comm().max(has_c0polyhedron);
2695 if (has_c0polyhedron)
2696 {
2697 num_face = cast_int<int>(local_num_c0polyhedron_faces);
2698 num_face_blk = 1;
2699 }
2700
2701 // If we're adding face elements they'll need copies of their nodes.
2702 // We also have to count of how many nodes (and gaps between nodes!)
2703 // are on each processor, to calculate offsets for any nodal data
2704 // writing later.
2706 if (_add_sides)
2707 {
2708 dof_id_type num_side_elem = 0;
2709 dof_id_type num_local_side_nodes = 0;
2710
2711 for (const auto & elem : mesh.active_local_element_ptr_range())
2712 {
2713 for (auto s : elem->side_index_range())
2714 {
2716 continue;
2717
2718 num_side_elem++;
2719 num_local_side_nodes += elem->nodes_on_side(s).size();
2720 }
2721 }
2722
2723 mesh.comm().sum(num_side_elem);
2724 num_elem += num_side_elem;
2725
2726 mesh.comm().allgather(num_local_side_nodes, _added_side_node_offsets);
2727 const processor_id_type n_proc = mesh.n_processors();
2728 libmesh_assert_equal_to(n_proc, _added_side_node_offsets.size());
2729
2730 for (auto p : make_range(n_proc-1))
2732
2734
2735 dof_id_type n_local_nodes = cast_int<dof_id_type>
2736 (std::distance(mesh.local_nodes_begin(),
2737 mesh.local_nodes_end()));
2738 dof_id_type n_total_nodes = n_local_nodes;
2739 mesh.comm().sum(n_total_nodes);
2740
2741 const dof_id_type max_nn = mesh.max_node_id();
2742 const dof_id_type n_gaps = max_nn - n_total_nodes;
2743 const dof_id_type gaps_per_processor = n_gaps / n_proc;
2744 const dof_id_type remainder_gaps = n_gaps % n_proc;
2745
2746 n_local_nodes = n_local_nodes + // Actual nodes
2747 gaps_per_processor + // Our even share of gaps
2748 (mesh.processor_id() < remainder_gaps); // Leftovers
2749
2750 mesh.comm().allgather(n_local_nodes, _true_node_offsets);
2751 for (auto p : make_range(n_proc-1))
2753 libmesh_assert_equal_to(_true_node_offsets[n_proc-1], mesh.max_node_id());
2754 }
2755
2756 // If _write_as_dimension is nonzero, use it to set num_dim in the Exodus file.
2761 else
2763
2764 if ((_run_only_on_proc0) && (this->processor_id() != 0))
2765 return;
2766
2767 // We need to know about all processors' subdomains
2768 subdomain_id_type subdomain_id_end = 0;
2769 auto subdomain_map = build_subdomain_map(mesh,
2770 _add_sides,
2771 /*reduce=*/!_run_only_on_proc0,
2772 subdomain_id_end);
2773
2774 if (!use_discontinuous)
2775 {
2776 // Don't rely on mesh.n_nodes() here. If ReplicatedMesh nodes
2777 // have been deleted without renumbering after, it will be
2778 // incorrect.
2779 num_nodes += cast_int<int>(std::distance(mesh.nodes_begin(),
2780 mesh.nodes_end()));
2781 }
2782 else
2783 {
2784 for (const auto & elem : mesh.active_element_ptr_range())
2785 num_nodes += elem->n_nodes();
2786 }
2787
2788 std::set<boundary_id_type> unique_side_boundaries;
2789 std::vector<boundary_id_type> unique_node_boundaries;
2790
2791 // Build set of unique sideset (+shellface) ids
2792 {
2793 // Start with "side" boundaries (i.e. of 3D elements)
2794 std::vector<boundary_id_type> side_boundaries;
2795 bi.build_side_boundary_ids(side_boundaries);
2796 unique_side_boundaries.insert(side_boundaries.begin(), side_boundaries.end());
2797
2798 // Add shell face boundaries to the list of side boundaries, since ExodusII
2799 // treats these the same way.
2800 std::vector<boundary_id_type> shellface_boundaries;
2801 bi.build_shellface_boundary_ids(shellface_boundaries);
2802 unique_side_boundaries.insert(shellface_boundaries.begin(), shellface_boundaries.end());
2803
2804 // Add any empty-but-named side boundary ids
2805 for (const auto & pr : bi.get_sideset_name_map())
2806 unique_side_boundaries.insert(pr.first);
2807 }
2808
2809 // Build set of unique nodeset ids
2810 bi.build_node_boundary_ids(unique_node_boundaries);
2811 for (const auto & pair : bi.get_nodeset_name_map())
2812 {
2813 const boundary_id_type id = pair.first;
2814
2815 if (std::find(unique_node_boundaries.begin(),
2816 unique_node_boundaries.end(), id)
2817 == unique_node_boundaries.end())
2818 unique_node_boundaries.push_back(id);
2819 }
2820
2821 num_side_sets = cast_int<int>(unique_side_boundaries.size());
2822 num_node_sets = cast_int<int>(unique_node_boundaries.size());
2823
2824 num_elem_blk = cast_int<int>(subdomain_map.size());
2825
2826 if (str_title.size() > MAX_LINE_LENGTH)
2827 {
2828 libMesh::err << "Warning, Exodus files cannot have titles longer than "
2829 << MAX_LINE_LENGTH
2830 << " characters. Your title will be truncated."
2831 << std::endl;
2832 str_title.resize(MAX_LINE_LENGTH);
2833 }
2834
2835 // Edge BCs are handled a bit differently than sidesets and nodesets.
2836 // They are written as separate "edge blocks", and then edge variables
2837 // can be defined on those blocks. That is, they are not written as
2838 // edge sets, since edge sets must refer to edges stored elsewhere.
2839 // We write a separate edge block for each unique boundary id that
2840 // we have.
2841 num_edge_blk = bi.get_edge_boundary_ids().size();
2842
2843 // Check whether the Mesh Elems have an extra_integer called "elemset_code".
2844 // If so, this means that the mesh defines elemsets via the
2845 // extra_integers capability of Elems.
2846 if (mesh.has_elem_integer("elemset_code"))
2847 {
2848 // unsigned int elemset_index =
2849 // mesh.get_elem_integer_index("elemset_code");
2850
2851 // Debugging
2852 // libMesh::out << "Mesh defines an elemset_code at index " << elemset_index << std::endl;
2853
2854 // Store the number of elemsets in the exo file header.
2856 }
2857
2858 // Build an ex_init_params() structure that is to be passed to the
2859 // newer ex_put_init_ext() API. The new API will eventually allow us
2860 // to store edge and face data in the Exodus file.
2861 //
2862 // Notes:
2863 // * We use C++11 zero initialization syntax to make sure that all
2864 // members of the struct (including ones we aren't using) are
2865 // given sensible values.
2866 // * For the "title" field, we manually do a null-terminated string
2867 // copy since std::string does not null-terminate but it does
2868 // return the number of characters successfully copied.
2869 exII::ex_init_params params = {};
2870 params.title[str_title.copy(params.title, MAX_LINE_LENGTH)] = '\0';
2871 params.num_dim = num_dim;
2872 params.num_nodes = num_nodes;
2873 params.num_elem = num_elem;
2874 params.num_elem_blk = num_elem_blk;
2875 params.num_node_sets = num_node_sets;
2876 params.num_side_sets = num_side_sets;
2877 params.num_elem_sets = num_elem_sets;
2878 params.num_edge_blk = num_edge_blk;
2879 params.num_edge = num_edge;
2880 params.num_face_blk = num_face_blk;
2881 params.num_face = num_face;
2882
2883 ex_err = exII::ex_put_init_ext(ex_id, &params);
2884 EX_CHECK_ERR(ex_err, "Error initializing new Exodus file.");
2885}
2886
2887
2888
2889void ExodusII_IO_Helper::write_nodal_coordinates(const MeshBase & mesh, bool use_discontinuous)
2890{
2891 if ((_run_only_on_proc0) && (this->processor_id() != 0))
2892 return;
2893
2894 // Clear existing data from any previous calls.
2895 x.clear();
2896 y.clear();
2897 z.clear();
2898 node_num_map.clear();
2899
2900 // Reserve space in the nodal coordinate vectors. num_nodes is
2901 // exact, this just allows us to do away with one potentially
2902 // error-inducing loop index.
2903 x.reserve(num_nodes);
2904 y.reserve(num_nodes);
2905 z.reserve(num_nodes);
2906
2907 auto push_node = [this](const Point & p) {
2908 x.push_back(p(0) + _coordinate_offset(0));
2909
2910#if LIBMESH_DIM > 1
2911 y.push_back(p(1) + _coordinate_offset(1));
2912#else
2913 y.push_back(0.);
2914#endif
2915#if LIBMESH_DIM > 2
2916 z.push_back(p(2) + _coordinate_offset(2));
2917#else
2918 z.push_back(0.);
2919#endif
2920 };
2921
2922 // And in the node_num_map. If the user has set the
2923 // _set_unique_ids_from_maps flag, then we will write the Node
2924 // unique_ids to the node_num_map, otherwise we will just write a
2925 // trivial node_num_map, since in that we don't write the unique_id
2926 // information to the Exodus file. In other words, set the
2927 // _set_unique_ids_from_maps flag to true on both the reading and
2928 // writing ExodusII_IO objects if you want to preserve the
2929 // node_num_map information without actually renumbering the Nodes
2930 // in libmesh according to the node_num_map.
2931 //
2932 // One reason why you might not want to actually renumber the Nodes
2933 // in libmesh according to the node_num_map is that it can introduce
2934 // undesirable large "gaps" in the numbering, e.g. Nodes numbered
2935 // [0, 1, 1000, 10001] which is not ideal for the ReplicatedMesh
2936 // _nodes data structure, which stores the Nodes in a contiguous
2937 // array based on Node id.
2938
2939 // Let's skip the node_num_map in the discontinuous and add_sides
2940 // cases, since we're effectively duplicating nodes for the sake of
2941 // discontinuous visualization, so it isn't clear how to deal with
2942 // node_num_map here. This means that writing meshes in such a way
2943 // won't work with element numberings that have id "holes".
2944
2945 if (!use_discontinuous && !_add_sides)
2946 node_num_map.reserve(num_nodes);
2947
2948 // Clear out any previously-mapped node IDs.
2950
2951 if (!use_discontinuous)
2952 {
2953 for (const auto & node_ptr : mesh.node_ptr_range())
2954 {
2955 const Node & node = *node_ptr;
2956
2957 push_node(node);
2958
2959 // Fill in node_num_map entry with the proper (1-based) node
2960 // id, unless we're not going to be able to keep the map up
2961 // later. If the user has chosen to _set_unique_ids_from_maps,
2962 // then we fill up the node_num_map with (1-based) unique
2963 // ids rather than node ids.
2964 if (!_add_sides)
2965 {
2966 if (this->set_unique_ids_from_maps)
2967 node_num_map.push_back(node.unique_id() + 1);
2968 else
2969 node_num_map.push_back(node.id() + 1);
2970 }
2971
2972 // Also map the zero-based libmesh node id to the (1-based)
2973 // index in the node_num_map it corresponds to
2974 // (this is equivalent to the current size of the "x" vector,
2975 // so we just use x.size()). This map is used to look up
2976 // an Exodus Node id given a libMesh Node id, so it does
2977 // involve unique_ids.
2978 libmesh_node_num_to_exodus[ cast_int<int>(node.id()) ] = cast_int<int>(x.size());
2979 } // end for (node_ptr)
2980 }
2981 else // use_discontinuous
2982 {
2983 for (const auto & elem : mesh.active_element_ptr_range())
2984 for (const Node & node : elem->node_ref_range())
2985 {
2986 push_node(node);
2987
2988 // Let's skip the node_num_map in the discontinuous
2989 // case, since we're effectively duplicating nodes for
2990 // the sake of discontinuous visualization, so it isn't
2991 // clear how to deal with node_num_map here. This means
2992 // that writing discontinuous meshes won't work with
2993 // element numberings that have "holes".
2994 }
2995 }
2996
2997 if (_add_sides)
2998 {
2999 // To match the numbering of parallel-generated nodal solutions
3000 // on fake side nodes, we need to loop through elements from
3001 // earlier ranks first.
3002 std::vector<std::vector<const Elem *>>
3003 elems_by_pid(mesh.n_processors());
3004
3005 for (const auto & elem : mesh.active_element_ptr_range())
3006 elems_by_pid[elem->processor_id()].push_back(elem);
3007
3008 for (auto p : index_range(elems_by_pid))
3009 for (const Elem * elem : elems_by_pid[p])
3010 for (auto s : elem->side_index_range())
3011 {
3013 continue;
3014
3015 const std::vector<unsigned int> side_nodes =
3016 elem->nodes_on_side(s);
3017
3018 for (auto n : side_nodes)
3019 push_node(elem->point(n));
3020 }
3021
3022 // Node num maps just don't make sense if we're adding a bunch
3023 // of visualization nodes that are independent copies of the
3024 // same libMesh node.
3025 node_num_map.clear();
3026 }
3027
3028 ex_err = exII::ex_put_coord
3029 (ex_id,
3030 x.empty() ? nullptr : MappedOutputVector(x, _single_precision).data(),
3031 y.empty() ? nullptr : MappedOutputVector(y, _single_precision).data(),
3032 z.empty() ? nullptr : MappedOutputVector(z, _single_precision).data());
3033
3034 EX_CHECK_ERR(ex_err, "Error writing coordinates to Exodus file.");
3035
3036 if (!use_discontinuous && !_add_sides)
3037 {
3038 // Also write the (1-based) node_num_map to the file.
3039 ex_err = exII::ex_put_node_num_map(ex_id, node_num_map.data());
3040 EX_CHECK_ERR(ex_err, "Error writing node_num_map");
3041 }
3042}
3043
3044
3045
3046void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_discontinuous)
3047{
3048 LOG_SCOPE("write_elements()", "ExodusII_IO_Helper");
3049
3050 if ((_run_only_on_proc0) && (this->processor_id() != 0))
3051 return;
3052
3053 // Map from block ID to a vector of element IDs in that block. Element
3054 // IDs are now of type dof_id_type, subdomain IDs are of type subdomain_id_type.
3055 subdomain_id_type subdomain_id_end = 0;
3056 auto subdomain_map = build_subdomain_map(mesh,
3057 _add_sides,
3058 /*reduce=*/!_run_only_on_proc0,
3059 subdomain_id_end);
3060
3061 // Allocate the polyhedron face block ID after both mesh subdomains and
3062 // any blocks synthesized for visualization sides.
3063 const int c0polyhedron_face_block_id = subdomain_map.empty() ? 0 :
3064 cast_int<int>(subdomain_map.rbegin()->first) + 1;
3065
3066 // element map vector
3067 num_elem_blk = cast_int<int>(subdomain_map.size());
3068 block_ids.resize(num_elem_blk);
3069
3070 std::vector<int> elem_blk_id;
3071 std::vector<int> num_elem_this_blk_vec;
3072 std::vector<int> num_nodes_per_elem_vec;
3073 std::vector<int> num_edges_per_elem_vec;
3074 std::vector<int> num_faces_per_elem_vec;
3075 std::vector<int> num_attr_vec;
3076 NamesData elem_type_table(num_elem_blk, _max_name_length);
3077
3078 // Note: It appears that there is a bug in exodusII::ex_put_name where
3079 // the index returned from the ex_id_lkup is erroneously used. For now
3080 // the work around is to use the alternative function ex_put_names, but
3081 // this function requires a char ** data structure.
3083
3084 num_elem = 0;
3085 bool has_c0polygon_blocks = false;
3086 bool has_c0polyhedron_blocks = false;
3087 int c0polyhedron_total_faces = 0;
3088 int c0polyhedron_total_face_nodes = 0;
3089
3090 // counter indexes into the block_ids vector
3091 unsigned int counter = 0;
3092 for (auto & [subdomain_id, element_id_vec] : subdomain_map)
3093 {
3094 block_ids[counter] = subdomain_id;
3095
3096 const ElemType elem_t = (subdomain_id >= subdomain_id_end) ?
3097 ElemType(subdomain_id - subdomain_id_end) :
3098 mesh.elem_ref(element_id_vec[0]).type();
3099
3100 if (subdomain_id >= subdomain_id_end)
3101 {
3103 libmesh_assert(element_id_vec.size() == 1);
3104 num_elem_this_blk_vec.push_back
3105 (cast_int<int>(element_id_vec[0]));
3106 names_table.push_back_entry
3107 (Utility::enum_to_string<ElemType>(elem_t));
3108 }
3109 else
3110 {
3111 libmesh_assert(!element_id_vec.empty());
3112 num_elem_this_blk_vec.push_back
3113 (cast_int<int>(element_id_vec.size()));
3114
3115 // A block id normally *is* a subdomain id, but a subdomain that
3116 // contains more than one element type is split across several
3117 // blocks by build_subdomain_map(): only its first element type
3118 // keeps the subdomain id, while every other type gets a
3119 // synthesized block id allocated above all subdomain ids. We
3120 // detect such synthesized blocks from the mismatch between the
3121 // block id and the actual subdomain of the elements it holds.
3122 const subdomain_id_type elem_subdomain_id =
3123 mesh.elem_ref(element_id_vec[0]).subdomain_id();
3124 const bool is_synthesized_block = (subdomain_id != elem_subdomain_id);
3125
3126 std::string block_name = mesh.subdomain_name(elem_subdomain_id);
3127
3128 if (is_synthesized_block)
3129 {
3130 // Suffix the (original) subdomain's name with the element
3131 // type, so the synthesized block is identifiable and its
3132 // name stays distinct from the block that kept the
3133 // subdomain id. This block becomes its own subdomain when
3134 // the file is read back in.
3135 const std::string type_suffix = Utility::enum_to_string<ElemType>(elem_t);
3136 block_name = block_name.empty()
3137 ? (std::to_string(elem_subdomain_id) + "_" + type_suffix)
3138 : (block_name + "_" + type_suffix);
3139
3140 // Informational: keep it to one rank so parallel (Nemesis)
3141 // writes will not repeat it once per processor.
3142 // NOTE this might miss logs if only occurs on other ranks
3143 if (this->processor_id() == 0)
3144 libMesh::out << "ExodusII_IO: subdomain " << elem_subdomain_id
3145 << " contains more than one element type; writing its "
3146 << type_suffix << " elements to a new block \"" << block_name
3147 << "\" (block id " << subdomain_id
3148 << ")"
3149 << std::endl;
3150 }
3151
3152 if (block_name.empty() && elem_t == C0POLYGON)
3153 block_name = "NSIDED_" + std::to_string(counter + 1);
3154 if (block_name.empty() && elem_t == C0POLYHEDRON)
3155 block_name = "NFACED_" + std::to_string(counter + 1);
3156 names_table.push_back_entry(block_name);
3157 }
3158
3159 num_elem += num_elem_this_blk_vec.back();
3160
3161 // Use the first element in this block to get representative information.
3162 // Note that Exodus assumes all elements in a block are of the same type!
3163 // We are using that same assumption here!
3164 const auto & conv = get_conversion(elem_t);
3165 int num_edges_per_elem = 0;
3166 int num_faces_per_elem = 0;
3167 if (elem_t == C0POLYGON)
3168 {
3169 if (subdomain_id >= subdomain_id_end)
3170 libmesh_not_implemented_msg("Support for C0POLYGON side blocks not yet implemented");
3171
3172 has_c0polygon_blocks = true;
3174
3175 for (auto elem_id : element_id_vec)
3176 {
3177 const Elem & elem = mesh.elem_ref(elem_id);
3178
3179 libmesh_error_msg_if(elem.type() != C0POLYGON,
3180 "Error: Exodus requires all elements with a given subdomain ID "
3181 "to be the same type.\n"
3182 << "Can't write both "
3183 << Utility::enum_to_string(elem.type())
3184 << " and C0POLYGON in the same block!");
3185
3186 num_nodes_per_elem += cast_int<int>(elem.n_nodes());
3187 }
3188 }
3189 else if (elem_t == C0POLYHEDRON)
3190 {
3191 if (subdomain_id >= subdomain_id_end)
3192 libmesh_not_implemented_msg("Support for C0POLYHEDRON side blocks not yet implemented");
3193
3194 has_c0polyhedron_blocks = true;
3196
3197 for (auto elem_id : element_id_vec)
3198 {
3199 const Elem & elem = mesh.elem_ref(elem_id);
3200
3201 libmesh_error_msg_if(elem.type() != C0POLYHEDRON,
3202 "Error: Exodus requires all elements with a given subdomain ID "
3203 "to be the same type.\n"
3204 << "Can't write both "
3205 << Utility::enum_to_string(elem.type())
3206 << " and C0POLYHEDRON in the same block!");
3207
3208 const int elem_n_sides = cast_int<int>(elem.n_sides());
3209 num_faces_per_elem += elem_n_sides;
3210 c0polyhedron_total_faces += elem_n_sides;
3211
3212 for (auto s : elem.side_index_range())
3213 c0polyhedron_total_face_nodes += cast_int<int>(elem.nodes_on_side(s).size());
3214 }
3215 }
3216 else
3217 {
3220 libmesh_not_implemented_msg("Support for Polygons/Polyhedra not yet implemented");
3221 }
3222
3223 elem_blk_id.push_back(subdomain_id);
3224 elem_type_table.push_back_entry(conv.exodus_elem_type().c_str());
3225 num_nodes_per_elem_vec.push_back(num_nodes_per_elem);
3226 num_attr_vec.push_back(0); // we don't currently use elem block attributes.
3227 num_edges_per_elem_vec.push_back(num_edges_per_elem); // We don't currently store any edge blocks
3228 num_faces_per_elem_vec.push_back(num_faces_per_elem);
3229 ++counter;
3230 }
3231
3232 if (has_c0polyhedron_blocks)
3233 {
3234 libmesh_assert_equal_to(num_face_blk, 1);
3235 libmesh_assert_equal_to(num_face, c0polyhedron_total_faces);
3236 }
3237
3238 // Here we reserve() space so that we can push_back() onto the
3239 // elem_num_map in the loops below.
3240 this->elem_num_map.reserve(num_elem);
3241
3242 // In the case of discontinuous plotting we initialize a map from
3243 // (element, node) pairs to the corresponding discontinuous node index.
3244 // This ordering must match the ordering used in write_nodal_coordinates.
3245 //
3246 // Note: This map takes the place of the libmesh_node_num_to_exodus map in
3247 // the discontinuous case.
3248 std::map<std::pair<dof_id_type, unsigned int>, dof_id_type> discontinuous_node_indices;
3249 dof_id_type node_counter = 1; // Exodus numbering is 1-based
3250 if (use_discontinuous)
3251 {
3252 for (const auto & elem : mesh.active_element_ptr_range())
3253 for (auto n : elem->node_index_range())
3254 discontinuous_node_indices[std::make_pair(elem->id(),n)] =
3255 node_counter++;
3256 }
3257 else
3258 node_counter = mesh.max_node_id() + 1; // Exodus numbering is 1-based
3259
3260 if (_add_sides)
3261 {
3262 for (const Elem * elem : mesh.active_element_ptr_range())
3263 {
3264 // We'll use "past-the-end" indices to indicate side node
3265 // copies
3266 unsigned int local_node_index = elem->n_nodes();
3267
3268 for (auto s : elem->side_index_range())
3269 {
3271 continue;
3272
3273 const std::vector<unsigned int> side_nodes =
3274 elem->nodes_on_side(s);
3275
3276 for (auto n : index_range(side_nodes))
3277 {
3278 libmesh_ignore(n);
3279 discontinuous_node_indices
3280 [std::make_pair(elem->id(),local_node_index++)] =
3281 node_counter++;
3282 }
3283 }
3284 }
3285 }
3286
3287 // Reference to the BoundaryInfo object for convenience.
3288 const BoundaryInfo & bi = mesh.get_boundary_info();
3289
3290 // Build list of (elem, edge, id) triples
3291 std::vector<BoundaryInfo::BCTuple> edge_tuples = bi.build_edge_list();
3292
3293 // Build the connectivity array for each edge block. The connectivity array
3294 // is a vector<int> with "num_edges * num_nodes_per_edge" entries. We write
3295 // the Exodus node numbers to the connectivity arrays so that they can
3296 // be used directly in the calls to exII::ex_put_conn() below. We also keep
3297 // track of the ElemType and the number of nodes for each boundary_id. All
3298 // edges with a given boundary_id must be of the same type.
3299 std::map<boundary_id_type, std::vector<int>> edge_id_to_conn;
3300 std::map<boundary_id_type, std::pair<ElemType, unsigned int>> edge_id_to_elem_type;
3301
3302 std::unique_ptr<const Elem> edge;
3303 for (const auto & t : edge_tuples)
3304 {
3305 dof_id_type elem_id = std::get<0>(t);
3306 unsigned int edge_id = std::get<1>(t);
3307 boundary_id_type b_id = std::get<2>(t);
3308
3309 // Build the edge in question
3310 mesh.elem_ptr(elem_id)->build_edge_ptr(edge, edge_id);
3311
3312 // Error checking: make sure that all edges in this block are
3313 // the same geometric type.
3314 if (const auto check_it = edge_id_to_elem_type.find(b_id);
3315 check_it == edge_id_to_elem_type.end())
3316 {
3317 // Keep track of the ElemType and number of nodes in this boundary id.
3318 edge_id_to_elem_type[b_id] = std::make_pair(edge->type(), edge->n_nodes());
3319 }
3320 else
3321 {
3322 // Make sure the existing data is consistent
3323 const auto & val_pair = check_it->second;
3324 libmesh_error_msg_if(val_pair.first != edge->type() || val_pair.second != edge->n_nodes(),
3325 "All edges in a block must have same geometric type.");
3326 }
3327
3328 // Get reference to the connectivity array for this block
3329 auto & conn = edge_id_to_conn[b_id];
3330
3331 // For each node on the edge, look up the exodus node id and
3332 // store it in the conn array. Note: all edge types have
3333 // identity node mappings so we don't bother with Conversion
3334 // objects here.
3335 for (auto n : edge->node_index_range())
3336 {
3337 // We look up Exodus node numbers differently if we are
3338 // writing a discontinuous Exodus file.
3339 int exodus_node_id = -1;
3340
3341 if (!use_discontinuous)
3342 {
3343 dof_id_type libmesh_node_id = edge->node_ptr(n)->id();
3344 exodus_node_id = libmesh_map_find
3345 (libmesh_node_num_to_exodus, cast_int<int>(libmesh_node_id));
3346 }
3347 else
3348 {
3349 // Get the node on the element containing this edge
3350 // which corresponds to edge node n. Then use that id to look up
3351 // the exodus_node_id in the discontinuous_node_indices map.
3352 unsigned int pn = mesh.elem_ptr(elem_id)->local_edge_node(edge_id, n);
3353 exodus_node_id = libmesh_map_find
3354 (discontinuous_node_indices, std::make_pair(elem_id, pn));
3355 }
3356
3357 conn.push_back(exodus_node_id);
3358 }
3359 }
3360
3361 // Make sure we have the same number of edge ids that we thought we would.
3362 libmesh_assert(static_cast<int>(edge_id_to_conn.size()) == num_edge_blk);
3363
3364 // Build data structures describing edge blocks. This information must be
3365 // be passed to exII::ex_put_concat_all_blocks() at the same time as the
3366 // information about elem blocks.
3367 std::vector<int> edge_blk_id;
3368 NamesData edge_type_table(num_edge_blk, _max_name_length);
3369 std::vector<int> num_edge_this_blk_vec;
3370 std::vector<int> num_nodes_per_edge_vec;
3371 std::vector<int> num_attr_edge_vec;
3372
3373 // We also build a data structure of edge block names which can
3374 // later be passed to exII::ex_put_names().
3375 NamesData edge_block_names_table(num_edge_blk, _max_name_length);
3376 NamesData face_block_names_table(num_face_blk, _max_name_length);
3377 if (has_c0polyhedron_blocks)
3378 face_block_names_table.push_back_entry("NSIDED_FACES");
3379
3380 // Note: We are going to use the edge **boundary** ids as **block** ids.
3381 for (const auto & pr : edge_id_to_conn)
3382 {
3383 // Store the edge block id in the array to be passed to Exodus.
3384 boundary_id_type id = pr.first;
3385 edge_blk_id.push_back(id);
3386
3387 // Set Exodus element type and number of nodes for this edge block.
3388 const auto & elem_type_node_count = edge_id_to_elem_type[id];
3389 const auto & conv = get_conversion(elem_type_node_count.first);
3390 edge_type_table.push_back_entry(conv.exodus_type.c_str());
3391 num_nodes_per_edge_vec.push_back(elem_type_node_count.second);
3392
3393 // The number of edges is the number of entries in the connectivity
3394 // array divided by the number of nodes per edge.
3395 num_edge_this_blk_vec.push_back(pr.second.size() / elem_type_node_count.second);
3396
3397 // We don't store any attributes currently
3398 num_attr_edge_vec.push_back(0);
3399
3400 // Store the name of this edge block
3401 edge_block_names_table.push_back_entry(bi.get_edgeset_name(id));
3402 }
3403
3404 if (has_c0polygon_blocks || has_c0polyhedron_blocks)
3405 {
3406 // ex_put_concat_all_blocks() does not define the per-polytope
3407 // entity count arrays required by NSIDED/NFACED blocks in all supported
3408 // Exodus versions. Define blocks individually through ex_put_block().
3409 if (has_c0polyhedron_blocks)
3410 {
3411 ex_err = exII::ex_put_block(ex_id,
3412 exII::EX_FACE_BLOCK,
3413 c0polyhedron_face_block_id,
3414 "NSIDED",
3415 c0polyhedron_total_faces,
3416 c0polyhedron_total_face_nodes,
3417 0,
3418 0,
3419 0);
3420 EX_CHECK_ERR(ex_err, "Error writing polyhedron face block.");
3421 }
3422
3423 for (auto i : index_range(elem_blk_id))
3424 {
3425 ex_err = exII::ex_put_block(ex_id,
3426 exII::EX_ELEM_BLOCK,
3427 elem_blk_id[i],
3428 elem_type_table.get_char_star(cast_int<int>(i)),
3429 num_elem_this_blk_vec[i],
3430 num_nodes_per_elem_vec[i],
3431 num_edges_per_elem_vec[i],
3432 num_faces_per_elem_vec[i],
3433 num_attr_vec[i]);
3434 EX_CHECK_ERR(ex_err, "Error writing element block.");
3435 }
3436
3437 for (auto i : index_range(edge_blk_id))
3438 {
3439 ex_err = exII::ex_put_block(ex_id,
3440 exII::EX_EDGE_BLOCK,
3441 edge_blk_id[i],
3442 edge_type_table.get_char_star(cast_int<int>(i)),
3443 num_edge_this_blk_vec[i],
3444 num_nodes_per_edge_vec[i],
3445 0,
3446 0,
3447 num_attr_edge_vec[i]);
3448 EX_CHECK_ERR(ex_err, "Error writing edge block.");
3449 }
3450 }
3451 else
3452 {
3453 // Zero-initialize and then fill in an exII::ex_block_params struct
3454 // with the data we have collected. This new API replaces the old
3455 // exII::ex_put_concat_elem_block() API, and will eventually allow
3456 // us to also allocate space for edge/face blocks if desired.
3457 //
3458 // TODO: It seems like we should be able to take advantage of the
3459 // optimization where you set define_maps==1, but when I tried this
3460 // I got the error: "failed to find node map size". I think the
3461 // problem is that we need to first specify a nonzero number of
3462 // node/elem maps during the call to ex_put_init_ext() in order for
3463 // this to work correctly.
3464 exII::ex_block_params params = {};
3465
3466 // Set pointers for information about elem blocks.
3467 params.elem_blk_id = elem_blk_id.data();
3468 params.elem_type = elem_type_table.get_char_star_star();
3469 params.num_elem_this_blk = num_elem_this_blk_vec.data();
3470 params.num_nodes_per_elem = num_nodes_per_elem_vec.data();
3471 params.num_edges_per_elem = num_edges_per_elem_vec.data();
3472 params.num_faces_per_elem = num_faces_per_elem_vec.data();
3473 params.num_attr_elem = num_attr_vec.data();
3474 params.define_maps = 0;
3475
3476 // Set pointers to edge block information only if we actually have some.
3477 if (num_edge_blk)
3478 {
3479 params.edge_blk_id = edge_blk_id.data();
3480 params.edge_type = edge_type_table.get_char_star_star();
3481 params.num_edge_this_blk = num_edge_this_blk_vec.data();
3482 params.num_nodes_per_edge = num_nodes_per_edge_vec.data();
3483 params.num_attr_edge = num_attr_edge_vec.data();
3484 }
3485
3486 ex_err = exII::ex_put_concat_all_blocks(ex_id, &params);
3487 EX_CHECK_ERR(ex_err, "Error writing element blocks.");
3488 }
3489
3490 // This counter is used to fill up the libmesh_elem_num_to_exodus map in the loop below.
3491 unsigned libmesh_elem_num_to_exodus_counter = 0;
3492
3493 // We need these later if we're adding fake sides, but we don't need
3494 // to recalculate it.
3495 auto num_elem_this_blk_it = num_elem_this_blk_vec.begin();
3496
3497 // We write "fake" ids to the elem_num_map when adding fake sides.
3498 // I don't think it's too important exactly what fake ids are used,
3499 // as long as they don't conflict with any other ids that are
3500 // already in the elem_num_map.
3501 auto next_fake_id = mesh.max_elem_id() + 1; // 1-based numbering in Exodus
3502#ifdef LIBMESH_ENABLE_UNIQUE_ID
3503 if (this->set_unique_ids_from_maps)
3504 next_fake_id = mesh.next_unique_id();
3505#endif
3506
3507 std::vector<int> face_connect;
3508 std::vector<int> c0polyhedron_face_node_counts;
3509 face_connect.reserve(c0polyhedron_total_face_nodes);
3510 c0polyhedron_face_node_counts.reserve(c0polyhedron_total_faces);
3511 int next_c0polyhedron_face_id = 1;
3512
3513 const auto get_exodus_node_id = [&](const Elem &elem,
3514 dof_id_type elem_id,
3515 unsigned int elem_node_index)
3516 -> int
3517 {
3518 if (!use_discontinuous)
3519 return libmesh_map_find(libmesh_node_num_to_exodus,
3520 cast_int<int>(elem.node_id(elem_node_index)));
3521
3522 return cast_int<int>(libmesh_map_find(discontinuous_node_indices,
3523 std::make_pair(elem_id, elem_node_index)));
3524 };
3525
3526 for (auto & [subdomain_id, element_id_vec] : subdomain_map)
3527 {
3528 // Use the first element in the block to get representative
3529 // information for a "real" block. Note that Exodus assumes all
3530 // elements in a block are of the same type! We are using that
3531 // same assumption here!
3532 const ElemType elem_t = (subdomain_id >= subdomain_id_end) ?
3533 ElemType(subdomain_id - subdomain_id_end) :
3534 mesh.elem_ref(element_id_vec[0]).type();
3535
3536 const auto & conv = get_conversion(elem_t);
3537 const bool is_c0polygon_block = (elem_t == C0POLYGON);
3538 const bool is_c0polyhedron_block = (elem_t == C0POLYHEDRON);
3539 const bool is_variable_connectivity_block =
3540 is_c0polygon_block || is_c0polyhedron_block;
3541 std::vector<int> c0polygon_node_counts;
3542 std::vector<int> c0polyhedron_face_counts;
3543
3544 if (is_variable_connectivity_block && subdomain_id >= subdomain_id_end)
3545 libmesh_not_implemented_msg("Support for " <<
3546 Utility::enum_to_string(elem_t) <<
3547 " side blocks not yet implemented");
3548
3549 if (!is_variable_connectivity_block)
3550 {
3553 libmesh_not_implemented_msg("Support for Polygons/Polyhedra not yet implemented");
3554 }
3555
3556 // If this is a *real* block, we just loop over vectors of
3557 // element ids to add.
3558 if (subdomain_id < subdomain_id_end)
3559 {
3560 if (is_variable_connectivity_block)
3561 connect.clear();
3562 if (is_c0polygon_block)
3563 c0polygon_node_counts.reserve(element_id_vec.size());
3564 else if (is_c0polyhedron_block)
3565 c0polyhedron_face_counts.reserve(element_id_vec.size());
3566 else
3567 connect.resize(element_id_vec.size()*num_nodes_per_elem);
3568
3569 const auto add_c0polygon_connectivity =
3570 [&](const Elem &elem, dof_id_type elem_id)
3571 {
3572 c0polygon_node_counts.push_back(cast_int<int>(elem.n_nodes()));
3573 for (auto elem_node_index : elem.node_index_range())
3574 connect.push_back(get_exodus_node_id(elem, elem_id, elem_node_index));
3575
3576 };
3577
3578 const auto add_c0polyhedron_connectivity =
3579 [&](const Elem &elem, dof_id_type elem_id)
3580 {
3581 c0polyhedron_face_counts.push_back(cast_int<int>(elem.n_sides()));
3582
3583 for (auto s: elem.side_index_range())
3584 {
3585 connect.push_back(next_c0polyhedron_face_id++);
3586
3587 const std::vector<unsigned int> side_nodes = elem.nodes_on_side(s);
3588 c0polyhedron_face_node_counts.push_back(cast_int<int>(side_nodes.size()));
3589
3590 for (const auto elem_node_index : side_nodes)
3591 face_connect.push_back(
3592 get_exodus_node_id(elem, elem_id, elem_node_index));
3593 }
3594 };
3595
3596 const auto add_fixed_connectivity =
3597 [&](const Elem &elem, dof_id_type elem_id, std::size_t elem_index)
3598 {
3599 for (unsigned int j = 0;
3600 j < static_cast<unsigned int>(num_nodes_per_elem);
3601 ++j)
3602 {
3603 const auto connect_index =
3604 cast_int<unsigned int>((elem_index * num_nodes_per_elem) + j);
3605 const auto elem_node_index = conv.get_inverse_node_map(j);
3606 connect[connect_index] =
3607 get_exodus_node_id(elem, elem_id, elem_node_index);
3608 }
3609 };
3610
3611 for (auto i : index_range(element_id_vec))
3612 {
3613 unsigned int elem_id = element_id_vec[i];
3614 libmesh_elem_num_to_exodus[elem_id] = ++libmesh_elem_num_to_exodus_counter; // 1-based indexing for Exodus
3615
3616 const Elem & elem = mesh.elem_ref(elem_id);
3617
3618 // We *might* be able to get away with writing mixed element
3619 // types which happen to have the same number of nodes, but
3620 // do we actually *want* to get away with that?
3621 // .) No visualization software would be able to handle it.
3622 // .) There'd be no way for us to read it back in reliably.
3623 // .) Even elements with the same number of nodes may have different connectivities (?)
3624
3625 // This needs to be more than an assert so we don't fail
3626 // with a mysterious segfault while trying to write mixed
3627 // element meshes in optimized mode.
3628 libmesh_error_msg_if(elem.type() != conv.libmesh_elem_type(),
3629 "Error: Exodus requires all elements with a given subdomain ID to be the same type.\n"
3630 << "Can't write both "
3631 << Utility::enum_to_string(elem.type())
3632 << " and "
3633 << Utility::enum_to_string(conv.libmesh_elem_type())
3634 << " in the same block!");
3635
3636 if (is_c0polygon_block)
3637 add_c0polygon_connectivity(elem, elem_id);
3638 else if (is_c0polyhedron_block)
3639 add_c0polyhedron_connectivity(elem, elem_id);
3640 else
3641 add_fixed_connectivity(elem, elem_id, i);
3642
3643 // push_back() either elem_id+1 or the current Elem's
3644 // unique_id+1 into the elem_num_map, depending on the value
3645 // of the set_unique_ids_from_maps flag.
3646 if (this->set_unique_ids_from_maps)
3647 this->elem_num_map.push_back(elem.unique_id() + 1);
3648 else
3649 this->elem_num_map.push_back(elem_id + 1);
3650
3651 } // end for(i)
3652 }
3653 else // subdomain_id >= subdomain_id_end
3654 {
3655 // If this is a "fake" block of added sides, we build those as
3656 // we go.
3658
3659 libmesh_assert(num_elem_this_blk_it != num_elem_this_blk_vec.end());
3660 num_elem_this_blk = *num_elem_this_blk_it;
3661
3663
3664 std::size_t connect_index = 0;
3665 for (const auto & elem : mesh.active_element_ptr_range())
3666 {
3667 unsigned int local_node_index = elem->n_nodes();
3668
3669 for (auto s : elem->side_index_range())
3670 {
3672 continue;
3673
3674 if (elem->side_type(s) != elem_t)
3675 continue;
3676
3677 const std::vector<unsigned int> side_nodes =
3678 elem->nodes_on_side(s);
3679
3680 for (auto n : index_range(side_nodes))
3681 {
3682 libmesh_ignore(n);
3683 const int exodus_node_id = libmesh_map_find
3684 (discontinuous_node_indices,
3685 std::make_pair(elem->id(), local_node_index++));
3686 libmesh_assert_less(connect_index, connect.size());
3687 connect[connect_index++] = exodus_node_id;
3688 }
3689 }
3690 }
3691
3692 // Store num_elem_this_blk "fake" ids into the
3693 // elem_num_map. Use a traditional for-loop to avoid unused
3694 // variable warnings about the loop counter.
3695 for (int i=0; i<num_elem_this_blk; ++i)
3696 this->elem_num_map.push_back(next_fake_id++);
3697 }
3698
3699 ++num_elem_this_blk_it;
3700
3701 ex_err = exII::ex_put_conn
3702 (ex_id,
3703 exII::EX_ELEM_BLOCK,
3704 subdomain_id,
3705 is_c0polyhedron_block ? nullptr : connect.data(), // node_conn
3706 nullptr, // elem_edge_conn (unused)
3707 is_c0polyhedron_block ? connect.data() : nullptr);
3708 EX_CHECK_ERR(ex_err, "Error writing element connectivities");
3709
3710 if (is_c0polygon_block)
3711 {
3712 ex_err = exII::ex_put_entity_count_per_polyhedra
3713 (ex_id,
3714 exII::EX_ELEM_BLOCK,
3715 subdomain_id,
3716 c0polygon_node_counts.data());
3717 EX_CHECK_ERR(ex_err, "Error writing polygon node counts");
3718 }
3719 if (is_c0polyhedron_block)
3720 {
3721 ex_err = exII::ex_put_entity_count_per_polyhedra
3722 (ex_id,
3723 exII::EX_ELEM_BLOCK,
3724 subdomain_id,
3725 c0polyhedron_face_counts.data());
3726 EX_CHECK_ERR(ex_err, "Error writing polyhedron face counts");
3727 }
3728 } // end for (auto & [subdomain_id, element_id_vec] : subdomain_map)
3729
3730 if (has_c0polyhedron_blocks)
3731 {
3732 libmesh_assert_equal_to(c0polyhedron_face_node_counts.size(),
3733 cast_int<std::size_t>(num_face));
3734 libmesh_assert_equal_to(next_c0polyhedron_face_id, num_face + 1);
3735
3736 ex_err = exII::ex_put_conn
3737 (ex_id,
3738 exII::EX_FACE_BLOCK,
3739 c0polyhedron_face_block_id,
3740 face_connect.data(), // node_conn
3741 nullptr, // elem_edge_conn (unused)
3742 nullptr); // elem_face_conn (unused)
3743 EX_CHECK_ERR(ex_err, "Error writing polyhedron face connectivities");
3744
3745 ex_err = exII::ex_put_entity_count_per_polyhedra
3746 (ex_id,
3747 exII::EX_FACE_BLOCK,
3748 c0polyhedron_face_block_id,
3749 c0polyhedron_face_node_counts.data());
3750 EX_CHECK_ERR(ex_err, "Error writing polyhedron face node counts");
3751 }
3752
3753 // write out the element number map that we created
3754 ex_err = exII::ex_put_elem_num_map(ex_id, elem_num_map.data());
3755 EX_CHECK_ERR(ex_err, "Error writing element map");
3756
3757 // Write out the block names
3758 if (num_elem_blk > 0)
3759 {
3760 ex_err = exII::ex_put_names(ex_id, exII::EX_ELEM_BLOCK, names_table.get_char_star_star());
3761 EX_CHECK_ERR(ex_err, "Error writing element block names");
3762 }
3763
3764 if (num_face_blk > 0)
3765 {
3766 ex_err = exII::ex_put_names
3767 (ex_id,
3768 exII::EX_FACE_BLOCK,
3769 face_block_names_table.get_char_star_star());
3770 EX_CHECK_ERR(ex_err, "Error writing face block names");
3771 }
3772
3773 // Write out edge blocks if we have any
3774 for (const auto & pr : edge_id_to_conn)
3775 {
3776 ex_err = exII::ex_put_conn
3777 (ex_id,
3778 exII::EX_EDGE_BLOCK,
3779 pr.first,
3780 pr.second.data(), // node_conn
3781 nullptr, // elem_edge_conn (unused)
3782 nullptr); // elem_face_conn (unused)
3783 EX_CHECK_ERR(ex_err, "Error writing element connectivities");
3784 }
3785
3786 // Write out the edge block names, if any.
3787 if (num_edge_blk > 0)
3788 {
3789 ex_err = exII::ex_put_names
3790 (ex_id,
3791 exII::EX_EDGE_BLOCK,
3792 edge_block_names_table.get_char_star_star());
3793 EX_CHECK_ERR(ex_err, "Error writing edge block names");
3794 }
3795}
3796
3797
3798
3799
3801{
3802 LOG_SCOPE("write_sidesets()", "ExodusII_IO_Helper");
3803
3804 if ((_run_only_on_proc0) && (this->processor_id() != 0))
3805 return;
3806
3807 // Maps from sideset id to lists of corresponding element ids and side ids
3808 std::map<int, std::vector<int>> elem_lists;
3809 std::map<int, std::vector<int>> side_lists;
3810 std::set<boundary_id_type> side_boundary_ids;
3811
3812 {
3813 // Accumulate the vectors to pass into ex_put_side_set
3814 // build_side_lists() returns a vector of (elem, side, bc) tuples.
3815 for (const auto & t : mesh.get_boundary_info().build_side_list())
3816 {
3817 std::vector<const Elem *> family;
3818#ifdef LIBMESH_ENABLE_AMR
3823 mesh.elem_ref(std::get<0>(t)).active_family_tree_by_side(family, std::get<1>(t), false);
3824#else
3825 family.push_back(mesh.elem_ptr(std::get<0>(t)));
3826#endif
3827
3828 for (const auto & f : family)
3829 {
3830 const auto & conv = get_conversion(mesh.elem_ptr(f->id())->type());
3831
3832 // Use the libmesh to exodus data structure map to get the proper sideset IDs
3833 // The data structure contains the "collapsed" contiguous ids
3834 elem_lists[std::get<2>(t)].push_back(libmesh_elem_num_to_exodus[f->id()]);
3835 side_lists[std::get<2>(t)].push_back(conv.get_inverse_side_map(std::get<1>(t)));
3836 }
3837 }
3838
3839 std::vector<boundary_id_type> tmp;
3841 side_boundary_ids.insert(tmp.begin(), tmp.end());
3842 }
3843
3844 {
3845 // add data for shell faces, if needed
3846
3847 // Accumulate the vectors to pass into ex_put_side_set
3848 for (const auto & t : mesh.get_boundary_info().build_shellface_list())
3849 {
3850 std::vector<const Elem *> family;
3851#ifdef LIBMESH_ENABLE_AMR
3856 mesh.elem_ref(std::get<0>(t)).active_family_tree_by_side(family, std::get<1>(t), false);
3857#else
3858 family.push_back(mesh.elem_ptr(std::get<0>(t)));
3859#endif
3860
3861 for (const auto & f : family)
3862 {
3863 const auto & conv = get_conversion(mesh.elem_ptr(f->id())->type());
3864
3865 // Use the libmesh to exodus data structure map to get the proper sideset IDs
3866 // The data structure contains the "collapsed" contiguous ids
3867 elem_lists[std::get<2>(t)].push_back(libmesh_elem_num_to_exodus[f->id()]);
3868 side_lists[std::get<2>(t)].push_back(conv.get_inverse_shellface_map(std::get<1>(t)));
3869 }
3870 }
3871
3872 std::vector<boundary_id_type> tmp;
3874 side_boundary_ids.insert(tmp.begin(), tmp.end());
3875 }
3876
3877 // Add any empty-but-named side boundary ids
3878 for (const auto & pr : mesh.get_boundary_info().get_sideset_name_map())
3879 side_boundary_ids.insert(pr.first);
3880
3881 // Write out the sideset names, but only if there is something to write
3882 if (side_boundary_ids.size() > 0)
3883 {
3884 NamesData names_table(side_boundary_ids.size(), _max_name_length);
3885
3886 std::vector<exII::ex_set> sets(side_boundary_ids.size());
3887
3888 // Loop over "side_boundary_ids" and "sets" simultaneously
3889 for (auto [i, it] = std::tuple{0u, side_boundary_ids.begin()}; i<sets.size(); ++i, ++it)
3890 {
3891 boundary_id_type ss_id = *it;
3893
3894 sets[i].id = ss_id;
3895 sets[i].type = exII::EX_SIDE_SET;
3896 sets[i].num_distribution_factor = 0;
3897 sets[i].distribution_factor_list = nullptr;
3898
3899 if (const auto elem_it = elem_lists.find(ss_id);
3900 elem_it == elem_lists.end())
3901 {
3902 sets[i].num_entry = 0;
3903 sets[i].entry_list = nullptr;
3904 sets[i].extra_list = nullptr;
3905 }
3906 else
3907 {
3908 sets[i].num_entry = elem_it->second.size();
3909 sets[i].entry_list = elem_it->second.data();
3910 sets[i].extra_list = libmesh_map_find(side_lists, ss_id).data();
3911 }
3912 }
3913
3914 ex_err = exII::ex_put_sets(ex_id, side_boundary_ids.size(), sets.data());
3915 EX_CHECK_ERR(ex_err, "Error writing sidesets");
3916
3917 ex_err = exII::ex_put_names(ex_id, exII::EX_SIDE_SET, names_table.get_char_star_star());
3918 EX_CHECK_ERR(ex_err, "Error writing sideset names");
3919 }
3920}
3921
3922
3923
3925{
3926 LOG_SCOPE("write_nodesets()", "ExodusII_IO_Helper");
3927
3928 if ((_run_only_on_proc0) && (this->processor_id() != 0))
3929 return;
3930
3931 // build_node_list() builds a sorted list of (node-id, bc-id) tuples
3932 // that is sorted by node-id, but we actually want it to be sorted
3933 // by bc-id, i.e. the second argument of the tuple.
3934 auto bc_tuples =
3936
3937 // We use std::stable_sort() here so that the entries within a
3938 // single nodeset remain sorted in node-id order, but now the
3939 // smallest boundary id's nodes appear first in the list, followed
3940 // by the second smallest, etc. That is, we are purposely doing two
3941 // different sorts here, with the first one being within the
3942 // build_node_list() call itself.
3943 std::stable_sort(bc_tuples.begin(), bc_tuples.end(),
3944 [](const BoundaryInfo::NodeBCTuple & t1,
3945 const BoundaryInfo::NodeBCTuple & t2)
3946 { return std::get<1>(t1) < std::get<1>(t2); });
3947
3948 std::vector<boundary_id_type> node_boundary_ids;
3949 mesh.get_boundary_info().build_node_boundary_ids(node_boundary_ids);
3950
3951 // Add any empty-but-named node boundary ids
3952 for (const auto & pair : mesh.get_boundary_info().get_nodeset_name_map())
3953 {
3954 const boundary_id_type id = pair.first;
3955
3956 if (std::find(node_boundary_ids.begin(),
3957 node_boundary_ids.end(), id)
3958 == node_boundary_ids.end())
3959 node_boundary_ids.push_back(id);
3960 }
3961
3962 // Write out the nodeset names, but only if there is something to write
3963 if (node_boundary_ids.size() > 0)
3964 {
3965 NamesData names_table(node_boundary_ids.size(), _max_name_length);
3966
3967 // Vectors to be filled and passed to exII::ex_put_concat_sets()
3968 // Use existing class members and avoid variable shadowing.
3969 nodeset_ids.clear();
3970 num_nodes_per_set.clear();
3971 num_node_df_per_set.clear();
3972 node_sets_node_index.clear();
3973 node_sets_node_list.clear();
3974
3975 // Pre-allocate space
3976 nodeset_ids.reserve(node_boundary_ids.size());
3977 num_nodes_per_set.reserve(node_boundary_ids.size());
3978 num_node_df_per_set.resize(node_boundary_ids.size()); // all zeros
3979 node_sets_node_index.reserve(node_boundary_ids.size());
3980 node_sets_node_list.reserve(bc_tuples.size());
3981
3982 // Assign entries to node_sets_node_list, keeping track of counts as we go.
3983 std::map<boundary_id_type, unsigned int> nodeset_counts;
3984 for (auto id : node_boundary_ids)
3985 nodeset_counts[id] = 0;
3986
3987 for (const auto & t : bc_tuples)
3988 {
3989 const dof_id_type & node_id = std::get<0>(t) + 1; // Note: we use 1-based node ids in Exodus!
3990 const boundary_id_type & nodeset_id = std::get<1>(t);
3991 node_sets_node_list.push_back(node_id);
3992 nodeset_counts[nodeset_id] += 1;
3993 }
3994
3995 // Fill in other indexing vectors needed by Exodus
3996 unsigned int running_sum = 0;
3997 for (const auto & pr : nodeset_counts)
3998 {
3999 nodeset_ids.push_back(pr.first);
4000 num_nodes_per_set.push_back(pr.second);
4001 node_sets_node_index.push_back(running_sum);
4002 names_table.push_back_entry(mesh.get_boundary_info().get_nodeset_name(pr.first));
4003 running_sum += pr.second;
4004 }
4005
4006 // Fill in an exII::ex_set_specs object which can then be passed to
4007 // the ex_put_concat_sets() function.
4008 exII::ex_set_specs set_data = {};
4009 set_data.sets_ids = nodeset_ids.data();
4010 set_data.num_entries_per_set = num_nodes_per_set.data();
4011 set_data.num_dist_per_set = num_node_df_per_set.data(); // zeros
4012 set_data.sets_entry_index = node_sets_node_index.data();
4013 set_data.sets_dist_index = node_sets_node_index.data(); // dummy value
4014 set_data.sets_entry_list = node_sets_node_list.data();
4015
4016 // Write all nodesets together.
4017 ex_err = exII::ex_put_concat_sets(ex_id, exII::EX_NODE_SET, &set_data);
4018 EX_CHECK_ERR(ex_err, "Error writing concatenated nodesets");
4019
4020 // Write out the nodeset names
4021 ex_err = exII::ex_put_names(ex_id, exII::EX_NODE_SET, names_table.get_char_star_star());
4022 EX_CHECK_ERR(ex_err, "Error writing nodeset names");
4023 }
4024}
4025
4026
4027
4028void ExodusII_IO_Helper::initialize_element_variables(std::vector<std::string> names,
4029 const std::vector<std::set<subdomain_id_type>> & vars_active_subdomains)
4030{
4031 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4032 return;
4033
4034 // Quick return if there are no element variables to write
4035 if (names.size() == 0)
4036 return;
4037
4038 // Be sure that variables in the file match what we are asking for
4039 if (num_elem_vars > 0)
4040 {
4041 this->check_existing_vars(ELEMENTAL, names, this->elem_var_names);
4042 return;
4043 }
4044
4045 // Quick return if we have already called this function
4047 return;
4048
4049 // Set the flag so we can skip this stuff on subsequent calls to
4050 // initialize_element_variables()
4052
4053 this->write_var_names(ELEMENTAL, names);
4054
4055 // Use the truth table to indicate which subdomain/variable pairs are
4056 // active according to vars_active_subdomains.
4057 std::vector<int> truth_tab(num_elem_blk*num_elem_vars, 0);
4058 for (auto var_num : index_range(vars_active_subdomains))
4059 {
4060 // If the list of active subdomains is empty, it is interpreted as being
4061 // active on *all* subdomains.
4062 std::set<subdomain_id_type> current_set;
4063 if (vars_active_subdomains[var_num].empty())
4064 for (auto block_id : block_ids)
4065 current_set.insert(restrict_int<subdomain_id_type>(block_id));
4066 else
4067 current_set = vars_active_subdomains[var_num];
4068
4069 // Find index into the truth table for each id in current_set.
4070 for (auto block_id : current_set)
4071 {
4072 auto it = std::find(block_ids.begin(), block_ids.end(), block_id);
4073 libmesh_error_msg_if(it == block_ids.end(),
4074 "ExodusII_IO_Helper: block id " << block_id << " not found in block_ids.");
4075
4076 std::size_t block_index =
4077 std::distance(block_ids.begin(), it);
4078
4079 std::size_t truth_tab_index = block_index*num_elem_vars + var_num;
4080 truth_tab[truth_tab_index] = 1;
4081 }
4082 }
4083
4084 ex_err = exII::ex_put_truth_table
4085 (ex_id,
4086 exII::EX_ELEM_BLOCK,
4089 truth_tab.data());
4090 EX_CHECK_ERR(ex_err, "Error writing element truth table.");
4091}
4092
4093
4094
4095void ExodusII_IO_Helper::initialize_nodal_variables(std::vector<std::string> names)
4096{
4097 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4098 return;
4099
4100 // Quick return if there are no nodal variables to write
4101 if (names.size() == 0)
4102 return;
4103
4104 // Quick return if we have already called this function
4106 return;
4107
4108 // Be sure that variables in the file match what we are asking for
4109 if (num_nodal_vars > 0)
4110 {
4111 this->check_existing_vars(NODAL, names, this->nodal_var_names);
4112 return;
4113 }
4114
4115 // Set the flag so we can skip the rest of this function on subsequent calls.
4117
4118 this->write_var_names(NODAL, names);
4119}
4120
4121
4122
4123void ExodusII_IO_Helper::initialize_global_variables(std::vector<std::string> names)
4124{
4125 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4126 return;
4127
4128 // Quick return if there are no global variables to write
4129 if (names.size() == 0)
4130 return;
4131
4133 return;
4134
4135 // Be sure that variables in the file match what we are asking for
4136 if (num_global_vars > 0)
4137 {
4138 this->check_existing_vars(GLOBAL, names, this->global_var_names);
4139 return;
4140 }
4141
4143
4144 this->write_var_names(GLOBAL, names);
4145}
4146
4147
4148
4150 std::vector<std::string> & names,
4151 std::vector<std::string> & names_from_file)
4152{
4153 // There may already be global variables in the file (for example,
4154 // if we're appending) and in that case, we
4155 // 1.) Cannot initialize them again.
4156 // 2.) Should check to be sure that the global variable names are the same.
4157
4158 // Fills up names_from_file for us
4159 this->read_var_names(type);
4160
4161 // Both the number of variables and their names (up to the first
4162 // MAX_STR_LENGTH characters) must match for the names we are
4163 // planning to write and the names already in the file.
4164 bool match =
4165 std::equal(names.begin(), names.end(),
4166 names_from_file.begin(),
4167 [this](const std::string & a,
4168 const std::string & b) -> bool
4169 {
4170 return a.compare(/*pos=*/0, /*len=*/_max_name_length, b) == 0;
4171 });
4172
4173 if (!match)
4174 {
4175 libMesh::err << "Error! The Exodus file already contains the variables:" << std::endl;
4176 for (const auto & name : names_from_file)
4177 libMesh::err << name << std::endl;
4178
4179 libMesh::err << "And you asked to write:" << std::endl;
4180 for (const auto & name : names)
4181 libMesh::err << name << std::endl;
4182
4183 libmesh_error_msg("Cannot overwrite existing variables in Exodus II file.");
4184 }
4185}
4186
4187
4188
4190{
4191 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4192 return;
4193
4195 {
4196 float cast_time = float(time);
4197 ex_err = exII::ex_put_time(ex_id, timestep, &cast_time);
4198 }
4199 else
4200 {
4201 double cast_time = double(time);
4202 ex_err = exII::ex_put_time(ex_id, timestep, &cast_time);
4203 }
4204 EX_CHECK_ERR(ex_err, "Error writing timestep.");
4205
4206 this->update();
4207}
4208
4209
4210
4211void
4213{
4214 LOG_SCOPE("write_elemsets()", "ExodusII_IO_Helper");
4215
4216 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4217 return;
4218
4219 // TODO: Add support for named elemsets
4220 // NamesData names_table(elemsets.size(), _max_name_length);
4221
4222 // We only need to write elemsets if the Mesh has an extra elem
4223 // integer called "elemset_code" defined on it.
4224 if (mesh.has_elem_integer("elemset_code"))
4225 {
4226 std::map<elemset_id_type, std::vector<int>> exodus_elemsets;
4227
4228 unsigned int elemset_index =
4229 mesh.get_elem_integer_index("elemset_code");
4230
4231 // Catch ids returned from MeshBase::get_elemsets() calls
4232 MeshBase::elemset_type set_ids;
4233 for (const auto & elem : mesh.element_ptr_range())
4234 {
4235 dof_id_type elemset_code =
4236 elem->get_extra_integer(elemset_index);
4237
4238 // Look up which element set ids (if any) this elemset_code corresponds to.
4239 mesh.get_elemsets(elemset_code, set_ids);
4240
4241 // Debugging
4242 // libMesh::out << "elemset_code = " << elemset_code << std::endl;
4243 // for (const auto & set_id : set_ids)
4244 // libMesh::out << set_id << " ";
4245 // libMesh::out << std::endl;
4246
4247 // Store this Elem id in every set to which it belongs.
4248 for (const auto & set_id : set_ids)
4249 exodus_elemsets[set_id].push_back(libmesh_elem_num_to_exodus[elem->id()]);
4250 }
4251
4252 // Debugging: print contents of exodus_elemsets map
4253 // for (const auto & [set_id, elem_ids] : exodus_elemsets)
4254 // {
4255 // libMesh::out << "elemset " << set_id << ": ";
4256 // for (const auto & elem_id : elem_ids)
4257 // libMesh::out << elem_id << " ";
4258 // libMesh::out << std::endl;
4259 // }
4260
4261 // Only continue if we actually had some elements in sets
4262 if (!exodus_elemsets.empty())
4263 {
4264 // Reserve space, loop over newly-created map, construct
4265 // exII::ex_set objects to be passed to exII::ex_put_sets(). Note:
4266 // we do non-const iteration since Exodus requires non-const pointers
4267 // to be passed to its APIs.
4268 std::vector<exII::ex_set> sets;
4269 sets.reserve(exodus_elemsets.size());
4270
4271 for (auto & [elem_set_id, ids_vec] : exodus_elemsets)
4272 {
4273 // TODO: Add support for named elemsets
4274 // names_table.push_back_entry(mesh.get_elemset_name(elem_set_id));
4275
4276 exII::ex_set & current_set = sets.emplace_back();
4277 current_set.id = elem_set_id;
4278 current_set.type = exII::EX_ELEM_SET;
4279 current_set.num_entry = ids_vec.size();
4280 current_set.num_distribution_factor = 0;
4281 current_set.entry_list = ids_vec.data();
4282 current_set.extra_list = nullptr; // extra_list is used for sidesets, not needed for elemsets
4283 current_set.distribution_factor_list = nullptr; // not used for elemsets
4284 }
4285
4286 // Sanity check: make sure the number of elemsets we already wrote to the header
4287 // matches the number of elemsets we just constructed by looping over the Mesh.
4288 libmesh_assert_msg(num_elem_sets == cast_int<int>(exodus_elemsets.size()),
4289 "Mesh has " << exodus_elemsets.size()
4290 << " elemsets, but header was written with num_elem_sets == " << num_elem_sets);
4291 libmesh_assert_msg(num_elem_sets == cast_int<int>(mesh.n_elemsets()),
4292 "mesh.n_elemsets() == " << mesh.n_elemsets()
4293 << ", but header was written with num_elem_sets == " << num_elem_sets);
4294
4295 ex_err = exII::ex_put_sets(ex_id, exodus_elemsets.size(), sets.data());
4296 EX_CHECK_ERR(ex_err, "Error writing elemsets");
4297
4298 // TODO: Add support for named elemsets
4299 // ex_err = exII::ex_put_names(ex_id, exII::EX_ELEM_SET, names_table.get_char_star_star());
4300 // EX_CHECK_ERR(ex_err, "Error writing elemset names");
4301 } // end if (!exodus_elemsets.empty())
4302 } // end if (mesh.has_elem_integer("elemset_code"))
4303}
4304
4305
4306
4307void
4310 int timestep,
4311 const std::vector<std::string> & var_names,
4312 const std::vector<std::set<boundary_id_type>> & side_ids,
4313 const std::vector<std::map<BoundaryInfo::BCTuple, Real>> & bc_vals)
4314{
4315 LOG_SCOPE("write_sideset_data()", "ExodusII_IO_Helper");
4316
4317 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4318 return;
4319
4320 // Write the sideset variable names to file. This function should
4321 // only be called once for SIDESET variables, repeated calls to
4322 // write_var_names overwrites/changes the order of names that were
4323 // there previously, and will mess up any data that has already been
4324 // written.
4325 this->write_var_names(SIDESET, var_names);
4326
4327 // I hope that we are allowed to call read_sideset_info() even
4328 // though we are in the middle of writing? It seems to work provided
4329 // that you have already written the mesh itself... read_sideset_info()
4330 // fills in the following data members:
4331 // .) num_side_sets
4332 // .) ss_ids
4333 this->read_sideset_info();
4334
4335 // Write "truth" table for sideset variables. The function
4336 // exII::ex_put_variable_param() must be called before
4337 // exII::ex_put_truth_table(). For us, this happens during the call
4338 // to ExodusII_IO_Helper::write_var_names(). sset_var_tab is a logically
4339 // (num_side_sets x num_sset_var) integer array of 0s and 1s
4340 // indicating which sidesets a given sideset variable is defined on.
4341 std::vector<int> sset_var_tab(num_side_sets * var_names.size());
4342
4343 // We now call read_sideset() once per sideset and write any sideset
4344 // variable values which are defined there.
4345 int offset=0;
4346 for (int ss=0; ss<num_side_sets; ++ss)
4347 {
4348 // We don't know num_sides_per_set for each set until we call
4349 // read_sideset(). The values for each sideset are stored (using
4350 // the offsets) into the 'elem_list' and 'side_list' arrays of
4351 // this class.
4352 offset += (ss > 0 ? num_sides_per_set[ss-1] : 0);
4353 this->read_sideset(ss, offset);
4354
4355 // For each variable in var_names, write the values for the
4356 // current sideset, if any.
4357 for (auto var : index_range(var_names))
4358 {
4359 // If this var has no values on this sideset, go to the next one.
4360 if (!side_ids[var].count(ss_ids[ss]))
4361 continue;
4362
4363 // Otherwise, fill in this entry of the sideset truth table.
4364 sset_var_tab[ss*var_names.size() + var] = 1;
4365
4366 // Data vector that will eventually be passed to exII::ex_put_var().
4367 std::vector<Real> sset_var_vals(num_sides_per_set[ss]);
4368
4369 // Get reference to the BCTuple -> Real map for this variable.
4370 const auto & data_map = bc_vals[var];
4371
4372 // Loop over elem_list, side_list entries in current sideset.
4373 for (int i=0; i<num_sides_per_set[ss]; ++i)
4374 {
4375 // Get elem_id and side_id from the respective lists that
4376 // are filled in by calling read_sideset().
4377 //
4378 // Note: these are Exodus-specific ids, so we have to convert them
4379 // to libmesh ids, as that is what will be in the bc_tuples.
4380 //
4381 // TODO: we should probably consult the exodus_elem_num_to_libmesh
4382 // mapping in order to figure out which libmesh element id 'elem_id'
4383 // actually corresponds to here, instead of just assuming it will be
4384 // off by one. Unfortunately that data structure does not seem to
4385 // be used at the moment. If we assume that write_sideset_data() is
4386 // always called following write(), then this should be a fairly safe
4387 // assumption...
4388 dof_id_type elem_id = elem_list[i + offset] - 1;
4389 unsigned int side_id = side_list[i + offset] - 1;
4390
4391 // Sanity check: make sure that the "off by one"
4392 // assumption we used above to set 'elem_id' is valid.
4393 libmesh_error_msg_if
4394 (libmesh_map_find(libmesh_elem_num_to_exodus, cast_int<int>(elem_id)) !=
4395 cast_int<dof_id_type>(elem_list[i + offset]),
4396 "Error mapping Exodus elem id to libmesh elem id.");
4397
4398 // Map from Exodus side ids to libmesh side ids.
4399 const auto & conv = get_conversion(mesh.elem_ptr(elem_id)->type());
4400
4401 // Map from Exodus side ids to libmesh side ids.
4402 unsigned int converted_side_id = conv.get_side_map(side_id);
4403
4404 // Construct a key so we can quickly see whether there is any
4405 // data for this variable in the map.
4406 BoundaryInfo::BCTuple key = std::make_tuple
4407 (elem_id,
4408 converted_side_id,
4409 ss_ids[ss]);
4410
4411 // Find the data for this (elem,side,id) tuple. Throw an
4412 // error if not found. Then store value in vector which
4413 // will be passed to Exodus.
4414 sset_var_vals[i] = libmesh_map_find(data_map, key);
4415 } // end for (i)
4416
4417 // As far as I can tell, there is no "concat" version of writing
4418 // sideset data, you have to call ex_put_sset_var() once per (variable,
4419 // sideset) pair.
4420 if (sset_var_vals.size() > 0)
4421 {
4422 ex_err = exII::ex_put_var
4423 (ex_id,
4424 timestep,
4425 exII::EX_SIDE_SET,
4426 var + 1, // 1-based variable index of current variable
4427 ss_ids[ss],
4429 MappedOutputVector(sset_var_vals, _single_precision).data());
4430 EX_CHECK_ERR(ex_err, "Error writing sideset vars.");
4431 }
4432 } // end for (var)
4433 } // end for (ss)
4434
4435 // Finally, write the sideset truth table.
4436 ex_err =
4437 exII::ex_put_truth_table(ex_id,
4438 exII::EX_SIDE_SET,
4440 cast_int<int>(var_names.size()),
4441 sset_var_tab.data());
4442 EX_CHECK_ERR(ex_err, "Error writing sideset var truth table.");
4443}
4444
4445
4446
4447void
4450 int timestep,
4451 std::vector<std::string> & var_names,
4452 std::vector<std::set<boundary_id_type>> & side_ids,
4453 std::vector<std::map<BoundaryInfo::BCTuple, Real>> & bc_vals)
4454{
4455 LOG_SCOPE("read_sideset_data()", "ExodusII_IO_Helper");
4456
4457 // This reads the sideset variable names into the local
4458 // sideset_var_names data structure.
4459 this->read_var_names(SIDESET);
4460
4461 if (num_sideset_vars)
4462 {
4463 // Read the sideset data truth table
4464 std::vector<int> sset_var_tab(num_side_sets * num_sideset_vars);
4465 ex_err = exII::ex_get_truth_table
4466 (ex_id,
4467 exII::EX_SIDE_SET,
4470 sset_var_tab.data());
4471 EX_CHECK_ERR(ex_err, "Error reading sideset variable truth table.");
4472
4473 // Set up/allocate space in incoming data structures.
4474 var_names = sideset_var_names;
4475 side_ids.resize(num_sideset_vars);
4476 bc_vals.resize(num_sideset_vars);
4477
4478 // Read the sideset data.
4479 //
4480 // Note: we assume that read_sideset() has already been called
4481 // for each sideset, so the required values in elem_list and
4482 // side_list are already present.
4483 //
4484 // TODO: As a future optimization, we could read only the values
4485 // requested by the user by looking at the input parameter
4486 // var_names and checking whether it already has entries in
4487 // it. We could do the same thing with the input side_ids
4488 // container and only read values for requested sidesets.
4489 int offset=0;
4490 for (int ss=0; ss<num_side_sets; ++ss)
4491 {
4492 offset += (ss > 0 ? num_sides_per_set[ss-1] : 0);
4493 for (int var=0; var<num_sideset_vars; ++var)
4494 {
4495 int is_present = sset_var_tab[num_sideset_vars*ss + var];
4496
4497 if (is_present)
4498 {
4499 // Record the fact that this variable is defined on this sideset.
4500 side_ids[var].insert(ss_ids[ss]);
4501
4502 // Note: the assumption here is that a previous call
4503 // to this->read_sideset_info() has already set the
4504 // values of num_sides_per_set, so we just use those values here.
4505 std::vector<Real> sset_var_vals(num_sides_per_set[ss]);
4506 ex_err = exII::ex_get_var
4507 (ex_id,
4508 timestep,
4509 exII::EX_SIDE_SET,
4510 var + 1, // 1-based sideset variable index!
4511 ss_ids[ss],
4513 MappedInputVector(sset_var_vals, _single_precision).data());
4514 EX_CHECK_ERR(ex_err, "Error reading sideset variable.");
4515
4516 for (int i=0; i<num_sides_per_set[ss]; ++i)
4517 {
4518 dof_id_type exodus_elem_id = elem_list[i + offset];
4519 unsigned int exodus_side_id = side_list[i + offset];
4520
4521 // FIXME: We should use exodus_elem_num_to_libmesh for this,
4522 // but it apparently is never set up, so just
4523 // subtract 1 from the Exodus elem id.
4524 dof_id_type converted_elem_id = exodus_elem_id - 1;
4525
4526 // Map Exodus side id to libmesh side id.
4527 // Map from Exodus side ids to libmesh side ids.
4528 const auto & conv = get_conversion(mesh.elem_ptr(converted_elem_id)->type());
4529
4530 // Map from Exodus side id to libmesh side id.
4531 // Note: the mapping is defined on 0-based indices, so subtract
4532 // 1 before doing the mapping.
4533 unsigned int converted_side_id = conv.get_side_map(exodus_side_id - 1);
4534
4535 // Make a BCTuple key from the converted information.
4536 BoundaryInfo::BCTuple key = std::make_tuple
4537 (converted_elem_id,
4538 converted_side_id,
4539 ss_ids[ss]);
4540
4541 // Store (elem, side, b_id) tuples in bc_vals[var]
4542 bc_vals[var].emplace(key, sset_var_vals[i]);
4543 } // end for (i)
4544 } // end if (present)
4545 } // end for (var)
4546 } // end for (ss)
4547 } // end if (num_sideset_vars)
4548}
4549
4550
4551void
4554 std::map<BoundaryInfo::BCTuple, unsigned int> & bc_array_indices)
4555{
4556 // Clear any existing data, we are going to build this data structure from scratch
4557 bc_array_indices.clear();
4558
4559 // Store the sideset data array indices.
4560 //
4561 // Note: we assume that read_sideset() has already been called
4562 // for each sideset, so the required values in elem_list and
4563 // side_list are already present.
4564 int offset=0;
4565 for (int ss=0; ss<num_side_sets; ++ss)
4566 {
4567 offset += (ss > 0 ? num_sides_per_set[ss-1] : 0);
4568 for (int i=0; i<num_sides_per_set[ss]; ++i)
4569 {
4570 dof_id_type exodus_elem_id = elem_list[i + offset];
4571 unsigned int exodus_side_id = side_list[i + offset];
4572
4573 // FIXME: We should use exodus_elem_num_to_libmesh for this,
4574 // but it apparently is never set up, so just
4575 // subtract 1 from the Exodus elem id.
4576 dof_id_type converted_elem_id = exodus_elem_id - 1;
4577
4578 // Conversion operator for this Elem type
4579 const auto & conv = get_conversion(mesh.elem_ptr(converted_elem_id)->type());
4580
4581 // Map from Exodus side id to libmesh side id.
4582 // Note: the mapping is defined on 0-based indices, so subtract
4583 // 1 before doing the mapping.
4584 unsigned int converted_side_id = conv.get_side_map(exodus_side_id - 1);
4585
4586 // Make a BCTuple key from the converted information.
4587 BoundaryInfo::BCTuple key = std::make_tuple
4588 (converted_elem_id,
4589 converted_side_id,
4590 ss_ids[ss]);
4591
4592 // Store (elem, side, b_id) tuple with corresponding array index
4593 bc_array_indices.emplace(key, cast_int<unsigned int>(i));
4594 } // end for (i)
4595 } // end for (ss)
4596}
4597
4598
4599
4601write_nodeset_data (int timestep,
4602 const std::vector<std::string> & var_names,
4603 const std::vector<std::set<boundary_id_type>> & node_boundary_ids,
4604 const std::vector<std::map<BoundaryInfo::NodeBCTuple, Real>> & bc_vals)
4605{
4606 LOG_SCOPE("write_nodeset_data()", "ExodusII_IO_Helper");
4607
4608 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4609 return;
4610
4611 // Write the nodeset variable names to file. This function should
4612 // only be called once for NODESET variables, repeated calls to
4613 // write_var_names() overwrites/changes the order of names that were
4614 // there previously, and will mess up any data that has already been
4615 // written.
4616 this->write_var_names(NODESET, var_names);
4617
4618 // For all nodesets, reads and fills in the arrays:
4619 // nodeset_ids
4620 // num_nodes_per_set
4621 // node_sets_node_index - starting index for each nodeset in the node_sets_node_list vector
4622 // node_sets_node_list
4623 // Note: we need these arrays so that we know what data to write
4624 this->read_all_nodesets();
4625
4626 // The "truth" table for nodeset variables. nset_var_tab is a
4627 // logically (num_node_sets x num_nset_var) integer array of 0s and
4628 // 1s indicating which nodesets a given nodeset variable is defined
4629 // on.
4630 std::vector<int> nset_var_tab(num_node_sets * var_names.size());
4631
4632 for (int ns=0; ns<num_node_sets; ++ns)
4633 {
4634 // The offset into the node_sets_node_list for the current nodeset
4635 int offset = node_sets_node_index[ns];
4636
4637 // For each variable in var_names, write the values for the
4638 // current nodeset, if any.
4639 for (auto var : index_range(var_names))
4640 {
4641 // If this var has no values on this nodeset, go to the next one.
4642 if (!node_boundary_ids[var].count(nodeset_ids[ns]))
4643 continue;
4644
4645 // Otherwise, fill in this entry of the nodeset truth table.
4646 nset_var_tab[ns*var_names.size() + var] = 1;
4647
4648 // Data vector that will eventually be passed to exII::ex_put_var().
4649 std::vector<Real> nset_var_vals(num_nodes_per_set[ns]);
4650
4651 // Get reference to the NodeBCTuple -> Real map for this variable.
4652 const auto & data_map = bc_vals[var];
4653
4654 // Loop over entries in current nodeset.
4655 for (int i=0; i<num_nodes_per_set[ns]; ++i)
4656 {
4657 // Here we convert Exodus node ids to libMesh node ids by
4658 // subtracting 1. We should probably use the
4659 // exodus_node_num_to_libmesh data structure for this, but
4660 // I don't think it is set up at the time when
4661 // write_nodeset_data() would normally be called.
4662 dof_id_type libmesh_node_id = node_sets_node_list[i + offset] - 1;
4663
4664 // Construct a key to look up values in data_map.
4666 std::make_tuple(libmesh_node_id, nodeset_ids[ns]);
4667
4668 // We require that the user provided either no values for
4669 // this (var, nodeset) combination (in which case we don't
4670 // reach this point) or a value for _every_ node in this
4671 // nodeset for this var, so we use the libmesh_map_find()
4672 // macro to check for this.
4673 nset_var_vals[i] = libmesh_map_find(data_map, key);
4674 } // end for (node in nodeset[ns])
4675
4676 // Write nodeset values to Exodus file
4677 if (nset_var_vals.size() > 0)
4678 {
4679 ex_err = exII::ex_put_var
4680 (ex_id,
4681 timestep,
4682 exII::EX_NODE_SET,
4683 var + 1, // 1-based variable index of current variable
4684 nodeset_ids[ns],
4686 MappedOutputVector(nset_var_vals, _single_precision).data());
4687 EX_CHECK_ERR(ex_err, "Error writing nodeset vars.");
4688 }
4689 } // end for (var in var_names)
4690 } // end for (ns)
4691
4692 // Finally, write the nodeset truth table.
4693 ex_err =
4694 exII::ex_put_truth_table(ex_id,
4695 exII::EX_NODE_SET,
4697 cast_int<int>(var_names.size()),
4698 nset_var_tab.data());
4699 EX_CHECK_ERR(ex_err, "Error writing nodeset var truth table.");
4700}
4701
4702
4703
4704void
4706write_elemset_data (int timestep,
4707 const std::vector<std::string> & var_names,
4708 const std::vector<std::set<elemset_id_type>> & elemset_ids_in,
4709 const std::vector<std::map<std::pair<dof_id_type, elemset_id_type>, Real>> & elemset_vals)
4710{
4711 LOG_SCOPE("write_elemset_data()", "ExodusII_IO_Helper");
4712
4713 if ((_run_only_on_proc0) && (this->processor_id() != 0))
4714 return;
4715
4716 // Write the elemset variable names to file. This function should
4717 // only be called once for ELEMSET variables, repeated calls to
4718 // write_var_names() overwrites/changes the order of names that were
4719 // there previously, and will mess up any data that has already been
4720 // written.
4721 this->write_var_names(ELEMSET, var_names);
4722
4723 // We now call the API to read the elemset info even though we are
4724 // in the middle of writing. This is a bit counter-intuitive, but it
4725 // seems to work provided that you have already written the mesh
4726 // itself... read_elemset_info() fills in the following data
4727 // members:
4728 // .) id_to_elemset_names
4729 // .) num_elems_per_set
4730 // .) num_elem_df_per_set
4731 // .) elemset_list
4732 // .) elemset_id_list
4733 // .) id_to_elemset_names
4734 this->read_elemset_info();
4735
4736 // The "truth" table for elemset variables. elemset_var_tab is a
4737 // logically (num_elem_sets x num_elemset_vars) integer array of 0s and
4738 // 1s indicating which elemsets a given elemset variable is defined
4739 // on.
4740 std::vector<int> elemset_var_tab(num_elem_sets * var_names.size());
4741
4742 int offset=0;
4743 for (int es=0; es<num_elem_sets; ++es)
4744 {
4745 // Debugging
4746 // libMesh::out << "Writing elemset variable values for elemset "
4747 // << es << ", elemset_id = " << elemset_ids[es]
4748 // << std::endl;
4749
4750 // We know num_elems_per_set because we called read_elemset_info() above.
4751 offset += (es > 0 ? num_elems_per_set[es-1] : 0);
4752 this->read_elemset(es, offset);
4753
4754 // For each variable in var_names, write the values for the
4755 // current elemset, if any.
4756 for (auto var : index_range(var_names))
4757 {
4758 // Debugging
4759 // libMesh::out << "Writing elemset variable values for var " << var << std::endl;
4760
4761 // If this var has no values on this elemset, go to the next one.
4762 if (!elemset_ids_in[var].count(elemset_ids[es]))
4763 continue;
4764
4765 // Otherwise, fill in this entry of the nodeset truth table.
4766 elemset_var_tab[es*var_names.size() + var] = 1;
4767
4768 // Data vector that will eventually be passed to exII::ex_put_var().
4769 std::vector<Real> elemset_var_vals(num_elems_per_set[es]);
4770
4771 // Get reference to the (elem_id, elemset_id) -> Real map for this variable.
4772 const auto & data_map = elemset_vals[var];
4773
4774 // Loop over entries in current elemset.
4775 for (int i=0; i<num_elems_per_set[es]; ++i)
4776 {
4777 // Here we convert Exodus elem ids to libMesh node ids
4778 // simply by subtracting 1. We should probably use the
4779 // exodus_elem_num_to_libmesh data structure for this,
4780 // but I don't think it is set up at the time when this
4781 // function is normally called.
4782 dof_id_type libmesh_elem_id = elemset_list[i + offset] - 1;
4783
4784 // Construct a key to look up values in data_map.
4785 std::pair<dof_id_type, elemset_id_type> key =
4786 std::make_pair(libmesh_elem_id, elemset_ids[es]);
4787
4788 // Debugging:
4789 // libMesh::out << "Searching for key = (" << key.first << ", " << key.second << ")" << std::endl;
4790
4791 // We require that the user provided either no values for
4792 // this (var, elemset) combination (in which case we don't
4793 // reach this point) or a value for _every_ elem in this
4794 // elemset for this var, so we use the libmesh_map_find()
4795 // macro to check for this.
4796 elemset_var_vals[i] = libmesh_map_find(data_map, key);
4797 } // end for (node in nodeset[ns])
4798
4799 // Write elemset values to Exodus file
4800 if (elemset_var_vals.size() > 0)
4801 {
4802 ex_err = exII::ex_put_var
4803 (ex_id,
4804 timestep,
4805 exII::EX_ELEM_SET,
4806 var + 1, // 1-based variable index of current variable
4807 elemset_ids[es],
4809 MappedOutputVector(elemset_var_vals, _single_precision).data());
4810 EX_CHECK_ERR(ex_err, "Error writing elemset vars.");
4811 }
4812 } // end for (var in var_names)
4813 } // end for (ns)
4814
4815 // Finally, write the elemset truth table to file.
4816 ex_err =
4817 exII::ex_put_truth_table(ex_id,
4818 exII::EX_ELEM_SET, // exII::ex_entity_type
4820 cast_int<int>(var_names.size()),
4821 elemset_var_tab.data());
4822 EX_CHECK_ERR(ex_err, "Error writing elemset var truth table.");
4823}
4824
4825
4826
4827void
4829read_elemset_data (int timestep,
4830 std::vector<std::string> & var_names,
4831 std::vector<std::set<elemset_id_type>> & elemset_ids_in,
4832 std::vector<std::map<std::pair<dof_id_type, elemset_id_type>, Real>> & elemset_vals)
4833{
4834 LOG_SCOPE("read_elemset_data()", "ExodusII_IO_Helper");
4835
4836 // This reads the elemset variable names into the local
4837 // elemset_var_names data structure.
4838 this->read_var_names(ELEMSET);
4839
4840 // Debugging
4841 // libMesh::out << "elmeset variable names:" << std::endl;
4842 // for (const auto & name : elemset_var_names)
4843 // libMesh::out << name << " ";
4844 // libMesh::out << std::endl;
4845
4846 if (num_elemset_vars)
4847 {
4848 // Debugging
4849 // std::cout << "Reading " << num_elem_sets
4850 // << " elemsets and " << num_elemset_vars
4851 // << " elemset variables." << std::endl;
4852
4853 // Read the elemset data truth table.
4854 std::vector<int> elemset_var_tab(num_elem_sets * num_elemset_vars);
4855 exII::ex_get_truth_table(ex_id,
4856 exII::EX_ELEM_SET, // exII::ex_entity_type
4859 elemset_var_tab.data());
4860 EX_CHECK_ERR(ex_err, "Error reading elemset variable truth table.");
4861
4862 // Debugging
4863 // libMesh::out << "Elemset variable truth table:" << std::endl;
4864 // for (const auto & val : elemset_var_tab)
4865 // libMesh::out << val << " ";
4866 // libMesh::out << std::endl;
4867
4868 // Debugging
4869 // for (auto i : make_range(num_elem_sets))
4870 // {
4871 // for (auto j : make_range(num_elemset_vars))
4872 // libMesh::out << elemset_var_tab[num_elemset_vars*i + j] << " ";
4873 // libMesh::out << std::endl;
4874 // }
4875
4876 // Set up/allocate space in incoming data structures. All vectors are
4877 // num_elemset_vars in length.
4878 var_names = elemset_var_names;
4879 elemset_ids_in.resize(num_elemset_vars);
4880 elemset_vals.resize(num_elemset_vars);
4881
4882 // Read the elemset data
4883 int offset=0;
4884 for (int es=0; es<num_elem_sets; ++es)
4885 {
4886 offset += (es > 0 ? num_elems_per_set[es-1] : 0);
4887 for (int var=0; var<num_elemset_vars; ++var)
4888 {
4889 int is_present = elemset_var_tab[num_elemset_vars*es + var];
4890
4891 if (is_present)
4892 {
4893 // Debugging
4894 // libMesh::out << "Variable " << var << " is present on elemset " << es << std::endl;
4895
4896 // Record the fact that this variable is defined on this elemset.
4897 elemset_ids_in[var].insert(elemset_ids[es]);
4898
4899 // Note: the assumption here is that a previous call
4900 // to this->read_elemset_info() has already set the
4901 // values of num_elems_per_set, so we just use those values here.
4902 std::vector<Real> elemset_var_vals(num_elems_per_set[es]);
4903 ex_err = exII::ex_get_var
4904 (ex_id,
4905 timestep,
4906 exII::EX_ELEM_SET, // exII::ex_entity_type
4907 var + 1, // 1-based sideset variable index!
4908 elemset_ids[es],
4910 MappedInputVector(elemset_var_vals, _single_precision).data());
4911 EX_CHECK_ERR(ex_err, "Error reading elemset variable.");
4912
4913 for (int i=0; i<num_elems_per_set[es]; ++i)
4914 {
4915 dof_id_type exodus_elem_id = elemset_list[i + offset];
4916
4917 // FIXME: We should use exodus_elem_num_to_libmesh for this,
4918 // but it apparently is never set up, so just
4919 // subtract 1 from the Exodus elem id.
4920 dof_id_type converted_elem_id = exodus_elem_id - 1;
4921
4922 // Make key based on the elem and set ids
4923 auto key = std::make_pair(converted_elem_id,
4924 static_cast<elemset_id_type>(elemset_ids[es]));
4925
4926 // Store value in the map
4927 elemset_vals[var].emplace(key, elemset_var_vals[i]);
4928 } // end for (i)
4929 } // end if (present)
4930 } // end for (var)
4931 } // end for (es)
4932 } // end if (num_elemset_vars)
4933}
4934
4935
4936
4938get_elemset_data_indices (std::map<std::pair<dof_id_type, elemset_id_type>, unsigned int> & elemset_array_indices)
4939{
4940 // Clear existing data, we are going to build these data structures from scratch
4941 elemset_array_indices.clear();
4942
4943 // Read the elemset data.
4944 //
4945 // Note: we assume that the functions
4946 // 1.) this->read_elemset_info() and
4947 // 2.) this->read_elemset()
4948 // have already been called, so that we already know e.g. how
4949 // many elems are in each set, their ids, etc.
4950 int offset=0;
4951 for (int es=0; es<num_elem_sets; ++es)
4952 {
4953 offset += (es > 0 ? num_elems_per_set[es-1] : 0);
4954
4955 // Note: we don't actually call exII::ex_get_var() here because
4956 // we don't need the values. We only need the indices into that vector
4957 // for each (elem_id, elemset_id) tuple.
4958 for (int i=0; i<num_elems_per_set[es]; ++i)
4959 {
4960 dof_id_type exodus_elem_id = elemset_list[i + offset];
4961
4962 // FIXME: We should use exodus_elem_num_to_libmesh for this,
4963 // but it apparently is never set up, so just
4964 // subtract 1 from the Exodus elem id.
4965 dof_id_type converted_elem_id = exodus_elem_id - 1;
4966
4967 // Make key based on the elem and set ids
4968 // Make a NodeBCTuple key from the converted information.
4969 auto key = std::make_pair(converted_elem_id,
4970 static_cast<elemset_id_type>(elemset_ids[es]));
4971
4972 // Store the array index of this (node, b_id) tuple
4973 elemset_array_indices.emplace(key, cast_int<unsigned int>(i));
4974 } // end for (i)
4975 } // end for (es)
4976}
4977
4978
4979
4981read_nodeset_data (int timestep,
4982 std::vector<std::string> & var_names,
4983 std::vector<std::set<boundary_id_type>> & node_boundary_ids,
4984 std::vector<std::map<BoundaryInfo::NodeBCTuple, Real>> & bc_vals)
4985{
4986 LOG_SCOPE("read_nodeset_data()", "ExodusII_IO_Helper");
4987
4988 // This reads the sideset variable names into the local
4989 // sideset_var_names data structure.
4990 this->read_var_names(NODESET);
4991
4992 if (num_nodeset_vars)
4993 {
4994 // Read the nodeset data truth table
4995 std::vector<int> nset_var_tab(num_node_sets * num_nodeset_vars);
4996 ex_err = exII::ex_get_truth_table
4997 (ex_id,
4998 exII::EX_NODE_SET,
5001 nset_var_tab.data());
5002 EX_CHECK_ERR(ex_err, "Error reading nodeset variable truth table.");
5003
5004 // Set up/allocate space in incoming data structures.
5005 var_names = nodeset_var_names;
5006 node_boundary_ids.resize(num_nodeset_vars);
5007 bc_vals.resize(num_nodeset_vars);
5008
5009 // Read the nodeset data.
5010 //
5011 // Note: we assume that the functions
5012 // 1.) this->read_nodeset_info() and
5013 // 2.) this->read_all_nodesets()
5014 // have already been called, so that we already know e.g. how
5015 // many nodes are in each set, their ids, etc.
5016 //
5017 // TODO: As a future optimization, we could read only the values
5018 // requested by the user by looking at the input parameter
5019 // var_names and checking whether it already has entries in
5020 // it.
5021 int offset=0;
5022 for (int ns=0; ns<num_node_sets; ++ns)
5023 {
5024 offset += (ns > 0 ? num_nodes_per_set[ns-1] : 0);
5025 for (int var=0; var<num_nodeset_vars; ++var)
5026 {
5027 int is_present = nset_var_tab[num_nodeset_vars*ns + var];
5028
5029 if (is_present)
5030 {
5031 // Record the fact that this variable is defined on this nodeset.
5032 node_boundary_ids[var].insert(nodeset_ids[ns]);
5033
5034 // Note: the assumption here is that a previous call
5035 // to this->read_nodeset_info() has already set the
5036 // values of num_nodes_per_set, so we just use those values here.
5037 std::vector<Real> nset_var_vals(num_nodes_per_set[ns]);
5038 ex_err = exII::ex_get_var
5039 (ex_id,
5040 timestep,
5041 exII::EX_NODE_SET,
5042 var + 1, // 1-based nodeset variable index!
5043 nodeset_ids[ns],
5045 MappedInputVector(nset_var_vals, _single_precision).data());
5046 EX_CHECK_ERR(ex_err, "Error reading nodeset variable.");
5047
5048 for (int i=0; i<num_nodes_per_set[ns]; ++i)
5049 {
5050 // The read_all_nodesets() function now reads all the node ids into the
5051 // node_sets_node_list vector, which is of length "total_nodes_in_all_sets"
5052 // The old read_nodset() function is no longer called as far as I can tell,
5053 // and should probably be removed? The "offset" that we are using only
5054 // depends on the current nodeset index and the num_nodes_per_set vector,
5055 // which gets filled in by the call to read_all_nodesets().
5056 dof_id_type exodus_node_id = node_sets_node_list[i + offset];
5057
5058 // FIXME: We should use exodus_node_num_to_libmesh for this,
5059 // but it apparently is never set up, so just
5060 // subtract 1 from the Exodus node id.
5061 dof_id_type converted_node_id = exodus_node_id - 1;
5062
5063 // Make a NodeBCTuple key from the converted information.
5064 BoundaryInfo::NodeBCTuple key = std::make_tuple
5065 (converted_node_id, nodeset_ids[ns]);
5066
5067 // Store (node, b_id) tuples in bc_vals[var]
5068 bc_vals[var].emplace(key, nset_var_vals[i]);
5069 } // end for (i)
5070 } // end if (present)
5071 } // end for (var)
5072 } // end for (ns)
5073 } // end if (num_nodeset_vars)
5074}
5075
5076
5077
5078void
5080get_nodeset_data_indices (std::map<BoundaryInfo::NodeBCTuple, unsigned int> & bc_array_indices)
5081{
5082 // Clear existing data, we are going to build these data structures from scratch
5083 bc_array_indices.clear();
5084
5085 // Read the nodeset data.
5086 //
5087 // Note: we assume that the functions
5088 // 1.) this->read_nodeset_info() and
5089 // 2.) this->read_all_nodesets()
5090 // have already been called, so that we already know e.g. how
5091 // many nodes are in each set, their ids, etc.
5092 int offset=0;
5093 for (int ns=0; ns<num_node_sets; ++ns)
5094 {
5095 offset += (ns > 0 ? num_nodes_per_set[ns-1] : 0);
5096 // Note: we don't actually call exII::ex_get_var() here because
5097 // we don't need the values. We only need the indices into that vector
5098 // for each (node_id, boundary_id) tuple.
5099 for (int i=0; i<num_nodes_per_set[ns]; ++i)
5100 {
5101 // The read_all_nodesets() function now reads all the node ids into the
5102 // node_sets_node_list vector, which is of length "total_nodes_in_all_sets"
5103 // The old read_nodset() function is no longer called as far as I can tell,
5104 // and should probably be removed? The "offset" that we are using only
5105 // depends on the current nodeset index and the num_nodes_per_set vector,
5106 // which gets filled in by the call to read_all_nodesets().
5107 dof_id_type exodus_node_id = node_sets_node_list[i + offset];
5108
5109 // FIXME: We should use exodus_node_num_to_libmesh for this,
5110 // but it apparently is never set up, so just
5111 // subtract 1 from the Exodus node id.
5112 dof_id_type converted_node_id = exodus_node_id - 1;
5113
5114 // Make a NodeBCTuple key from the converted information.
5115 BoundaryInfo::NodeBCTuple key = std::make_tuple
5116 (converted_node_id, nodeset_ids[ns]);
5117
5118 // Store the array index of this (node, b_id) tuple
5119 bc_array_indices.emplace(key, cast_int<unsigned int>(i));
5120 } // end for (i)
5121 } // end for (ns)
5122}
5123
5125(const MeshBase & mesh,
5126 const std::vector<Real> & values,
5127 int timestep,
5128 const std::vector<std::set<subdomain_id_type>> & vars_active_subdomains)
5129{
5130 LOG_SCOPE("write_element_values()", "ExodusII_IO_Helper");
5131
5132 if ((_run_only_on_proc0) && (this->processor_id() != 0))
5133 return;
5134
5135 // Ask the file how many element vars it has, store it in the num_elem_vars variable.
5136 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_ELEM_BLOCK, &num_elem_vars);
5137 EX_CHECK_ERR(ex_err, "Error reading number of elemental variables.");
5138
5139 // We will eventually loop over the element blocks (subdomains) and
5140 // write the data one block at a time. Build a data structure that
5141 // maps each subdomain to a list of element ids it contains.
5142 std::map<subdomain_id_type, std::vector<unsigned int>> subdomain_map;
5143 for (const auto & elem : mesh.active_element_ptr_range())
5144 subdomain_map[elem->subdomain_id()].push_back(elem->id());
5145
5146 // Use mesh.n_elem() to access into the values vector rather than
5147 // the number of elements the Exodus writer thinks the mesh has,
5148 // which may not include inactive elements.
5149 dof_id_type n_elem = mesh.n_elem();
5150
5151 // Sanity check: we must have an entry in vars_active_subdomains for
5152 // each variable that we are potentially writing out.
5153 libmesh_assert_equal_to
5154 (vars_active_subdomains.size(),
5155 static_cast<unsigned>(num_elem_vars));
5156
5157 // For each variable, create a 'data' array which holds all the elemental variable
5158 // values *for a given block* on this processor, then write that data vector to file
5159 // before moving onto the next block.
5160 for (unsigned int var_id=0; var_id<static_cast<unsigned>(num_elem_vars); ++var_id)
5161 {
5162 // The size of the subdomain map is the number of blocks.
5163 auto it = subdomain_map.begin();
5164
5165 // Reference to the set of active subdomains for the current variable.
5166 const auto & active_subdomains
5167 = vars_active_subdomains[var_id];
5168
5169 for (unsigned int j=0; it!=subdomain_map.end(); ++it, ++j)
5170 {
5171 // Skip any variable/subdomain pairs that are inactive.
5172 // Note that if active_subdomains is empty, it is interpreted
5173 // as being active on *all* subdomains.
5174 if (!(active_subdomains.empty() || active_subdomains.count(it->first)))
5175 continue;
5176
5177 // Get reference to list of elem ids which are in the
5178 // current subdomain and count, allocate storage to hold
5179 // data that will be written to file.
5180 const auto & elem_nums = it->second;
5181 const unsigned int num_elems_this_block =
5182 cast_int<unsigned int>(elem_nums.size());
5183 std::vector<Real> data(num_elems_this_block);
5184
5185 // variable-major ordering is:
5186 // (u1, u2, u3, ..., uN), (v1, v2, v3, ..., vN), ...
5187 // where N is the number of elements.
5188 for (unsigned int k=0; k<num_elems_this_block; ++k)
5189 data[k] = values[var_id*n_elem + elem_nums[k]];
5190
5191 ex_err = exII::ex_put_var
5192 (ex_id,
5193 timestep,
5194 exII::EX_ELEM_BLOCK,
5195 var_id+1,
5196 this->get_block_id(j),
5197 num_elems_this_block,
5199
5200 EX_CHECK_ERR(ex_err, "Error writing element values.");
5201 }
5202 }
5203
5204 this->update();
5205}
5206
5207
5208
5210(const MeshBase & mesh,
5211 const std::vector<Real> & values,
5212 int timestep,
5213 const std::vector<std::set<subdomain_id_type>> & vars_active_subdomains,
5214 const std::vector<std::string> & derived_var_names,
5215 const std::map<subdomain_id_type, std::vector<std::string>> & subdomain_to_var_names)
5216{
5217 if ((_run_only_on_proc0) && (this->processor_id() != 0))
5218 return;
5219
5220 // Ask the file how many element vars it has, store it in the num_elem_vars variable.
5221 ex_err = exII::ex_get_variable_param(ex_id, exII::EX_ELEM_BLOCK, &num_elem_vars);
5222 EX_CHECK_ERR(ex_err, "Error reading number of elemental variables.");
5223
5224 // We will eventually loop over the element blocks (subdomains) and
5225 // write the data one block (subdomain) at a time. Build a data
5226 // structure that keeps track of how many elements are in each
5227 // subdomain. This will allow us to reserve space in the data vector
5228 // we are going to write.
5229 std::map<subdomain_id_type, unsigned int> subdomain_to_n_elem;
5230 for (const auto & elem : mesh.active_element_ptr_range())
5231 subdomain_to_n_elem[elem->subdomain_id()] += 1;
5232
5233 // Sanity check: we must have an entry in vars_active_subdomains for
5234 // each variable that we are potentially writing out.
5235 libmesh_assert_equal_to
5236 (vars_active_subdomains.size(),
5237 static_cast<unsigned>(num_elem_vars));
5238
5239 // The size of the subdomain map is the number of blocks.
5240 auto subdomain_to_n_elem_iter = subdomain_to_n_elem.begin();
5241
5242 // Store range of active Elem pointers. We are going to loop over
5243 // the elements n_vars * n_subdomains times, so let's make sure
5244 // the predicated iterators aren't slowing us down too much.
5245 ConstElemRange elem_range
5246 (mesh.active_elements_begin(),
5247 mesh.active_elements_end());
5248
5249 for (unsigned int sbd_idx=0;
5250 subdomain_to_n_elem_iter != subdomain_to_n_elem.end();
5251 ++subdomain_to_n_elem_iter, ++sbd_idx)
5252 for (unsigned int var_id=0; var_id<static_cast<unsigned>(num_elem_vars); ++var_id)
5253 {
5254 // Reference to the set of active subdomains for the current variable.
5255 const auto & active_subdomains
5256 = vars_active_subdomains[var_id];
5257
5258 // If the vars_active_subdomains container passed to this function
5259 // has an empty entry, it means the variable really is not active on
5260 // _any_ subdomains, not that it is active on _all_ subdomains. This
5261 // is just due to the way that we build the vars_active_subdomains
5262 // container.
5263 if (!active_subdomains.count(subdomain_to_n_elem_iter->first))
5264 continue;
5265
5266 // Vector to hold values that will be written to Exodus file.
5267 std::vector<Real> data;
5268 data.reserve(subdomain_to_n_elem_iter->second);
5269
5270 unsigned int values_offset = 0;
5271 for (auto & elem : elem_range)
5272 {
5273 // We'll use the Elem's subdomain id in several places below.
5274 subdomain_id_type sbd_id = elem->subdomain_id();
5275
5276 // Get reference to the list of variable names defining
5277 // the indexing for the current Elem's subdomain.
5278 auto subdomain_to_var_names_iter =
5279 subdomain_to_var_names.find(sbd_id);
5280
5281 // It's possible, but unusual, for there to be an Elem
5282 // from a subdomain that has no active variables from the
5283 // set of variables we are currently writing. If that
5284 // happens, we can just go to the next Elem because we
5285 // don't need to advance the offset into the values
5286 // vector, etc.
5287 if (subdomain_to_var_names_iter == subdomain_to_var_names.end())
5288 continue;
5289
5290 const auto & var_names_this_sbd
5291 = subdomain_to_var_names_iter->second;
5292
5293 // Only extract values if Elem is in the current subdomain.
5294 if (sbd_id == subdomain_to_n_elem_iter->first)
5295 {
5296 // Location of current var_id in the list of all variables on this
5297 // subdomain. FIXME: linear search but it's over a typically relatively
5298 // short vector of active variable names on this subdomain. We could do
5299 // a nested std::map<string,index> instead of a std::vector where the
5300 // location of the string is implicitly the index..
5301 auto pos =
5302 std::find(var_names_this_sbd.begin(),
5303 var_names_this_sbd.end(),
5304 derived_var_names[var_id]);
5305
5306 libmesh_error_msg_if(pos == var_names_this_sbd.end(),
5307 "Derived name " << derived_var_names[var_id] << " not found!");
5308
5309 // Find the current variable's location in the list of all variable
5310 // names on the current Elem's subdomain.
5311 auto true_index =
5312 std::distance(var_names_this_sbd.begin(), pos);
5313
5314 data.push_back(values[values_offset + true_index]);
5315 }
5316
5317 // The "true" offset is how much we have to advance the index for each Elem
5318 // in this subdomain.
5319 auto true_offset = var_names_this_sbd.size();
5320
5321 // Increment to the next Elem's values
5322 values_offset += true_offset;
5323 } // for elem
5324
5325 // Now write 'data' to Exodus file, in single precision if requested.
5326 if (!data.empty())
5327 {
5328 ex_err = exII::ex_put_var
5329 (ex_id,
5330 timestep,
5331 exII::EX_ELEM_BLOCK,
5332 var_id+1,
5333 this->get_block_id(sbd_idx),
5334 data.size(),
5336
5337 EX_CHECK_ERR(ex_err, "Error writing element values.");
5338 }
5339 } // for each var_id
5340
5341 this->update();
5342}
5343
5344
5345
5346void
5348 const std::vector<Real> & values,
5349 int timestep)
5350{
5351 if ((_run_only_on_proc0) && (this->processor_id() != 0))
5352 return;
5353
5354 if (!values.empty())
5355 {
5356 libmesh_assert_equal_to(values.size(), std::size_t(num_nodes));
5357
5358 ex_err = exII::ex_put_var
5359 (ex_id,
5360 timestep,
5361 exII::EX_NODAL,
5362 var_id,
5363 1, // exII::ex_entity_id, not sure exactly what this is but in the ex_put_nodal_var.c shim, they pass 1
5364 num_nodes,
5365 MappedOutputVector(values, _single_precision).data());
5366
5367 EX_CHECK_ERR(ex_err, "Error writing nodal values.");
5368
5369 this->update();
5370 }
5371}
5372
5373
5374
5375void ExodusII_IO_Helper::write_information_records(const std::vector<std::string> & records)
5376{
5377 if ((_run_only_on_proc0) && (this->processor_id() != 0))
5378 return;
5379
5380 // There may already be information records in the file (for
5381 // example, if we're appending) and in that case, according to the
5382 // Exodus documentation, writing more information records is not
5383 // supported.
5384 int num_info = inquire(*this, exII::EX_INQ_INFO, "Error retrieving the number of information records from file!");
5385 if (num_info > 0)
5386 {
5387 libMesh::err << "Warning! The Exodus file already contains information records.\n"
5388 << "Exodus does not support writing additional records in this situation."
5389 << std::endl;
5390 return;
5391 }
5392
5393 int num_records = cast_int<int>(records.size());
5394
5395 if (num_records > 0)
5396 {
5397 NamesData info(num_records, MAX_LINE_LENGTH);
5398
5399 // If an entry is longer than MAX_LINE_LENGTH characters it's not an error, we just
5400 // write the first MAX_LINE_LENGTH characters to the file.
5401 for (const auto & record : records)
5402 info.push_back_entry(record);
5403
5404 ex_err = exII::ex_put_info(ex_id, num_records, info.get_char_star_star());
5405 EX_CHECK_ERR(ex_err, "Error writing global values.");
5406
5407 this->update();
5408 }
5409}
5410
5411
5412
5413void ExodusII_IO_Helper::write_global_values(const std::vector<Real> & values, int timestep)
5414{
5415 if ((_run_only_on_proc0) && (this->processor_id() != 0))
5416 return;
5417
5418 if (!values.empty())
5419 {
5420 ex_err = exII::ex_put_var
5421 (ex_id,
5422 timestep,
5423 exII::EX_GLOBAL,
5424 1, // var index
5425 0, // obj_id (not used)
5427 MappedOutputVector(values, _single_precision).data());
5428
5429 EX_CHECK_ERR(ex_err, "Error writing global values.");
5430
5431 this->update();
5432 }
5433}
5434
5435
5436
5438{
5439 ex_err = exII::ex_update(ex_id);
5440 EX_CHECK_ERR(ex_err, "Error flushing buffers to file.");
5441}
5442
5443
5444
5445void ExodusII_IO_Helper::read_global_values(std::vector<Real> & values, int timestep)
5446{
5447 if ((_run_only_on_proc0) && (this->processor_id() != 0))
5448 return;
5449
5450 values.clear();
5451 values.resize(num_global_vars);
5452 ex_err = exII::ex_get_var
5453 (ex_id,
5454 timestep,
5455 exII::EX_GLOBAL,
5456 1, // var_index
5457 1, // obj_id
5459 MappedInputVector(values, _single_precision).data());
5460
5461 EX_CHECK_ERR(ex_err, "Error reading global values.");
5462}
5463
5464
5465
5470
5471
5473{
5474 _write_hdf5 = write_hdf5;
5475}
5476
5477
5478void ExodusII_IO_Helper::set_max_name_length(unsigned int max_length)
5479{
5480 // Opt mode error, because this may be exposed to users
5481 libmesh_error_msg_if (max_length > libmesh_max_str_length,
5482 "Exodus maximum name length is limited to " <<
5483 libmesh_max_str_length << " characters");
5484
5485 // Devel+dbg mode assertion, because developers should do better
5487
5488 _max_name_length = max_length;
5489}
5490
5491
5496
5497
5498
5503
5504
5505std::vector<std::string>
5506ExodusII_IO_Helper::get_complex_names(const std::vector<std::string> & names,
5507 bool write_complex_abs) const
5508{
5509 std::vector<std::string> complex_names;
5510
5511 // This will loop over all names and create new "complex" names
5512 // (i.e. names that start with r_, i_ or a_)
5513 for (const auto & name : names)
5514 {
5515 complex_names.push_back("r_" + name);
5516 complex_names.push_back("i_" + name);
5517 if (write_complex_abs)
5518 complex_names.push_back("a_" + name);
5519 }
5520
5521 return complex_names;
5522}
5523
5524
5525
5526std::vector<std::set<subdomain_id_type>>
5529(const std::vector<std::set<subdomain_id_type>> & vars_active_subdomains,
5530 bool write_complex_abs) const
5531{
5532 std::vector<std::set<subdomain_id_type>> complex_vars_active_subdomains;
5533
5534 for (auto & s : vars_active_subdomains)
5535 {
5536 // Push back the same data enough times for the real, imag, (and
5537 // possibly modulus) for the complex-valued solution.
5538 complex_vars_active_subdomains.push_back(s);
5539 complex_vars_active_subdomains.push_back(s);
5540 if (write_complex_abs)
5541 complex_vars_active_subdomains.push_back(s);
5542 }
5543
5544 return complex_vars_active_subdomains;
5545}
5546
5547
5548
5549std::map<subdomain_id_type, std::vector<std::string>>
5552(const std::map<subdomain_id_type, std::vector<std::string>> & subdomain_to_var_names,
5553 bool write_complex_abs) const
5554{
5555 // Eventual return value
5556 std::map<subdomain_id_type, std::vector<std::string>> ret;
5557
5558 unsigned int num_complex_outputs = write_complex_abs ? 3 : 2;
5559
5560 for (const auto & pr : subdomain_to_var_names)
5561 {
5562 // Initialize entry for current subdomain
5563 auto & vec = ret[pr.first];
5564
5565 // Get list of non-complex variable names active on this subdomain.
5566 const auto & varnames = pr.second;
5567
5568 // Allocate space for the complex-valued entries
5569 vec.reserve(num_complex_outputs * varnames.size());
5570
5571 // For each varname in the input map, write three variable names
5572 // to the output formed by prepending "r_", "i_", and "a_",
5573 // respectively.
5574 for (const auto & varname : varnames)
5575 {
5576 vec.push_back("r_" + varname);
5577 vec.push_back("i_" + varname);
5578 if (write_complex_abs)
5579 vec.push_back("a_" + varname);
5580 }
5581 }
5582 return ret;
5583}
5584
5585
5586
5588{
5589 if (!node_map)
5590 return i;
5591
5592 libmesh_assert_less (i, node_map->size());
5593 return (*node_map)[i];
5594}
5595
5596
5597
5599{
5600 if (!inverse_node_map)
5601 return i;
5602
5603 libmesh_assert_less (i, inverse_node_map->size());
5604 return (*inverse_node_map)[i];
5605}
5606
5607
5608
5610{
5611 if (!side_map)
5612 return i;
5613
5614 // If we asked for a side that doesn't exist, return an invalid_id
5615 // and allow higher-level code to handle it.
5616 if (static_cast<size_t>(i) >= side_map->size())
5617 return invalid_id;
5618
5619 return (*side_map)[i];
5620}
5621
5622
5623
5625{
5626 // For identity side mappings, we our convention is to return a 1-based index.
5627 if (!inverse_side_map)
5628 return i + 1;
5629
5630 libmesh_assert_less (i, inverse_side_map->size());
5631 return (*inverse_side_map)[i];
5632}
5633
5634
5635
5641{
5642 if (!shellface_map)
5643 return i;
5644
5645 libmesh_assert_less (i, shellface_map->size());
5646 return (*shellface_map)[i];
5647}
5648
5649
5650
5652{
5653 if (!inverse_shellface_map)
5654 return i + 1;
5655
5656 libmesh_assert_less (i, inverse_shellface_map->size());
5657 return (*inverse_shellface_map)[i];
5658}
5659
5660
5661
5663{
5664 return libmesh_type;
5665}
5666
5667
5668
5670{
5671 return exodus_type;
5672}
5673
5674
5675
5680{
5681 return shellface_index_offset;
5682}
5683
5684ExodusII_IO_Helper::NamesData::NamesData(size_t n_strings, size_t string_length) :
5685 data_table(n_strings),
5686 data_table_pointers(n_strings),
5687 counter(0),
5688 table_size(n_strings)
5689{
5690 for (size_t i=0; i<n_strings; ++i)
5691 {
5692 data_table[i].resize(string_length + 1);
5693
5694 // Properly terminate these C-style strings, just to be safe.
5695 data_table[i][0] = '\0';
5696
5697 // Set pointer into the data_table
5698 data_table_pointers[i] = data_table[i].data();
5699 }
5700}
5701
5702
5703
5705{
5706 libmesh_assert_less (counter, table_size);
5707
5708 // 1.) Copy the C++ string into the vector<char>...
5709 size_t num_copied = name.copy(data_table[counter].data(), data_table[counter].size()-1);
5710
5711 // 2.) ...And null-terminate it.
5712 data_table[counter][num_copied] = '\0';
5713
5714 // Go to next row
5715 ++counter;
5716}
5717
5718
5719
5721{
5722 return data_table_pointers.data();
5723}
5724
5725
5726
5728{
5729 libmesh_error_msg_if(static_cast<unsigned>(i) >= table_size,
5730 "Requested char * " << i << " but only have " << table_size << "!");
5731
5732 return data_table[i].data();
5733}
5734
5735
5736
5737} // namespace libMesh
5738
5739
5740
5741#endif // #ifdef LIBMESH_HAVE_EXODUS_API
unsigned int dim
void ErrorVector unsigned int
void max(const T &r, T &o, Request &req) const
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
The BoundaryInfo class contains information relevant to boundary conditions including storing faces,...
void add_edge(const dof_id_type elem, const unsigned short int edge, const boundary_id_type id)
Add edge edge of element number elem with boundary id id to the boundary information data structure.
std::tuple< dof_id_type, unsigned short int, boundary_id_type > BCTuple
Create a list of (element_id, side_id, boundary_id) tuples for relevant sides.
std::vector< BCTuple > build_side_list(BCTupleSortBy sort_by=BCTupleSortBy::ELEM_ID) const
std::size_t n_edge_conds() const
std::vector< NodeBCTuple > build_node_list(NodeBCTupleSortBy sort_by=NodeBCTupleSortBy::NODE_ID) const
const std::set< boundary_id_type > & get_edge_boundary_ids() const
std::vector< BCTuple > build_shellface_list() const
Create a list of (element_id, shellface_id, boundary_id) tuples for all relevant shellfaces.
const std::string & get_nodeset_name(boundary_id_type id) const
std::tuple< dof_id_type, boundary_id_type > NodeBCTuple
Create a list of (node_id, boundary_id) tuples for all relevant nodes.
const std::string & get_sideset_name(boundary_id_type id) const
std::string & edgeset_name(boundary_id_type id)
const std::string & get_edgeset_name(boundary_id_type id) const
void build_node_boundary_ids(std::vector< boundary_id_type > &b_ids) const
Builds the list of unique node boundary ids.
void build_side_boundary_ids(std::vector< boundary_id_type > &b_ids) const
Builds the list of unique side boundary ids.
std::vector< BCTuple > build_edge_list() const
Create a list of (element_id, edge_id, boundary_id) tuples for all relevant edges.
void build_shellface_boundary_ids(std::vector< boundary_id_type > &b_ids) const
Builds the list of unique shellface boundary ids.
const std::map< boundary_id_type, std::string > & get_nodeset_name_map() const
const std::map< boundary_id_type, std::string > & get_sideset_name_map() const
The DofObject defines an abstract base class for objects that have degrees of freedom associated with...
Definition dof_object.h:55
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...
processor_id_type processor_id() const
Definition dof_object.h:895
unique_id_type unique_id() const
Definition dof_object.h:849
dof_id_type id() const
Definition dof_object.h:833
void set_unique_id(unique_id_type new_id)
Sets the unique_id for this DofObject.
Definition dof_object.h:862
This is the base class from which all geometric element types are derived.
Definition elem.h:96
const Point & point(const unsigned int i) const
Definition elem.h:2583
virtual unsigned int n_nodes() const =0
SimpleRange< NodeRefIter > node_ref_range()
Returns a range with all nodes of an element, usable in range-based for loops.
Definition elem.h:2803
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition elem.C:382
virtual std::vector< unsigned int > nodes_on_side(const unsigned int) const =0
static ElemType first_order_equivalent_type(const ElemType et)
Definition elem.C:3201
virtual unsigned int local_edge_node(unsigned int edge, unsigned int edge_node) const =0
Similar to Elem::local_side_node(), but instead of a side id, takes an edge id and a node id on that ...
subdomain_id_type subdomain_id() const
Definition elem.h:2712
static constexpr unsigned int type_to_n_nodes_map[INVALID_ELEM]
This array maps the integer representation of the ElemType enum to the number of nodes in the element...
Definition elem.h:664
virtual std::unique_ptr< Elem > build_edge_ptr(const unsigned int i)=0
IntRange< unsigned short > edge_index_range() const
Definition elem.h:2830
virtual ElemType type() const =0
IntRange< unsigned short > node_index_range() const
Definition elem.h:2821
virtual ElemType side_type(const unsigned int s) const =0
virtual unsigned int n_sides() const =0
dof_id_type node_id(const unsigned int i) const
Definition elem.h:2605
virtual bool infinite() const =0
IntRange< unsigned short > side_index_range() const
Definition elem.h:2848
static bool redundant_added_side(const Elem &elem, unsigned int side)
This class is used as both an external data structure for passing around Exodus file header informati...
static const int invalid_id
An invalid_id that can be returned to signal failure in case something goes wrong.
int dim
The element dimension; useful since we don't seem to have a cheap way to look this up from ElemType.
int n_nodes
The number of nodes per element; useful likewise.
This class is useful for managing anything that requires a char ** input/output in ExodusII file.
void push_back_entry(const std::string &name)
Adds another name to the current data table.
char * get_char_star(int i)
Provide access to the i'th underlying char *.
std::vector< std::vector< char > > data_table
char ** get_char_star_star()
Provide access to the underlying C data table.
NamesData(size_t n_strings, size_t string_length)
Constructor.
This is the ExodusII_IO_Helper class.
void write_as_dimension(unsigned dim)
Sets the value of _write_as_dimension.
void read_nodeset_data(int timestep, std::vector< std::string > &var_names, std::vector< std::set< boundary_id_type > > &node_boundary_ids, std::vector< std::map< BoundaryInfo::NodeBCTuple, Real > > &bc_vals)
Read nodeset variables, if any, into the provided data structures.
void read_block_info()
Reads information for all of the blocks in the ExodusII mesh file.
void update()
Uses ex_update() to flush buffers to file.
void read_elemset_data(int timestep, std::vector< std::string > &var_names, std::vector< std::set< elemset_id_type > > &elemset_ids_in, std::vector< std::map< std::pair< dof_id_type, elemset_id_type >, Real > > &elemset_vals)
Read elemset variables, if any, into the provided data structures.
void write_var_names_impl(const char *var_type, int &count, const std::vector< std::string > &names)
write_var_names() dispatches to this function.
std::map< subdomain_id_type, std::vector< std::string > > get_complex_subdomain_to_var_names(const std::map< subdomain_id_type, std::vector< std::string > > &subdomain_to_var_names, bool write_complex_abs) const
Takes a map from subdomain id -> vector of active variable names as input and returns a corresponding...
void read_nodeset_info()
Reads information about all of the nodesets in the ExodusII mesh file.
void use_mesh_dimension_instead_of_spatial_dimension(bool val)
Sets the underlying value of the boolean flag _use_mesh_dimension_instead_of_spatial_dimension.
void write_sideset_data(const MeshBase &mesh, int timestep, const std::vector< std::string > &var_names, const std::vector< std::set< boundary_id_type > > &side_ids, const std::vector< std::map< BoundaryInfo::BCTuple, Real > > &bc_vals)
Write sideset data for the requested timestep.
virtual void write_nodal_coordinates(const MeshBase &mesh, bool use_discontinuous=false)
Writes the nodal coordinates contained in "mesh".
void read_node_num_map()
Reads the optional node_num_map from the ExodusII mesh file.
void read_time_steps()
Reads and stores the timesteps in the 'time_steps' array.
bool _add_sides
Set to true iff we want to write separate "side" elements too.
dof_id_type get_libmesh_id(int exodus_id, const std::vector< int > &num_map)
Internal implementation for the two sets of functions above.
std::vector< std::string > get_complex_names(const std::vector< std::string > &names, bool write_complex_abs) const
std::vector< std::string > nodal_var_names
void read_and_store_header_info()
Reads an ExodusII mesh file header, and stores required information on this object.
ExodusHeaderInfo read_header() const
Reads an ExodusII mesh file header, leaving this object's internal data structures unchanged.
virtual void write_sidesets(const MeshBase &mesh)
Writes the sidesets contained in "mesh".
std::map< int, std::string > id_to_ns_names
void read_elemental_var_values(std::string elemental_var_name, int time_step, std::map< dof_id_type, Real > &elem_var_value_map)
Reads elemental values for the variable 'elemental_var_name' at the specified timestep into the 'elem...
std::vector< std::set< subdomain_id_type > > get_complex_vars_active_subdomains(const std::vector< std::set< subdomain_id_type > > &vars_active_subdomains, bool write_complex_abs) const
returns a "tripled" copy of vars_active_subdomains, which is necessary in the complex-valued case.
std::vector< int > num_elem_df_per_set
void write_elemset_data(int timestep, const std::vector< std::string > &var_names, const std::vector< std::set< elemset_id_type > > &elemset_ids_in, const std::vector< std::map< std::pair< dof_id_type, elemset_id_type >, Real > > &elemset_vals)
Write elemset data for the requested timestep.
int get_node_set_id(int index)
Get the node set id for the given node set index.
std::map< dof_id_type, Real > nodal_var_values
void write_nodeset_data(int timestep, const std::vector< std::string > &var_names, const std::vector< std::set< boundary_id_type > > &node_boundary_ids, const std::vector< std::map< BoundaryInfo::NodeBCTuple, Real > > &bc_vals)
Write nodeset data for the requested timestep.
virtual void initialize_element_variables(std::vector< std::string > names, const std::vector< std::set< subdomain_id_type > > &vars_active_subdomains)
Sets up the nodal variables.
void close() noexcept
Closes the ExodusII mesh file.
void read_qa_records()
Reads the QA records from an ExodusII file.
void write_element_values_element_major(const MeshBase &mesh, const std::vector< Real > &values, int timestep, const std::vector< std::set< subdomain_id_type > > &vars_active_subdomains, const std::vector< std::string > &derived_var_names, const std::map< subdomain_id_type, std::vector< std::string > > &subdomain_to_var_names)
Same as the function above, but assume the input 'values' vector is in element-major order,...
void read_all_nodesets()
New API that reads all nodesets simultaneously.
std::vector< int > num_nodes_per_set
std::vector< std::string > sideset_var_names
void read_bex_cv_blocks()
Reads the optional bex_cv_blocks from the ExodusII mesh file.
void get_sideset_data_indices(const MeshBase &mesh, std::map< BoundaryInfo::BCTuple, unsigned int > &bc_array_indices)
Similar to read_sideset_data(), but instead of creating one std::map per sideset per variable,...
void set_dof_object_unique_id(MeshBase &mesh, DofObject *dof_object, int exodus_mapped_id)
dof_id_type get_libmesh_elem_id(int exodus_elem_id)
void message(std::string_view msg)
Prints the message defined in msg.
virtual void write_nodesets(const MeshBase &mesh)
Writes the nodesets contained in "mesh".
std::string get_block_name(int index)
Get the block name for the given block index if supplied in the mesh file.
void write_nodal_values(int var_id, const std::vector< Real > &values, int timestep)
Writes the vector of values to a nodal variable.
void read_elemset_info()
Reads information about all of the elemsets in the ExodusII mesh file.
std::vector< std::vector< std::vector< Real > > > bex_dense_constraint_vecs
void read_nodes()
Reads the nodal data (x,y,z coordinates) from the ExodusII mesh file.
std::vector< int > node_sets_dist_index
ExodusII_IO_Helper(const ParallelObject &parent, bool v=false, bool run_only_on_proc0=true, bool single_precision=false)
Constructor.
virtual void read_var_names_impl(const char *var_type, int &count, std::vector< std::string > &result)
read_var_names() dispatches to this function.
std::vector< int > num_elems_per_set
const char * get_elem_type() const
std::vector< int > elem_face_counts
int get_side_set_id(int index)
Get the side set id for the given side set index.
void set_max_name_length(unsigned int max_length)
Set how many characters to use in names when opening a file for writing.
void read_var_names(ExodusVarType type)
void read_sideset(int id, int offset)
Reads information about sideset id and inserts it into the global sideset array at the position offse...
int get_block_id(int index)
Get the block number for the given block index.
void write_var_names(ExodusVarType type, const std::vector< std::string > &names)
Wraps calls to exII::ex_put_var_names() and exII::ex_put_var_param().
void read_elemset(int id, int offset)
Reads information about elemset id and inserts it into the global elemset array at the position offse...
dof_id_type get_libmesh_node_id(int exodus_node_id)
Helper function that takes a (1-based) Exodus node/elem id and determines the corresponding libMesh N...
std::vector< std::vector< int > > c0polyhedron_face_connect
void write_timestep(int timestep, Real time)
Writes the time for the timestep.
std::string get_node_set_name(int index)
Get the node set name for the given node set index if supplied in the mesh file.
std::vector< std::string > global_var_names
void print_nodes(std::ostream &out_stream=libMesh::out)
Prints the nodal information, by default to libMesh::out.
std::vector< int > elem_node_counts
std::map< int, std::string > id_to_edge_block_names
void read_sideset_info()
Reads information about all of the sidesets in the ExodusII mesh file.
void conditionally_set_elem_unique_id(MeshBase &mesh, Elem *elem, int zero_based_elem_num_map_index)
virtual void create(std::string filename)
Opens an ExodusII mesh file named filename for writing.
std::map< std::string, ElemType > element_equivalence_map
Defines equivalence classes of Exodus element types that map to libmesh ElemTypes.
std::vector< int > num_sides_per_set
std::vector< std::string > elem_var_names
void print_header()
Prints the ExodusII mesh file header, which includes the mesh title, the number of nodes,...
std::map< dof_id_type, dof_id_type > libmesh_elem_num_to_exodus
void read_global_values(std::vector< Real > &values, int timestep)
Reads the vector of global variables.
void read_edge_blocks(MeshBase &mesh)
Read in edge blocks, storing information in the BoundaryInfo object.
void get_nodeset_data_indices(std::map< BoundaryInfo::NodeBCTuple, unsigned int > &bc_array_indices)
Similar to read_nodeset_data(), but instead of creating one std::map per nodeset per variable,...
void open(const char *filename, bool read_only)
Opens an ExodusII mesh file named filename.
virtual void write_elements(const MeshBase &mesh, bool use_discontinuous=false)
Writes the elements contained in "mesh".
std::vector< std::string > elemset_var_names
ExodusVarType
Wraps calls to exII::ex_get_var_names() and exII::ex_get_var_param().
std::map< dof_id_type, dof_id_type > libmesh_node_num_to_exodus
std::vector< dof_id_type > _true_node_offsets
If we're adding "fake" sides to visualize SIDE_DISCONTINUOUS variables, we also need to know how many...
std::map< int, std::map< ElemType, ExodusII_IO_Helper::Conversion > > conversion_map
Associates libMesh ElemTypes with node/face/edge/etc.
void initialize_global_variables(std::vector< std::string > names)
Sets up the global variables.
void read_sideset_data(const MeshBase &mesh, int timestep, std::vector< std::string > &var_names, std::vector< std::set< boundary_id_type > > &side_ids, std::vector< std::map< BoundaryInfo::BCTuple, Real > > &bc_vals)
Read sideset variables, if any, into the provided data structures.
void check_existing_vars(ExodusVarType type, std::vector< std::string > &names, std::vector< std::string > &names_from_file)
When appending: during initialization, check that variable names in the file match those you attempt ...
void set_hdf5_writing(bool write_hdf5)
Set to true (the default) to write files in an HDF5-based file format (when HDF5 is available),...
void set_coordinate_offset(Point p)
Allows you to set a vector that is added to the coordinates of all of the nodes.
void write_information_records(const std::vector< std::string > &records)
Writes the vector of information records.
std::map< int, std::string > id_to_ss_names
std::vector< int > node_sets_node_index
void read_elem_num_map()
Reads the optional node_num_map from the ExodusII mesh file.
void read_face_blocks()
Reads NSIDED face blocks used by NFACED element blocks.
void read_num_time_steps()
Reads the number of timesteps currently stored in the Exodus file and stores it in the num_time_steps...
std::vector< int > node_sets_node_list
const ExodusII_IO_Helper::Conversion & get_conversion(const ElemType type) const
void initialize_nodal_variables(std::vector< std::string > names)
Sets up the nodal variables.
void write_global_values(const std::vector< Real > &values, int timestep)
Writes the vector of global variables.
void get_elemset_data_indices(std::map< std::pair< dof_id_type, elemset_id_type >, unsigned int > &elemset_array_indices)
Similar to read_elemset_data(), but instead of creating one std::map per elemset per variable,...
std::string get_side_set_name(int index)
Get the side set name for the given side set index if supplied in the mesh file.
std::vector< std::string > nodeset_var_names
void conditionally_set_node_unique_id(MeshBase &mesh, Node *node, int zero_based_node_num_map_index)
Helper function that conditionally sets the unique_id of the passed-in Node/Elem.
std::vector< int > num_node_df_per_set
void read_nodal_var_values(std::string nodal_var_name, int time_step)
Reads the nodal values for the variable 'nodal_var_name' at the specified time into the 'nodal_var_va...
std::vector< std::vector< long unsigned int > > bex_cv_conn
virtual void initialize(std::string title, const MeshBase &mesh, bool use_discontinuous=false)
Initializes the Exodus file.
std::map< int, std::string > id_to_block_names
std::map< int, std::string > id_to_elemset_names
std::vector< Real > node_sets_dist_fact
void write_elemsets(const MeshBase &mesh)
Write elemsets stored on the Mesh to the exo file.
void write_element_values(const MeshBase &mesh, const std::vector< Real > &values, int timestep, const std::vector< std::set< subdomain_id_type > > &vars_active_subdomains)
Writes the vector of values to the element variables.
void read_elem_in_block(int block)
Reads all of the element connectivity for block block in the ExodusII mesh file.
std::vector< dof_id_type > _added_side_node_offsets
If we're adding "fake" sides to visualize SIDE_DISCONTINUOUS variables, _added_side_node_offsets[p] g...
The IntRange templated class is intended to make it easy to loop over integers which are indices of a...
Definition int_range.h:54
This is the MeshBase class.
Definition mesh_base.h:81
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition mesh_base.h:170
virtual const Node * node_ptr(const dof_id_type i) const =0
unsigned int mesh_dimension() const
Definition mesh_base.C:435
virtual dof_id_type n_elem() const =0
unsigned int spatial_dimension() const
Definition mesh_base.h:2682
unsigned int get_elem_integer_index(std::string_view name) const
Definition mesh_base.C:689
virtual dof_id_type max_node_id() const =0
virtual const Elem * elem_ptr(const dof_id_type i) const =0
virtual void set_next_unique_id(unique_id_type id)=0
Sets the next available unique id to be used.
bool has_elem_integer(std::string_view name) const
Definition mesh_base.C:701
virtual dof_id_type max_elem_id() const =0
unsigned int n_elemsets() const
Returns the number of unique elemset ids which have been added via add_elemset_code(),...
Definition mesh_base.C:487
std::string & subdomain_name(subdomain_id_type id)
Definition mesh_base.C:1902
virtual const Elem & elem_ref(const dof_id_type i) const
Definition mesh_base.h:785
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:492
std::set< elemset_id_type > elemset_type
Typedef for the "set" container used to store elemset ids.
Definition mesh_base.h:463
unique_id_type next_unique_id() const
Definition mesh_base.h:607
virtual dof_id_type n_active_elem() const =0
virtual bool is_serial_on_zero() const
Definition mesh_base.h:364
A Node is like a Point, but with more information.
Definition node.h:55
An object whose state is distributed along a set of processors.
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
processor_id_type n_processors() const
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:41
The StoredRange class defines a contiguous, divisible set of objects.
static const Real b
MeshBase & mesh
Tnew cast_int(Told oldvar)
void elem_types(const MeshBase &mesh, std::vector< ElemType > &et)
Fills in a vector of all element types in the mesh.
Definition mesh_tools.C:723
std::string enum_to_string(const T e)
The libMesh namespace provides an interface to certain functionality in the library.
OStreamProxy err
uint8_t unique_id_type
Definition id_types.h:86
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
ElemType
Defines an enum for geometric element types.
int8_t boundary_id_type
Definition id_types.h:51
void libmesh_ignore(const Args &...)
libmesh_assert(ctx)
const unsigned int invalid_uint
A number which is used quite often to represent an invalid or uninitialized value for an unsigned int...
Definition libmesh.h:303
OStreamProxy out
TestClass subdomain_id_type
Based on the 4-byte comment warning above, this probably doesn't work with exodusII at all....
Definition id_types.h:43
uint8_t dof_id_type
Definition id_types.h:67
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
uint8_t processor_id_type
Definition id_types.h:104
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176
const boundary_id_type side_id
This class facilitates reading in vectors from Exodus file that may be of a different floating point ...
MappedInputVector(std::vector< Real > &vec_in, bool single_precision_in)
This class facilitates inline conversion of an input data vector to a different precision level,...
MappedOutputVector(const std::vector< Real > &vec_in, bool single_precision_in)
The FPEDisabler class puts Floating-Point Exception (FPE) trapping on hold during its lifetime,...