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