Add MITC4 drilling diagnostics output and comparison contract
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -27,6 +28,13 @@
|
||||
#include "fesa/math/vector3.h"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
#include "mitc4_fault_test_helpers.h"
|
||||
|
||||
#include "io/hdf5/hdf5_result_writer.h"
|
||||
#include "io/hdf5/hdf5_self_check.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kStepRoot = "/steps/Step-1/frames/0";
|
||||
@@ -241,7 +249,8 @@ fesa::ModelDefinition MakeShellDefinition(const std::filesystem::path& source) {
|
||||
return definition;
|
||||
}
|
||||
|
||||
WriterFixture MakeShellFixture(const std::filesystem::path& source) {
|
||||
WriterFixture MakeShellFixture(const std::filesystem::path& source,
|
||||
std::optional<fesa::Mitc4DrillingMode> mode = fesa::Mitc4DrillingMode::kDirectSpring) {
|
||||
auto domain_result = fesa::Domain::Create(MakeShellDefinition(source));
|
||||
if (!domain_result.HasValue()) {
|
||||
throw std::runtime_error{
|
||||
@@ -303,6 +312,8 @@ WriterFixture MakeShellFixture(const std::filesystem::path& source) {
|
||||
candidate.physical_strain_energy = 123.5;
|
||||
candidate.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
|
||||
candidate.verification_metrics = {1.0e-13, 2.0e-13, 3.0e-13};
|
||||
if (mode) candidate.mitc4_drilling = fesa::Mitc4DrillingEvidence{
|
||||
*mode, {0.5, 1.0, 1.5}, {1.0, 2.0, 3.0, 3.5, 4.0, 4.5}};
|
||||
const fesa::Status commit =
|
||||
state->CommitShellResults({0U}, std::move(candidate));
|
||||
if (!commit.IsOk()) {
|
||||
@@ -1343,3 +1354,149 @@ TEST(Hdf5ResultsWriter,
|
||||
EXPECT_EQ(ReadBytes(final), valid_bytes);
|
||||
EXPECT_EQ(EntryCount(directory.Path()), 1U);
|
||||
}
|
||||
|
||||
|
||||
TEST(Hdf5ResultsWriter, DrillingActualModesRoundTripWithoutDefaultInference) {
|
||||
for (const auto mode : {fesa::Mitc4DrillingMode::kDirectSpring,
|
||||
fesa::Mitc4DrillingMode::kLegacyCorotatedTwist}) {
|
||||
TempDirectory directory{"drilling-roundtrip"};
|
||||
auto fixture = MakeShellFixture(directory.Path() / "shell.inp", mode);
|
||||
const auto output = directory.Path() / "results.h5";
|
||||
ASSERT_TRUE(fesa::Hdf5ResultsWriter{}.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||
const auto file = OpenFile(output);
|
||||
const auto stored = fesa::hdf5_internal::ReadDrillingDiagnostics(file.Get());
|
||||
ASSERT_TRUE(stored);
|
||||
EXPECT_EQ(stored->formulation, fesa::Mitc4DrillingModeName(mode));
|
||||
ASSERT_EQ(stored->frames.size(), 1U);
|
||||
EXPECT_EQ(stored->frames[0].raw_equilibrium, fixture.state->Equilibrium());
|
||||
EXPECT_EQ(stored->frames[0].drilling_moment, fixture.state->Mitc4Drilling()->drilling_moment);
|
||||
EXPECT_EQ(stored->frames[0].equilibrium_with_drilling,
|
||||
fixture.state->Mitc4Drilling()->equilibrium_with_drilling);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Hdf5ResultsWriter, DrillingMissingActualStatePreservesFinal) {
|
||||
TempDirectory directory{"drilling-missing"};
|
||||
auto fixture = MakeShellFixture(directory.Path() / "shell.inp", std::nullopt);
|
||||
const auto output = directory.Path() / "results.h5";
|
||||
const std::vector<char> sentinel{'p','r','i','o','r'};
|
||||
WriteBytes(output, sentinel);
|
||||
EXPECT_FALSE(fesa::Hdf5ResultsWriter{}.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||
EXPECT_EQ(ReadBytes(output), sentinel);
|
||||
EXPECT_EQ(EntryCount(directory.Path()), 1U);
|
||||
}
|
||||
|
||||
TEST(Hdf5ResultsWriter, DrillingReadbackCannotTreatRemovedMarkersAsHistorical) {
|
||||
for (const bool remove_markers : {true, false}) {
|
||||
TempDirectory directory{"drilling-readback"};
|
||||
auto fixture = MakeShellFixture(directory.Path() / "shell.inp");
|
||||
const auto output = directory.Path() / "results.h5";
|
||||
ASSERT_TRUE(fesa::Hdf5ResultsWriter{}.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||
{
|
||||
fesa::hdf5_internal::Hdf5Handle file{
|
||||
H5Fopen(output.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT), H5Fclose};
|
||||
if (remove_markers) {
|
||||
fesa::hdf5_internal::Hdf5Handle metadata{
|
||||
H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
ASSERT_GE(H5Adelete(metadata.Get(), "mitc4_drilling_formulation"), 0);
|
||||
ASSERT_GE(H5Adelete(metadata.Get(), "drilling_diagnostics_version"), 0);
|
||||
EXPECT_FALSE(fesa::hdf5_internal::ReadDrillingDiagnostics(file.Get()));
|
||||
} else {
|
||||
ASSERT_GE(H5Ldelete(file.Get(), "/steps/Step-1/frames/0/global/drilling_moment", H5P_DEFAULT), 0);
|
||||
ASSERT_GE(H5Ldelete(file.Get(), "/steps/Step-1/frames/0/global/equilibrium_with_drilling", H5P_DEFAULT), 0);
|
||||
}
|
||||
}
|
||||
const auto plan = fesa::hdf5_internal::BuildWriterPlan(output, *fixture.domain, *fixture.state, {});
|
||||
ASSERT_TRUE(plan.HasValue());
|
||||
EXPECT_FALSE(fesa::hdf5_internal::SelfCheck(output, *fixture.domain, *fixture.state, 0U,
|
||||
plan.Value().model_data).IsOk());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(Hdf5ResultsWriter, DrillingReaderRejectsMalformedAndInconsistentEvidence) {
|
||||
for (const std::string mutation : {"partial-mode", "partial-version", "mode", "version", "version-type",
|
||||
"mode-type", "shape", "type", "endian", "nan", "inf", "algebra", "force"}) {
|
||||
SCOPED_TRACE(mutation);
|
||||
TempDirectory directory{"drilling-invalid"};
|
||||
auto fixture = MakeShellFixture(directory.Path() / "shell.inp");
|
||||
const auto output = directory.Path() / "results.h5";
|
||||
ASSERT_TRUE(fesa::Hdf5ResultsWriter{}.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||
fesa::hdf5_internal::Hdf5ErrorSilencer silence;
|
||||
fesa::hdf5_internal::Hdf5Handle file{
|
||||
H5Fopen(output.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT), H5Fclose};
|
||||
fesa::hdf5_internal::Hdf5Handle metadata{
|
||||
H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
const std::string path = "/steps/Step-1/frames/0/global/drilling_moment";
|
||||
if (mutation == "partial-mode" || mutation == "partial-version") {
|
||||
ASSERT_GE(H5Adelete(metadata.Get(), mutation == "partial-mode" ?
|
||||
"mitc4_drilling_formulation" : "drilling_diagnostics_version"), 0);
|
||||
} else if (mutation == "mode" || mutation == "mode-type") {
|
||||
ASSERT_GE(H5Adelete(metadata.Get(), "mitc4_drilling_formulation"), 0);
|
||||
if (mutation == "mode") fesa::hdf5_internal::WriteStringAttribute(metadata.Get(), "mitc4_drilling_formulation", "unknown");
|
||||
else fesa::hdf5_internal::WriteUint64Attribute(metadata.Get(), "mitc4_drilling_formulation", 1U);
|
||||
} else if (mutation == "version" || mutation == "version-type") {
|
||||
ASSERT_GE(H5Adelete(metadata.Get(), "drilling_diagnostics_version"), 0);
|
||||
if (mutation == "version") fesa::hdf5_internal::WriteUint64Attribute(metadata.Get(), "drilling_diagnostics_version", 2U);
|
||||
else fesa::hdf5_internal::WriteDoubleAttribute(metadata.Get(), "drilling_diagnostics_version", 1.0);
|
||||
} else if (mutation == "shape" || mutation == "type" || mutation == "endian") {
|
||||
ASSERT_GE(H5Ldelete(file.Get(), path.c_str(), H5P_DEFAULT), 0);
|
||||
const hsize_t size = mutation == "shape" ? 2U : 3U;
|
||||
fesa::hdf5_internal::Hdf5Handle space{H5Screate_simple(1, &size, nullptr), H5Sclose};
|
||||
fesa::hdf5_internal::Hdf5Handle dataset{H5Dcreate2(file.Get(), path.c_str(),
|
||||
mutation == "type" ? H5T_IEEE_F32LE : (mutation == "endian" ? H5T_IEEE_F64BE : H5T_IEEE_F64LE),
|
||||
space.Get(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT), H5Dclose};
|
||||
ASSERT_GE(dataset.Get(), 0);
|
||||
} else {
|
||||
const std::string target = mutation == "inf" ? "/steps/Step-1/frames/0/global/equilibrium" :
|
||||
(mutation == "nan" ? path : "/steps/Step-1/frames/0/global/equilibrium_with_drilling");
|
||||
fesa::hdf5_internal::Hdf5Handle dataset{H5Dopen2(file.Get(), target.c_str(), H5P_DEFAULT), H5Dclose};
|
||||
std::array<double, 6> values{1., 2., 3., 3.5, 4., 4.5};
|
||||
if (mutation == "nan") values[0] = std::numeric_limits<double>::quiet_NaN();
|
||||
else if (mutation == "inf") values[0] = std::numeric_limits<double>::infinity();
|
||||
else values[mutation == "force" ? 0U : 3U] = 1e-100;
|
||||
ASSERT_GE(H5Dwrite(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, values.data()), 0);
|
||||
}
|
||||
EXPECT_THROW((void)fesa::hdf5_internal::ReadDrillingDiagnostics(file.Get()),
|
||||
fesa::hdf5_internal::Hdf5Failure);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(Hdf5ResultsWriter, DrillingHistoricalSelfCheckDoesNotInferMode) {
|
||||
TempDirectory directory{"drilling-historical"};
|
||||
auto fixture = MakeShellFixture(directory.Path() / "shell.inp");
|
||||
const auto output = directory.Path() / "results.h5";
|
||||
ASSERT_TRUE(fesa::Hdf5ResultsWriter{}.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||
{
|
||||
fesa::hdf5_internal::Hdf5Handle file{
|
||||
H5Fopen(output.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT), H5Fclose};
|
||||
fesa::hdf5_internal::Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||
ASSERT_GE(H5Adelete(metadata.Get(), "mitc4_drilling_formulation"), 0);
|
||||
ASSERT_GE(H5Adelete(metadata.Get(), "drilling_diagnostics_version"), 0);
|
||||
ASSERT_GE(H5Ldelete(file.Get(), "/steps/Step-1/frames/0/global/drilling_moment", H5P_DEFAULT), 0);
|
||||
ASSERT_GE(H5Ldelete(file.Get(), "/steps/Step-1/frames/0/global/equilibrium_with_drilling", H5P_DEFAULT), 0);
|
||||
}
|
||||
auto historical = MakeShellFixture(directory.Path() / "shell.inp", std::nullopt);
|
||||
const auto plan = fesa::hdf5_internal::BuildWriterPlan(output, *historical.domain, *historical.state, {});
|
||||
ASSERT_TRUE(plan.HasValue());
|
||||
EXPECT_TRUE(fesa::hdf5_internal::SelfCheck(output, *historical.domain, *historical.state, 0U,
|
||||
plan.Value().model_data).IsOk());
|
||||
EXPECT_FALSE(historical.state->Mitc4Drilling());
|
||||
}
|
||||
|
||||
|
||||
TEST(Hdf5ResultsWriter, DrillingLifecycleFaultsPreserveFinalBytes) {
|
||||
TempDirectory directory{"drilling-faults"};
|
||||
auto fixture = MakeShellFixture(directory.Path() / "shell.inp");
|
||||
const auto path = directory.Path() / "results.h5";
|
||||
const std::vector<char> prior{'p','r','i','o','r'};
|
||||
for (const std::string fault : {"candidate-close", "reader-close", "markers", "datasets"}) {
|
||||
SCOPED_TRACE(fault);
|
||||
WriteBytes(path, prior);
|
||||
EXPECT_FALSE(fesa::hdf5_internal::WriteStaticFile(path, *fixture.domain, *fixture.state, {},
|
||||
fesa::test::Mitc4LifecycleFault(fault)).IsOk());
|
||||
EXPECT_EQ(ReadBytes(path), prior);
|
||||
EXPECT_EQ(EntryCount(directory.Path()), 1U);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user