Files
mfem/fem/datacollection.hpp
T
2022-05-25 04:22:46 +02:00

639 lines
22 KiB
C++

// Copyright (c) 2010-2022, 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_DATACOLLECTION
#define MFEM_DATACOLLECTION
#include "../config/config.hpp"
#include "gridfunc.hpp"
#ifdef MFEM_USE_MPI
#include "pgridfunc.hpp"
#endif
#include <string>
#include <map>
#include <fstream>
#include <optional>
namespace mfem
{
/// Lightweight adaptor over an std::map from strings to pointer to T
template<typename T>
class NamedFieldsMap
{
public:
typedef std::map<std::string, T*> MapType;
typedef typename MapType::iterator iterator;
typedef typename MapType::const_iterator const_iterator;
/// Register field @a field with name @a fname
/** Replace existing field associated with @a fname (and optionally
delete associated pointer if @a own_data is true) */
void Register(const std::string& fname, T* field, bool own_data)
{
T*& ref = field_map[fname];
if (own_data)
{
delete ref; // if newly allocated -> ref is null -> OK
}
ref = field;
}
/// Unregister association between field @a field and name @a fname
/** Optionally delete associated pointer if @a own_data is true */
void Deregister(const std::string& fname, bool own_data)
{
iterator it = field_map.find(fname);
if ( it != field_map.end() )
{
if (own_data)
{
delete it->second;
}
field_map.erase(it);
}
}
/// Clear all associations between names and fields
/** Delete associated pointers when @a own_data is true */
void DeleteData(bool own_data)
{
for (iterator it = field_map.begin(); it != field_map.end(); ++it)
{
if (own_data)
{
delete it->second;
}
it->second = NULL;
}
}
/// Predicate to check if a field is associated with name @a fname
bool Has(const std::string& fname) const
{
return field_map.find(fname) != field_map.end();
}
/// Get a pointer to the field associated with name @a fname
/** @return Pointer to field associated with @a fname or NULL */
T* Get(const std::string& fname) const
{
const_iterator it = field_map.find(fname);
return it != field_map.end() ? it->second : NULL;
}
/// Returns a const reference to the underlying map
const MapType& GetMap() const { return field_map; }
/// Returns the number of registered fields
int NumFields() const { return field_map.size(); }
/// Returns a begin iterator to the registered fields
iterator begin() { return field_map.begin(); }
/// Returns a begin const iterator to the registered fields
const_iterator begin() const { return field_map.begin(); }
/// Returns an end iterator to the registered fields
iterator end() { return field_map.end(); }
/// Returns an end const iterator to the registered fields
const_iterator end() const { return field_map.end(); }
/// Returns an iterator to the field @a fname
iterator find(const std::string& fname)
{ return field_map.find(fname); }
/// Returns a const iterator to the field @a fname
const_iterator find(const std::string& fname) const
{ return field_map.find(fname); }
/// Clears the map of registered fields without reclaiming memory
void clear() { field_map.clear(); }
protected:
MapType field_map;
};
/** A class for collecting finite element data that is part of the same
simulation. Currently, this class groups together grid functions (fields),
quadrature functions (q-fields), and the mesh that they are defined on. */
class DataCollection
{
private:
/// A collection of named GridFunctions
typedef NamedFieldsMap<GridFunction> GFieldMap;
/// A collection of named QuadratureFunctions
typedef NamedFieldsMap<QuadratureFunction> QFieldMap;
public:
typedef GFieldMap::MapType FieldMapType;
typedef GFieldMap::iterator FieldMapIterator;
typedef GFieldMap::const_iterator FieldMapConstIterator;
typedef QFieldMap::MapType QFieldMapType;
typedef QFieldMap::iterator QFieldMapIterator;
typedef QFieldMap::const_iterator QFieldMapConstIterator;
/// Format constants to be used with SetFormat().
/** Derived classes can define their own format enumerations and override the
method SetFormat() to perform input validation. */
enum Format
{
SERIAL_FORMAT = 0, /**<
MFEM's serial ascii format, using the methods Mesh::Print() /
ParMesh::Print(), and GridFunction::Save() / ParGridFunction::Save().*/
PARALLEL_FORMAT = 1 /**<
MFEM's parallel ascii format, using the methods ParMesh::ParPrint() and
GridFunction::Save() / ParGridFunction::Save(). */
};
protected:
/// Name of the collection, used as a directory name when saving
std::string name;
/** @brief A path where the directory with results is saved.
If not empty, it has '/' at the end. */
std::string prefix_path;
/** A FieldMap mapping registered field names to GridFunction pointers. */
GFieldMap field_map;
/** A FieldMap mapping registered names to QuadratureFunction pointers. */
QFieldMap q_field_map;
/// The (common) mesh for the collected fields
Mesh *mesh;
/// Time cycle; for time-dependent simulations cycle >= 0, otherwise = -1.
/** When cycle >= 0, it is appended to directory names. */
int cycle;
/// Physical time (for time-dependent simulations)
double time;
/// Time step i.e. delta_t (for time-dependent simulations)
double time_step;
/// Serial or parallel run? False iff mesh is a ParMesh
bool serial;
/// Append rank to any output file names.
bool appendRankToFileName;
/// MPI rank (in parallel)
int myid;
/// Number of MPI ranks (in parallel)
int num_procs;
#ifdef MFEM_USE_MPI
/// Associated MPI communicator
MPI_Comm m_comm;
#endif
/// Precision (number of digits) used for the text output of doubles
int precision;
/// Number of digits used for the cycle and MPI rank in filenames
int pad_digits_cycle, pad_digits_rank;
/// Default value for precision
static const int precision_default = 6;
/// Default value for pad_digits_*
static const int pad_digits_default = 6;
/// Output mesh format: see the #Format enumeration
int format;
int compression;
/// Should the collection delete its mesh and fields
bool own_data;
/// Error state
int error;
/// Delete data owned by the DataCollection keeping field information
void DeleteData();
/// Delete data owned by the DataCollection including field information
void DeleteAll();
std::string GetMeshShortFileName() const;
std::string GetMeshFileName() const;
std::string GetFieldFileName(const std::string &field_name) const;
/// Save one field to disk, assuming the collection directory exists
void SaveOneField(const FieldMapIterator &it);
/// Save one q-field to disk, assuming the collection directory exists
void SaveOneQField(const QFieldMapIterator &it);
// Helper method
static int create_directory(const std::string &dir_name,
const Mesh *mesh, int myid);
public:
/// Initialize the collection with its name and Mesh.
/** When @a mesh_ is NULL, then the real mesh can be set with SetMesh(). */
explicit DataCollection(const std::string& collection_name,
Mesh *mesh_ = NULL);
/// Add a grid function to the collection
virtual void RegisterField(const std::string& field_name, GridFunction *gf)
{ field_map.Register(field_name, gf, own_data); }
/// Remove a grid function from the collection
virtual void DeregisterField(const std::string& field_name)
{ field_map.Deregister(field_name, own_data); }
/// Add a QuadratureFunction to the collection.
virtual void RegisterQField(const std::string& q_field_name,
QuadratureFunction *qf)
{ q_field_map.Register(q_field_name, qf, own_data); }
/// Remove a QuadratureFunction from the collection
virtual void DeregisterQField(const std::string& field_name)
{ q_field_map.Deregister(field_name, own_data); }
/// Check if a grid function is part of the collection
bool HasField(const std::string& field_name) const
{ return field_map.Has(field_name); }
/// Get a pointer to a grid function in the collection.
/** Returns NULL if @a field_name is not in the collection. */
GridFunction *GetField(const std::string& field_name)
{ return field_map.Get(field_name); }
#ifdef MFEM_USE_MPI
/// Return the associated MPI communicator or MPI_COMM_NULL.
MPI_Comm GetComm() const { return m_comm; }
/// Get a pointer to a parallel grid function in the collection.
/** Returns NULL if @a field_name is not in the collection.
@note The GridFunction pointer stored in the collection is statically
cast to ParGridFunction pointer. */
ParGridFunction *GetParField(const std::string& field_name)
{ return static_cast<ParGridFunction*>(GetField(field_name)); }
#endif
/// Check if a QuadratureFunction with the given name is in the collection.
bool HasQField(const std::string& q_field_name) const
{ return q_field_map.Has(q_field_name); }
/// Get a pointer to a QuadratureFunction in the collection.
/** Returns NULL if @a field_name is not in the collection. */
QuadratureFunction *GetQField(const std::string& q_field_name)
{ return q_field_map.Get(q_field_name); }
/// Get a const reference to the internal field map.
/** The keys in the map are the field names and the values are pointers to
GridFunction%s. */
const FieldMapType &GetFieldMap() const
{ return field_map.GetMap(); }
/// Get a const reference to the internal q-field map.
/** The keys in the map are the q-field names and the values are pointers to
QuadratureFunction%s. */
const QFieldMapType &GetQFieldMap() const
{ return q_field_map.GetMap(); }
/// Get a pointer to the mesh in the collection
Mesh *GetMesh() { return mesh; }
/// Set/change the mesh associated with the collection
/** When passed a Mesh, assumes the serial case: MPI rank id is set to 0 and
MPI num_procs is set to 1. When passed a ParMesh, MPI info from the
ParMesh is used to set the DataCollection's MPI rank and num_procs. */
virtual void SetMesh(Mesh *new_mesh);
#ifdef MFEM_USE_MPI
/// Set/change the mesh associated with the collection.
/** For this case, @a comm is used to set the DataCollection's MPI rank id
and MPI num_procs, which influences the how files are saved for domain
decomposed meshes. */
virtual void SetMesh(MPI_Comm comm, Mesh *new_mesh);
#endif
/// Set time cycle (for time-dependent simulations)
void SetCycle(int c) { cycle = c; }
/// Set physical time (for time-dependent simulations)
void SetTime(double t) { time = t; }
/// Set the simulation time step (for time-dependent simulations)
void SetTimeStep(double ts) { time_step = ts; }
/// Get time cycle (for time-dependent simulations)
int GetCycle() const { return cycle; }
/// Get physical time (for time-dependent simulations)
double GetTime() const { return time; }
/// Get the simulation time step (for time-dependent simulations)
double GetTimeStep() const { return time_step; }
/// Get the name of the collection
const std::string& GetCollectionName() const { return name; }
/// Set the ownership of collection data
void SetOwnData(bool o) { own_data = o; }
/// Set the precision (number of digits) used for the text output of doubles
void SetPrecision(int prec) { precision = prec; }
/// Set the number of digits used for both the cycle and the MPI rank
void SetPadDigits(int digits) { pad_digits_cycle=pad_digits_rank = digits; }
/// Set the number of digits used for the cycle
void SetPadDigitsCycle(int digits) { pad_digits_cycle = digits; }
/// Set the number of digits used for the MPI rank in filenames
void SetPadDigitsRank(int digits) { pad_digits_rank = digits; }
/// Set the desired output mesh and data format.
/** See the enumeration #Format for valid options. Derived classes can define
their own format enumerations and override this method to perform input
validation. */
virtual void SetFormat(int fmt);
/// Set the flag for use of gz compressed files
virtual void SetCompression(bool comp);
/// Set the path where the DataCollection will be saved.
void SetPrefixPath(const std::string &prefix);
/// Get the path where the DataCollection will be saved.
const std::string &GetPrefixPath() const { return prefix_path; }
/// Save the collection to disk.
/** By default, everything is saved in the "prefix_path" directory with
subdirectory name "collection_name" or "collection_name_cycle" for
time-dependent simulations. */
virtual void Save();
/// Save the mesh, creating the collection directory.
virtual void SaveMesh();
/// Save one field, assuming the collection directory already exists.
virtual void SaveField(const std::string &field_name);
/// Save one q-field, assuming the collection directory already exists.
virtual void SaveQField(const std::string &q_field_name);
/// Load the collection. Not implemented in the base class DataCollection.
virtual void Load(int cycle_ = 0);
/// Delete the mesh and fields if owned by the collection
virtual ~DataCollection();
/// Errors returned by Error()
enum { NO_ERROR = 0, READ_ERROR = 1, WRITE_ERROR = 2 };
/// Get the current error state
int Error() const { return error; }
/// Reset the error state
void ResetError(int err_state = NO_ERROR) { error = err_state; }
#ifdef MFEM_USE_MPI
friend class ParMesh;
#endif
};
/// Helper class for VisIt visualization data
class VisItFieldInfo
{
public:
std::string association;
int num_components;
int lod;
VisItFieldInfo() { association = ""; num_components = 0; lod = 1;}
VisItFieldInfo(std::string association_, int num_components_, int lod_ = 1)
{ association = association_; num_components = num_components_; lod =lod_;}
};
/// Data collection with VisIt I/O routines
class VisItDataCollection : public DataCollection
{
protected:
// Additional data needed in the VisIt root file, which describes the mesh
// and all the fields in the collection
int spatial_dim, topo_dim;
int visit_levels_of_detail;
int visit_max_levels_of_detail;
std::map<std::string, VisItFieldInfo> field_info_map;
typedef std::map<std::string, VisItFieldInfo>::iterator FieldInfoMapIterator;
/// Prepare the VisIt root file in JSON format for the current collection
std::string GetVisItRootString();
/// Read in a VisIt root file in JSON format
void ParseVisItRootString(const std::string& json);
void UpdateMeshInfo();
// Helper functions for Load()
void LoadVisItRootFile(const std::string& root_name);
void LoadMesh();
void LoadFields();
public:
/// Constructor. The collection name is used when saving the data.
/** If @a mesh_ is NULL, then the mesh can be set later by calling either
SetMesh() or Load(). The latter works only in serial. */
VisItDataCollection(const std::string& collection_name, Mesh *mesh_ = NULL);
#ifdef MFEM_USE_MPI
/// Construct a parallel VisItDataCollection to be loaded from files.
/** Before loading the collection with Load(), some parameters in the
collection can be adjusted, e.g. SetPadDigits(), SetPrefixPath(), etc. */
VisItDataCollection(MPI_Comm comm, const std::string& collection_name,
Mesh *mesh_ = NULL);
#endif
/// Set/change the mesh associated with the collection
virtual void SetMesh(Mesh *new_mesh);
#ifdef MFEM_USE_MPI
/// Set/change the mesh associated with the collection.
virtual void SetMesh(MPI_Comm comm, Mesh *new_mesh);
#endif
/// Add a grid function to the collection and update the root file
virtual void RegisterField(const std::string& field_name, GridFunction *gf);
/// Add a quadrature function to the collection and update the root file.
/** Visualization of quadrature function is not supported in VisIt(3.12).
A patch has been sent to VisIt developers in June 2020. */
virtual void RegisterQField(const std::string& q_field_name,
QuadratureFunction *qf);
/// Set VisIt parameter: default levels of detail for the MultiresControl
void SetLevelsOfDetail(int levels_of_detail);
/// Set VisIt parameter: maximum levels of detail for the MultiresControl
void SetMaxLevelsOfDetail(int max_levels_of_detail);
/** @brief Delete all data owned by VisItDataCollection including field data
information. */
void DeleteAll();
/// Save the collection and a VisIt root file
virtual void Save();
/// Save a VisIt root file for the collection
void SaveRootFile();
/// Load the collection based on its VisIt data (described in its root file)
virtual void Load(int cycle_ = 0);
/// We will delete the mesh and fields if we own them
virtual ~VisItDataCollection() {}
};
/// Helper class for ParaView visualization data
class ParaViewDataCollection : public DataCollection
{
private:
int levels_of_detail;
std::fstream pvd_stream;
VTKFormat pv_data_format;
bool high_order_output;
bool restart_mode;
protected:
void WritePVTUHeader(std::ostream &out);
void WritePVTUFooter(std::ostream &out, const std::string &vtu_prefix);
void SaveDataVTU(std::ostream &out, int ref);
void SaveGFieldVTU(std::ostream& out, int ref_, const FieldMapIterator& it);
const char *GetDataFormatString() const;
const char *GetDataTypeString() const;
std::string GenerateCollectionPath();
std::string GenerateVTUFileName(const std::string &prefix, int rank);
std::string GenerateVTUPath();
std::string GeneratePVDFileName();
std::string GeneratePVTUFileName(const std::string &prefix);
std::string GeneratePVTUPath();
public:
/// Constructor. The collection name is used when saving the data.
/** If @a mesh_ is NULL, then the mesh can be set later by calling SetMesh().
Before saving the data collection, some parameters in the collection can
be adjusted, e.g. SetPadDigits(), SetPrefixPath(), etc. */
ParaViewDataCollection(const std::string& collection_name,
mfem::Mesh *mesh_ = NULL);
/// Set refinement levels - every element is uniformly split based on
/// levels_of_detail_
void SetLevelsOfDetail(int levels_of_detail_);
/// Save the collection - the directory name is constructed based on the
/// cycle value
virtual void Save() override;
/// Set the data format for the ParaView output files. Possible options are
/// VTKFormat::ASCII, VTKFormat::BINARY, and VTKFormat::BINARY32.
/// The ASCII and BINARY options output double precision data, whereas the
/// BINARY32 option outputs single precision data.
void SetDataFormat(VTKFormat fmt);
/// Set the zlib compression level. 0 indicates no compression, -1 indicates
/// the default compression level. Otherwise, specify a number between 1 and
/// 9, 1 being the fastest, and 9 being the best compression. Compression
/// only takes effect if the output format is BINARY or BINARY32. MFEM must
/// be compiled with MFEM_USE_ZLIB = YES.
void SetCompressionLevel(int compression_level_);
/// Enable or disable zlib compression. If the input is true, use the default
/// zlib compression level (unless the compression level has previously been
/// set by calling SetCompressionLevel).
void SetCompression(bool compression_) override;
/// Returns true if the output format is BINARY or BINARY32, false if ASCII.
bool IsBinaryFormat() const;
/// Sets whether or not to output the data as high-order elements (false
/// by default). Reading high-order data requires ParaView 5.5 or later.
void SetHighOrderOutput(bool high_order_output_);
/// Enable or disable restart mode. If restart is enabled, new writes will
/// preserve timestep metadata for any solutions prior to the currently
/// defined time.
void UseRestartMode(bool restart_mode_);
/// Load the collection - not implemented in the ParaView writer
virtual void Load(int cycle_ = 0) override;
};
/** Basically a helper to store a ".meta" file with triplets of the form (cycle,
* time, dt) along the fields for each cycle. */
class MFEMDataCollection final : public DataCollection
{
public:
struct MetaInfo
{
int cycle;
double t;
friend std::istream& operator>>(std::istream& in,
mfem::MFEMDataCollection::MetaInfo& i);
};
private:
std::string GetMetafileName()
{
return prefix_path + GetCollectionName() + ".meta";
}
// We require these two for loading.
std::vector<std::string> field_names_meta;
std::vector<std::string> q_field_names_meta;
bool adaptive_mesh = false;
bool first_io = true;
public:
//! Constructor to store stuff
MFEMDataCollection(const std::string& collection_name,
Mesh& mesh_, bool adaptive_mesh_);
#ifdef MFEM_USE_MPI
//! Constructor to store stuff
MFEMDataCollection(const std::string& collection_name,
ParMesh& mesh_, bool adaptive_mesh_);
#endif
#ifdef MFEM_USE_MPI
//! Constructor to load stuff
MFEMDataCollection(const std::string& collection_name
, Format format_ = PARALLEL_FORMAT
);
#else
//! Constructor to load stuff
MFEMDataCollection(const std::string& collection_name
, Format format_ = SERIAL_FORMAT
);
#endif
void ResetMetadata();
#ifdef MFEM_USE_MPI
ParMesh* GetParMesh()
{
return dynamic_cast<ParMesh*>(GetMesh());
}
#endif
void Save() override;
std::string CyclePathName(int cycle) const;
bool IsAMRData() const
{
return adaptive_mesh;
}
void Load(int cycle_) override;
std::optional<std::vector<MetaInfo>> ReloadMetaInfo();
/// Delete the mesh and fields if owned by the collection
~MFEMDataCollection() = default;
};
}
#endif