feat: add eigen2 frequency pipeline and fail-closed validation

Implement MITC4 mass, constrained whitening, MKL FEAST, modal HDF5, and deterministic Value comparison. MSVC Debug builds; 394 of 395 tests pass. Original-model all-c residual and the documented S3 static contract conflict remain unresolved; this is an implementation checkpoint, not feature acceptance.
This commit is contained in:
KOKO\Mimi
2026-09-09 03:14:30 +09:00
parent e74ac247db
commit 22c9d4435c
39 changed files with 7240 additions and 541 deletions
@@ -1,5 +1,3 @@
#include "nonlinear_static_reference_comparison.h"
#include <gtest/gtest.h>
#include <algorithm>
@@ -13,6 +11,8 @@
#include <string>
#include <vector>
#include "nonlinear_static_reference_comparison.h"
#ifndef FESA_TEST_SOURCE_DIR
#error FESA_TEST_SOURCE_DIR must identify the repository root.
#endif
@@ -34,13 +34,10 @@ std::string ReadBytes(const std::filesystem::path& path) {
fesa::test::NonlinearReferenceCase MakeCase(const std::string& case_id) {
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
const bool fine = case_id == "shellnl-meshfine-s4-final";
const bool nonlinear = case_id == "shellnl-s4-final" || fine;
const bool nonlinear = case_id == "shellnl-s4-final";
const std::filesystem::path directory =
root / "reference" / (fine ? "shellNL-meshfine"
: (nonlinear ? "shellNL" : "shell"));
const std::string stem = fine ? "shellNL-meshfine"
: (nonlinear ? "shellNL" : "shell");
root / "reference" / (nonlinear ? "shellNL" : "shell");
const std::string stem = nonlinear ? "shellNL" : "shell";
return {case_id,
directory / (stem + ".inp"),
directory / (stem + " displacements.csv"),
@@ -105,9 +102,8 @@ void ExpectInternalFamiliesPassed(
<< family.family_identity << " rows=" << family.row_count
<< " numerator=" << family.numerator << " scale=" << family.scale
<< " metric="
<< (family.normalized_metric.has_value()
? *family.normalized_metric
: -1.0)
<< (family.normalized_metric.has_value() ? *family.normalized_metric
: -1.0)
<< " diagnostic=" << family.diagnostic_code;
}
}
@@ -159,14 +155,14 @@ TEST(NonlinearStaticReference,
EXPECT_EQ(generated.Value().copied_input_path, copied.Value());
EXPECT_TRUE(std::filesystem::is_regular_file(
generated.Value().generated_results_hdf5));
EXPECT_TRUE(std::filesystem::is_regular_file(
generated.Value().generated_log_path));
EXPECT_TRUE(
std::filesystem::is_regular_file(generated.Value().generated_log_path));
EXPECT_EQ(generated.Value().generated_results_hdf5.filename(),
case_id == "shellnl-s4-final" ? "shellNL_results.h5"
: "shell_results.h5");
EXPECT_EQ(generated.Value().generated_log_path.filename(),
case_id == "shellnl-s4-final" ? "shellNL_log.txt"
: "shell_log.txt");
: "shell_results.h5");
EXPECT_EQ(
generated.Value().generated_log_path.filename(),
case_id == "shellnl-s4-final" ? "shellNL_log.txt" : "shell_log.txt");
const auto comparison = fesa::test::CompareHdf5ToReferenceCsv(
reference_case, generated.Value().generated_results_hdf5);
@@ -182,13 +178,12 @@ TEST(NonlinearStaticReference,
ExpectFamily(comparison.Value(),
case_id == "shellnl-s4-final" ? "coarse.translation"
: "linear.translation",
: "linear.translation",
147U, fesa::test::NonlinearReferenceDisposition::kBlocking);
ExpectFamily(comparison.Value(),
case_id == "shellnl-s4-final" ? "coarse.rotation"
: "linear.rotation",
147U,
fesa::test::NonlinearReferenceDisposition::kWarningOnly);
ExpectFamily(
comparison.Value(),
case_id == "shellnl-s4-final" ? "coarse.rotation" : "linear.rotation",
147U, fesa::test::NonlinearReferenceDisposition::kWarningOnly);
if (case_id == "shellnl-s4-final") {
ExpectFamily(comparison.Value(), "coarse.local_reaction_inplane_force",
48U, fesa::test::NonlinearReferenceDisposition::kReviewOnly);
@@ -219,9 +214,9 @@ TEST(NonlinearStaticReference,
std::ofstream stream{invalid_hdf5, std::ios::binary | std::ios::trunc};
stream << "not an HDF5 file";
stream.close();
EXPECT_FALSE(fesa::test::CompareHdf5ToReferenceCsv(reference_case,
invalid_hdf5)
.HasValue());
EXPECT_FALSE(
fesa::test::CompareHdf5ToReferenceCsv(reference_case, invalid_hdf5)
.HasValue());
fesa::test::NonlinearReferenceComparisonReport report{};
report.blocking_passed = true;
@@ -230,17 +225,29 @@ TEST(NonlinearStaticReference,
{"UR1", "UR2", "UR3"},
fesa::test::NonlinearReferenceDisposition::kWarningOnly,
147U,
{"nonlinear.rotation", {"UR1", "UR2", "UR3"}, false, 1.0, 0.01,
0U, 0.02, 0.02, 0U, false, false, "relative-rms-exceeded", {}, 147U}});
EXPECT_EQ(fesa::test::ClassifyReferenceComparison(report),
fesa::test::NonlinearReferenceClassification::kWarningOnlyDifferences);
{"nonlinear.rotation",
{"UR1", "UR2", "UR3"},
false,
1.0,
0.01,
0U,
0.02,
0.02,
0U,
false,
false,
"relative-rms-exceeded",
{},
147U}});
EXPECT_EQ(
fesa::test::ClassifyReferenceComparison(report),
fesa::test::NonlinearReferenceClassification::kWarningOnlyDifferences);
}
// NSNR-REF-020.
TEST(NonlinearStaticReference, AuditsFinePrimaryAndCoarseCompanionArtifacts) {
const std::array<std::string, 3> case_ids{
"shellnl-meshfine-s4-final", "shellnl-s4-final",
"shell-s4-linear-limit"};
TEST(NonlinearStaticReference, AuditsRetainedStaticArtifacts) {
const std::array<std::string, 2> case_ids{"shellnl-s4-final",
"shell-s4-linear-limit"};
for (const auto& case_id : case_ids) {
const auto reference_case = MakeCase(case_id);
const auto before = fesa::test::ArtifactCheck(reference_case);
@@ -281,16 +288,14 @@ TEST(NonlinearStaticReference,
fesa::test::NonlinearReferenceDisposition::kReviewOnly);
ExpectFamily(comparison.Value(), "coarse.local_reaction_transverse_force",
24U, fesa::test::NonlinearReferenceDisposition::kReviewOnly);
ExpectFamily(comparison.Value(), "coarse.local_reaction_physical_moment",
48U, fesa::test::NonlinearReferenceDisposition::kReviewOnly);
ExpectFamily(comparison.Value(), "coarse.local_reaction_physical_moment", 48U,
fesa::test::NonlinearReferenceDisposition::kReviewOnly);
EXPECT_TRUE(comparison.Value().external_blocking_passed);
}
// NSNR-REF-022.
TEST(NonlinearStaticReference,
BlocksOnGlobalBalanceAndAcceptedFreeResidual) {
for (const std::string case_id :
{"shellnl-meshfine-s4-final", "shellnl-s4-final"}) {
TEST(NonlinearStaticReference, BlocksOnGlobalBalanceAndAcceptedFreeResidual) {
for (const std::string case_id : {"shellnl-s4-final"}) {
const auto reference_case = MakeCase(case_id);
const auto copied =
fesa::test::CopyInputToExecutionDirectory(reference_case);
@@ -302,9 +307,7 @@ TEST(NonlinearStaticReference,
reference_case, generated.Value().generated_results_hdf5);
ASSERT_TRUE(comparison.HasValue()) << case_id;
const std::string prefix = case_id == "shellnl-meshfine-s4-final"
? "fine."
: "coarse.";
const std::string prefix = "coarse.";
const std::array<std::string, 4> identities{
prefix + "global_force_balance", prefix + "global_moment_balance",
prefix + "accepted_free_force_residual",
@@ -313,7 +316,8 @@ TEST(NonlinearStaticReference,
const auto found = std::find_if(
comparison.Value().internal_equilibrium.begin(),
comparison.Value().internal_equilibrium.end(),
[&identity](const fesa::test::NonlinearInternalEquilibriumReport& row) {
[&identity](
const fesa::test::NonlinearInternalEquilibriumReport& row) {
return row.family_identity == identity;
});
ASSERT_NE(found, comparison.Value().internal_equilibrium.end())
@@ -321,9 +325,8 @@ TEST(NonlinearStaticReference,
EXPECT_TRUE(found->passed)
<< identity << " numerator=" << found->numerator
<< " scale=" << found->scale << " metric="
<< (found->normalized_metric.has_value()
? *found->normalized_metric
: -1.0)
<< (found->normalized_metric.has_value() ? *found->normalized_metric
: -1.0)
<< " diagnostic=" << found->diagnostic_code;
}
EXPECT_TRUE(comparison.Value().internal_blocking_passed);
@@ -334,19 +337,26 @@ TEST(NonlinearStaticReference,
}
// NSNR-REF-023.
TEST(NonlinearStaticReference,
RejectsMissingExtraDuplicateAndNonfiniteRowsBeforeToleranceAndRecordsReviewOnlyFindings) {
TEST(
NonlinearStaticReference,
RejectsMissingExtraDuplicateAndNonfiniteRowsBeforeToleranceAndRecordsReviewOnlyFindings) {
fesa::test::NonlinearReferenceComparisonReport report{};
report.blocking_passed = true;
report.external_blocking_passed = true;
report.internal_blocking_passed = true;
report.precheck_findings = {
{"blocking", fesa::test::NonlinearReferenceDisposition::kBlocking,
"missing-row", {}},
{"warning", fesa::test::NonlinearReferenceDisposition::kWarningOnly,
"duplicate-row", {}},
{"review", fesa::test::NonlinearReferenceDisposition::kReviewOnly,
"nonfinite-row", {}}};
{"blocking",
fesa::test::NonlinearReferenceDisposition::kBlocking,
"missing-row",
{}},
{"warning",
fesa::test::NonlinearReferenceDisposition::kWarningOnly,
"duplicate-row",
{}},
{"review",
fesa::test::NonlinearReferenceDisposition::kReviewOnly,
"nonfinite-row",
{}}};
EXPECT_EQ(fesa::test::ClassifyReferenceComparison(report),
fesa::test::NonlinearReferenceClassification::kFailed);
@@ -359,22 +369,22 @@ TEST(NonlinearStaticReference,
GlobalForceAndMomentBalanceUseApprovedInclusiveOneEighthGate) {
for (const std::string& identity :
{"fine.global_force_balance", "fine.global_moment_balance"}) {
const auto below = fesa::test::EvaluateGlobalBalance(
identity, 5.0e-9, 1.0, 0.0);
const auto below =
fesa::test::EvaluateGlobalBalance(identity, 5.0e-9, 1.0, 0.0);
EXPECT_TRUE(below.passed) << identity;
ASSERT_TRUE(below.normalized_metric.has_value()) << identity;
EXPECT_EQ(*below.normalized_metric, 5.0e-9) << identity;
const auto boundary = fesa::test::EvaluateGlobalBalance(
identity, 1.0e-8, 1.0, 0.0);
const auto boundary =
fesa::test::EvaluateGlobalBalance(identity, 1.0e-8, 1.0, 0.0);
EXPECT_TRUE(boundary.passed) << identity;
ASSERT_TRUE(boundary.normalized_metric.has_value()) << identity;
EXPECT_EQ(*boundary.normalized_metric, 1.0e-8) << identity;
const auto above = fesa::test::EvaluateGlobalBalance(
identity, std::nextafter(1.0e-8,
std::numeric_limits<double>::infinity()),
1.0, 0.0);
identity,
std::nextafter(1.0e-8, std::numeric_limits<double>::infinity()), 1.0,
0.0);
EXPECT_FALSE(above.passed) << identity;
ASSERT_TRUE(above.normalized_metric.has_value()) << identity;
EXPECT_GT(*above.normalized_metric, 1.0e-8) << identity;