Add MITC4 drilling diagnostics output and comparison contract

This commit is contained in:
KOKO\Mimi
2026-10-20 09:45:58 +09:00
parent 3d5c296651
commit a73d3fc9ca
31 changed files with 1175 additions and 45 deletions
@@ -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());
}