Co-authored-by: camierjs <camierjs@gmail.com> Co-authored-by: Veselin Dobrev <dobrev@llnl.gov>
385 lines
13 KiB
C++
385 lines
13 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.
|
|
|
|
#include "mfem.hpp"
|
|
#include "unit_tests.hpp"
|
|
#include "general/tinyxml2.h"
|
|
#include <stdio.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h> // rmdir
|
|
#else
|
|
#include <direct.h> // _rmdir
|
|
#define rmdir(dir) _rmdir(dir)
|
|
#endif
|
|
|
|
#ifdef MFEM_USE_HDF5
|
|
#include <hdf5.h>
|
|
#include <hdf5_hl.h>
|
|
#endif
|
|
|
|
using namespace mfem;
|
|
|
|
TEST_CASE("Save and load from collections", "[DataCollection]")
|
|
{
|
|
SECTION("VisIt data files")
|
|
{
|
|
// Set up a small mesh and a couple of grid function on that mesh
|
|
Mesh mesh = Mesh::MakeCartesian2D(2, 3, Element::QUADRILATERAL, 0, 2.0, 3.0);
|
|
FiniteElementCollection *fec = new LinearFECollection;
|
|
FiniteElementSpace *fespace = new FiniteElementSpace(&mesh, fec);
|
|
GridFunction *u = new GridFunction(fespace);
|
|
GridFunction *v = new GridFunction(fespace);
|
|
|
|
int N = u->Size();
|
|
for (int i = 0; i < N; ++i)
|
|
{
|
|
(*u)(i) = double(i);
|
|
(*v)(i) = double(N - i - 1);
|
|
}
|
|
|
|
int intOrder = 3;
|
|
|
|
QuadratureSpace *qspace = new QuadratureSpace(&mesh, intOrder);
|
|
QuadratureFunction *qs = new QuadratureFunction(qspace, 1);
|
|
QuadratureFunction *qv = new QuadratureFunction(qspace, 2);
|
|
|
|
int Nq = qs->Size();
|
|
for (int i = 0; i < Nq; ++i)
|
|
{
|
|
(*qs)(i) = double(i);
|
|
(*qv)(2*i+0) = double(i);
|
|
(*qv)(2*i+1) = double(Nq - i - 1);
|
|
}
|
|
|
|
|
|
SECTION("Uncompressed MFEM format")
|
|
{
|
|
// Collect the mesh and grid functions into a DataCollection and test that they got in there
|
|
VisItDataCollection dc("base", &mesh);
|
|
dc.RegisterField("u", u);
|
|
dc.RegisterField("v", v);
|
|
dc.RegisterQField("qs",qs);
|
|
dc.RegisterQField("qv",qv);
|
|
dc.SetCycle(5);
|
|
dc.SetTime(8.0);
|
|
REQUIRE(dc.GetMesh() == &mesh);
|
|
bool has_u = dc.HasField("u");
|
|
REQUIRE(has_u);
|
|
bool has_v = dc.HasField("v");
|
|
REQUIRE(has_v);
|
|
bool has_qs = dc.HasQField("qs");
|
|
REQUIRE(has_qs);
|
|
bool has_qv = dc.HasQField("qv");
|
|
REQUIRE(has_qv);
|
|
REQUIRE(dc.GetCycle() == 5);
|
|
REQUIRE(dc.GetTime() == 8.0);
|
|
|
|
// Save the DataCollection and load it into a new DataCollection for comparison
|
|
dc.SetPadDigitsCycle(5);
|
|
dc.SetPadDigitsRank(5);
|
|
dc.Save();
|
|
|
|
VisItDataCollection dc_new("base");
|
|
dc_new.SetPadDigitsCycle(5);
|
|
dc_new.SetPadDigitsRank(5);
|
|
dc_new.Load(dc.GetCycle());
|
|
Mesh* mesh_new = dc_new.GetMesh();
|
|
GridFunction *u_new = dc_new.GetField("u");
|
|
GridFunction *v_new = dc_new.GetField("v");
|
|
QuadratureFunction *qs_new = dc_new.GetQField("qs");
|
|
QuadratureFunction *qv_new = dc_new.GetQField("qv");
|
|
REQUIRE(mesh_new);
|
|
REQUIRE(u_new);
|
|
REQUIRE(v_new);
|
|
REQUIRE(qs_new);
|
|
REQUIRE(qv_new);
|
|
|
|
// Compare some collection parameters for old and new
|
|
std::string name, name_new;
|
|
name = dc.GetCollectionName();
|
|
name_new = dc_new.GetCollectionName();
|
|
REQUIRE(name == name_new);
|
|
REQUIRE(dc.GetCycle() == dc_new.GetCycle());
|
|
REQUIRE(dc.GetTime() == dc_new.GetTime());
|
|
|
|
// Compare the new mesh with the old mesh
|
|
// (Just a basic comparison here, a full comparison should be done in Mesh unit testing)
|
|
REQUIRE(mesh.Dimension() == mesh_new->Dimension());
|
|
REQUIRE(mesh.SpaceDimension() == mesh_new->SpaceDimension());
|
|
|
|
Vector vert, vert_diff;
|
|
mesh.GetVertices(vert);
|
|
mesh_new->GetVertices(vert_diff);
|
|
vert_diff -= vert;
|
|
REQUIRE(vert_diff.Normlinf() < 1e-10);
|
|
|
|
// Compare the old and new grid functions
|
|
// (Just a basic comparison here, a full comparison should be done in GridFunction unit testing)
|
|
Vector u_diff(*u_new), v_diff(*v_new);
|
|
u_diff -= *u;
|
|
v_diff -= *v;
|
|
REQUIRE(u_diff.Normlinf() < 1e-10);
|
|
REQUIRE(v_diff.Normlinf() < 1e-10);
|
|
|
|
// Compare the old and new quadrature functions
|
|
// (Just a basic comparison here, a full comparison should be done in GridFunction unit testing)
|
|
Vector qs_diff(*qs_new), qv_diff(*qv_new);
|
|
qs_diff -= *qs;
|
|
qv_diff -= *qv;
|
|
REQUIRE(qs_diff.Normlinf() < 1e-10);
|
|
REQUIRE(qv_diff.Normlinf() < 1e-10);
|
|
|
|
// Cleanup all the files
|
|
REQUIRE(remove("base_00005.mfem_root") == 0);
|
|
REQUIRE(remove("base_00005/mesh.00000") == 0);
|
|
REQUIRE(remove("base_00005/u.00000") == 0);
|
|
REQUIRE(remove("base_00005/v.00000") == 0);
|
|
REQUIRE(remove("base_00005/qs.00000") == 0);
|
|
REQUIRE(remove("base_00005/qv.00000") == 0);
|
|
REQUIRE(rmdir("base_00005") == 0);
|
|
}
|
|
|
|
#ifdef MFEM_USE_ZLIB
|
|
SECTION("Compressed MFEM format")
|
|
{
|
|
// Collect the mesh and grid functions into a DataCollection and test that they got in there
|
|
VisItDataCollection dc("base", &mesh);
|
|
dc.RegisterField("u", u);
|
|
dc.RegisterField("v", v);
|
|
dc.RegisterQField("qs",qs);
|
|
dc.RegisterQField("qv",qv);
|
|
dc.SetCycle(5);
|
|
dc.SetTime(8.0);
|
|
REQUIRE(dc.GetMesh() == &mesh);
|
|
bool has_u = dc.HasField("u");
|
|
REQUIRE(has_u);
|
|
bool has_v = dc.HasField("v");
|
|
REQUIRE(has_v);
|
|
bool has_qs = dc.HasQField("qs");
|
|
REQUIRE(has_qs);
|
|
bool has_qv = dc.HasQField("qv");
|
|
REQUIRE(has_qv);
|
|
REQUIRE(dc.GetCycle() == 5);
|
|
REQUIRE(dc.GetTime() == 8.0);
|
|
|
|
// Save the DataCollection and load it into a new DataCollection for comparison
|
|
dc.SetPadDigitsCycle(5);
|
|
dc.SetPadDigitsRank(5);
|
|
dc.SetCompression(true);
|
|
dc.Save();
|
|
|
|
VisItDataCollection dc_new("base");
|
|
dc_new.SetPadDigitsCycle(5);
|
|
dc_new.SetPadDigitsRank(5);
|
|
dc_new.Load(dc.GetCycle());
|
|
Mesh *mesh_new = dc_new.GetMesh();
|
|
GridFunction *u_new = dc_new.GetField("u");
|
|
GridFunction *v_new = dc_new.GetField("v");
|
|
QuadratureFunction *qs_new = dc_new.GetQField("qs");
|
|
QuadratureFunction *qv_new = dc_new.GetQField("qv");
|
|
REQUIRE(mesh_new);
|
|
REQUIRE(u_new);
|
|
REQUIRE(v_new);
|
|
REQUIRE(qs_new);
|
|
REQUIRE(qv_new);
|
|
|
|
// Compare some collection parameters for old and new
|
|
std::string name, name_new;
|
|
name = dc.GetCollectionName();
|
|
name_new = dc_new.GetCollectionName();
|
|
REQUIRE(name == name_new);
|
|
REQUIRE(dc.GetCycle() == dc_new.GetCycle());
|
|
REQUIRE(dc.GetTime() == dc_new.GetTime());
|
|
|
|
// Compare the new mesh with the old mesh
|
|
// (Just a basic comparison here, a full comparison should be done in Mesh unit testing)
|
|
REQUIRE(mesh.Dimension() == mesh_new->Dimension());
|
|
REQUIRE(mesh.SpaceDimension() == mesh_new->SpaceDimension());
|
|
|
|
Vector vert, vert_diff;
|
|
mesh.GetVertices(vert);
|
|
mesh_new->GetVertices(vert_diff);
|
|
vert_diff -= vert;
|
|
REQUIRE(vert_diff.Normlinf() < 1e-10);
|
|
|
|
// Compare the old and new grid functions
|
|
// (Just a basic comparison here, a full comparison should be done in GridFunction unit testing)
|
|
Vector u_diff(*u_new), v_diff(*v_new);
|
|
u_diff -= *u;
|
|
v_diff -= *v;
|
|
REQUIRE(u_diff.Normlinf() < 1e-10);
|
|
REQUIRE(v_diff.Normlinf() < 1e-10);
|
|
|
|
// Compare the old and new quadrature functions
|
|
// (Just a basic comparison here, a full comparison should be done in GridFunction unit testing)
|
|
Vector qs_diff(*qs_new), qv_diff(*qv_new);
|
|
qs_diff -= *qs;
|
|
qv_diff -= *qv;
|
|
REQUIRE(qs_diff.Normlinf() < 1e-10);
|
|
REQUIRE(qv_diff.Normlinf() < 1e-10);
|
|
|
|
// Cleanup all the files
|
|
REQUIRE(remove("base_00005.mfem_root") == 0);
|
|
REQUIRE(remove("base_00005/mesh.00000") == 0);
|
|
REQUIRE(remove("base_00005/u.00000") == 0);
|
|
REQUIRE(remove("base_00005/v.00000") == 0);
|
|
REQUIRE(remove("base_00005/qs.00000") == 0);
|
|
REQUIRE(remove("base_00005/qv.00000") == 0);
|
|
REQUIRE(rmdir("base_00005") == 0);
|
|
}
|
|
#endif
|
|
delete fec;
|
|
delete fespace;
|
|
delete u;
|
|
delete v;
|
|
delete qspace;
|
|
delete qs;
|
|
delete qv;
|
|
}
|
|
|
|
}
|
|
|
|
void SaveDataCollection(DataCollection &dc, int cycle, double t)
|
|
{
|
|
dc.SetCycle(cycle);
|
|
dc.SetTime(t);
|
|
dc.Save();
|
|
}
|
|
|
|
TEST_CASE("ParaView restart mode", "[ParaView]")
|
|
{
|
|
Mesh mesh = Mesh::MakeCartesian2D(2, 3, Element::QUADRILATERAL);
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
GridFunction u(&fes);
|
|
u = 0.0;
|
|
|
|
// Write initial dataset with three timesteps: 0, 1, 2.
|
|
{
|
|
ParaViewDataCollection dc("ParaView", &mesh);
|
|
dc.RegisterField("u", &u);
|
|
SaveDataCollection(dc, 0, 0);
|
|
SaveDataCollection(dc, 1, 1);
|
|
SaveDataCollection(dc, 2, 2);
|
|
}
|
|
|
|
// Using restart mode, append to the existing dataset, overwriting timesteps
|
|
// 1 and 2 with 1 and 1.5.
|
|
{
|
|
ParaViewDataCollection dc("ParaView", &mesh);
|
|
dc.UseRestartMode(true);
|
|
dc.RegisterField("u", &u);
|
|
SaveDataCollection(dc, 1, 1.0);
|
|
SaveDataCollection(dc, 2, 1.5);
|
|
}
|
|
|
|
// Parse the resulting PVD file, and verify that the structure is correct,
|
|
// and that it contains three timesteps: 0, 1, and 1.5.
|
|
using namespace tinyxml2;
|
|
auto StringCompare = [](const char *s1, const char *s2)
|
|
{
|
|
if (s1 == NULL || s2 == NULL) { return false; }
|
|
return strcmp(s1, s2) == 0;
|
|
};
|
|
auto VerifyDataset = [StringCompare](const XMLElement *ds, double t_ref)
|
|
{
|
|
REQUIRE(ds);
|
|
REQUIRE(StringCompare(ds->Name(), "DataSet"));
|
|
const char *timestep = ds->Attribute("timestep");
|
|
REQUIRE(timestep);
|
|
double t = std::stod(timestep);
|
|
REQUIRE(t == MFEM_Approx(t_ref));
|
|
};
|
|
|
|
XMLDocument xml;
|
|
xml.LoadFile("ParaView/ParaView.pvd");
|
|
REQUIRE(xml.ErrorID() == XML_SUCCESS);
|
|
|
|
const XMLElement *vtkfile = xml.FirstChildElement();
|
|
REQUIRE(vtkfile);
|
|
REQUIRE(StringCompare(vtkfile->Name(), "VTKFile"));
|
|
const XMLElement *collection = vtkfile->FirstChildElement();
|
|
REQUIRE(collection);
|
|
REQUIRE(StringCompare(collection->Name(), "Collection"));
|
|
|
|
const XMLElement *dataset = collection->FirstChildElement();
|
|
VerifyDataset(dataset, 0.0);
|
|
dataset = dataset->NextSiblingElement();
|
|
VerifyDataset(dataset, 1.0);
|
|
dataset = dataset->NextSiblingElement();
|
|
VerifyDataset(dataset, 1.5);
|
|
REQUIRE(dataset->NextSiblingElement() == nullptr);
|
|
|
|
// Clean up
|
|
for (int c=0; c<=2; ++c)
|
|
{
|
|
std::string prefix = "ParaView/Cycle00000" + std::to_string(c);
|
|
REQUIRE(remove((prefix + "/data.pvtu").c_str()) == 0);
|
|
REQUIRE(remove((prefix + "/proc000000.vtu").c_str()) == 0);
|
|
REQUIRE(rmdir(prefix.c_str()) == 0);
|
|
}
|
|
REQUIRE(remove("ParaView/ParaView.pvd") == 0);
|
|
REQUIRE(rmdir("ParaView") == 0);
|
|
}
|
|
|
|
#ifdef MFEM_USE_HDF5
|
|
|
|
TEST_CASE("ParaView VTKHDF restart mode", "[ParaView]")
|
|
{
|
|
Mesh mesh = Mesh::MakeCartesian2D(2, 3, Element::QUADRILATERAL);
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
GridFunction u(&fes);
|
|
u = 0.0;
|
|
|
|
// Write initial dataset with three timesteps: 0, 1, 2.
|
|
{
|
|
ParaViewHDFDataCollection dc("ParaView", &mesh);
|
|
dc.RegisterField("u", &u);
|
|
SaveDataCollection(dc, 0, 0);
|
|
SaveDataCollection(dc, 1, 1);
|
|
SaveDataCollection(dc, 2, 2);
|
|
}
|
|
|
|
auto read_time_vals = []()
|
|
{
|
|
const hid_t f = H5Fopen("ParaView.vtkhdf", H5F_ACC_RDONLY, H5P_DEFAULT);
|
|
hsize_t size;
|
|
H5LTget_dataset_info(f, "/VTKHDF/Steps/Values", &size, nullptr, nullptr);
|
|
std::vector<double> vals(size);
|
|
H5LTread_dataset(f, "/VTKHDF/Steps/Values", H5T_NATIVE_DOUBLE, vals.data());
|
|
H5Fclose(f);
|
|
return vals;
|
|
};
|
|
|
|
// Check that the time steps were written correctly
|
|
REQUIRE(read_time_vals() == std::vector<double>({0.0, 1.0, 2.0}));
|
|
|
|
// Using restart mode, append to the existing dataset, overwriting timesteps
|
|
// 1 and 2 with 1 and 1.5.
|
|
{
|
|
ParaViewHDFDataCollection dc("ParaView", &mesh);
|
|
dc.UseRestartMode(true);
|
|
dc.RegisterField("u", &u);
|
|
SaveDataCollection(dc, 1, 1.0);
|
|
SaveDataCollection(dc, 2, 1.5);
|
|
}
|
|
|
|
// Check that the time steps were updated correctly
|
|
REQUIRE(read_time_vals() == std::vector<double>({0.0, 1.0, 1.5}));
|
|
|
|
// Clean up
|
|
REQUIRE(remove("ParaView.vtkhdf") == 0);
|
|
}
|
|
|
|
#endif // MFEM_USE_HDF5
|