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