Files
mfem/mesh/vtkhdf.hpp
T

342 lines
12 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_VTKHDF
#define MFEM_VTKHDF
#include "../config/config.hpp"
#ifdef MFEM_USE_HDF5
#include "../fem/gridfunc.hpp"
#include <hdf5.h>
#include <cstdint>
#include <unordered_map>
#if defined(MFEM_USE_MPI) && defined(H5_HAVE_PARALLEL)
#define MFEM_PARALLEL_HDF5
#endif
namespace mfem
{
/// @brief Low-level class for writing %VTKHDF data (for use in ParaView).
///
/// Users should typically use ParaViewHDFDataCollection, Mesh::SaveVTKHDF, or
/// GridFunction::SaveVTKHDF instead.
class VTKHDF
{
public:
/// Helper used in VTKHDF::VTKHDF for enabling/disabling restart mode.
struct Restart
{
bool enabled = false;
real_t time = 0.0;
Restart() = default;
Restart(real_t time_) : enabled(true), time(time_) { }
Restart(bool enabled_, real_t time_) : enabled(enabled_), time(time_) { }
static Restart Disabled() { return Restart(); }
};
private:
#ifdef MFEM_USE_MPI
/// MPI communicator (only available if MPI is enabled).
MPI_Comm comm = MPI_COMM_NULL;
#endif
/// Size of the MPI communicator (1 if MPI is not enabled).
const int mpi_size = 1;
/// Rank within MPI communicator (0 if MPI is not enabled).
const int mpi_rank = 0;
/// File access property list (needed for MPI I/O).
hid_t fapl = H5I_INVALID_HID;
/// HDF5 file handle.
hid_t file = H5I_INVALID_HID;
/// The 'VTKHDF' root group within the file.
hid_t vtk = H5I_INVALID_HID;
/// Data transfer property list.
hid_t dxpl = H5P_DEFAULT;
/// The group to cell data (element attributes).
hid_t cell_data = H5I_INVALID_HID;
/// The group to store point data (e.g. grid functions).
hid_t point_data = H5I_INVALID_HID;
/// Compression level (-1 means disabled, 0 through 9 enabled). Default is 6.
int compression_level = 6;
/// Wrapper for storing dataset dimensions (max ndims is 2D in VTKHDF).
struct Dims
{
static constexpr size_t MAX_NDIMS = 2;
std::array<hsize_t, MAX_NDIMS> data = { }; // Zero initialized
int ndims = 0;
Dims() = default;
Dims(int ndims_) : ndims(ndims_) { MFEM_ASSERT(ndims <= MAX_NDIMS, ""); }
Dims(int ndims_, hsize_t val) : Dims(ndims_) { data.fill(val); }
template <typename T>
Dims(std::initializer_list<T> data_) : Dims(int(data_.size()))
{ std::copy(data_.begin(), data_.end(), data.begin()); }
operator hsize_t*() { return data.data(); }
hsize_t &operator[](int i) { return data[i]; }
hsize_t TotalSize() const;
};
/// @name Needed for time-dependent data sets.
///@{
/// The group to store time step information.
hid_t steps = H5I_INVALID_HID;
/// Number of time steps saved.
unsigned long nsteps = 0;
/// Keep track of the offsets into the data arrays at each time step.
struct Offsets
{
hsize_t current = 0;
hsize_t next = 0;
void Update(hsize_t offset)
{
current = next;
next += offset;
}
};
hsize_t part_offset; ///< Offset into the "NumberOf" arrays.
Offsets point_offsets; ///< Offsets into the point-sized arrays.
Offsets cell_offsets; ///< Offsets into the cell-sized arrays.
Offsets connectivity_offsets; ///< Offsets into the connectivity arrays.
/// Offsets into named point data arrays (for saved GridFunctions).
std::unordered_map<std::string,Offsets> point_data_offsets;
/// Track when the mesh has changed (enable reusing the previous saved mesh).
class MeshId
{
const Mesh *mesh_ptr = nullptr;
long sequence = -1;
long nodes_sequence = -1;
bool high_order = true;
int ref = -1;
public:
MeshId() = default;
/// @brief Is the given @a mesh different from the cached MeshId?
///
/// The mesh is the same if the pointer is the same and sequence and nodes
/// sequence are both the same.
///
/// If HasChanged() returns true, then new mesh data should be saved. If
/// it returns false, then the mesh doesn't need to be saved again.
bool HasChanged(const Mesh &mesh) const
{
if (mesh_ptr == &mesh)
{
return sequence != mesh.GetSequence() ||
nodes_sequence != mesh.GetNodesSequence();
}
return true;
}
/// @brief Is the given @a mesh different from the cached MeshId, taking
/// into account the order and refinement level of the previously saved
/// mesh.
bool HasChanged(const Mesh &mesh, bool high_order_, int ref_) const
{
return HasChanged(mesh) || high_order != high_order_ || ref != ref_;
}
/// Set the cached MeshId given @a mesh.
void Set(const Mesh &mesh, bool high_order_, int ref_)
{
mesh_ptr = &mesh;
sequence = mesh.GetSequence();
nodes_sequence = mesh.GetNodesSequence();
high_order = high_order_;
ref = ref_;
}
/// Return the refinement level of the previously saved mesh.
int GetRefinementLevel() const { return ref; }
};
/// The most recently saved MeshId.
MeshId mesh_id;
/// Number of points of most recently saved mesh.
hsize_t last_np = 0;
///@}
/// Hold rank-offset and total size for parallel I/O.
struct OffsetTotal { size_t offset; size_t total; };
/// Ensure that the 'Steps' group and 'PointDataOffsets' subgroup exist.
void EnsureSteps();
/// @brief Ensure that the dataset named @a name in @a f exists.
///
/// If the dataset does not exist, create it and return the ID. If the
/// dataset exists, open it and return the ID.
///
/// The rank (number of dimensions) of the dataset is given by @a ndims and
/// its data type is given by @a type.
///
/// If the dataset does not exist, it will initially have size @a dims.
/// Otherwise, it will be resized to append data of size @a dims, and @a dims
/// will be set to the new total size.
hid_t EnsureDataset(hid_t f, const std::string &name, hid_t type, Dims &dims);
/// @brief Ensure the named group is open, creating it if needed. Set @a
/// group to the ID.
///
/// If @a group has already been opened, do nothing.
void EnsureGroup(const std::string &name, hid_t &group);
/// Appends data in parallel to the dataset named @a name in @a f.
///
/// Data is appended along the zeroth dimension. Data of length @a locsize
/// will be written at offset @a offset. The sum over all MPI ranks of @a
/// locsize is equal to the zeroth dimension of @a globsize. The data is
/// written in row-major order.
template <typename T>
void AppendParData(hid_t f, const std::string &name, hsize_t locsize,
hsize_t offset, Dims globsize, T *data);
/// Appends data in parallel to the dataset named @a name in @a f.
///
/// The input parameter @a dims is used to determine the rank of the data to
/// be written, and the extent of all but the first dimension. For example,
/// if dims = (0), then the written dimensions will be (N), where N is the
/// sum of the size of @a data across all MPI ranks. If dims = (0, m), then
/// the written dimensions will be (N/m, m). The data will be written in
/// row-major ordering.
///
/// The row offset and total are returned.
template <typename T>
OffsetTotal AppendParVector(hid_t f, const std::string &name,
const std::vector<T> &data, Dims dims = Dims(1));
/// @brief Append a single value to the dataset named @a name in @a f.
template <typename T>
void AppendValue(const hid_t f, const std::string &name, T value);
/// Gather the value 'loc' from all MPI ranks and return the resulting array.
template <typename T>
std::vector<T> AllGather(const T loc) const;
/// @brief Returns the pair (offset, total) across MPI ranks given local data
/// 'loc'.
///
/// If loc_i represents the value of loc on MPI rank i, then the returned
/// offset is the sum from i = 0 to R, where R is the current MPI rank. The
/// returned total is the sum over all MPI ranks.
///
/// Requires performing 'gather all' operation.
OffsetTotal GetOffsetAndTotal(const size_t loc) const;
/// @brief Return true if the VTKHDF file is using MPI, false otherwise.
///
/// The object is using MPI if the MPI communicator was passed to the
/// constructor. This is only possible if MFEM_USE_MPI is enabled. Even with
/// MPI enabled, a non-MPI VTKHDF object may be created.
bool UsingMpi() const;
/// Calls MPI_Barrier if in parallel, no-op otherwise.
void Barrier() const;
/// Return the HDF5 type ID corresponding to type @a T.
template <typename T> static hid_t GetTypeID();
/// Common setup (VTK group creation, etc.) for serial and parallel.
void SetupVTKHDF();
/// Create a new file (deleting existing file if needed).
void CreateFile(const std::string &filename, Restart restart);
/// Read the entire named dataset.
template <typename T>
std::vector<T> ReadDataset(const std::string &name) const;
/// Read a single value from the named dataset.
template <typename T>
T ReadValue(const std::string &name, hsize_t index) const;
/// Truncate the named dataset after position size in the first dimension.
void TruncateDataset(const std::string &name, hsize_t size);
/// Truncate all datasets on and after time @a t.
void Truncate(const real_t t);
public:
/// @brief Create a new %VTKHDF file for serial I/O.
///
/// If @a restart is enabled, then the file (if it exists) will be opened,
/// and time steps before the given time will be preserved. If @a restart
/// is not enabled, the file (if it exists) will be deleted, and a new file
/// will be created.
VTKHDF(const std::string &filename, Restart restart = Restart::Disabled());
#ifdef MFEM_PARALLEL_HDF5
/// @brief Create a new %VTKHDF file for parallel I/O.
///
/// If @a restart is enabled, then the file (if it exists) will be opened,
/// and time steps before the given time will be preserved. If @a restart
/// is not enabled, the file (if it exists) will be deleted, and a new file
/// will be created.
///
/// If using restart mode, the file must have previously been saved with the
/// same number of MPI ranks.
VTKHDF(const std::string &filename, MPI_Comm comm_,
Restart restart = Restart::Disabled());
#endif
/// @name Not copyable or movable.
///@{
VTKHDF(const VTKHDF &) = delete;
VTKHDF(VTKHDF &&) = delete;
VTKHDF &operator=(const VTKHDF &) = delete;
VTKHDF &operator=(VTKHDF &&) = delete;
///@}
/// Update the time step data after saving the mesh and grid functions.
void UpdateSteps(real_t t);
/// Disable zlib compression.
void DisableCompression() { compression_level = -1; }
/// @brief Enable zlib compression at the specified level.
///
/// @a level must be between 0 and 9, in increasing order of compression.
void EnableCompression(int level = 6) { compression_level = level; }
/// @brief Save the mesh, appending as a new time step.
///
/// If @a high_order is true, @a ref determines the polynomial degree of the
/// mesh elements (-1 indicates the same as the mesh nodes). If @a high_order
/// is false, the elements are uniformly subdivided according to @a ref.
template <typename FP_T = real_t>
void SaveMesh(const Mesh &mesh, bool high_order = true, int ref = -1);
/// Save the grid function with the given name, appending as a new time step.
template <typename FP_T = real_t>
void SaveGridFunction(const GridFunction &gf, const std::string &name);
/// Flush the file.
void Flush();
///< Destructor. Close the file.
~VTKHDF();
};
} // namespace mfem
#endif // MFEM_USE_HDF5
#endif // MFEM_VTKHDF