Files
FESADev/tests/unit/io/hdf5/hdf5_writer_components_test.cpp

192 lines
7.5 KiB
C++

#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);
}