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