feat(cpp-object-oriented-modular-refactoring): step 6 - io-application-google-style
This commit is contained in:
@@ -1,10 +1,5 @@
|
||||
#include "reference_comparison.hpp"
|
||||
|
||||
#include "fesa/app/fesa_application.hpp"
|
||||
|
||||
#include <hdf5.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <hdf5.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -17,6 +12,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/app/fesa_application.h"
|
||||
#include "reference_comparison.h"
|
||||
|
||||
#ifndef FESA_TEST_SOURCE_DIR
|
||||
#error FESA_TEST_SOURCE_DIR must identify the repository root.
|
||||
#endif
|
||||
@@ -27,250 +25,224 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kStressPath =
|
||||
"/steps/Step-1/frames/0/element/stress_s11";
|
||||
constexpr const char* kStressPath = "/steps/Step-1/frames/0/element/stress_s11";
|
||||
constexpr std::size_t kExpectedRowCount = 176U;
|
||||
constexpr std::size_t kExpectedMetricCount = 16U;
|
||||
|
||||
class Hdf5Handle {
|
||||
public:
|
||||
using Closer = herr_t (*)(hid_t);
|
||||
public:
|
||||
using Closer = herr_t (*)(hid_t);
|
||||
|
||||
Hdf5Handle(const hid_t value, Closer closer)
|
||||
: value_{value}, closer_{closer} {}
|
||||
Hdf5Handle(const Hdf5Handle&) = delete;
|
||||
Hdf5Handle& operator=(const Hdf5Handle&) = delete;
|
||||
~Hdf5Handle() {
|
||||
if (value_ >= 0 && closer_ != nullptr) {
|
||||
(void)closer_(value_);
|
||||
}
|
||||
Hdf5Handle(const hid_t value, Closer closer)
|
||||
: value_{value}, closer_{closer} {}
|
||||
Hdf5Handle(const Hdf5Handle&) = delete;
|
||||
Hdf5Handle& operator=(const Hdf5Handle&) = delete;
|
||||
~Hdf5Handle() {
|
||||
if (value_ >= 0 && closer_ != nullptr) {
|
||||
(void)closer_(value_);
|
||||
}
|
||||
}
|
||||
|
||||
hid_t get() const noexcept { return value_; }
|
||||
hid_t Get() const noexcept { return value_; }
|
||||
|
||||
private:
|
||||
hid_t value_;
|
||||
Closer closer_;
|
||||
private:
|
||||
hid_t value_;
|
||||
Closer closer_;
|
||||
};
|
||||
|
||||
struct ReferenceSnapshotEntry {
|
||||
std::filesystem::path relativePath;
|
||||
bool isDirectory;
|
||||
std::string bytes;
|
||||
std::filesystem::file_time_type lastWriteTime;
|
||||
std::filesystem::path relative_path;
|
||||
bool is_directory;
|
||||
std::string bytes;
|
||||
std::filesystem::file_time_type last_write_time;
|
||||
};
|
||||
|
||||
std::string readBytes(const std::filesystem::path& path) {
|
||||
std::ifstream stream{path, std::ios::binary};
|
||||
if (!stream) {
|
||||
throw std::runtime_error{"Unable to read reference evidence: " +
|
||||
path.string()};
|
||||
}
|
||||
return {std::istreambuf_iterator<char>{stream},
|
||||
std::istreambuf_iterator<char>{}};
|
||||
std::string ReadBytes(const std::filesystem::path& path) {
|
||||
std::ifstream stream{path, std::ios::binary};
|
||||
if (!stream) {
|
||||
throw std::runtime_error{"Unable to read reference evidence: " +
|
||||
path.string()};
|
||||
}
|
||||
return {std::istreambuf_iterator<char>{stream},
|
||||
std::istreambuf_iterator<char>{}};
|
||||
}
|
||||
|
||||
std::vector<ReferenceSnapshotEntry> snapshotTree(
|
||||
std::vector<ReferenceSnapshotEntry> SnapshotTree(
|
||||
const std::filesystem::path& root) {
|
||||
std::vector<ReferenceSnapshotEntry> entries;
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator{root}) {
|
||||
const bool isDirectory = entry.is_directory();
|
||||
if (!isDirectory && !entry.is_regular_file()) {
|
||||
throw std::runtime_error{"Unexpected reference-tree entry type."};
|
||||
}
|
||||
entries.push_back({
|
||||
std::filesystem::relative(entry.path(), root),
|
||||
isDirectory,
|
||||
isDirectory ? std::string{} : readBytes(entry.path()),
|
||||
entry.last_write_time()});
|
||||
std::vector<ReferenceSnapshotEntry> entries;
|
||||
for (const auto& entry :
|
||||
std::filesystem::recursive_directory_iterator{root}) {
|
||||
const bool is_directory = entry.is_directory();
|
||||
if (!is_directory && !entry.is_regular_file()) {
|
||||
throw std::runtime_error{"Unexpected reference-tree entry type."};
|
||||
}
|
||||
std::sort(
|
||||
entries.begin(),
|
||||
entries.end(),
|
||||
[](const ReferenceSnapshotEntry& left,
|
||||
const ReferenceSnapshotEntry& right) {
|
||||
return left.relativePath.generic_string() <
|
||||
right.relativePath.generic_string();
|
||||
});
|
||||
return entries;
|
||||
entries.push_back({std::filesystem::relative(entry.path(), root),
|
||||
is_directory,
|
||||
is_directory ? std::string{} : ReadBytes(entry.path()),
|
||||
entry.last_write_time()});
|
||||
}
|
||||
std::sort(entries.begin(), entries.end(),
|
||||
[](const ReferenceSnapshotEntry& left,
|
||||
const ReferenceSnapshotEntry& right) {
|
||||
return left.relative_path.generic_string() <
|
||||
right.relative_path.generic_string();
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
|
||||
void expectTreeUnchanged(
|
||||
const std::vector<ReferenceSnapshotEntry>& before,
|
||||
const std::vector<ReferenceSnapshotEntry>& after) {
|
||||
ASSERT_EQ(after.size(), before.size());
|
||||
for (std::size_t index = 0U; index < before.size(); ++index) {
|
||||
EXPECT_EQ(after[index].relativePath, before[index].relativePath);
|
||||
EXPECT_EQ(after[index].isDirectory, before[index].isDirectory);
|
||||
EXPECT_EQ(after[index].bytes, before[index].bytes)
|
||||
<< before[index].relativePath.string();
|
||||
EXPECT_EQ(after[index].lastWriteTime, before[index].lastWriteTime)
|
||||
<< before[index].relativePath.string();
|
||||
}
|
||||
void ExpectTreeUnchanged(const std::vector<ReferenceSnapshotEntry>& before,
|
||||
const std::vector<ReferenceSnapshotEntry>& after) {
|
||||
ASSERT_EQ(after.size(), before.size());
|
||||
for (std::size_t index = 0U; index < before.size(); ++index) {
|
||||
EXPECT_EQ(after[index].relative_path, before[index].relative_path);
|
||||
EXPECT_EQ(after[index].is_directory, before[index].is_directory);
|
||||
EXPECT_EQ(after[index].bytes, before[index].bytes)
|
||||
<< before[index].relative_path.string();
|
||||
EXPECT_EQ(after[index].last_write_time, before[index].last_write_time)
|
||||
<< before[index].relative_path.string();
|
||||
}
|
||||
}
|
||||
|
||||
double norm(const std::array<double, 3>& value) {
|
||||
return std::sqrt(
|
||||
value[0U] * value[0U] + value[1U] * value[1U] +
|
||||
value[2U] * value[2U]);
|
||||
double Norm(const std::array<double, 3>& value) {
|
||||
return std::sqrt(value[0U] * value[0U] + value[1U] * value[1U] +
|
||||
value[2U] * value[2U]);
|
||||
}
|
||||
|
||||
std::array<double, 3> sum(
|
||||
const std::array<double, 3>& left,
|
||||
const std::array<double, 3>& right) {
|
||||
return {
|
||||
left[0U] + right[0U],
|
||||
left[1U] + right[1U],
|
||||
left[2U] + right[2U]};
|
||||
std::array<double, 3> Sum(const std::array<double, 3>& left,
|
||||
const std::array<double, 3>& right) {
|
||||
return {left[0U] + right[0U], left[1U] + right[1U], left[2U] + right[2U]};
|
||||
}
|
||||
|
||||
std::size_t stressRowCount(const std::filesystem::path& results) {
|
||||
const hid_t fileId =
|
||||
H5Fopen(results.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
|
||||
if (fileId < 0) {
|
||||
throw std::runtime_error{"Unable to open authoritative HDF5 output."};
|
||||
}
|
||||
const Hdf5Handle file{fileId, H5Fclose};
|
||||
if (H5Lexists(file.get(), kStressPath, H5P_DEFAULT) <= 0) {
|
||||
throw std::runtime_error{"Mandatory stress_s11 dataset is missing."};
|
||||
}
|
||||
const hid_t datasetId = H5Dopen2(file.get(), kStressPath, H5P_DEFAULT);
|
||||
if (datasetId < 0) {
|
||||
throw std::runtime_error{"Unable to open mandatory stress_s11 dataset."};
|
||||
}
|
||||
const Hdf5Handle dataset{datasetId, H5Dclose};
|
||||
const hid_t spaceId = H5Dget_space(dataset.get());
|
||||
if (spaceId < 0) {
|
||||
throw std::runtime_error{"Unable to inspect stress_s11 dataspace."};
|
||||
}
|
||||
const Hdf5Handle space{spaceId, H5Sclose};
|
||||
if (H5Sget_simple_extent_ndims(space.get()) != 1) {
|
||||
throw std::runtime_error{"stress_s11 must be a flat row dataset."};
|
||||
}
|
||||
hsize_t count = 0U;
|
||||
if (H5Sget_simple_extent_dims(space.get(), &count, nullptr) < 0) {
|
||||
throw std::runtime_error{"Unable to read stress_s11 extent."};
|
||||
}
|
||||
return static_cast<std::size_t>(count);
|
||||
std::size_t StressRowCount(const std::filesystem::path& results) {
|
||||
const hid_t file_id =
|
||||
H5Fopen(results.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
|
||||
if (file_id < 0) {
|
||||
throw std::runtime_error{"Unable to open authoritative HDF5 output."};
|
||||
}
|
||||
const Hdf5Handle file{file_id, H5Fclose};
|
||||
if (H5Lexists(file.Get(), kStressPath, H5P_DEFAULT) <= 0) {
|
||||
throw std::runtime_error{"Mandatory stress_s11 dataset is missing."};
|
||||
}
|
||||
const hid_t dataset_id = H5Dopen2(file.Get(), kStressPath, H5P_DEFAULT);
|
||||
if (dataset_id < 0) {
|
||||
throw std::runtime_error{"Unable to open mandatory stress_s11 dataset."};
|
||||
}
|
||||
const Hdf5Handle dataset{dataset_id, H5Dclose};
|
||||
const hid_t space_id = H5Dget_space(dataset.Get());
|
||||
if (space_id < 0) {
|
||||
throw std::runtime_error{"Unable to inspect stress_s11 dataspace."};
|
||||
}
|
||||
const Hdf5Handle space{space_id, H5Sclose};
|
||||
if (H5Sget_simple_extent_ndims(space.Get()) != 1) {
|
||||
throw std::runtime_error{"stress_s11 must be a flat row dataset."};
|
||||
}
|
||||
hsize_t count = 0U;
|
||||
if (H5Sget_simple_extent_dims(space.Get(), &count, nullptr) < 0) {
|
||||
throw std::runtime_error{"Unable to read stress_s11 extent."};
|
||||
}
|
||||
return static_cast<std::size_t>(count);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
TEST(B33ReferenceComparison,
|
||||
GeneratesAuthoritativeHdf5AndComparisonEvidence) {
|
||||
const std::filesystem::path sourceRoot{FESA_TEST_SOURCE_DIR};
|
||||
const std::filesystem::path binaryRoot{FESA_TEST_BINARY_DIR};
|
||||
const auto referenceDirectory =
|
||||
sourceRoot / "reference" / "cantilever beam";
|
||||
const auto input = referenceDirectory / "cantilever beam.inp";
|
||||
const auto outputDirectory =
|
||||
binaryRoot / "reference" / "cantilever-beam-b33";
|
||||
const auto results = outputDirectory / "results.h5";
|
||||
const auto comparison = outputDirectory / "comparison.json";
|
||||
TEST(B33ReferenceComparison, GeneratesAuthoritativeHdf5AndComparisonEvidence) {
|
||||
const std::filesystem::path source_root{FESA_TEST_SOURCE_DIR};
|
||||
const std::filesystem::path binary_root{FESA_TEST_BINARY_DIR};
|
||||
const auto reference_directory =
|
||||
source_root / "reference" / "cantilever beam";
|
||||
const auto input = reference_directory / "cantilever beam.inp";
|
||||
const auto output_directory =
|
||||
binary_root / "reference" / "cantilever-beam-b33";
|
||||
const auto results = output_directory / "results.h5";
|
||||
const auto comparison = output_directory / "comparison.json";
|
||||
|
||||
// Only the exact build-local evidence directory is reset; the approved
|
||||
// reference tree is snapshotted and subsequently opened read-only.
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(outputDirectory, error);
|
||||
error.clear();
|
||||
ASSERT_TRUE(std::filesystem::create_directories(outputDirectory, error));
|
||||
ASSERT_FALSE(error);
|
||||
const auto referenceBefore = snapshotTree(referenceDirectory);
|
||||
ASSERT_EQ(referenceBefore.size(), 4U);
|
||||
EXPECT_EQ(
|
||||
referenceBefore[0U].relativePath,
|
||||
std::filesystem::path{"cantilever beam displacements.csv"});
|
||||
EXPECT_EQ(
|
||||
referenceBefore[1U].relativePath,
|
||||
std::filesystem::path{"cantilever beam elemental forces.csv"});
|
||||
EXPECT_EQ(
|
||||
referenceBefore[2U].relativePath,
|
||||
std::filesystem::path{"cantilever beam reactions.csv"});
|
||||
EXPECT_EQ(
|
||||
referenceBefore[3U].relativePath,
|
||||
std::filesystem::path{"cantilever beam.inp"});
|
||||
// Only the exact build-local evidence directory is reset; the approved
|
||||
// reference tree is snapshotted and subsequently opened read-only.
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(output_directory, error);
|
||||
error.clear();
|
||||
ASSERT_TRUE(std::filesystem::create_directories(output_directory, error));
|
||||
ASSERT_FALSE(error);
|
||||
const auto reference_before = SnapshotTree(reference_directory);
|
||||
ASSERT_EQ(reference_before.size(), 4U);
|
||||
EXPECT_EQ(reference_before[0U].relative_path,
|
||||
std::filesystem::path{"cantilever beam displacements.csv"});
|
||||
EXPECT_EQ(reference_before[1U].relative_path,
|
||||
std::filesystem::path{"cantilever beam elemental forces.csv"});
|
||||
EXPECT_EQ(reference_before[2U].relative_path,
|
||||
std::filesystem::path{"cantilever beam reactions.csv"});
|
||||
EXPECT_EQ(reference_before[3U].relative_path,
|
||||
std::filesystem::path{"cantilever beam.inp"});
|
||||
|
||||
fesa::FesaApplication application;
|
||||
// FesaApplication receives application operands/options; main strips argv[0].
|
||||
ASSERT_EQ(
|
||||
application.run(
|
||||
{input.string(), "--output", results.string()}),
|
||||
0);
|
||||
ASSERT_TRUE(std::filesystem::is_regular_file(results));
|
||||
ASSERT_GT(H5Fis_hdf5(results.string().c_str()), 0);
|
||||
EXPECT_GT(stressRowCount(results), 0U);
|
||||
fesa::FesaApplication application;
|
||||
// FesaApplication receives application operands/options; main strips argv[0].
|
||||
ASSERT_EQ(application.Run({input.string(), "--output", results.string()}), 0);
|
||||
ASSERT_TRUE(std::filesystem::is_regular_file(results));
|
||||
ASSERT_GT(H5Fis_hdf5(results.string().c_str()), 0);
|
||||
EXPECT_GT(StressRowCount(results), 0U);
|
||||
|
||||
auto comparisonResult = fesa::test::ReferenceComparison::compare(
|
||||
results, referenceDirectory);
|
||||
ASSERT_TRUE(comparisonResult.HasValue());
|
||||
const auto& report = comparisonResult.Value();
|
||||
ASSERT_TRUE(report.passed);
|
||||
ASSERT_EQ(report.rows.size(), kExpectedRowCount);
|
||||
ASSERT_EQ(report.metrics.size(), kExpectedMetricCount);
|
||||
EXPECT_TRUE(std::all_of(
|
||||
report.rows.begin(),
|
||||
report.rows.end(),
|
||||
[](const fesa::test::RowDecision& row) {
|
||||
return row.passed && std::isfinite(row.absoluteError) &&
|
||||
std::isfinite(row.tolerance) && row.tolerance > 0.0 &&
|
||||
row.fesa.modelId == "cantilever-beam-b33" &&
|
||||
row.reference.modelId == "cantilever-beam-b33" &&
|
||||
row.fesa.stepName == "Step-1" &&
|
||||
row.reference.stepName == "Step-1" &&
|
||||
row.fesa.frameIndex == 0U &&
|
||||
row.reference.frameIndex == 0U &&
|
||||
row.fesa.instanceName == "PART-1_1-1" &&
|
||||
row.reference.instanceName == "PART-1_1-1" &&
|
||||
!row.fesa.hdf5DatasetPath.empty();
|
||||
}));
|
||||
EXPECT_TRUE(std::all_of(
|
||||
report.metrics.begin(),
|
||||
report.metrics.end(),
|
||||
[](const fesa::test::ComponentMetrics& metric) {
|
||||
return std::isfinite(metric.referenceScale) &&
|
||||
std::isfinite(metric.maximumAbsoluteError) &&
|
||||
std::isfinite(metric.maximumNormalizedError) &&
|
||||
std::isfinite(metric.rmsError) &&
|
||||
std::isfinite(metric.normError) &&
|
||||
metric.maximumNormalizedError <= 1.0;
|
||||
}));
|
||||
auto comparison_result =
|
||||
fesa::test::ReferenceComparison::Compare(results, reference_directory);
|
||||
ASSERT_TRUE(comparison_result.HasValue());
|
||||
const auto& report = comparison_result.Value();
|
||||
ASSERT_TRUE(report.passed);
|
||||
ASSERT_EQ(report.rows.size(), kExpectedRowCount);
|
||||
ASSERT_EQ(report.metrics.size(), kExpectedMetricCount);
|
||||
EXPECT_TRUE(std::all_of(
|
||||
report.rows.begin(), report.rows.end(),
|
||||
[](const fesa::test::RowDecision& row) {
|
||||
return row.passed && std::isfinite(row.absolute_error) &&
|
||||
std::isfinite(row.tolerance) && row.tolerance > 0.0 &&
|
||||
row.fesa.model_id == "cantilever-beam-b33" &&
|
||||
row.reference.model_id == "cantilever-beam-b33" &&
|
||||
row.fesa.step_name == "Step-1" &&
|
||||
row.reference.step_name == "Step-1" &&
|
||||
row.fesa.frame_index == 0U && row.reference.frame_index == 0U &&
|
||||
row.fesa.instance_name == "PART-1_1-1" &&
|
||||
row.reference.instance_name == "PART-1_1-1" &&
|
||||
!row.fesa.hdf5_dataset_path.empty();
|
||||
}));
|
||||
EXPECT_TRUE(
|
||||
std::all_of(report.metrics.begin(), report.metrics.end(),
|
||||
[](const fesa::test::ComponentMetrics& metric) {
|
||||
return std::isfinite(metric.reference_scale) &&
|
||||
std::isfinite(metric.maximum_absolute_error) &&
|
||||
std::isfinite(metric.maximum_normalized_error) &&
|
||||
std::isfinite(metric.rms_error) &&
|
||||
std::isfinite(metric.norm_error) &&
|
||||
metric.maximum_normalized_error <= 1.0;
|
||||
}));
|
||||
|
||||
EXPECT_FALSE(report.stressComparisonApplicable);
|
||||
EXPECT_NE(report.stressComparisonReason.find("N/A"), std::string::npos);
|
||||
EXPECT_NE(report.stressComparisonReason.find("HDF5"), std::string::npos);
|
||||
EXPECT_TRUE(report.physicsEvidence.endpointConsistencyPassed);
|
||||
EXPECT_TRUE(std::isfinite(report.physicsEvidence.freeResidualNorm));
|
||||
EXPECT_LE(report.physicsEvidence.freeResidualNorm, 1.0e-3);
|
||||
EXPECT_LE(
|
||||
norm(sum(
|
||||
report.physicsEvidence.appliedForce,
|
||||
report.physicsEvidence.reactionForce)),
|
||||
1.0e-3);
|
||||
EXPECT_LE(
|
||||
norm(sum(
|
||||
report.physicsEvidence.appliedMomentAboutOrigin,
|
||||
report.physicsEvidence.reactionMomentAboutOrigin)),
|
||||
1.0e-2);
|
||||
EXPECT_FALSE(report.stress_comparison_applicable);
|
||||
EXPECT_NE(report.stress_comparison_reason.find("N/A"), std::string::npos);
|
||||
EXPECT_NE(report.stress_comparison_reason.find("HDF5"), std::string::npos);
|
||||
EXPECT_TRUE(report.physics_evidence.endpoint_consistency_passed);
|
||||
EXPECT_TRUE(std::isfinite(report.physics_evidence.free_residual_norm));
|
||||
EXPECT_LE(report.physics_evidence.free_residual_norm, 1.0e-3);
|
||||
EXPECT_LE(Norm(Sum(report.physics_evidence.applied_force,
|
||||
report.physics_evidence.reaction_force)),
|
||||
1.0e-3);
|
||||
EXPECT_LE(Norm(Sum(report.physics_evidence.applied_moment_about_origin,
|
||||
report.physics_evidence.reaction_moment_about_origin)),
|
||||
1.0e-2);
|
||||
|
||||
ASSERT_TRUE(
|
||||
fesa::test::ReferenceComparison::writeDeterministicJson(
|
||||
report, comparison)
|
||||
.IsOk());
|
||||
ASSERT_TRUE(std::filesystem::is_regular_file(comparison));
|
||||
const std::string json = readBytes(comparison);
|
||||
EXPECT_NE(json.find("\"stress_comparison_applicable\":false"),
|
||||
std::string::npos);
|
||||
EXPECT_NE(json.find("\"physics_evidence\""), std::string::npos);
|
||||
ASSERT_TRUE(fesa::test::ReferenceComparison::WriteDeterministicJson(
|
||||
report, comparison)
|
||||
.IsOk());
|
||||
ASSERT_TRUE(std::filesystem::is_regular_file(comparison));
|
||||
const std::string json = ReadBytes(comparison);
|
||||
EXPECT_NE(json.find("\"stress_comparison_applicable\":false"),
|
||||
std::string::npos);
|
||||
EXPECT_NE(json.find("\"physics_evidence\""), std::string::npos);
|
||||
|
||||
std::vector<std::string> generatedNames;
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator{outputDirectory}) {
|
||||
generatedNames.push_back(entry.path().filename().string());
|
||||
}
|
||||
std::sort(generatedNames.begin(), generatedNames.end());
|
||||
EXPECT_EQ(
|
||||
generatedNames,
|
||||
(std::vector<std::string>{"comparison.json", "results.h5"}));
|
||||
std::vector<std::string> generated_names;
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator{output_directory}) {
|
||||
generated_names.push_back(entry.path().filename().string());
|
||||
}
|
||||
std::sort(generated_names.begin(), generated_names.end());
|
||||
EXPECT_EQ(generated_names,
|
||||
(std::vector<std::string>{"comparison.json", "results.h5"}));
|
||||
|
||||
expectTreeUnchanged(referenceBefore, snapshotTree(referenceDirectory));
|
||||
ExpectTreeUnchanged(reference_before, SnapshotTree(reference_directory));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
#include "mitc4_reference_comparison.hpp"
|
||||
|
||||
#include "fesa/app/fesa_application.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -14,6 +10,9 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/app/fesa_application.h"
|
||||
#include "mitc4_reference_comparison.h"
|
||||
|
||||
#ifndef FESA_TEST_SOURCE_DIR
|
||||
#error FESA_TEST_SOURCE_DIR must identify the repository root.
|
||||
#endif
|
||||
@@ -30,150 +29,139 @@ constexpr const char* kIntegrationRule =
|
||||
constexpr std::size_t kNodeCount = 49U;
|
||||
constexpr std::size_t kComponentCount = 6U;
|
||||
|
||||
std::string readBytes(const std::filesystem::path& path) {
|
||||
std::ifstream stream{path, std::ios::binary};
|
||||
if (!stream) {
|
||||
throw std::runtime_error{"Unable to read declared reference artifact."};
|
||||
}
|
||||
return {std::istreambuf_iterator<char>{stream},
|
||||
std::istreambuf_iterator<char>{}};
|
||||
std::string ReadBytes(const std::filesystem::path& path) {
|
||||
std::ifstream stream{path, std::ios::binary};
|
||||
if (!stream) {
|
||||
throw std::runtime_error{"Unable to read declared reference artifact."};
|
||||
}
|
||||
return {std::istreambuf_iterator<char>{stream},
|
||||
std::istreambuf_iterator<char>{}};
|
||||
}
|
||||
|
||||
struct ArtifactSnapshot {
|
||||
std::string bytes;
|
||||
std::filesystem::file_time_type lastWriteTime;
|
||||
std::string bytes;
|
||||
std::filesystem::file_time_type last_write_time;
|
||||
};
|
||||
|
||||
ArtifactSnapshot snapshot(const std::filesystem::path& path) {
|
||||
return {readBytes(path), std::filesystem::last_write_time(path)};
|
||||
ArtifactSnapshot Snapshot(const std::filesystem::path& path) {
|
||||
return {ReadBytes(path), std::filesystem::last_write_time(path)};
|
||||
}
|
||||
|
||||
void expectUnchanged(
|
||||
const std::filesystem::path& path, const ArtifactSnapshot& before) {
|
||||
EXPECT_EQ(readBytes(path), before.bytes) << path.string();
|
||||
EXPECT_EQ(std::filesystem::last_write_time(path), before.lastWriteTime)
|
||||
<< path.string();
|
||||
void ExpectUnchanged(const std::filesystem::path& path,
|
||||
const ArtifactSnapshot& before) {
|
||||
EXPECT_EQ(ReadBytes(path), before.bytes) << path.string();
|
||||
EXPECT_EQ(std::filesystem::last_write_time(path), before.last_write_time)
|
||||
<< path.string();
|
||||
}
|
||||
|
||||
struct CaseEvidence {
|
||||
fesa::test::Mitc4ComparisonReport report;
|
||||
std::filesystem::path comparisonJson;
|
||||
fesa::test::Mitc4ComparisonReport report;
|
||||
std::filesystem::path comparison_json;
|
||||
};
|
||||
|
||||
CaseEvidence runCase(
|
||||
const std::string& caseId,
|
||||
const std::string& sourceElementType,
|
||||
const std::filesystem::path& referenceDirectory,
|
||||
const std::filesystem::path& input,
|
||||
const std::filesystem::path& csv,
|
||||
const std::string& outputName) {
|
||||
const auto inputBefore = snapshot(input);
|
||||
const auto csvBefore = snapshot(csv);
|
||||
const std::filesystem::path outputDirectory =
|
||||
std::filesystem::path{FESA_TEST_BINARY_DIR} / "reference" / outputName;
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(outputDirectory, error);
|
||||
error.clear();
|
||||
if (!std::filesystem::create_directories(outputDirectory, error) || error) {
|
||||
throw std::runtime_error{"Unable to create MITC4 evidence directory."};
|
||||
}
|
||||
const auto results = outputDirectory / "results.h5";
|
||||
const auto comparison = outputDirectory / "comparison.json";
|
||||
CaseEvidence RunCase(const std::string& case_id,
|
||||
const std::string& source_element_type,
|
||||
const std::filesystem::path& reference_directory,
|
||||
const std::filesystem::path& input,
|
||||
const std::filesystem::path& csv,
|
||||
const std::string& output_name) {
|
||||
const auto input_before = Snapshot(input);
|
||||
const auto csv_before = Snapshot(csv);
|
||||
const std::filesystem::path output_directory =
|
||||
std::filesystem::path{FESA_TEST_BINARY_DIR} / "reference" / output_name;
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(output_directory, error);
|
||||
error.clear();
|
||||
if (!std::filesystem::create_directories(output_directory, error) || error) {
|
||||
throw std::runtime_error{"Unable to create MITC4 evidence directory."};
|
||||
}
|
||||
const auto results = output_directory / "results.h5";
|
||||
const auto comparison = output_directory / "comparison.json";
|
||||
|
||||
fesa::FesaApplication application;
|
||||
EXPECT_EQ(
|
||||
application.run({input.string(), "--output", results.string()}), 0);
|
||||
EXPECT_TRUE(std::filesystem::is_regular_file(results));
|
||||
auto comparisonResult = fesa::test::Mitc4ReferenceComparison::compare(
|
||||
{caseId, sourceElementType, input, csv, results});
|
||||
if (!comparisonResult.HasValue()) {
|
||||
std::string diagnostics;
|
||||
for (const auto& diagnostic :
|
||||
comparisonResult.GetStatus().Diagnostics()) {
|
||||
diagnostics += "\n" + diagnostic.code + ": " + diagnostic.message;
|
||||
}
|
||||
ADD_FAILURE() << "MITC4 comparison precheck failed for " << caseId
|
||||
<< diagnostics;
|
||||
return {{}, comparison};
|
||||
fesa::FesaApplication application;
|
||||
EXPECT_EQ(application.Run({input.string(), "--output", results.string()}), 0);
|
||||
EXPECT_TRUE(std::filesystem::is_regular_file(results));
|
||||
auto comparison_result = fesa::test::Mitc4ReferenceComparison::Compare(
|
||||
{case_id, source_element_type, input, csv, results});
|
||||
if (!comparison_result.HasValue()) {
|
||||
std::string diagnostics;
|
||||
for (const auto& diagnostic : comparison_result.GetStatus().Diagnostics()) {
|
||||
diagnostics += "\n" + diagnostic.code + ": " + diagnostic.message;
|
||||
}
|
||||
EXPECT_TRUE(
|
||||
fesa::test::Mitc4ReferenceComparison::writeDeterministicJson(
|
||||
comparisonResult.Value(), comparison)
|
||||
.IsOk());
|
||||
EXPECT_TRUE(std::filesystem::is_regular_file(comparison));
|
||||
ADD_FAILURE() << "MITC4 comparison precheck failed for " << case_id
|
||||
<< diagnostics;
|
||||
return {{}, comparison};
|
||||
}
|
||||
EXPECT_TRUE(fesa::test::Mitc4ReferenceComparison::WriteDeterministicJson(
|
||||
comparison_result.Value(), comparison)
|
||||
.IsOk());
|
||||
EXPECT_TRUE(std::filesystem::is_regular_file(comparison));
|
||||
|
||||
std::vector<std::string> generated;
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator{outputDirectory}) {
|
||||
generated.push_back(entry.path().filename().string());
|
||||
}
|
||||
std::sort(generated.begin(), generated.end());
|
||||
EXPECT_EQ(
|
||||
generated,
|
||||
(std::vector<std::string>{"comparison.json", "results.h5"}));
|
||||
EXPECT_TRUE(std::filesystem::is_directory(referenceDirectory));
|
||||
expectUnchanged(input, inputBefore);
|
||||
expectUnchanged(csv, csvBefore);
|
||||
return {std::move(comparisonResult.Value()), comparison};
|
||||
std::vector<std::string> generated;
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator{output_directory}) {
|
||||
generated.push_back(entry.path().filename().string());
|
||||
}
|
||||
std::sort(generated.begin(), generated.end());
|
||||
EXPECT_EQ(generated,
|
||||
(std::vector<std::string>{"comparison.json", "results.h5"}));
|
||||
EXPECT_TRUE(std::filesystem::is_directory(reference_directory));
|
||||
ExpectUnchanged(input, input_before);
|
||||
ExpectUnchanged(csv, csv_before);
|
||||
return {std::move(comparison_result.Value()), comparison};
|
||||
}
|
||||
|
||||
void expectCommonMetadata(
|
||||
const fesa::test::Mitc4ComparisonReport& report,
|
||||
const std::string& caseId,
|
||||
const std::string& sourceElementType) {
|
||||
EXPECT_EQ(report.caseId, caseId);
|
||||
EXPECT_EQ(report.sourceElementType, sourceElementType);
|
||||
EXPECT_EQ(report.internalFormulation, kInternalFormulation);
|
||||
EXPECT_EQ(report.integrationRule, kIntegrationRule);
|
||||
void ExpectCommonMetadata(const fesa::test::Mitc4ComparisonReport& report,
|
||||
const std::string& case_id,
|
||||
const std::string& source_element_type) {
|
||||
EXPECT_EQ(report.case_id, case_id);
|
||||
EXPECT_EQ(report.source_element_type, source_element_type);
|
||||
EXPECT_EQ(report.internal_formulation, kInternalFormulation);
|
||||
EXPECT_EQ(report.integration_rule, kIntegrationRule);
|
||||
}
|
||||
|
||||
void expectComparisonCoverage(
|
||||
const fesa::test::Mitc4ComparisonReport& report) {
|
||||
ASSERT_EQ(report.rows.size(), kNodeCount * kComponentCount);
|
||||
ASSERT_EQ(report.metrics.size(), kComponentCount);
|
||||
ASSERT_EQ(report.vectorMetrics.size(), kNodeCount);
|
||||
EXPECT_TRUE(report.passed);
|
||||
const std::size_t blockingRows = static_cast<std::size_t>(std::count_if(
|
||||
report.rows.begin(), report.rows.end(),
|
||||
[](const fesa::test::Mitc4RowDecision& row) {
|
||||
return row.blocking;
|
||||
}));
|
||||
const std::size_t rotationRows = report.rows.size() - blockingRows;
|
||||
EXPECT_EQ(blockingRows, kNodeCount * 3U);
|
||||
EXPECT_EQ(rotationRows, kNodeCount * 3U);
|
||||
EXPECT_TRUE(std::all_of(
|
||||
report.rows.begin(), report.rows.end(),
|
||||
[](const fesa::test::Mitc4RowDecision& row) {
|
||||
return !row.blocking || row.withinTolerance;
|
||||
}));
|
||||
EXPECT_EQ(
|
||||
report.warnings.size(),
|
||||
static_cast<std::size_t>(std::count_if(
|
||||
report.rows.begin(), report.rows.end(),
|
||||
[](const fesa::test::Mitc4RowDecision& row) {
|
||||
return !row.blocking && !row.withinTolerance;
|
||||
})));
|
||||
void ExpectComparisonCoverage(const fesa::test::Mitc4ComparisonReport& report) {
|
||||
ASSERT_EQ(report.rows.size(), kNodeCount * kComponentCount);
|
||||
ASSERT_EQ(report.metrics.size(), kComponentCount);
|
||||
ASSERT_EQ(report.vector_metrics.size(), kNodeCount);
|
||||
EXPECT_TRUE(report.passed);
|
||||
const std::size_t blocking_rows = static_cast<std::size_t>(std::count_if(
|
||||
report.rows.begin(), report.rows.end(),
|
||||
[](const fesa::test::Mitc4RowDecision& row) { return row.blocking; }));
|
||||
const std::size_t rotation_rows = report.rows.size() - blocking_rows;
|
||||
EXPECT_EQ(blocking_rows, kNodeCount * 3U);
|
||||
EXPECT_EQ(rotation_rows, kNodeCount * 3U);
|
||||
EXPECT_TRUE(std::all_of(report.rows.begin(), report.rows.end(),
|
||||
[](const fesa::test::Mitc4RowDecision& row) {
|
||||
return !row.blocking || row.within_tolerance;
|
||||
}));
|
||||
EXPECT_EQ(report.warnings.size(),
|
||||
static_cast<std::size_t>(
|
||||
std::count_if(report.rows.begin(), report.rows.end(),
|
||||
[](const fesa::test::Mitc4RowDecision& row) {
|
||||
return !row.blocking && !row.within_tolerance;
|
||||
})));
|
||||
}
|
||||
|
||||
// MITC4-E2E-S4-001
|
||||
TEST(Mitc4S4Reference, PreservesS4AndWritesCommonMitc4Metadata) {
|
||||
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
|
||||
const auto directory = root / "reference" / "shell";
|
||||
const auto evidence = runCase(
|
||||
"shell-s4", "S4", directory, directory / "shell.inp",
|
||||
directory / "shell displacements.csv", "mitc4-shell-s4-metadata");
|
||||
expectCommonMetadata(evidence.report, "shell-s4", "S4");
|
||||
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
|
||||
const auto directory = root / "reference" / "shell";
|
||||
const auto evidence =
|
||||
RunCase("shell-s4", "S4", directory, directory / "shell.inp",
|
||||
directory / "shell displacements.csv", "mitc4-shell-s4-metadata");
|
||||
ExpectCommonMetadata(evidence.report, "shell-s4", "S4");
|
||||
}
|
||||
|
||||
// MITC4-E2E-S4-002
|
||||
TEST(Mitc4S4Reference, PassesBlockingUAndReportsEveryUrRow) {
|
||||
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
|
||||
const auto directory = root / "reference" / "shell";
|
||||
const auto evidence = runCase(
|
||||
"shell-s4", "S4", directory, directory / "shell.inp",
|
||||
directory / "shell displacements.csv", "mitc4-shell-s4-comparison");
|
||||
expectCommonMetadata(evidence.report, "shell-s4", "S4");
|
||||
expectComparisonCoverage(evidence.report);
|
||||
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
|
||||
const auto directory = root / "reference" / "shell";
|
||||
const auto evidence = RunCase(
|
||||
"shell-s4", "S4", directory, directory / "shell.inp",
|
||||
directory / "shell displacements.csv", "mitc4-shell-s4-comparison");
|
||||
ExpectCommonMetadata(evidence.report, "shell-s4", "S4");
|
||||
ExpectComparisonCoverage(evidence.report);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
#ifndef FESA_TESTS_REFERENCE_MITC4_REFERENCE_COMPARISON_H_
|
||||
#define FESA_TESTS_REFERENCE_MITC4_REFERENCE_COMPARISON_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa::test {
|
||||
|
||||
struct Mitc4ReferenceCase {
|
||||
std::string case_id;
|
||||
std::string expected_source_element_type;
|
||||
std::filesystem::path input_path;
|
||||
std::filesystem::path displacement_csv_path;
|
||||
std::filesystem::path results_hdf5_path;
|
||||
};
|
||||
|
||||
struct Mitc4RowDecision {
|
||||
std::string case_id;
|
||||
std::string instance_name;
|
||||
std::int64_t source_node_label;
|
||||
std::string component;
|
||||
double fesa_value;
|
||||
double reference_value;
|
||||
double absolute_error;
|
||||
double tolerance;
|
||||
double normalized_error;
|
||||
bool blocking;
|
||||
bool within_tolerance;
|
||||
};
|
||||
|
||||
struct Mitc4ComponentMetrics {
|
||||
std::string component;
|
||||
double reference_scale;
|
||||
double tolerance;
|
||||
double maximum_absolute_error;
|
||||
double maximum_normalized_error;
|
||||
double rms_error;
|
||||
double vector_norm_error;
|
||||
std::size_t worst_row;
|
||||
};
|
||||
|
||||
struct Mitc4VectorMetrics {
|
||||
std::string instance_name;
|
||||
std::int64_t source_node_label;
|
||||
double displacement_norm_error;
|
||||
double rotation_norm_error;
|
||||
};
|
||||
|
||||
struct Mitc4Warning {
|
||||
std::string code;
|
||||
std::size_t row;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct Mitc4ComparisonReport {
|
||||
std::string case_id;
|
||||
std::string source_element_type;
|
||||
std::string internal_formulation;
|
||||
std::string integration_rule;
|
||||
std::vector<Mitc4RowDecision> rows;
|
||||
std::vector<Mitc4ComponentMetrics> metrics;
|
||||
std::vector<Mitc4VectorMetrics> vector_metrics;
|
||||
std::vector<Mitc4Warning> warnings;
|
||||
std::size_t worst_row;
|
||||
bool passed;
|
||||
};
|
||||
|
||||
class Mitc4ReferenceComparison {
|
||||
public:
|
||||
static Result<Mitc4ComparisonReport> Compare(
|
||||
const Mitc4ReferenceCase& reference_case);
|
||||
static Status WriteDeterministicJson(
|
||||
const Mitc4ComparisonReport& report,
|
||||
const std::filesystem::path& output_json);
|
||||
};
|
||||
|
||||
} // namespace fesa::test
|
||||
|
||||
#endif // FESA_TESTS_REFERENCE_MITC4_REFERENCE_COMPARISON_H_
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::test {
|
||||
|
||||
struct Mitc4ReferenceCase {
|
||||
std::string caseId;
|
||||
std::string expectedSourceElementType;
|
||||
std::filesystem::path inputPath;
|
||||
std::filesystem::path displacementCsvPath;
|
||||
std::filesystem::path resultsHdf5Path;
|
||||
};
|
||||
|
||||
struct Mitc4RowDecision {
|
||||
std::string caseId;
|
||||
std::string instanceName;
|
||||
std::int64_t sourceNodeLabel;
|
||||
std::string component;
|
||||
double fesaValue;
|
||||
double referenceValue;
|
||||
double absoluteError;
|
||||
double tolerance;
|
||||
double normalizedError;
|
||||
bool blocking;
|
||||
bool withinTolerance;
|
||||
};
|
||||
|
||||
struct Mitc4ComponentMetrics {
|
||||
std::string component;
|
||||
double referenceScale;
|
||||
double tolerance;
|
||||
double maximumAbsoluteError;
|
||||
double maximumNormalizedError;
|
||||
double rmsError;
|
||||
double vectorNormError;
|
||||
std::size_t worstRow;
|
||||
};
|
||||
|
||||
struct Mitc4VectorMetrics {
|
||||
std::string instanceName;
|
||||
std::int64_t sourceNodeLabel;
|
||||
double displacementNormError;
|
||||
double rotationNormError;
|
||||
};
|
||||
|
||||
struct Mitc4Warning {
|
||||
std::string code;
|
||||
std::size_t row;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct Mitc4ComparisonReport {
|
||||
std::string caseId;
|
||||
std::string sourceElementType;
|
||||
std::string internalFormulation;
|
||||
std::string integrationRule;
|
||||
std::vector<Mitc4RowDecision> rows;
|
||||
std::vector<Mitc4ComponentMetrics> metrics;
|
||||
std::vector<Mitc4VectorMetrics> vectorMetrics;
|
||||
std::vector<Mitc4Warning> warnings;
|
||||
std::size_t worstRow;
|
||||
bool passed;
|
||||
};
|
||||
|
||||
class Mitc4ReferenceComparison {
|
||||
public:
|
||||
static Result<Mitc4ComparisonReport> compare(
|
||||
const Mitc4ReferenceCase& referenceCase);
|
||||
static Status writeDeterministicJson(
|
||||
const Mitc4ComparisonReport& report,
|
||||
const std::filesystem::path& outputJson);
|
||||
};
|
||||
|
||||
} // namespace fesa::test
|
||||
File diff suppressed because it is too large
Load Diff
+1235
-1325
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
#ifndef FESA_TESTS_REFERENCE_REFERENCE_COMPARISON_H_
|
||||
#define FESA_TESTS_REFERENCE_REFERENCE_COMPARISON_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa::test {
|
||||
|
||||
enum class ComparisonQuantity { kDisplacement, kReaction, kSectionResultant };
|
||||
|
||||
struct CanonicalComparisonRow {
|
||||
std::string model_id;
|
||||
std::string step_name;
|
||||
std::size_t frame_index;
|
||||
std::string instance_name;
|
||||
std::int64_t source_node_label;
|
||||
ComparisonQuantity quantity;
|
||||
std::string component;
|
||||
double value;
|
||||
std::string unit_dimension;
|
||||
std::string coordinate_system;
|
||||
std::string hdf5_dataset_path;
|
||||
};
|
||||
|
||||
struct RowDecision {
|
||||
CanonicalComparisonRow fesa;
|
||||
CanonicalComparisonRow reference;
|
||||
double absolute_error;
|
||||
double tolerance;
|
||||
bool passed;
|
||||
};
|
||||
|
||||
struct ComponentMetrics {
|
||||
ComparisonQuantity quantity;
|
||||
std::string component;
|
||||
double reference_scale;
|
||||
double maximum_absolute_error;
|
||||
double maximum_normalized_error;
|
||||
double rms_error;
|
||||
double norm_error;
|
||||
std::size_t worst_row;
|
||||
};
|
||||
|
||||
struct PhysicsEvidence {
|
||||
double free_residual_norm;
|
||||
std::array<double, 3> applied_force;
|
||||
std::array<double, 3> reaction_force;
|
||||
std::array<double, 3> applied_moment_about_origin;
|
||||
std::array<double, 3> reaction_moment_about_origin;
|
||||
bool endpoint_consistency_passed;
|
||||
};
|
||||
|
||||
struct ComparisonReport {
|
||||
std::vector<RowDecision> rows;
|
||||
std::vector<ComponentMetrics> metrics;
|
||||
PhysicsEvidence physics_evidence;
|
||||
bool stress_comparison_applicable;
|
||||
std::string stress_comparison_reason;
|
||||
bool passed;
|
||||
};
|
||||
|
||||
// Test-only comparison support keeps Abaqus artifacts read-only and exposes no
|
||||
// backend handles to the implementation or downstream verification Steps.
|
||||
class ReferenceComparison {
|
||||
public:
|
||||
static Result<ComparisonReport> Compare(
|
||||
const std::filesystem::path& results_hdf5,
|
||||
const std::filesystem::path& legacy_reference_directory);
|
||||
static Status WriteDeterministicJson(
|
||||
const ComparisonReport& report, const std::filesystem::path& output_json);
|
||||
};
|
||||
|
||||
} // namespace fesa::test
|
||||
|
||||
#endif // FESA_TESTS_REFERENCE_REFERENCE_COMPARISON_H_
|
||||
@@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::test {
|
||||
|
||||
enum class ComparisonQuantity {
|
||||
displacement,
|
||||
reaction,
|
||||
sectionResultant
|
||||
};
|
||||
|
||||
struct CanonicalComparisonRow {
|
||||
std::string modelId;
|
||||
std::string stepName;
|
||||
std::size_t frameIndex;
|
||||
std::string instanceName;
|
||||
std::int64_t sourceNodeLabel;
|
||||
ComparisonQuantity quantity;
|
||||
std::string component;
|
||||
double value;
|
||||
std::string unitDimension;
|
||||
std::string coordinateSystem;
|
||||
std::string hdf5DatasetPath;
|
||||
};
|
||||
|
||||
struct RowDecision {
|
||||
CanonicalComparisonRow fesa;
|
||||
CanonicalComparisonRow reference;
|
||||
double absoluteError;
|
||||
double tolerance;
|
||||
bool passed;
|
||||
};
|
||||
|
||||
struct ComponentMetrics {
|
||||
ComparisonQuantity quantity;
|
||||
std::string component;
|
||||
double referenceScale;
|
||||
double maximumAbsoluteError;
|
||||
double maximumNormalizedError;
|
||||
double rmsError;
|
||||
double normError;
|
||||
std::size_t worstRow;
|
||||
};
|
||||
|
||||
struct PhysicsEvidence {
|
||||
double freeResidualNorm;
|
||||
std::array<double, 3> appliedForce;
|
||||
std::array<double, 3> reactionForce;
|
||||
std::array<double, 3> appliedMomentAboutOrigin;
|
||||
std::array<double, 3> reactionMomentAboutOrigin;
|
||||
bool endpointConsistencyPassed;
|
||||
};
|
||||
|
||||
struct ComparisonReport {
|
||||
std::vector<RowDecision> rows;
|
||||
std::vector<ComponentMetrics> metrics;
|
||||
PhysicsEvidence physicsEvidence;
|
||||
bool stressComparisonApplicable;
|
||||
std::string stressComparisonReason;
|
||||
bool passed;
|
||||
};
|
||||
|
||||
// Test-only comparison support keeps Abaqus artifacts read-only and exposes no
|
||||
// backend handles to the implementation or downstream verification Steps.
|
||||
class ReferenceComparison {
|
||||
public:
|
||||
static Result<ComparisonReport> compare(
|
||||
const std::filesystem::path& resultsHdf5,
|
||||
const std::filesystem::path& legacyReferenceDirectory);
|
||||
static Status writeDeterministicJson(
|
||||
const ComparisonReport& report,
|
||||
const std::filesystem::path& outputJson);
|
||||
};
|
||||
|
||||
} // namespace fesa::test
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user