#include "mitc4_reference_comparison.hpp" #include "fesa/app/fesa_application.hpp" #include #include #include #include #include #include #include #include #include #include #ifndef FESA_TEST_SOURCE_DIR #error FESA_TEST_SOURCE_DIR must identify the repository root. #endif #ifndef FESA_TEST_BINARY_DIR #error FESA_TEST_BINARY_DIR must identify the CMake binary root. #endif namespace { constexpr const char* kInternalFormulation = "FESA-MITC4"; constexpr const char* kIntegrationRule = "2x2x2-gauss; mitc4-edge-midpoint-shear"; constexpr std::size_t kNodeCount = 49U; constexpr std::size_t kComponentCount = 6U; std::string readBytes(const std::filesystem::path& path) { std::ifstream stream{path, std::ios::binary}; if (!stream) { throw std::runtime_error{"Unable to read declared reference artifact."}; } return {std::istreambuf_iterator{stream}, std::istreambuf_iterator{}}; } struct ArtifactSnapshot { std::string bytes; std::filesystem::file_time_type lastWriteTime; }; ArtifactSnapshot snapshot(const std::filesystem::path& path) { return {readBytes(path), std::filesystem::last_write_time(path)}; } void expectUnchanged( const std::filesystem::path& path, const ArtifactSnapshot& before) { EXPECT_EQ(readBytes(path), before.bytes) << path.string(); EXPECT_EQ(std::filesystem::last_write_time(path), before.lastWriteTime) << path.string(); } struct CaseEvidence { fesa::test::Mitc4ComparisonReport report; std::filesystem::path comparisonJson; }; CaseEvidence runCase( const std::string& caseId, const std::string& sourceElementType, const std::filesystem::path& referenceDirectory, const std::filesystem::path& input, const std::filesystem::path& csv, const std::string& outputName) { const auto inputBefore = snapshot(input); const auto csvBefore = snapshot(csv); const std::filesystem::path outputDirectory = std::filesystem::path{FESA_TEST_BINARY_DIR} / "reference" / outputName; std::error_code error; std::filesystem::remove_all(outputDirectory, error); error.clear(); if (!std::filesystem::create_directories(outputDirectory, error) || error) { throw std::runtime_error{"Unable to create MITC4 evidence directory."}; } const auto results = outputDirectory / "results.h5"; const auto comparison = outputDirectory / "comparison.json"; fesa::FesaApplication application; EXPECT_EQ( application.run({input.string(), "--output", results.string()}), 0); EXPECT_TRUE(std::filesystem::is_regular_file(results)); auto comparisonResult = fesa::test::Mitc4ReferenceComparison::compare( {caseId, sourceElementType, input, csv, results}); if (!comparisonResult.hasValue()) { ADD_FAILURE() << "MITC4 comparison precheck failed for " << caseId; return {{}, comparison}; } EXPECT_TRUE( fesa::test::Mitc4ReferenceComparison::writeDeterministicJson( comparisonResult.value(), comparison) .isOk()); EXPECT_TRUE(std::filesystem::is_regular_file(comparison)); std::vector generated; for (const auto& entry : std::filesystem::directory_iterator{outputDirectory}) { generated.push_back(entry.path().filename().string()); } std::sort(generated.begin(), generated.end()); EXPECT_EQ( generated, (std::vector{"comparison.json", "results.h5"})); EXPECT_TRUE(std::filesystem::is_directory(referenceDirectory)); expectUnchanged(input, inputBefore); expectUnchanged(csv, csvBefore); return {std::move(comparisonResult.value()), comparison}; } void expectCommonMetadata( const fesa::test::Mitc4ComparisonReport& report, const std::string& caseId, const std::string& sourceElementType) { EXPECT_EQ(report.caseId, caseId); EXPECT_EQ(report.sourceElementType, sourceElementType); EXPECT_EQ(report.internalFormulation, kInternalFormulation); EXPECT_EQ(report.integrationRule, kIntegrationRule); } void expectComparisonCoverage( const fesa::test::Mitc4ComparisonReport& report) { ASSERT_EQ(report.rows.size(), kNodeCount * kComponentCount); ASSERT_EQ(report.metrics.size(), kComponentCount); ASSERT_EQ(report.vectorMetrics.size(), kNodeCount); EXPECT_TRUE(report.passed); const std::size_t blockingRows = static_cast(std::count_if( report.rows.begin(), report.rows.end(), [](const fesa::test::Mitc4RowDecision& row) { return row.blocking; })); const std::size_t rotationRows = report.rows.size() - blockingRows; EXPECT_EQ(blockingRows, kNodeCount * 3U); EXPECT_EQ(rotationRows, kNodeCount * 3U); EXPECT_TRUE(std::all_of( report.rows.begin(), report.rows.end(), [](const fesa::test::Mitc4RowDecision& row) { return !row.blocking || row.withinTolerance; })); EXPECT_EQ( report.warnings.size(), static_cast(std::count_if( report.rows.begin(), report.rows.end(), [](const fesa::test::Mitc4RowDecision& row) { return !row.blocking && !row.withinTolerance; }))); } // MITC4-E2E-S4-001 TEST(Mitc4S4Reference, PreservesS4AndWritesCommonMitc4Metadata) { const std::filesystem::path root{FESA_TEST_SOURCE_DIR}; const auto directory = root / "reference" / "shell"; const auto evidence = runCase( "shell-s4", "S4", directory, directory / "shell.inp", directory / "shell displacements.csv", "mitc4-shell-s4-metadata"); expectCommonMetadata(evidence.report, "shell-s4", "S4"); } // MITC4-E2E-S4-002 TEST(Mitc4S4Reference, PassesBlockingUAndReportsEveryUrRow) { const std::filesystem::path root{FESA_TEST_SOURCE_DIR}; const auto directory = root / "reference" / "shell"; const auto evidence = runCase( "shell-s4", "S4", directory, directory / "shell.inp", directory / "shell displacements.csv", "mitc4-shell-s4-comparison"); expectCommonMetadata(evidence.report, "shell-s4", "S4"); expectComparisonCoverage(evidence.report); } // MITC4-E2E-S4R-001 TEST(Mitc4S4RReference, PreservesS4RAndWritesCommonMitc4Metadata) { const std::filesystem::path root{FESA_TEST_SOURCE_DIR}; const auto directory = root / "reference" / "shellR"; const auto evidence = runCase( "shell-s4r", "S4R", directory, directory / "shellR.inp", directory / "shellR displacements.csv", "mitc4-shell-s4r-metadata"); expectCommonMetadata(evidence.report, "shell-s4r", "S4R"); } // MITC4-E2E-S4R-002 TEST(Mitc4S4RReference, PassesBlockingUAndReportsEveryUrRow) { const std::filesystem::path root{FESA_TEST_SOURCE_DIR}; const auto directory = root / "reference" / "shellR"; const auto evidence = runCase( "shell-s4r", "S4R", directory, directory / "shellR.inp", directory / "shellR displacements.csv", "mitc4-shell-s4r-comparison"); expectCommonMetadata(evidence.report, "shell-s4r", "S4R"); expectComparisonCoverage(evidence.report); } } // namespace