192 lines
7.5 KiB
C++
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);
|
|
}
|