MFEM v4.9.0
Finite element discretization library
Loading...
Searching...
No Matches
exodus_writer.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12#include "mesh_headers.hpp"
13#include <unordered_set>
14#include <cstdarg>
15
16#ifdef MFEM_USE_NETCDF
17#include "netcdf.h"
18#endif
19
20// Call NetCDF functions inside the macro. This will provide basic error-handling.
21#define CHECK_NETCDF_CODE(return_code)\
22{\
23 if ((return_code) != NC_NOERR)\
24 {\
25 MFEM_ABORT("NetCDF error: " << nc_strerror((return_code)));\
26 }\
27}
28
29#if defined(MFEM_USE_DOUBLE)
30#define MFEM_NETCDF_REAL_T NC_DOUBLE
31#elif defined(MFEM_USE_SINGLE)
32#define MFEM_NETCDF_REAL_T NC_FLOAT
33#endif
34
35namespace mfem
36{
37
38#ifdef MFEM_USE_NETCDF
39
40namespace ExodusIISideMaps
41{
42/// Convert from the MFEM face numbering to the ExodusII face numbering.
44{
45 2, 3, 1, 4
46};
47
49{
50 5, 1, 2, 3, 4, 6
51};
52
54{
55 4, 5, 1, 2, 3
56};
57
59{
60 5, 1, 2, 3, 4
61};
62}
63
64namespace ExodusIINodeOrderings
65{
66/// Convert from the MFEM (0-based) node ordering to the ExodusII 1-based node
67/// ordering.
69{
70 1, 2, 3, 4, 5, 8, 6, 7, 9, 10
71};
72
74{
75 1, 2, 3, 4, 5, 6, 7, 8, 9,
76 10, 11, 12, 17, 18, 19, 20, 13, 14,
77 15, 16, 27, 21, 26, 25, 23, 22, 24
78};
79
81{
82 1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14, 15, 10, 11, 12, 16, 17, 18
83};
84
86{
87 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
88};
89}
90
91
92namespace ExodusIILabels
93{
94// Variable labels
95const char * EXODUS_TITLE_LABEL = "title";
96const char * EXODUS_NUM_ELEM_LABEL = "num_elem";
97const char * EXODUS_FLOATING_POINT_WORD_SIZE_LABEL = "floating_point_word_size";
98const char * EXODUS_API_VERSION_LABEL = "api_version";
99const char * EXODUS_DATABASE_VERSION_LABEL = "version";
100const char * EXODUS_MAX_NAME_LENGTH_LABEL = "maximum_name_length";
101const char * EXODUS_MAX_LINE_LENGTH_LABEL = "maximum_line_length";
102const char * EXODUS_NUM_BLOCKS_LABEL = "block_dim";
103const char * EXODUS_COORDX_LABEL = "coordx";
104const char * EXODUS_COORDY_LABEL = "coordy";
105const char * EXODUS_COORDZ_LABEL = "coordz";
106const char * EXODUS_NUM_BOUNDARIES_LABEL = "boundary_ids_dim";
107const char * EXODUS_FILE_SIZE_LABEL = "file_size";
108const char * EXODUS_NUM_DIM_LABEL = "num_dim";
109const char * EXODUS_NUM_NODE_SETS_LABEL = "num_node_sets";
110const char * EXODUS_TIME_STEP_LABEL = "time_step";
111const char * EXODUS_ELEMENT_TYPE_LABEL = "elem_type";
112const char * EXODUS_NUM_SIDE_SETS_LABEL = "num_side_sets";
113const char * EXODUS_SIDE_SET_IDS_LABEL = "ss_prop1";
114const char * EXODUS_ELEMENT_BLOCK_IDS_LABEL = "eb_prop1";
115const char * EXODUS_NUM_ELEMENT_BLOCKS_LABEL = "num_el_blk";
116const char * EXODUS_MESH_TITLE = "MFEM mesh";
117
118// Current version as of 2024-03-21.
119const float EXODUS_API_VERSION = 4.72;
120const float EXODUS_DATABASE_VERSION = 4.72;
121
124}
125
126/**
127 * Helper class for writing a mesh to an ExodusII file.
128 */
129class ExodusIIWriter
130{
131public:
132 /// @brief Default constructor. Opens ExodusII file.
133 /// @param mesh The mesh to write to the file.
134 ExodusIIWriter(Mesh & mesh) : mesh{mesh} {}
135
136 ExodusIIWriter() = delete;
137
138 /// @brief Closes ExodusII file if it has been opened.
139 ~ExodusIIWriter();
140
141 /// @brief Writes the mesh to an ExodusII file.
142 /// @param fpath The path to the file.
143 /// @param flags NC_CLOBBER will overwrite existing file.
144 void PrintExodusII(const std::string &fpath, int flags = NC_CLOBBER);
145
146 /// @brief Static method for writing a mesh to an ExodusII file.
147 /// @param mesh The mesh to write to the file.
148 /// @param fpath The path to the file.
149 /// @param flags NetCDF file flags.
150 static void PrintExodusII(Mesh & mesh, const std::string &fpath,
151 int flags = NC_CLOBBER);
152
153protected:
154 /// @brief Closes any open file and creates a NetCDF file using selected flags.
155 void OpenExodusII(const std::string &fpath, int flags);
156
157 /// @brief Closes any open file.
158 void CloseExodusII();
159
160 /// @brief Generates blocks based on the elements in the mesh. We iterate
161 /// over the mesh elements and use the attributes as the element blocks. We
162 /// assume that all elements belonging to the same block will share the same
163 /// attribute. We perform a safety check to verify that all elements in the
164 /// block have the same element type.
165 void GenerateExodusIIElementBlocks();
166
167 /// @brief Extracts boundary ids and determines the element IDs and side IDs
168 /// (Exodus II) for each boundary element.
169 void GenerateExodusIIBoundaryInfo();
170
171 /// @brief Iterates over the elements to extract a unique set of node IDs
172 /// (or vertex IDs if first-order).
173 std::unordered_set<int> GenerateUniqueNodeIDs();
174
175 /// @brief Populates vectors with x, y, z coordinates from mesh.
176 void ExtractVertexCoordinates(std::vector<real_t> &coordx,
177 std::vector<real_t> &coordy,
178 std::vector<real_t> &coordz);
179
180 /// @brief Writes node connectivity for a particular block.
181 /// @param block_id The block to write to the file.
182 void WriteNodeConnectivityForBlock(const int block_id);
183
184 /// @brief Writes boundary information to file.
185 void WriteBoundaries();
186
187 /// @brief Writes the block IDs to the file.
188 void WriteBlockIDs();
189
190 /// @brief Writes a title to the file.
191 void WriteTitle();
192
193 /// @brief Writes the number of elements in the mesh.
194 void WriteNumOfElements();
195
196 /// @brief Writes the floating-point word size (sizeof(real_t)).
197 void WriteFloatingPointWordSize();
198
199 /// @brief Writes the API version.
200 void WriteAPIVersion();
201
202 /// @brief Writes the database version.
203 void WriteDatabaseVersion();
204
205 /// @brief Writes the maximum length of a line.
206 void WriteMaxLineLength();
207
208 /// @brief Writes the maximum length of a name.
209 void WriteMaxNameLength();
210
211 /// @brief Writes the number of blocks.
212 void WriteNumElementBlocks();
213
214 /// @brief Writes all element block parameters.
215 void WriteElementBlocks();
216
217 /// @brief Called by @a WriteElementBlockParameters in for-loop.
218 /// @param block_id Block to write parameters.
219 void WriteElementBlockParameters(int block_id);
220
221 /// @brief Writes the coordinates of nodes.
222 void WriteNodalCoordinates();
223
224 /// @brief Writes the file size (normal=0; large=1). Coordinates are specified
225 /// separately as components for large files (i.e. xxx, yyy, zzz) as opposed
226 /// to (xyz, xyz, xyz) for normal files.
227 void WriteFileSize();
228
229 /// @brief Writes the nodesets. Currently, we do not support nodesets.
230 void WriteNodeSets();
231
232 /// @brief Writes the mesh dimension.
233 void WriteMeshDimension();
234
235 /// @brief Writes the number of timesteps. Currently, we do not support
236 /// multiple timesteps.
237 void WriteTimesteps();
238
239 /// @brief Writes a dummy variable. This is to circumvent a bug in LibMesh where
240 /// it will skip the x-coordinate when reading in an ExodusII file if the id of
241 /// the x-coordinates is 0. To prevent this, we define a dummy variable before
242 /// defining the coordinates. This ensures that the coordinate variable IDs have
243 /// values greater than zero. See: https://github.com/libMesh/libmesh/issues/3823
244 void WriteDummyVariable();
245
246 /// @brief Wrapper around @a nc_def_dim with error handling.
247 void DefineDimension(const char *name, size_t len, int *dim_id);
248
249 /// @brief Wrapper around @a nc_def_var with error handling.
250 void DefineVar(const char *name, nc_type xtype, int ndims, const int *dimidsp,
251 int *varidp);
252
253 /// @brief Write variable data to the file. This is a wrapper around
254 /// @a nc_put_var with error handling.
255 void PutVar(int varid, const void * data);
256
257 /// @brief Combine @a DefineVar with @a PutVar.
258 void DefineAndPutVar(const char *name, nc_type xtype, int ndims,
259 const int *dimidsp, const void *data);
260
261 /// @brief Write attribute to the file. This is a wrapper around @a nc_put_att
262 /// with error handling.
263 void PutAtt(int varid, const char *name, nc_type xtype, size_t len,
264 const void * data);
265
266 /// @brief Returns a pointer to a static buffer containing the character
267 /// string with formatting. Used to generate variable labels.
268 char * GenerateLabel(const char * format, ...);
269
270 /// @brief Writes boiler-plate information for ExodusII file format including
271 /// title, database version, file size etc.
272 void WriteExodusIIFileInformation();
273
274 /// @brief Writes all information about the mesh to the ExodusII file.
275 void WriteExodusIIMeshInformation();
276
277private:
278 /// @brief Verifies that the nodal FESpace exists and is H1, order 2.
279 void CheckNodalFESpaceIsSecondOrderH1() const;
280
281 // ExodusII file ID.
282 int exid{-1};
283
284 /// Flag to check if a file is currently open.
285 bool file_open{false};
286
287 // Reference to mesh we would like to write-out.
288 Mesh & mesh;
289
290 // Block information.
291 std::vector<int> block_ids;
292 std::map<int, Element::Type> element_type_for_block_id;
293 std::map<int, std::vector<int>> element_ids_for_block_id;
294
295 std::vector<int> boundary_ids;
296 std::map<int, std::vector<int>> exodusII_element_ids_for_boundary_id;
297 std::map<int, std::vector<int>> exodusII_side_ids_for_boundary_id;
298};
299
300void Mesh::PrintExodusII(const std::string &fpath)
301{
302 ExodusIIWriter::PrintExodusII(*this, fpath);
303}
304
305void ExodusIIWriter::DefineDimension(const char *name, size_t len, int *dim_id)
306{
307 nc_redef(exid);
308 CHECK_NETCDF_CODE(nc_def_dim(exid, name, len, dim_id));
309}
310
311void ExodusIIWriter::DefineVar(const char *name, nc_type xtype, int ndims,
312 const int *dimidsp, int *varidp)
313{
314 nc_redef(exid); // Switch to define mode.
315 CHECK_NETCDF_CODE(nc_def_var(exid, name, xtype, ndims, dimidsp,
316 varidp));
317}
318
319void ExodusIIWriter::PutAtt(int varid, const char *name, nc_type xtype,
320 size_t len, const void * data)
321{
322 nc_redef(exid);
323 CHECK_NETCDF_CODE(nc_put_att(exid, varid, name, xtype, len, data));
324}
325
326void ExodusIIWriter::PutVar(int varid, const void * data)
327{
328 nc_enddef(exid); // Switch to data mode.
329 CHECK_NETCDF_CODE(nc_put_var(exid, varid, data));
330}
331
332void ExodusIIWriter::DefineAndPutVar(const char *name, nc_type xtype, int ndims,
333 const int *dimidsp, const void *data)
334{
335 int varid;
336 DefineVar(name, xtype, ndims, dimidsp, &varid);
337 PutVar(varid, data);
338}
339
340void ExodusIIWriter::WriteExodusIIFileInformation()
341{
342 WriteTitle();
343
344 WriteDatabaseVersion();
345 WriteAPIVersion();
346
347 WriteFloatingPointWordSize();
348
349 WriteFileSize();
350
351 WriteMaxNameLength();
352 WriteMaxLineLength();
353
354 WriteDummyVariable();
355}
356
357void ExodusIIWriter::WriteExodusIIMeshInformation()
358{
359 WriteMeshDimension();
360 WriteNumOfElements();
361
362 WriteTimesteps();
363
364 WriteNodalCoordinates();
365
366 WriteElementBlocks();
367 WriteBoundaries();
368 WriteNodeSets();
369}
370
371void ExodusIIWriter::PrintExodusII(const std::string &fpath, int flags)
372{
373 OpenExodusII(fpath, flags);
374
375 WriteExodusIIFileInformation();
376 WriteExodusIIMeshInformation();
377
378 CloseExodusII();
379
380 mfem::out << "Mesh successfully written to Exodus II file" << std::endl;
381}
382
383void ExodusIIWriter::PrintExodusII(Mesh &mesh, const std::string &fpath,
384 int flags)
385{
386 ExodusIIWriter writer(mesh);
387
388 writer.PrintExodusII(fpath, flags);
389}
390
391void ExodusIIWriter::OpenExodusII(const std::string &fpath, int flags)
392{
393 CloseExodusII(); // Close any open files.
394
395 CHECK_NETCDF_CODE(nc_create(fpath.c_str(), flags, &exid));
396
397 file_open = true;
398}
399
400void ExodusIIWriter::CloseExodusII()
401{
402 if (!file_open) { return; } // No files open.
403
404 CHECK_NETCDF_CODE(nc_close(exid));
405
406 file_open = false;
407 exid = (-1); // Set to negative value (valid IDs are positive!)
408}
409
410ExodusIIWriter::~ExodusIIWriter()
411{
412 CloseExodusII();
413}
414
415void ExodusIIWriter::WriteTitle()
416{
417 PutAtt(NC_GLOBAL, ExodusIILabels::EXODUS_TITLE_LABEL, NC_CHAR,
420}
421
422void ExodusIIWriter::WriteNumOfElements()
423{
424 int num_elem_id;
425 DefineDimension(ExodusIILabels::EXODUS_NUM_ELEM_LABEL, mesh.GetNE(),
426 &num_elem_id);
427}
428
429void ExodusIIWriter::WriteFloatingPointWordSize()
430{
431 const int word_size = sizeof(real_t);
433 NC_INT, 1,
434 &word_size);
435}
436
437void ExodusIIWriter::WriteAPIVersion()
438{
439 PutAtt(NC_GLOBAL, ExodusIILabels::EXODUS_API_VERSION_LABEL, MFEM_NETCDF_REAL_T,
440 1,
442}
443
444void ExodusIIWriter::WriteDatabaseVersion()
445{
447 MFEM_NETCDF_REAL_T, 1,
449}
450
451void ExodusIIWriter::WriteMaxNameLength()
452{
453 PutAtt(NC_GLOBAL, ExodusIILabels::EXODUS_MAX_NAME_LENGTH_LABEL, NC_INT, 1,
455}
456
457void ExodusIIWriter::WriteMaxLineLength()
458{
459 PutAtt(NC_GLOBAL, ExodusIILabels::EXODUS_MAX_LINE_LENGTH_LABEL, NC_INT, 1,
461}
462
463void ExodusIIWriter::WriteBlockIDs()
464{
465 int block_dim;
466 DefineDimension(ExodusIILabels::EXODUS_NUM_BLOCKS_LABEL, block_ids.size(),
467 &block_dim);
468
469 DefineAndPutVar(ExodusIILabels::EXODUS_ELEMENT_BLOCK_IDS_LABEL, NC_INT, 1,
470 &block_dim,
471 block_ids.data());
472}
473
474void ExodusIIWriter::WriteElementBlocks()
475{
476 GenerateExodusIIElementBlocks();
477
478 WriteNumElementBlocks();
479 WriteBlockIDs();
480
481 for (int block_id : block_ids)
482 {
483 WriteElementBlockParameters(block_id);
484 }
485}
486
487char * ExodusIIWriter::GenerateLabel(const char * format, ...)
488{
489 va_list arglist;
490 va_start(arglist, format);
491
492 const int buffer_size = 100;
493
494 static char buffer[buffer_size];
495 int nwritten = vsnprintf(buffer, buffer_size, format, arglist);
496
497 bool ok = (nwritten > 0 && nwritten < buffer_size);
498 if (!ok)
499 {
500 MFEM_ABORT("Unable to write characters to buffer.");
501 }
502
503 va_end(arglist);
504 return buffer;
505}
506
507void ExodusIIWriter::WriteElementBlockParameters(int block_id)
508{
509 char * label{nullptr};
510
511 const std::vector<int> & block_element_ids = element_ids_for_block_id.at(
512 block_id);
513 const Element * front_element = mesh.GetElement(block_element_ids.front());
514
515 // 1. Define number of elements in the block.
516 label = GenerateLabel("num_el_in_blk%d", block_id);
517
518 int num_el_in_blk_id;
519 DefineDimension(label, block_element_ids.size(),
520 &num_el_in_blk_id);
521
522 // 2. Define number of nodes per element.
523 label = GenerateLabel("num_nod_per_el%d", block_id);
524
525 int num_node_per_el_id;
526 if (mesh.GetNodes())
527 {
528 // Safety check: H1, order 2 fespace.
529 CheckNodalFESpaceIsSecondOrderH1();
530
531 // Higher order. Get the first element from the block.
532 const FiniteElementSpace * fespace = mesh.GetNodalFESpace();
533
534 auto & block_elements = element_ids_for_block_id.at(block_id);
535
536 int first_element_id = block_elements.front();
537
538 Array<int> dofs;
539 fespace->GetElementDofs(first_element_id, dofs);
540
541 DefineDimension(label, dofs.Size(),
542 &num_node_per_el_id);
543 }
544 else
545 {
546 DefineDimension(label, front_element->GetNVertices(),
547 &num_node_per_el_id);
548 }
549
550 // 3. Define number of edges per element:
551 label = GenerateLabel("num_edg_per_el%d", block_id);
552
553 int num_edg_per_el_id;
554 DefineDimension(label, front_element->GetNEdges(),
555 &num_edg_per_el_id);
556
557 // 4. Define number of faces per element.
558 label = GenerateLabel("num_fac_per_el%d", block_id);
559
560 int num_fac_per_el_id;
561 DefineDimension(label, front_element->GetNFaces(),
562 &num_fac_per_el_id);
563
564 // 5. Define element node connectivity for block.
565 WriteNodeConnectivityForBlock(block_id);
566
567 // 6. Define the element type.
568 std::string element_type;
569
570 const FiniteElementSpace * fespace = mesh.GetNodalFESpace();
571
572 // Safety check: assume that the elements are of the same order.
573 MFEM_ASSERT((!fespace || (fespace &&
574 !fespace->IsVariableOrder())),
575 "Spaces with varying element orders are not supported.");
576
577 bool higher_order = (fespace && fespace->GetMaxElementOrder() > 1);
578
579 switch (front_element->GetType())
580 {
582 element_type = higher_order ? "HEX27" : "Hex8";
583 break;
585 element_type = higher_order ? "TETRA10" : "TETRA4";
586 break;
587 case Element::WEDGE:
588 element_type = higher_order ? "WEDGE18" : "WEDGE6";
589 break;
590 case Element::PYRAMID:
591 element_type = higher_order ? "PYRAMID14" : "PYRAMID5";
592 break;
593 default:
594 MFEM_ABORT("Unsupported MFEM element type: " << front_element->GetType());
595 }
596
597 label = GenerateLabel("connect%d", block_id);
598
599 int connect_id;
600 CHECK_NETCDF_CODE(nc_inq_varid(exid, label, &connect_id));
601
602 PutAtt(connect_id, ExodusIILabels::EXODUS_ELEMENT_TYPE_LABEL, NC_CHAR,
603 element_type.length(),
604 element_type.c_str());
605}
606
607void ExodusIIWriter::WriteNodalCoordinates()
608{
609 // 1. Generate the unique node IDs.
610 std::unordered_set<int> unique_node_ids = GenerateUniqueNodeIDs();
611 const size_t num_nodes = unique_node_ids.size();
612
613 // 2. Define the "num_nodes" dimension.
614 int num_nodes_id;
615 DefineDimension("num_nodes", num_nodes, &num_nodes_id);
616
617 // 3. Extract the nodal coordinates.
618 // NB: writes in format real_t (double or float); ndims = 1 (vector).
619 // https://docs.unidata.ucar.edu/netcdf-c/current/group__variables.html#gac7e8662c51f3bb07d1fc6d6c6d9052c8
620 std::vector<real_t> coordx(num_nodes);
621 std::vector<real_t> coordy(num_nodes);
622 std::vector<real_t> coordz(mesh.Dimension() == 3 ? num_nodes : 0);
623
624 ExtractVertexCoordinates(coordx, coordy, coordz);
625
626 // 4. Define and put the nodal coordinates.
627 DefineAndPutVar(ExodusIILabels::EXODUS_COORDX_LABEL, MFEM_NETCDF_REAL_T, 1,
628 &num_nodes_id,
629 coordx.data());
630 DefineAndPutVar(ExodusIILabels::EXODUS_COORDY_LABEL, MFEM_NETCDF_REAL_T, 1,
631 &num_nodes_id,
632 coordy.data());
633
634 if (mesh.Dimension() == 3)
635 {
636 DefineAndPutVar(ExodusIILabels::EXODUS_COORDZ_LABEL, MFEM_NETCDF_REAL_T, 1,
637 &num_nodes_id,
638 coordz.data());
639 }
640}
641
642void ExodusIIWriter::WriteBoundaries()
643{
644 // 1. Generate boundary info.
645 GenerateExodusIIBoundaryInfo();
646
647 // 2. Define the number of boundaries.
648 int num_side_sets_ids;
650 boundary_ids.size(),
651 &num_side_sets_ids);
652
653 // 3. Boundary IDs.
654 int boundary_ids_dim;
656 boundary_ids.size(),
657 &boundary_ids_dim);
658
659 DefineAndPutVar(ExodusIILabels::EXODUS_SIDE_SET_IDS_LABEL, NC_INT, 1,
660 &boundary_ids_dim,
661 boundary_ids.data());
662
663 // 4. Number of boundary elements.
664 for (int boundary_id : boundary_ids)
665 {
666 size_t num_elements_for_boundary = exodusII_element_ids_for_boundary_id.at(
667 boundary_id).size();
668
669 char * label = GenerateLabel("num_side_ss%d", boundary_id);
670
671 int num_side_ss_id;
672 DefineDimension(label, num_elements_for_boundary,
673 &num_side_ss_id);
674 }
675
676 // 5. Boundary side IDs.
677 for (int boundary_id : boundary_ids)
678 {
679 const std::vector<int> & side_ids = exodusII_side_ids_for_boundary_id.at(
680 boundary_id);
681
682 char * label = GenerateLabel("side_ss%d_dim", boundary_id);
683
684 int side_id_dim;
685 DefineDimension(label, side_ids.size(), &side_id_dim);
686
687 label = GenerateLabel("side_ss%d", boundary_id);
688 DefineAndPutVar(label, NC_INT, 1, &side_id_dim, side_ids.data());
689 }
690
691 // 6. Boundary element IDs.
692 for (int boundary_id : boundary_ids)
693 {
694 const std::vector<int> & element_ids = exodusII_element_ids_for_boundary_id.at(
695 boundary_id);
696
697 char * label = GenerateLabel("elem_ss%d_dim", boundary_id);
698
699 int elem_ids_dim;
700 DefineDimension(label, element_ids.size(), &elem_ids_dim);
701
702 label = GenerateLabel("elem_ss%d", boundary_id);
703 DefineAndPutVar(label, NC_INT, 1, &elem_ids_dim,
704 element_ids.data());
705 }
706}
707
708void ExodusIIWriter::WriteNodeConnectivityForBlock(const int block_id)
709{
710 std::vector<int> block_node_connectivity;
711
712 int * node_ordering_map = nullptr;
713
714 // Apply mappings to convert from MFEM --> ExodusII orderings.
715 Element::Type block_type = element_type_for_block_id.at(block_id);
716
717 switch (block_type)
718 {
720 node_ordering_map = (int *)
722 break;
724 node_ordering_map = (int *)
726 break;
728 node_ordering_map = (int *)
730 break;
732 node_ordering_map = (int *)
734 break;
735 default:
736 MFEM_ABORT("Higher-order elements of type '" << block_type <<
737 "' are not supported.");
738 }
739
740 const FiniteElementSpace * fespace = mesh.GetNodalFESpace();
741
742 Array<int> element_dofs;
743 for (int element_id : element_ids_for_block_id.at(block_id))
744 {
745 if (fespace)
746 {
747 fespace->GetElementDofs(element_id, element_dofs);
748
749 for (int j = 0; j < element_dofs.Size(); j++)
750 {
751 int dof_index = node_ordering_map[j] - 1;
752 int dof = element_dofs[dof_index];
753
754 block_node_connectivity.push_back(dof + 1); // 1-based indexing.
755 }
756 }
757 else
758 {
759 mesh.GetElementVertices(element_id, element_dofs);
760
761 for (int vertex_id : element_dofs)
762 {
763 block_node_connectivity.push_back(vertex_id + 1); // 1-based indexing.
764 }
765 }
766 }
767
768 char * label = GenerateLabel("connect%d_dim", block_id);
769
770 int node_connectivity_dim;
771 DefineDimension(label, block_node_connectivity.size(),
772 &node_connectivity_dim);
773
774 // NB: 1 == vector!; name is arbitrary; NC_INT or NCINT64?
775 label = GenerateLabel("connect%d", block_id);
776 DefineAndPutVar(label, NC_INT, 1, &node_connectivity_dim,
777 block_node_connectivity.data());
778}
779
780
781void ExodusIIWriter::ExtractVertexCoordinates(std::vector<real_t> & coordx,
782 std::vector<real_t> & coordy,
783 std::vector<real_t> & coordz)
784{
785 if (mesh.GetNodes()) // Higher-order.
786 {
787 std::unordered_set<int> unordered_node_ids = GenerateUniqueNodeIDs();
788
789 std::vector<int> sorted_node_ids(unordered_node_ids.size());
790 sorted_node_ids.assign(unordered_node_ids.begin(), unordered_node_ids.end());
791 std::sort(sorted_node_ids.begin(), sorted_node_ids.end());
792
793 real_t coordinates[3];
794 for (size_t i = 0; i < sorted_node_ids.size(); i++)
795 {
796 int node_id = sorted_node_ids[i];
797
798 mesh.GetNode(node_id, coordinates);
799
800 coordx[node_id] = coordinates[0];
801 coordy[node_id] = coordinates[1];
802
803 if (mesh.Dimension() == 3)
804 {
805 coordz[node_id] = coordinates[2];
806 }
807 }
808 }
809 else // First-order.
810 {
811 for (int ivertex = 0; ivertex < mesh.GetNV(); ivertex++)
812 {
813 real_t *coordinates = mesh.GetVertex(ivertex);
814
815 coordx[ivertex] = coordinates[0];
816 coordy[ivertex] = coordinates[1];
817
818 if (mesh.Dimension() == 3)
819 {
820 coordz[ivertex] = coordinates[2];
821 }
822 }
823 }
824}
825
826void ExodusIIWriter::WriteFileSize()
827{
828 // Store Exodus file size (normal==0; large==1). NB: coordinates specifed
829 // separately as components for large file.
830 const int file_size = 1;
831
832 PutAtt(NC_GLOBAL, ExodusIILabels::EXODUS_FILE_SIZE_LABEL, NC_INT, 1,
833 &file_size);
834}
835
836void ExodusIIWriter::WriteMeshDimension()
837{
838 int num_dim_id;
839 DefineDimension(ExodusIILabels::EXODUS_NUM_DIM_LABEL, mesh.Dimension(),
840 &num_dim_id);
841}
842
843void ExodusIIWriter::WriteNodeSets()
844{
845 // Nodesets are not currently implemented; set to zero.
846 int num_node_sets_ids;
848 &num_node_sets_ids);
849}
850
851void ExodusIIWriter::WriteTimesteps()
852{
853 // Set number of timesteps (ASSUME single timestep for initial verision).
854 int timesteps_dim;
855 DefineDimension(ExodusIILabels::EXODUS_TIME_STEP_LABEL, 1, &timesteps_dim);
856}
857
858void ExodusIIWriter::WriteDummyVariable()
859{
860 int dummy_var_dim_id, dummy_value = 1;
861
862 DefineDimension("dummy_var_dim", 1, &dummy_var_dim_id);
863
864 DefineAndPutVar("dummy_var", NC_INT, 1, &dummy_var_dim_id,
865 &dummy_value);
866}
867
868void ExodusIIWriter::GenerateExodusIIElementBlocks()
869{
870 block_ids.clear();
871 element_ids_for_block_id.clear();
872 element_type_for_block_id.clear();
873
874 std::unordered_set<int> observed_block_ids;
875
876 // Iterate over the elements in the mesh.
877 for (int ielement = 0; ielement < mesh.GetNE(); ielement++)
878 {
879 Element::Type element_type = mesh.GetElementType(ielement);
880
881 int block_id = mesh.GetAttribute(ielement);
882
883 if (observed_block_ids.count(block_id) == 0)
884 {
885 block_ids.push_back(block_id);
886
887 element_type_for_block_id[block_id] = element_type;
888 element_ids_for_block_id[block_id] = { ielement };
889
890 observed_block_ids.insert(block_id);
891 }
892 else
893 {
894 auto & block_element_ids = element_ids_for_block_id.at(block_id);
895 block_element_ids.push_back(ielement);
896
897 // Safety check: ensure that the element type matches what we have on record
898 // for the block.
899 if (element_type != element_type_for_block_id.at(block_id))
900 {
901 MFEM_ABORT("Multiple element types are defined for block: " << block_id);
902 }
903 }
904 }
905}
906
907void ExodusIIWriter::WriteNumElementBlocks()
908{
909 int num_elem_blk_id;
911 block_ids.size(),
912 &num_elem_blk_id);
913}
914
915
916std::unordered_set<int> ExodusIIWriter::GenerateUniqueNodeIDs()
917{
918 std::unordered_set<int> unique_node_ids;
919
920 const FiniteElementSpace * fespace = mesh.GetNodalFESpace();
921
922 mfem::Array<int> element_dofs;
923 for (int ielement = 0; ielement < mesh.GetNE(); ielement++)
924 {
925 if (fespace) // Higher-order
926 {
927 fespace->GetElementDofs(ielement, element_dofs);
928 }
929 else
930 {
931 mesh.GetElementVertices(ielement, element_dofs);
932 }
933
934 for (int dof : element_dofs)
935 {
936 unique_node_ids.insert(dof);
937 }
938 }
939
940 return unique_node_ids;
941}
942
943void ExodusIIWriter::GenerateExodusIIBoundaryInfo()
944{
945 // Store the unique boundary IDs.
946 boundary_ids.clear();
947 exodusII_element_ids_for_boundary_id.clear();
948 exodusII_side_ids_for_boundary_id.clear();
949
950 // Generate a mapping from the MFEM face index to the MFEM element ID.
951 // Note that if we have multiple element IDs for a face index then the
952 // face is shared between them and it cannot possibly be an external boundary
953 // face since that can only have a single element associated with it. Therefore
954 // we remove it from the array.
955 struct GlobalFaceIndexInfo
956 {
957 int element_index;
958 int local_face_index;
959
960 GlobalFaceIndexInfo() : element_index{0}, local_face_index{0} {}
961
962 GlobalFaceIndexInfo(int element_index, int local_face_index)
963 {
964 this->element_index = element_index;
965 this->local_face_index = local_face_index;
966 }
967 };
968
969 std::unordered_map<int, GlobalFaceIndexInfo>
970 mfem_face_index_info_for_global_face_index;
971 std::unordered_set<int> blacklisted_global_face_indices;
972
973 Array<int> global_face_indices, orient;
974 for (int ielement = 0; ielement < mesh.GetNE(); ielement++)
975 {
976 mesh.GetElementFaces(ielement, global_face_indices, orient);
977
978 for (int iface = 0; iface < global_face_indices.Size(); iface++)
979 {
980 int face_index = global_face_indices[iface];
981
982 if (blacklisted_global_face_indices.count(face_index))
983 {
984 continue;
985 }
986
987 if (mfem_face_index_info_for_global_face_index.count(face_index))
988 {
989 // Now we've seen it twice!
990 blacklisted_global_face_indices.insert(face_index);
991 mfem_face_index_info_for_global_face_index.erase(face_index);
992 continue;
993 }
994
995 mfem_face_index_info_for_global_face_index[face_index] = GlobalFaceIndexInfo(
996 ielement, iface);
997 }
998 }
999
1000 std::unordered_set<int> unique_boundary_attributes;
1001
1002 for (int ibdr_element = 0; ibdr_element < mesh.GetNBE(); ibdr_element++)
1003 {
1004 int boundary_id = mesh.GetBdrAttribute(ibdr_element);
1005 int bdr_element_face_index = mesh.GetBdrElementFaceIndex(ibdr_element);
1006
1007 // Skip any interior boundary faces.
1008 if (mesh.FaceIsInterior(bdr_element_face_index))
1009 {
1010 MFEM_WARNING("Skipping internal boundary " << ibdr_element);
1011 continue;
1012 }
1013
1014 // Locate match.
1015 auto & element_face_info = mfem_face_index_info_for_global_face_index.at(
1016 bdr_element_face_index);
1017
1018 int ielement = element_face_info.element_index;
1019 int iface = element_face_info.local_face_index;
1020
1021 // 1. Convert MFEM 0-based element index to ExodusII 1-based element ID.
1022 int exodusII_element_id = ielement + 1;
1023
1024 // 2. Convert MFEM 0-based face index to ExodusII 1-based face ID (different ordering).
1025 int exodusII_face_id;
1026
1027 Element::Type element_type = mesh.GetElementType(ielement);
1028 switch (element_type)
1029 {
1031 exodusII_face_id = ExodusIISideMaps::mfem_to_exodusII_side_map_tet4[iface];
1032 break;
1034 exodusII_face_id = ExodusIISideMaps::mfem_to_exodusII_side_map_hex8[iface];
1035 break;
1038 break;
1041 break;
1042 default:
1043 MFEM_ABORT("Cannot handle element of type " << element_type);
1044 }
1045
1046 unique_boundary_attributes.insert(boundary_id);
1047
1048 exodusII_element_ids_for_boundary_id[boundary_id].push_back(
1049 exodusII_element_id);
1050 exodusII_side_ids_for_boundary_id[boundary_id].push_back(exodusII_face_id);
1051 }
1052
1053 boundary_ids.assign(unique_boundary_attributes.begin(),
1054 unique_boundary_attributes.end());
1055 std::sort(boundary_ids.begin(), boundary_ids.end());
1056}
1057
1058void ExodusIIWriter::CheckNodalFESpaceIsSecondOrderH1() const
1059{
1060 const FiniteElementSpace * fespace = mesh.GetNodalFESpace();
1061 if (!fespace) // Mesh does not have nodes.
1062 {
1063 MFEM_ABORT("The mesh has no nodal fespace.");
1064 }
1065
1066 // Expect order 2.
1067 const int fespace_order = fespace->GetMaxElementOrder();
1068 if (fespace_order != 2)
1069 {
1070 MFEM_ABORT("Nodal fespace is of order " << fespace_order <<
1071 ". Expected 2nd order.");
1072 }
1073
1074 // Get a pointer to the FE collection associated with the fespace.
1075 const FiniteElementCollection * fec = fespace->FEColl();
1076 if (!fec)
1077 {
1078 MFEM_ABORT("No FECollection associated with nodal fespace.");
1079 }
1080
1081 // Expect H1 FEC.
1082 if (strncmp(fec->Name(), "H1", 2) != 0)
1083 {
1084 MFEM_ABORT("Nodal fespace's FECollection is '" << fec->Name() <<
1085 "'. Expected H1.");
1086 }
1087}
1088
1089#endif
1090
1091}
Type
Constants for the classes derived from Element.
Definition element.hpp:41
Mesh data type.
Definition mesh.hpp:65
void PrintExodusII(const std::string &fpath)
Export a mesh to an Exodus II file.
mfem::real_t real_t
const char * EXODUS_DATABASE_VERSION_LABEL
const char * EXODUS_NUM_DIM_LABEL
const char * EXODUS_COORDY_LABEL
const char * EXODUS_MESH_TITLE
const char * EXODUS_NUM_ELEM_LABEL
const char * EXODUS_COORDZ_LABEL
const char * EXODUS_TIME_STEP_LABEL
const char * EXODUS_FILE_SIZE_LABEL
const char * EXODUS_TITLE_LABEL
const char * EXODUS_NUM_BOUNDARIES_LABEL
const char * EXODUS_NUM_NODE_SETS_LABEL
const char * EXODUS_NUM_BLOCKS_LABEL
const char * EXODUS_NUM_ELEMENT_BLOCKS_LABEL
const float EXODUS_DATABASE_VERSION
const char * EXODUS_MAX_LINE_LENGTH_LABEL
const char * EXODUS_ELEMENT_TYPE_LABEL
const char * EXODUS_COORDX_LABEL
const char * EXODUS_ELEMENT_BLOCK_IDS_LABEL
const char * EXODUS_MAX_NAME_LENGTH_LABEL
const char * EXODUS_API_VERSION_LABEL
const char * EXODUS_NUM_SIDE_SETS_LABEL
const float EXODUS_API_VERSION
const char * EXODUS_FLOATING_POINT_WORD_SIZE_LABEL
const char * EXODUS_SIDE_SET_IDS_LABEL
const int mfem_to_exodusII_node_ordering_pyramid14[]
const int mfem_to_exodusII_node_ordering_wedge18[]
const int mfem_to_exodusII_node_ordering_tet10[]
const int mfem_to_exodusII_node_ordering_hex27[]
const int mfem_to_exodusII_side_map_wedge6[]
const int mfem_to_exodusII_side_map_hex8[]
const int mfem_to_exodusII_side_map_pyramid5[]
const int mfem_to_exodusII_side_map_tet4[]
Convert from the MFEM face numbering to the ExodusII face numbering.
OutStream out(std::cout)
Global stream used by the library for standard output. Initially it uses the same std::streambuf as s...
Definition globals.hpp:66
float real_t
Definition config.hpp:46