feat(cpp-object-oriented-modular-refactoring): step 23 - hdf5-writer-modules
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/analysis_state.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/model/domain.h"
|
||||
#include "io/hdf5/hdf5_atomic_file.h"
|
||||
#include "io/hdf5/hdf5_result_writer.h"
|
||||
#include "io/hdf5/hdf5_self_check.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct WriterFixture {
|
||||
std::unique_ptr<fesa::Domain> domain;
|
||||
std::unique_ptr<fesa::DofManager> dofs;
|
||||
std::unique_ptr<fesa::AnalysisState> state;
|
||||
};
|
||||
|
||||
fesa::ModelDefinition MakeDefinition(const std::filesystem::path& source) {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||
definition.nodes = {{{"Beam-1", 101, "101"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||
{{"Beam-1", 202, "202"}, {3.0, 4.0, 0.0}, {source, 11U}}};
|
||||
definition.materials = {{"Steel", 210.0e9, 0.3, {source, 20U}}};
|
||||
definition.sections = {{"General",
|
||||
0.02,
|
||||
3.0e-5,
|
||||
0.0,
|
||||
4.0e-5,
|
||||
5.0e-5,
|
||||
{0.0, 0.0, 1.0},
|
||||
{{-0.1, 0.2}},
|
||||
{source, 30U}}};
|
||||
definition.elements = {
|
||||
{{"Beam-1", 303, "303"}, {0U, 1U}, 0U, 0U, {source, 40U}}};
|
||||
definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
||||
return definition;
|
||||
}
|
||||
|
||||
WriterFixture MakeFixture(const std::filesystem::path& source) {
|
||||
auto domain_result = fesa::Domain::Create(MakeDefinition(source));
|
||||
if (!domain_result.HasValue()) {
|
||||
throw std::runtime_error{"Component fixture Domain construction failed."};
|
||||
}
|
||||
auto domain =
|
||||
std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
|
||||
auto model_result = fesa::AnalysisModel::Create(*domain);
|
||||
if (!model_result.HasValue()) {
|
||||
throw std::runtime_error{
|
||||
"Component fixture AnalysisModel construction failed."};
|
||||
}
|
||||
const fesa::AnalysisModel model = std::move(model_result.Value());
|
||||
auto dofs_result = fesa::DofManager::Create(model);
|
||||
if (!dofs_result.HasValue()) {
|
||||
throw std::runtime_error{
|
||||
"Component fixture DofManager construction failed."};
|
||||
}
|
||||
auto dofs =
|
||||
std::make_unique<fesa::DofManager>(std::move(dofs_result.Value()));
|
||||
auto state = std::make_unique<fesa::AnalysisState>(
|
||||
fesa::AnalysisState::Create(*dofs, {"Step-1", 0U}));
|
||||
for (std::size_t index = 0U; index < state->Displacement().Size(); ++index) {
|
||||
state->Displacement()[index] = 0.25 + static_cast<double>(index);
|
||||
state->ExternalForce()[index] = 100.0 + static_cast<double>(index);
|
||||
state->InternalForce()[index] = 200.0 + static_cast<double>(index);
|
||||
state->Residual()[index] = 100.0 + static_cast<double>(index);
|
||||
state->Reaction()[index] = 100.0 + static_cast<double>(index);
|
||||
}
|
||||
state->EndpointResults() = {{0U,
|
||||
0,
|
||||
domain->Nodes()[0U].source_id,
|
||||
{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
|
||||
{11.0, 12.0, 13.0, 14.0}},
|
||||
{0U,
|
||||
1,
|
||||
domain->Nodes()[1U].source_id,
|
||||
{7.0, 8.0, 9.0, 10.0, 11.0, 12.0},
|
||||
{15.0, 16.0, 17.0, 18.0}}};
|
||||
state->GaussResults() = {
|
||||
{0U, 1, {0.01, 0.02, 0.03, 0.04}, {21.0, 22.0, 23.0, 24.0}},
|
||||
{0U, 2, {0.05, 0.06, 0.07, 0.08}, {25.0, 26.0, 27.0, 28.0}}};
|
||||
state->StressResults() = {{0U, 1, 1U, -0.1, 0.2, 31.0, "input"},
|
||||
{0U, 2, 1U, -0.1, 0.2, 32.0, "input"}};
|
||||
return {std::move(domain), std::move(dofs), std::move(state)};
|
||||
}
|
||||
|
||||
std::vector<char> ReadBytes(const std::filesystem::path& path) {
|
||||
std::ifstream input{path, std::ios::binary};
|
||||
return {std::istreambuf_iterator<char>{input},
|
||||
std::istreambuf_iterator<char>{}};
|
||||
}
|
||||
|
||||
void WriteBytes(const std::filesystem::path& path,
|
||||
const std::vector<char>& bytes) {
|
||||
std::ofstream output{path, std::ios::binary | std::ios::trunc};
|
||||
output.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
|
||||
if (!output) {
|
||||
throw std::runtime_error{"Unable to write component-test bytes."};
|
||||
}
|
||||
}
|
||||
|
||||
void ExpectOutputFailure(const fesa::Status& status,
|
||||
const std::string& expected_code) {
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].code, expected_code);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Hdf5WriterComponents, BuildsStableBackendNeutralBeamDatasetPlans) {
|
||||
const auto directory =
|
||||
std::filesystem::temp_directory_path() / "fesa-hdf5-component-plan";
|
||||
auto fixture = MakeFixture(directory / "beam.inp");
|
||||
|
||||
auto plan_result = fesa::hdf5_internal::BuildWriterPlan(
|
||||
directory / "results.h5", *fixture.domain, *fixture.state, {});
|
||||
|
||||
ASSERT_TRUE(plan_result.HasValue());
|
||||
const auto& datasets = plan_result.Value().result_datasets;
|
||||
ASSERT_EQ(datasets.size(), 6U);
|
||||
EXPECT_EQ(datasets[0U].path, "/steps/Step-1/frames/0/nodal/displacement");
|
||||
EXPECT_EQ(datasets[0U].dimensions, std::vector<std::size_t>({2U, 6U}));
|
||||
EXPECT_EQ(datasets[2U].path,
|
||||
"/steps/Step-1/frames/0/element/end_force_local");
|
||||
EXPECT_EQ(datasets[2U].dimensions, std::vector<std::size_t>({1U, 2U, 6U}));
|
||||
EXPECT_EQ(datasets[5U].path,
|
||||
"/steps/Step-1/frames/0/element/generalized_resultant");
|
||||
EXPECT_TRUE(fesa::hdf5_internal::ValidateFiniteInventory(datasets).IsOk());
|
||||
}
|
||||
|
||||
TEST(Hdf5WriterComponents, RejectsNonfiniteDatasetPlanInventory) {
|
||||
fesa::hdf5_internal::DoubleDatasetPlan plan{
|
||||
"/steps/Step-1/frames/0/global/energy",
|
||||
{1U},
|
||||
{std::numeric_limits<double>::quiet_NaN()},
|
||||
"PHYSICAL_STRAIN_ENERGY",
|
||||
"force*length",
|
||||
"global",
|
||||
"global"};
|
||||
|
||||
ExpectOutputFailure(
|
||||
fesa::hdf5_internal::ValidateFiniteInventory({std::move(plan)}),
|
||||
"invalid-result-dataset-plan");
|
||||
}
|
||||
|
||||
TEST(Hdf5WriterComponents,
|
||||
SelfCheckFailurePreservesExistingFinalThroughAtomicSeam) {
|
||||
const auto directory =
|
||||
std::filesystem::temp_directory_path() / "fesa-hdf5-component-atomic";
|
||||
std::error_code ignored;
|
||||
std::filesystem::remove_all(directory, ignored);
|
||||
ASSERT_TRUE(std::filesystem::create_directory(directory));
|
||||
const auto final = directory / "results.h5";
|
||||
const auto candidate = directory / ".results.h5.candidate";
|
||||
const std::vector<char> sentinel{'v', 'a', 'l', 'i', 'd'};
|
||||
WriteBytes(final, sentinel);
|
||||
WriteBytes(candidate, {'n', 'o', 't', '-', 'h', 'd', 'f', '5'});
|
||||
auto fixture = MakeFixture(directory / "beam.inp");
|
||||
auto plan_result = fesa::hdf5_internal::BuildWriterPlan(
|
||||
final, *fixture.domain, *fixture.state, {});
|
||||
ASSERT_TRUE(plan_result.HasValue());
|
||||
|
||||
const fesa::Status self_check =
|
||||
fesa::hdf5_internal::SelfCheck(candidate, *fixture.domain, *fixture.state,
|
||||
0U, plan_result.Value().model_data);
|
||||
ExpectOutputFailure(self_check, "hdf5-write-failure");
|
||||
const fesa::Status finalize =
|
||||
fesa::hdf5_internal::AtomicFinalizeValidatedCandidate(candidate, final,
|
||||
self_check);
|
||||
|
||||
ExpectOutputFailure(finalize, "hdf5-write-failure");
|
||||
EXPECT_EQ(ReadBytes(final), sentinel);
|
||||
EXPECT_FALSE(std::filesystem::exists(candidate));
|
||||
std::filesystem::remove_all(directory, ignored);
|
||||
}
|
||||
Reference in New Issue
Block a user