346 lines
11 KiB
C++
346 lines
11 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <fesa/validation/comparison.hpp>
|
|
|
|
namespace {
|
|
|
|
std::array<double, 6> values(
|
|
const double first,
|
|
const double second,
|
|
const double third,
|
|
const double fourth,
|
|
const double fifth,
|
|
const double sixth) {
|
|
return {first, second, third, fourth, fifth, sixth};
|
|
}
|
|
|
|
fesa::ResultFrame result_frame() {
|
|
fesa::NodalFrame nodal{
|
|
{fesa::NodeId{10}, fesa::NodeId{11}, fesa::NodeId{12}},
|
|
{
|
|
fesa::EntityOrigin{"BeamPart", "Part-1-1", 101},
|
|
fesa::EntityOrigin{"BeamPart", "Part-1-1", 102},
|
|
fesa::EntityOrigin{"BeamPart", "Part-1-1", 103},
|
|
},
|
|
{
|
|
values(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
|
|
values(7.0, 8.0, 9.0, 10.0, 11.0, 12.0),
|
|
values(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
|
},
|
|
{
|
|
values(13.0, 14.0, 15.0, 16.0, 17.0, 18.0),
|
|
values(19.0, 20.0, 21.0, 22.0, 23.0, 24.0),
|
|
values(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
|
},
|
|
};
|
|
fesa::BeamElementFrame beam{
|
|
fesa::ElementId{20},
|
|
fesa::EntityOrigin{"BeamPart", "Part-1-1", 501},
|
|
{
|
|
fesa::Vec3{1.0, 0.0, 0.0},
|
|
fesa::Vec3{0.0, 1.0, 0.0},
|
|
fesa::Vec3{0.0, 0.0, 1.0},
|
|
},
|
|
{{
|
|
{
|
|
-1.0,
|
|
fesa::NodeId{10},
|
|
values(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
|
values(25.0, 26.0, 27.0, 28.0, 29.0, 30.0),
|
|
31.0,
|
|
{},
|
|
},
|
|
{
|
|
1.0,
|
|
fesa::NodeId{11},
|
|
values(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
|
values(32.0, 33.0, 34.0, 35.0, 36.0, 37.0),
|
|
38.0,
|
|
{},
|
|
},
|
|
}},
|
|
};
|
|
return {1.0, std::move(nodal), {{std::move(beam)}}, {}};
|
|
}
|
|
|
|
fesa::ComparisonSample sample(
|
|
const fesa::ReferenceQuantity quantity,
|
|
fesa::ResultPosition position,
|
|
std::vector<double> reference,
|
|
std::vector<double> actual,
|
|
const fesa::Tolerance tolerance) {
|
|
return {
|
|
quantity,
|
|
std::move(position),
|
|
std::move(reference),
|
|
std::move(actual),
|
|
tolerance,
|
|
};
|
|
}
|
|
|
|
bool has_failure(
|
|
const std::vector<fesa::Diagnostic>& failures,
|
|
const std::string_view code) {
|
|
return std::ranges::any_of(
|
|
failures,
|
|
[code](const fesa::Diagnostic& failure) {
|
|
return failure.stage == fesa::DiagnosticStage::validation &&
|
|
failure.severity == fesa::Severity::error &&
|
|
failure.code == code;
|
|
});
|
|
}
|
|
|
|
TEST(ComparisonMetric, RejectsNearZeroErrorBeyondAbsoluteScale) {
|
|
const auto input = sample(
|
|
fesa::ReferenceQuantity::displacement,
|
|
{"Part-1-1", 101, std::nullopt},
|
|
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
|
{2.0e-9, 0.0, 0.0, 0.0, 0.0, 0.0},
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
const auto report = fesa::compare_samples(
|
|
std::span<const fesa::ComparisonSample>{&input, 1});
|
|
|
|
ASSERT_FALSE(report.passed);
|
|
EXPECT_DOUBLE_EQ(report.maximum_normalized_error, 2.0);
|
|
ASSERT_EQ(report.failures.size(), 1U);
|
|
EXPECT_EQ(report.failures[0].code, "validation.tolerance_exceeded");
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("quantity=displacement"),
|
|
std::string::npos);
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("entity=101"),
|
|
std::string::npos);
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("component=Ux"),
|
|
std::string::npos);
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("reference="),
|
|
std::string::npos);
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("actual="),
|
|
std::string::npos);
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("normalized_error="),
|
|
std::string::npos);
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("relative_tolerance="),
|
|
std::string::npos);
|
|
EXPECT_NE(
|
|
report.failures[0].message.find("absolute_scale="),
|
|
std::string::npos);
|
|
}
|
|
|
|
TEST(ComparisonMetric, RejectsRepresentativeLargeRelativeError) {
|
|
const auto input = sample(
|
|
fesa::ReferenceQuantity::reaction,
|
|
{"Part-1-1", 101, std::nullopt},
|
|
{1.0e6, 1.0e6, 1.0e6, 1.0e6, 1.0e6, 1.0e6},
|
|
{1.0e6 + 20.0, 1.0e6, 1.0e6, 1.0e6, 1.0e6, 1.0e6},
|
|
{1.0e-5, 0.0});
|
|
|
|
const auto report = fesa::compare_samples(
|
|
std::span<const fesa::ComparisonSample>{&input, 1});
|
|
|
|
EXPECT_FALSE(report.passed);
|
|
EXPECT_DOUBLE_EQ(report.maximum_normalized_error, 2.0);
|
|
EXPECT_TRUE(has_failure(
|
|
report.failures, "validation.tolerance_exceeded"));
|
|
}
|
|
|
|
TEST(ComparisonMetric, AcceptsErrorsAtTheExplicitToleranceBoundary) {
|
|
const std::vector<fesa::ComparisonSample> inputs{
|
|
sample(
|
|
fesa::ReferenceQuantity::displacement,
|
|
{"Part-1-1", 101, std::nullopt},
|
|
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
|
{1.0e-9, 0.0, 0.0, 0.0, 0.0, 0.0},
|
|
{1.0e-5, 1.0e-9}),
|
|
sample(
|
|
fesa::ReferenceQuantity::reaction,
|
|
{"Part-1-1", 101, std::nullopt},
|
|
{1.0e6, 1.0e6, 1.0e6, 1.0e6, 1.0e6, 1.0e6},
|
|
{1.0e6 + 10.0, 1.0e6, 1.0e6, 1.0e6, 1.0e6, 1.0e6},
|
|
{1.0e-5, 0.0}),
|
|
};
|
|
|
|
const auto report = fesa::compare_samples(inputs);
|
|
|
|
EXPECT_TRUE(report.passed);
|
|
EXPECT_DOUBLE_EQ(report.maximum_normalized_error, 1.0);
|
|
EXPECT_TRUE(report.failures.empty());
|
|
}
|
|
|
|
TEST(ComparisonMetric, RejectsNonfiniteValues) {
|
|
const auto input = sample(
|
|
fesa::ReferenceQuantity::centroid_stress,
|
|
{"Part-1-1", 501, 101},
|
|
{10.0},
|
|
{std::numeric_limits<double>::quiet_NaN()},
|
|
{1.0e-5, 1.0e-6});
|
|
|
|
const auto report = fesa::compare_samples(
|
|
std::span<const fesa::ComparisonSample>{&input, 1});
|
|
|
|
EXPECT_FALSE(report.passed);
|
|
EXPECT_TRUE(std::isinf(report.maximum_normalized_error));
|
|
EXPECT_TRUE(has_failure(
|
|
report.failures, "validation.nonfinite_comparison_value"));
|
|
}
|
|
|
|
TEST(ComparisonMetric, RejectsDuplicateQuantityAndPosition) {
|
|
const std::vector<fesa::ComparisonSample> inputs{
|
|
sample(
|
|
fesa::ReferenceQuantity::displacement,
|
|
{"Part-1-1", 101, std::nullopt},
|
|
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
|
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
|
{1.0e-5, 1.0e-9}),
|
|
sample(
|
|
fesa::ReferenceQuantity::displacement,
|
|
{"Part-1-1", 101, std::nullopt},
|
|
{1.0, 1.0, 1.0, 1.0, 1.0, 1.0},
|
|
{1.0, 1.0, 1.0, 1.0, 1.0, 1.0},
|
|
{1.0e-5, 1.0e-9}),
|
|
};
|
|
|
|
const auto report = fesa::compare_samples(inputs);
|
|
|
|
EXPECT_FALSE(report.passed);
|
|
EXPECT_TRUE(std::isinf(report.maximum_normalized_error));
|
|
EXPECT_TRUE(has_failure(
|
|
report.failures, "validation.duplicate_result_position"));
|
|
}
|
|
|
|
TEST(ComparisonMetric, RejectsComponentCountMismatch) {
|
|
const auto input = sample(
|
|
fesa::ReferenceQuantity::internal_force,
|
|
{"Part-1-1", 501, 101},
|
|
{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
|
|
{1.0, 2.0, 3.0, 4.0, 5.0},
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
const auto report = fesa::compare_samples(
|
|
std::span<const fesa::ComparisonSample>{&input, 1});
|
|
|
|
EXPECT_FALSE(report.passed);
|
|
EXPECT_TRUE(std::isinf(report.maximum_normalized_error));
|
|
EXPECT_TRUE(has_failure(
|
|
report.failures, "validation.component_count_mismatch"));
|
|
}
|
|
|
|
TEST(EntityMatching, MatchesNodalResultByInstanceAndExternalLabel) {
|
|
const auto frame = result_frame();
|
|
const std::array reference{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
|
|
|
const auto match = fesa::make_comparison_sample(
|
|
frame,
|
|
fesa::ReferenceQuantity::displacement,
|
|
{"Part-1-1", 102, std::nullopt},
|
|
reference,
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
ASSERT_TRUE(match.sample.has_value());
|
|
EXPECT_TRUE(match.failures.empty());
|
|
EXPECT_EQ(
|
|
match.sample->actual,
|
|
(std::vector<double>{7.0, 8.0, 9.0, 10.0, 11.0, 12.0}));
|
|
}
|
|
|
|
TEST(EntityMatching, MatchesReactionComponentsWithoutUsingDisplacement) {
|
|
const auto frame = result_frame();
|
|
const std::array reference{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
|
|
|
const auto match = fesa::make_comparison_sample(
|
|
frame,
|
|
fesa::ReferenceQuantity::reaction,
|
|
{"Part-1-1", 101, std::nullopt},
|
|
reference,
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
ASSERT_TRUE(match.sample.has_value());
|
|
EXPECT_TRUE(match.failures.empty());
|
|
EXPECT_EQ(
|
|
match.sample->actual,
|
|
(std::vector<double>{13.0, 14.0, 15.0, 16.0, 17.0, 18.0}));
|
|
}
|
|
|
|
TEST(EntityMatching, MatchesElementResultByElementAndEndNodeLabels) {
|
|
const auto frame = result_frame();
|
|
const std::array reference{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
|
|
|
const auto match = fesa::make_comparison_sample(
|
|
frame,
|
|
fesa::ReferenceQuantity::internal_force,
|
|
{"Part-1-1", 501, 102},
|
|
reference,
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
ASSERT_TRUE(match.sample.has_value());
|
|
EXPECT_TRUE(match.failures.empty());
|
|
EXPECT_EQ(
|
|
match.sample->actual,
|
|
(std::vector<double>{32.0, 33.0, 34.0, 35.0, 36.0, 37.0}));
|
|
}
|
|
|
|
TEST(EntityMatching, MatchesCentroidStressAtTheRequestedElementEnd) {
|
|
const auto frame = result_frame();
|
|
const std::array reference{0.0};
|
|
|
|
const auto match = fesa::make_comparison_sample(
|
|
frame,
|
|
fesa::ReferenceQuantity::centroid_stress,
|
|
{"Part-1-1", 501, 101},
|
|
reference,
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
ASSERT_TRUE(match.sample.has_value());
|
|
EXPECT_TRUE(match.failures.empty());
|
|
EXPECT_EQ(match.sample->actual, (std::vector<double>{31.0}));
|
|
}
|
|
|
|
TEST(EntityMatching, RejectsUnknownResultOrigin) {
|
|
const auto frame = result_frame();
|
|
const std::array reference{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
|
|
|
const auto match = fesa::make_comparison_sample(
|
|
frame,
|
|
fesa::ReferenceQuantity::reaction,
|
|
{"Part-1-1", 999, std::nullopt},
|
|
reference,
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
EXPECT_FALSE(match.sample.has_value());
|
|
EXPECT_TRUE(has_failure(
|
|
match.failures, "validation.unknown_result_origin"));
|
|
}
|
|
|
|
TEST(EntityMatching, RejectsNodeThatIsNotAnEndOfTheElement) {
|
|
const auto frame = result_frame();
|
|
const std::array reference{0.0};
|
|
|
|
const auto match = fesa::make_comparison_sample(
|
|
frame,
|
|
fesa::ReferenceQuantity::centroid_stress,
|
|
{"Part-1-1", 501, 103},
|
|
reference,
|
|
{1.0e-5, 1.0e-9});
|
|
|
|
EXPECT_FALSE(match.sample.has_value());
|
|
EXPECT_TRUE(has_failure(
|
|
match.failures, "validation.invalid_element_node_pair"));
|
|
}
|
|
|
|
} // namespace
|