feat: complete first MITC4 FEAST eigen implementation validation

This commit is contained in:
KOKO\Mimi
2026-09-10 02:24:04 +09:00
parent 7ebdce373c
commit d5e08f868b
26 changed files with 1452 additions and 82 deletions
@@ -388,6 +388,9 @@ Result<EigenReferenceComparisonReport> EigenReferenceComparison::Compare(
report.case_id = reference_case.case_id;
report.source_step_name = frequency.source_step_name_raw;
report.requested_mode_count = requested;
report.policy_id =
ReferenceTolerancePolicy::kMitc4EigenFirstImplementationPolicyId;
report.relative_rms_tolerance = ReferenceTolerancePolicy::kRelativeTolerance;
std::vector<ReferenceToleranceFamily> families;
families.reserve(modes.size());
for (const auto& mode : modes) {
@@ -404,7 +407,8 @@ Result<EigenReferenceComparisonReport> EigenReferenceComparison::Compare(
true,
{{mode.eigenvalue, row->second}}});
}
const auto evaluation = ReferenceTolerancePolicy::Evaluate(families);
const auto evaluation =
ReferenceTolerancePolicy::EvaluateMitc4EigenFirstImplementation(families);
if (!evaluation.valid || evaluation.families.size() != families.size()) {
Fail("nonfinite-comparison-value",
"Tolerance evaluation rejected modal values.");
@@ -34,6 +34,8 @@ struct EigenReferenceComparisonReport {
std::vector<EigenReferenceRowDecision> rows;
std::vector<ReferenceToleranceFamilyDecision> families;
bool passed;
std::string policy_id;
double relative_rms_tolerance;
};
/// @brief Compares modal eigenvalues without changing the approved input or
@@ -5,9 +5,12 @@
#include <atomic>
#include <chrono>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include "fesa/io/abaqus/input_reader.h"
@@ -117,7 +120,32 @@ TEST(EigenReferenceComparison,
auto reference_case = fesa::test::EigenReferenceCase{"x", inp, txt, h5};
auto ok = fesa::test::EigenReferenceComparison::Compare(reference_case);
ASSERT_TRUE(ok.HasValue());
EXPECT_EQ(ok.Value().policy_id, "mitc4-eigen-first-implementation");
EXPECT_DOUBLE_EQ(ok.Value().relative_rms_tolerance, 0.05);
EXPECT_EQ(ok.Value().families.size(), 2U);
// Only synthetic FESA output is changed; each TXT mode remains an independent
// scalar family, including at both inclusive five-percent boundaries.
for (const auto& sample :
std::vector<std::pair<double, bool>>{
{103.0, true}, {105.0, true}, {95.0, true},
{std::nextafter(105.0, std::numeric_limits<double>::infinity()), false},
{std::nextafter(95.0, 0.0), false}}) {
SCOPED_TRACE(sample.first);
f = H5Fopen(h5.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT);
ds = H5Dopen2(f, "/steps/Step-1/eigen/modes", H5P_DEFAULT);
t = H5Dget_type(ds);
v[0].eigenvalue = sample.first;
ASSERT_GE(H5Dwrite(ds, t, H5S_ALL, H5S_ALL, H5P_DEFAULT, v), 0);
H5Tclose(t);
H5Dclose(ds);
H5Fclose(f);
const auto comparison =
fesa::test::EigenReferenceComparison::Compare(reference_case);
ASSERT_TRUE(comparison.HasValue());
EXPECT_EQ(comparison.Value().passed, sample.second);
EXPECT_EQ(comparison.Value().families[0].rms_passed, sample.second);
EXPECT_TRUE(comparison.Value().families[1].passed);
}
struct TextMutation {
std::string text;
std::string expected_code;
+31 -8
View File
@@ -19,10 +19,9 @@ bool IsFiniteFamily(const ReferenceToleranceFamily& family) {
});
}
}
ReferenceToleranceEvaluation ReferenceTolerancePolicy::Evaluate(
const std::vector<ReferenceToleranceFamily>& families) {
ReferenceToleranceEvaluation EvaluateFamilies(
const std::vector<ReferenceToleranceFamily>& families,
const double relative_rms_tolerance) {
ReferenceToleranceEvaluation evaluation{true, {}, true, {}};
evaluation.families.reserve(families.size());
@@ -41,7 +40,8 @@ ReferenceToleranceEvaluation ReferenceTolerancePolicy::Evaluate(
decision.reference_scale =
(std::max)(decision.reference_scale, std::abs(row.reference_value));
}
decision.near_zero_band = kNearZeroRatio * decision.reference_scale;
decision.near_zero_band =
ReferenceTolerancePolicy::kNearZeroRatio * decision.reference_scale;
double error_norm = 0.0;
for (std::size_t index = 0U; index < family.rows.size(); ++index) {
@@ -74,12 +74,14 @@ ReferenceToleranceEvaluation ReferenceTolerancePolicy::Evaluate(
row_decision.passed = absolute_error <= row_decision.threshold;
} else {
row_decision.threshold =
kRelativeTolerance * std::abs(row.reference_value);
ReferenceTolerancePolicy::kRelativeTolerance *
std::abs(row.reference_value);
row_decision.relative_error =
absolute_error / std::abs(row.reference_value);
row_decision.relative_error_applicable = true;
row_decision.branch = ReferenceToleranceBranch::kRelative;
row_decision.passed = row_decision.relative_error <= kRelativeTolerance;
row_decision.passed = row_decision.relative_error <=
ReferenceTolerancePolicy::kRelativeTolerance;
}
decision.rows.push_back(row_decision);
}
@@ -100,7 +102,7 @@ ReferenceToleranceEvaluation ReferenceTolerancePolicy::Evaluate(
if (!std::isfinite(decision.relative_rms)) {
return {false, "nonfinite-comparison-value", false, {}};
}
decision.rms_passed = decision.relative_rms <= kRelativeRmsTolerance;
decision.rms_passed = decision.relative_rms <= relative_rms_tolerance;
}
decision.passed = rows_passed && decision.rms_passed;
if (decision.blocking && !decision.passed) {
@@ -112,4 +114,25 @@ ReferenceToleranceEvaluation ReferenceTolerancePolicy::Evaluate(
return evaluation;
}
} // namespace
ReferenceToleranceEvaluation ReferenceTolerancePolicy::Evaluate(
const std::vector<ReferenceToleranceFamily>& families) {
return EvaluateFamilies(families, kRelativeRmsTolerance);
}
ReferenceToleranceEvaluation
ReferenceTolerancePolicy::EvaluateMitc4EigenFirstImplementation(
const std::vector<ReferenceToleranceFamily>& families) {
if (families.empty() ||
!std::all_of(families.begin(), families.end(), [](const auto& family) {
return family.blocking && family.rows.size() == 1U &&
family.components.size() == 1U &&
family.components.front() == "Value";
})) {
return {false, "invalid-eigen-tolerance-family", false, {}};
}
return EvaluateFamilies(families, kRelativeTolerance);
}
}
@@ -63,9 +63,16 @@ class ReferenceTolerancePolicy {
static constexpr double kNearZeroRatio = 0.01;
static constexpr double kRelativeTolerance = 0.05;
static constexpr double kRelativeRmsTolerance = 0.01;
static constexpr const char* kMitc4EigenFirstImplementationPolicyId =
"mitc4-eigen-first-implementation";
static ReferenceToleranceEvaluation Evaluate(
const std::vector<ReferenceToleranceFamily>& families);
/// @brief Applies the approved five-percent RMS limit to blocking singleton
/// Value families from the MITC4 eigen first implementation only.
static ReferenceToleranceEvaluation EvaluateMitc4EigenFirstImplementation(
const std::vector<ReferenceToleranceFamily>& families);
};
}
@@ -115,4 +115,59 @@ TEST(ReferenceTolerancePolicy,
EXPECT_TRUE(evaluation.families.empty());
}
TEST(ReferenceTolerancePolicy,
EigenFirstImplementationDoesNotRelaxCommonPolicy) {
const std::vector<ReferenceToleranceFamily> families{
MakeFamily("mode-1", {"Value"}, true, {{105.0, 100.0}})};
const auto common = ReferenceTolerancePolicy::Evaluate(families);
const auto eigen =
ReferenceTolerancePolicy::EvaluateMitc4EigenFirstImplementation(families);
ASSERT_TRUE(common.valid);
ASSERT_TRUE(eigen.valid);
EXPECT_FALSE(common.passed);
EXPECT_TRUE(eigen.passed);
EXPECT_DOUBLE_EQ(eigen.families[0].relative_rms, 0.05);
EXPECT_DOUBLE_EQ(common.families[0].relative_rms,
eigen.families[0].relative_rms);
}
TEST(ReferenceTolerancePolicy,
EigenFirstImplementationPreservesZeroAndFiniteChecks) {
for (const auto& row : std::vector<ReferenceToleranceRow>{
{0.0, 0.0}, {1.0, 0.0},
{std::numeric_limits<double>::quiet_NaN(), 1.0}}) {
const auto result =
ReferenceTolerancePolicy::EvaluateMitc4EigenFirstImplementation(
{MakeFamily("mode-1", {"Value"}, true, {row})});
if (!std::isfinite(row.fesa_value)) {
EXPECT_FALSE(result.valid);
EXPECT_FALSE(result.passed);
} else {
ASSERT_TRUE(result.valid);
EXPECT_EQ(result.passed, row.fesa_value == 0.0);
if (row.fesa_value != 0.0) {
EXPECT_EQ(result.families[0].diagnostic_code,
"zero-reference-scale-nonzero-error");
}
}
}
}
TEST(ReferenceTolerancePolicy,
EigenFirstImplementationRejectsNonmodalFamilies) {
for (const auto& families :
std::vector<std::vector<ReferenceToleranceFamily>>{
{},
{MakeFamily("mode-1", {"U1"}, true, {{100.0, 100.0}})},
{MakeFamily("mode-1", {"Value"}, false, {{100.0, 100.0}})},
{MakeFamily("modes", {"Value"}, true,
{{100.0, 100.0}, {100.0, 100.0}})}}) {
const auto result =
ReferenceTolerancePolicy::EvaluateMitc4EigenFirstImplementation(families);
EXPECT_FALSE(result.valid);
EXPECT_FALSE(result.passed);
EXPECT_EQ(result.diagnostic_code, "invalid-eigen-tolerance-family");
}
}
}