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