262 lines
10 KiB
C++
262 lines
10 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#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
|
|
|
|
#ifndef FESA_TEST_BINARY_DIR
|
|
#error FESA_TEST_BINARY_DIR must identify the CMake binary root.
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
fesa::test::NonlinearReferenceCase MakePressureCase() {
|
|
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
|
|
const std::filesystem::path directory =
|
|
root / "reference" / "shellNL-meshfine-pressure";
|
|
return {"shellnl-meshfine-pressure-s4-follower",
|
|
directory / "shellNL-meshfine-pressure.inp",
|
|
directory / "shellNL-meshfine-pressure displacements.csv",
|
|
directory / "shellNL-meshfine-pressure reactions.csv",
|
|
directory / "shellNL-meshfine-pressure stresses.csv",
|
|
std::filesystem::path{FESA_TEST_BINARY_DIR} / "reference" /
|
|
"shell-surface-pressure-load",
|
|
fesa::test::NonlinearReferenceArtifactDisposition::kRequired,
|
|
fesa::test::NonlinearReferenceArtifactDisposition::kRequired,
|
|
fesa::test::NonlinearReferenceArtifactDisposition::kReviewOnly,
|
|
fesa::test::NonlinearReferenceArtifactDisposition::kExcluded};
|
|
}
|
|
|
|
std::optional<std::filesystem::path> FindCachedResults(
|
|
const fesa::test::NonlinearReferenceCase& reference_case) {
|
|
const std::filesystem::path solver_library =
|
|
std::filesystem::path{FESA_TEST_BINARY_DIR} / "src" / "fesa" / "Debug" /
|
|
"fesa_solver.lib";
|
|
std::error_code error;
|
|
const auto solver_time =
|
|
std::filesystem::last_write_time(solver_library, error);
|
|
if (error ||
|
|
!std::filesystem::is_directory(reference_case.execution_directory)) {
|
|
return std::nullopt;
|
|
}
|
|
std::vector<std::filesystem::path> candidates;
|
|
for (const auto& entry : std::filesystem::recursive_directory_iterator(
|
|
reference_case.execution_directory, error)) {
|
|
if (error) {
|
|
return std::nullopt;
|
|
}
|
|
if (entry.path().filename() == "shellNL-meshfine-pressure_results.h5" &&
|
|
std::filesystem::is_regular_file(entry.path()) &&
|
|
std::filesystem::last_write_time(entry.path(), error) >= solver_time &&
|
|
!error) {
|
|
candidates.push_back(entry.path());
|
|
}
|
|
}
|
|
std::sort(candidates.begin(), candidates.end());
|
|
return candidates.empty()
|
|
? std::nullopt
|
|
: std::optional<std::filesystem::path>{candidates.back()};
|
|
}
|
|
|
|
const fesa::test::NonlinearReferenceComparisonReport& RunPressureComparison() {
|
|
static const auto report = [] {
|
|
const auto reference_case = MakePressureCase();
|
|
const auto cached = FindCachedResults(reference_case);
|
|
if (cached.has_value()) {
|
|
const auto comparison =
|
|
fesa::test::CompareHdf5ToReferenceCsv(reference_case, *cached);
|
|
if (comparison.HasValue()) {
|
|
return comparison.Value();
|
|
}
|
|
}
|
|
const auto copied =
|
|
fesa::test::CopyInputToExecutionDirectory(reference_case);
|
|
if (!copied.HasValue()) {
|
|
throw std::runtime_error{"Unable to copy pressure reference input."};
|
|
}
|
|
const auto generated = fesa::test::RunFesaApplicationOnCopiedInput(
|
|
reference_case, copied.Value());
|
|
if (!generated.HasValue()) {
|
|
throw std::runtime_error{"Unable to run pressure reference input."};
|
|
}
|
|
const auto comparison = fesa::test::CompareHdf5ToReferenceCsv(
|
|
reference_case, generated.Value().generated_results_hdf5);
|
|
if (!comparison.HasValue()) {
|
|
throw std::runtime_error{"Unable to compare pressure reference output."};
|
|
}
|
|
return comparison.Value();
|
|
}();
|
|
return report;
|
|
}
|
|
|
|
const fesa::test::NonlinearReferenceFamilyReport& FindFamily(
|
|
const fesa::test::NonlinearReferenceComparisonReport& report,
|
|
const std::string& identity) {
|
|
const auto found = std::find_if(
|
|
report.families.begin(), report.families.end(),
|
|
[&identity](const fesa::test::NonlinearReferenceFamilyReport& family) {
|
|
return family.family_identity == identity;
|
|
});
|
|
if (found == report.families.end()) {
|
|
throw std::runtime_error{"Required comparison family is absent."};
|
|
}
|
|
return *found;
|
|
}
|
|
|
|
fesa::test::ReferenceToleranceFamilyDecision MakeTolerance(const bool passed) {
|
|
fesa::test::ReferenceToleranceFamilyDecision result{};
|
|
result.passed = passed;
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(ShellSurfacePressureReference,
|
|
AuditsExactRequiredArtifactsAndIgnoresOptionalReviewArtifacts) {
|
|
const auto artifacts = fesa::test::ArtifactCheck(MakePressureCase());
|
|
|
|
ASSERT_TRUE(artifacts.HasValue());
|
|
EXPECT_EQ(MakePressureCase().reaction_artifact_disposition,
|
|
fesa::test::NonlinearReferenceArtifactDisposition::kReviewOnly);
|
|
EXPECT_EQ(MakePressureCase().stress_artifact_disposition,
|
|
fesa::test::NonlinearReferenceArtifactDisposition::kExcluded);
|
|
ASSERT_EQ(artifacts.Value().size(), 2U);
|
|
EXPECT_EQ(artifacts.Value()[0U].byte_size, 56665U);
|
|
EXPECT_EQ(artifacts.Value()[0U].sha256,
|
|
"53718CCC9B41C23FCC1B509A51D66F1347E87ACA43A74F1D6CEC4D2085F4B90E");
|
|
EXPECT_EQ(artifacts.Value()[1U].byte_size, 111182U);
|
|
EXPECT_EQ(artifacts.Value()[1U].sha256,
|
|
"2FE53F98EAC9F04779100721C3EDF8049EB1DBFE733EBCBD819AEEED03B81198");
|
|
}
|
|
|
|
TEST(ShellSurfacePressureReference,
|
|
SelectsUniqueSchemaOneFactorOneAcceptedFrame) {
|
|
const auto& report = RunPressureComparison();
|
|
|
|
EXPECT_TRUE(report.blocking_passed);
|
|
EXPECT_EQ(report.case_id, "shellnl-meshfine-pressure-s4-follower");
|
|
}
|
|
|
|
TEST(ShellSurfacePressureReference,
|
|
Projects729WideRowsTo2187LogicalRowsPerFamily) {
|
|
const auto& report = RunPressureComparison();
|
|
|
|
EXPECT_EQ(FindFamily(report, "pressure_nodal_translation_blocking").row_count,
|
|
2187U);
|
|
EXPECT_EQ(FindFamily(report, "pressure_nodal_rotation_warning").row_count,
|
|
2187U);
|
|
}
|
|
|
|
TEST(ShellSurfacePressureReference,
|
|
MatchesByInstanceNodeLabelAndComponentNotRowOrder) {
|
|
const auto& report = RunPressureComparison();
|
|
|
|
const auto& translation =
|
|
FindFamily(report, "pressure_nodal_translation_blocking");
|
|
EXPECT_EQ(translation.components,
|
|
(std::vector<std::string>{"U1", "U2", "U3"}));
|
|
EXPECT_EQ(translation.tolerance.rows.size(), 2187U);
|
|
}
|
|
|
|
TEST(
|
|
ShellSurfacePressureReference,
|
|
RejectsSchemaIdentityCardinalityMissingExtraDuplicateAndNonfiniteBeforeTolerance) {
|
|
const auto& report = RunPressureComparison();
|
|
const std::filesystem::path invalid_hdf5 =
|
|
report.generated_results_hdf5.parent_path() / "invalid-pressure.h5";
|
|
{
|
|
std::ofstream stream{invalid_hdf5, std::ios::binary | std::ios::trunc};
|
|
stream << "not an HDF5 file";
|
|
}
|
|
EXPECT_FALSE(
|
|
fesa::test::CompareHdf5ToReferenceCsv(MakePressureCase(), invalid_hdf5)
|
|
.HasValue());
|
|
|
|
fesa::test::NonlinearReferenceComparisonReport precheck_report{};
|
|
precheck_report.blocking_passed = true;
|
|
precheck_report.precheck_findings = {
|
|
{"pressure_nodal_translation_blocking",
|
|
fesa::test::NonlinearReferenceDisposition::kBlocking,
|
|
"missing-extra-duplicate-or-nonfinite",
|
|
{}}};
|
|
EXPECT_EQ(fesa::test::ClassifyReferenceComparison(precheck_report),
|
|
fesa::test::NonlinearReferenceClassification::kFailed);
|
|
}
|
|
|
|
TEST(ShellSurfacePressureReference,
|
|
ClassifiesTranslationBlockingAndRotationWarningOnly) {
|
|
fesa::test::NonlinearReferenceComparisonReport report{};
|
|
report.blocking_passed = true;
|
|
report.families = {{"pressure_nodal_translation_blocking",
|
|
{"U1", "U2", "U3"},
|
|
fesa::test::NonlinearReferenceDisposition::kBlocking,
|
|
2187U,
|
|
MakeTolerance(true)},
|
|
{"pressure_nodal_rotation_warning",
|
|
{"UR1", "UR2", "UR3"},
|
|
fesa::test::NonlinearReferenceDisposition::kWarningOnly,
|
|
2187U,
|
|
MakeTolerance(false)}};
|
|
|
|
EXPECT_EQ(
|
|
fesa::test::ClassifyReferenceComparison(report),
|
|
fesa::test::NonlinearReferenceClassification::kWarningOnlyDifferences);
|
|
}
|
|
|
|
TEST(ShellSurfacePressureReference,
|
|
ExcludesReactionAndStressFamiliesFromVerdict) {
|
|
const auto& report = RunPressureComparison();
|
|
|
|
EXPECT_EQ(report.families.size(), 2U);
|
|
EXPECT_EQ(
|
|
FindFamily(report, "pressure_nodal_translation_blocking").disposition,
|
|
fesa::test::NonlinearReferenceDisposition::kBlocking);
|
|
EXPECT_EQ(FindFamily(report, "pressure_nodal_rotation_warning").disposition,
|
|
fesa::test::NonlinearReferenceDisposition::kWarningOnly);
|
|
}
|
|
|
|
TEST(ShellSurfacePressureReference,
|
|
UsesUnchangedCommonScaleNearZeroRelativeAndRmsPolicy) {
|
|
const auto& translation = FindFamily(RunPressureComparison(),
|
|
"pressure_nodal_translation_blocking");
|
|
|
|
EXPECT_EQ(translation.tolerance.near_zero_band,
|
|
fesa::test::ReferenceTolerancePolicy::kNearZeroRatio *
|
|
translation.tolerance.reference_scale);
|
|
EXPECT_LE(translation.tolerance.relative_rms,
|
|
fesa::test::ReferenceTolerancePolicy::kRelativeRmsTolerance);
|
|
EXPECT_TRUE(translation.tolerance.rms_passed);
|
|
}
|
|
|
|
TEST(ShellSurfacePressureReference, WritesDeterministicComparisonReport) {
|
|
const auto& first = RunPressureComparison();
|
|
const auto comparison = fesa::test::CompareHdf5ToReferenceCsv(
|
|
MakePressureCase(), first.generated_results_hdf5);
|
|
|
|
ASSERT_TRUE(comparison.HasValue());
|
|
ASSERT_EQ(first.families.size(), comparison.Value().families.size());
|
|
for (std::size_t index = 0U; index < first.families.size(); ++index) {
|
|
const auto& expected = first.families[index];
|
|
const auto& actual = comparison.Value().families[index];
|
|
EXPECT_EQ(actual.family_identity, expected.family_identity);
|
|
EXPECT_EQ(actual.components, expected.components);
|
|
EXPECT_EQ(actual.disposition, expected.disposition);
|
|
EXPECT_EQ(actual.row_count, expected.row_count);
|
|
EXPECT_DOUBLE_EQ(actual.tolerance.reference_scale,
|
|
expected.tolerance.reference_scale);
|
|
EXPECT_DOUBLE_EQ(actual.tolerance.relative_rms,
|
|
expected.tolerance.relative_rms);
|
|
EXPECT_EQ(actual.tolerance.worst_row, expected.tolerance.worst_row);
|
|
}
|
|
}
|