Add MITC4 drilling diagnostics output and comparison contract
This commit is contained in:
@@ -21,6 +21,8 @@
|
||||
#error FESA_TEST_BINARY_DIR must identify the CMake binary root.
|
||||
#endif
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ReadBytes(const std::filesystem::path& path) {
|
||||
@@ -409,3 +411,75 @@ TEST(NonlinearStaticReference,
|
||||
EXPECT_FALSE(nonzero.normalized_metric.has_value()) << identity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(NonlinearStaticReference, DrillingSyntheticMomentSelectsOnlyExistingInternalFamily) {
|
||||
const auto reference_case = MakeCase("shellnl-s4-final");
|
||||
const auto copied = fesa::test::CopyInputToExecutionDirectory(reference_case);
|
||||
ASSERT_TRUE(copied.HasValue());
|
||||
const auto generated = fesa::test::RunFesaApplicationOnCopiedInput(reference_case, copied.Value());
|
||||
ASSERT_TRUE(generated.HasValue());
|
||||
const auto& path = generated.Value().generated_results_hdf5;
|
||||
const auto baseline = fesa::test::CompareHdf5ToReferenceCsv(reference_case, path);
|
||||
ASSERT_TRUE(baseline.HasValue());
|
||||
ASSERT_TRUE(baseline.Value().drilling_diagnostics);
|
||||
const auto final_index = baseline.Value().drilling_diagnostics->frames.back().frame_index;
|
||||
const std::string root = "/steps/Step-1/frames/" + std::to_string(final_index) + "/global/";
|
||||
const auto moment_gate = [](const auto& report) -> const auto& {
|
||||
const auto found = std::find_if(report.internal_equilibrium.begin(), report.internal_equilibrium.end(),
|
||||
[](const auto& family) { return family.family_identity == "coarse.global_moment_balance"; });
|
||||
if (found == report.internal_equilibrium.end()) throw std::runtime_error{"Missing existing moment family."};
|
||||
return *found;
|
||||
};
|
||||
// This mutates only a generated working output into an algebra fixture.
|
||||
// Its comparator verdict is not physics reference evidence.
|
||||
for (const std::string mode : {"direct-initial-director-spring", "legacy-corotated-twist", "markerless"}) {
|
||||
{
|
||||
fesa::hdf5_internal::Hdf5Handle file{H5Fopen(path.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);
|
||||
if (mode != "markerless") {
|
||||
fesa::hdf5_internal::WriteStringAttribute(metadata.Get(), "mitc4_drilling_formulation", mode);
|
||||
fesa::hdf5_internal::WriteUint64Attribute(metadata.Get(), "drilling_diagnostics_version", 1U);
|
||||
}
|
||||
for (const auto& entry : std::vector<std::pair<std::string, std::vector<double>>>{
|
||||
{"equilibrium", {0.,0.,0.,1e6,0.,0.}}, {"drilling_moment", {1e6,0.,0.}},
|
||||
{"equilibrium_with_drilling", {0.,0.,0.,0.,0.,0.}}}) {
|
||||
fesa::hdf5_internal::Hdf5Handle dataset{H5Dopen2(file.Get(), (root + entry.first).c_str(), H5P_DEFAULT), H5Dclose};
|
||||
ASSERT_GE(H5Dwrite(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, entry.second.data()), 0);
|
||||
}
|
||||
}
|
||||
const auto compared = fesa::test::CompareHdf5ToReferenceCsv(reference_case, path);
|
||||
ASSERT_TRUE(compared.HasValue());
|
||||
EXPECT_TRUE(compared.Value().external_blocking_passed);
|
||||
EXPECT_EQ(compared.Value().families.size(), baseline.Value().families.size());
|
||||
EXPECT_EQ(compared.Value().internal_equilibrium.size(), baseline.Value().internal_equilibrium.size());
|
||||
const auto& gate = moment_gate(compared.Value());
|
||||
EXPECT_EQ(gate.scale, moment_gate(baseline.Value()).scale);
|
||||
EXPECT_EQ(gate.numerator, mode == "direct-initial-director-spring" ? 0.0 : 1e6);
|
||||
EXPECT_EQ(gate.passed, mode == "direct-initial-director-spring");
|
||||
EXPECT_EQ(compared.Value().drilling_diagnostics.has_value(), mode != "markerless");
|
||||
}
|
||||
// Restore new metadata, retain exact raw-minus-M algebra, and expose a real imbalance.
|
||||
{
|
||||
fesa::hdf5_internal::Hdf5Handle file{H5Fopen(path.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT), H5Fclose};
|
||||
fesa::hdf5_internal::WriteDrillingMetadata(file.Get(), fesa::Mitc4DrillingMode::kDirectSpring);
|
||||
for (const auto& entry : std::vector<std::pair<std::string, std::vector<double>>>{
|
||||
{"equilibrium", {0.,0.,0.,2e6,0.,0.}}, {"equilibrium_with_drilling", {0.,0.,0.,1e6,0.,0.}}}) {
|
||||
fesa::hdf5_internal::Hdf5Handle dataset{H5Dopen2(file.Get(), (root + entry.first).c_str(), H5P_DEFAULT), H5Dclose};
|
||||
ASSERT_GE(H5Dwrite(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, entry.second.data()), 0);
|
||||
}
|
||||
}
|
||||
const auto imbalanced = fesa::test::CompareHdf5ToReferenceCsv(reference_case, path);
|
||||
ASSERT_TRUE(imbalanced.HasValue());
|
||||
EXPECT_FALSE(moment_gate(imbalanced.Value()).passed);
|
||||
EXPECT_EQ(moment_gate(imbalanced.Value()).scale, moment_gate(baseline.Value()).scale);
|
||||
{
|
||||
fesa::hdf5_internal::Hdf5Handle file{H5Fopen(path.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT), H5Fclose};
|
||||
ASSERT_GE(H5Ldelete(file.Get(), "/steps/Step-1/frames/0/global/drilling_moment", H5P_DEFAULT), 0);
|
||||
}
|
||||
EXPECT_FALSE(fesa::test::CompareHdf5ToReferenceCsv(reference_case, path).HasValue());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user