feat: complete first MITC4 FEAST eigen implementation validation
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user