1375 lines
56 KiB
C++
1375 lines
56 KiB
C++
#define NOMINMAX
|
|
#include <Windows.h>
|
|
|
|
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
|
|
|
#include "fesa/analysis/analysis_model.hpp"
|
|
#include "fesa/analysis/analysis_state.hpp"
|
|
#include "fesa/build_info.h"
|
|
#include "fesa/fem/dof_manager.hpp"
|
|
#include "fesa/model/domain.h"
|
|
|
|
#include <hdf5.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
constexpr const char* kStepRoot = "/steps/Step-1/frames/0";
|
|
|
|
class Hdf5Handle {
|
|
public:
|
|
using Closer = herr_t (*)(hid_t);
|
|
|
|
Hdf5Handle() = default;
|
|
Hdf5Handle(const hid_t value, Closer closer) : value_{value}, closer_{closer} {}
|
|
Hdf5Handle(const Hdf5Handle&) = delete;
|
|
Hdf5Handle& operator=(const Hdf5Handle&) = delete;
|
|
Hdf5Handle(Hdf5Handle&& other) noexcept
|
|
: value_{other.value_}, closer_{other.closer_} {
|
|
other.value_ = -1;
|
|
other.closer_ = nullptr;
|
|
}
|
|
Hdf5Handle& operator=(Hdf5Handle&& other) noexcept {
|
|
if (this != &other) {
|
|
reset();
|
|
value_ = other.value_;
|
|
closer_ = other.closer_;
|
|
other.value_ = -1;
|
|
other.closer_ = nullptr;
|
|
}
|
|
return *this;
|
|
}
|
|
~Hdf5Handle() { reset(); }
|
|
|
|
hid_t get() const noexcept { return value_; }
|
|
|
|
private:
|
|
void reset() noexcept {
|
|
if (value_ >= 0 && closer_ != nullptr) {
|
|
(void)closer_(value_);
|
|
}
|
|
value_ = -1;
|
|
closer_ = nullptr;
|
|
}
|
|
|
|
hid_t value_{-1};
|
|
Closer closer_{nullptr};
|
|
};
|
|
|
|
class WinHandle {
|
|
public:
|
|
explicit WinHandle(HANDLE value) : value_{value} {}
|
|
WinHandle(const WinHandle&) = delete;
|
|
WinHandle& operator=(const WinHandle&) = delete;
|
|
~WinHandle() {
|
|
if (value_ != INVALID_HANDLE_VALUE) {
|
|
(void)CloseHandle(value_);
|
|
}
|
|
}
|
|
HANDLE get() const noexcept { return value_; }
|
|
|
|
private:
|
|
HANDLE value_{INVALID_HANDLE_VALUE};
|
|
};
|
|
|
|
class TempDirectory {
|
|
public:
|
|
explicit TempDirectory(const std::string& label) {
|
|
static std::atomic<std::uint64_t> sequence{0U};
|
|
path_ = std::filesystem::temp_directory_path() /
|
|
("fesa-step23-" + label + "-" +
|
|
std::to_string(GetCurrentProcessId()) + "-" +
|
|
std::to_string(sequence.fetch_add(1U)));
|
|
std::error_code error;
|
|
if (!std::filesystem::create_directory(path_, error) || error) {
|
|
throw std::runtime_error{"Unable to create the Step 23 test directory."};
|
|
}
|
|
}
|
|
TempDirectory(const TempDirectory&) = delete;
|
|
TempDirectory& operator=(const TempDirectory&) = delete;
|
|
~TempDirectory() {
|
|
std::error_code ignored;
|
|
std::filesystem::remove_all(path_, ignored);
|
|
}
|
|
const std::filesystem::path& path() const noexcept { return path_; }
|
|
|
|
private:
|
|
std::filesystem::path path_;
|
|
};
|
|
|
|
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,
|
|
const bool useDefaultCentroid) {
|
|
fesa::ModelDefinition definition{};
|
|
definition.source_path = source;
|
|
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
|
definition.nodes = {
|
|
{{u8"Beam-\u03b1", 101, "101"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
|
{{u8"Beam-\u03b1", 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},
|
|
useDefaultCentroid
|
|
? std::vector<std::array<double, 2>>{}
|
|
: std::vector<std::array<double, 2>>{{{-0.1, 0.2}, {0.3, -0.4}}},
|
|
{source, 30U}}};
|
|
definition.elements = {{
|
|
{u8"Beam-\u03b1", 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,
|
|
const bool useDefaultCentroid = false) {
|
|
auto domainResult = fesa::Domain::Create(
|
|
makeDefinition(source, useDefaultCentroid));
|
|
if (!domainResult.HasValue()) {
|
|
throw std::runtime_error{"Writer fixture Domain construction failed."};
|
|
}
|
|
auto domain = std::make_unique<fesa::Domain>(
|
|
std::move(domainResult.Value()));
|
|
|
|
auto modelResult = fesa::AnalysisModel::create(*domain);
|
|
if (!modelResult.HasValue()) {
|
|
throw std::runtime_error{"Writer fixture AnalysisModel construction failed."};
|
|
}
|
|
const fesa::AnalysisModel model = std::move(modelResult.Value());
|
|
auto dofsResult = fesa::DofManager::create(model);
|
|
if (!dofsResult.HasValue()) {
|
|
throw std::runtime_error{"Writer fixture DofManager construction failed."};
|
|
}
|
|
auto dofs = std::make_unique<fesa::DofManager>(
|
|
std::move(dofsResult.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 + 2.0 * static_cast<double>(index);
|
|
state->residual()[index] = 100.0 + static_cast<double>(index);
|
|
state->reaction()[index] = 100.0 + static_cast<double>(index);
|
|
}
|
|
|
|
const auto& nodes = domain->Nodes();
|
|
state->endpointResults() = {
|
|
{0U,
|
|
0,
|
|
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,
|
|
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}}};
|
|
if (useDefaultCentroid) {
|
|
state->stressResults() = {
|
|
{0U, 1, 0U, 0.0, 0.0, 31.0, "fesa-default"},
|
|
{0U, 2, 0U, 0.0, 0.0, 32.0, "fesa-default"}};
|
|
} else {
|
|
state->stressResults() = {
|
|
{0U, 1, 1U, -0.1, 0.2, 31.0, "input"},
|
|
{0U, 1, 2U, 0.3, -0.4, 32.0, "input"},
|
|
{0U, 2, 1U, -0.1, 0.2, 33.0, "input"},
|
|
{0U, 2, 2U, 0.3, -0.4, 34.0, "input"}};
|
|
}
|
|
return {std::move(domain), std::move(dofs), std::move(state)};
|
|
}
|
|
|
|
fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
|
|
fesa::ModelDefinition definition{};
|
|
definition.source_path = source;
|
|
definition.source_content_identity = "fnv1a64:fedcba9876543210";
|
|
definition.nodes = {
|
|
{{"Shell-1", 11, "11"}, {-1.0, -1.0, 0.0}, {source, 10U}},
|
|
{{"Shell-1", 12, "12"}, {1.0, -1.0, 0.0}, {source, 11U}},
|
|
{{"Shell-1", 13, "13"}, {1.0, 1.0, 0.0}, {source, 12U}},
|
|
{{"Shell-1", 14, "14"}, {-1.0, 1.0, 0.0}, {source, 13U}}};
|
|
definition.materials = {
|
|
{"ShellSteel", 210.0e9, 0.3, {source, 20U}}};
|
|
definition.shell_sections = {
|
|
{"PlateSet", 0.02, 0U, {source, 30U}}};
|
|
definition.shell_elements = {{
|
|
{"Shell-1", 401, "401"},
|
|
fesa::ShellSourceElementType::kS4r,
|
|
{0U, 1U, 2U, 3U},
|
|
0U,
|
|
0U,
|
|
{source, 40U}}};
|
|
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
|
definition.shell_node_initial_frames.push_back({
|
|
static_cast<fesa::EntityIndex>(node),
|
|
{0.0, 0.0, 1.0},
|
|
{1.0, 0.0, 0.0},
|
|
{0.0, 1.0, 0.0}});
|
|
}
|
|
definition.node_sets = {
|
|
{"Fixed", {}, {0U}, {source, 50U}}};
|
|
definition.steps = {{
|
|
"Step-1",
|
|
{{"Fixed", 1, 3, 0.0, {source, 60U}},
|
|
{"Fixed", 4, 4, 0.125, {source, 61U}}},
|
|
{},
|
|
0.1,
|
|
1.0,
|
|
0.01,
|
|
1.0,
|
|
{source, 59U}}};
|
|
return definition;
|
|
}
|
|
|
|
WriterFixture makeShellFixture(const std::filesystem::path& source) {
|
|
auto domainResult = fesa::Domain::Create(makeShellDefinition(source));
|
|
if (!domainResult.HasValue()) {
|
|
throw std::runtime_error{"Shell writer fixture Domain construction failed."};
|
|
}
|
|
auto domain = std::make_unique<fesa::Domain>(
|
|
std::move(domainResult.Value()));
|
|
auto modelResult = fesa::AnalysisModel::create(*domain);
|
|
if (!modelResult.HasValue()) {
|
|
throw std::runtime_error{"Shell writer fixture AnalysisModel construction failed."};
|
|
}
|
|
const fesa::AnalysisModel model = std::move(modelResult.Value());
|
|
auto dofsResult = fesa::DofManager::create(model);
|
|
if (!dofsResult.HasValue()) {
|
|
throw std::runtime_error{"Shell writer fixture DofManager construction failed."};
|
|
}
|
|
auto dofs = std::make_unique<fesa::DofManager>(
|
|
std::move(dofsResult.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.01 * static_cast<double>(index + 1U);
|
|
state->externalForce()[index] = 10.0 + static_cast<double>(index);
|
|
state->internalForce()[index] = 20.0 + static_cast<double>(index);
|
|
state->residual()[index] = 30.0 + static_cast<double>(index);
|
|
state->reaction()[index] = 40.0 + static_cast<double>(index);
|
|
}
|
|
|
|
const double gauss = 1.0 / std::sqrt(3.0);
|
|
const std::array<std::array<double, 2>, 4> coordinates{{
|
|
{-gauss, -gauss},
|
|
{gauss, -gauss},
|
|
{gauss, gauss},
|
|
{-gauss, gauss}}};
|
|
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
|
|
fesa::ShellMidsurfaceLocation::gp1,
|
|
fesa::ShellMidsurfaceLocation::gp2,
|
|
fesa::ShellMidsurfaceLocation::gp3,
|
|
fesa::ShellMidsurfaceLocation::gp4};
|
|
fesa::ShellStateCandidate candidate{};
|
|
for (std::size_t point = 0U; point < locations.size(); ++point) {
|
|
const double base = 100.0 * static_cast<double>(point + 1U);
|
|
candidate.rows.push_back({
|
|
0U,
|
|
locations[point],
|
|
coordinates[point],
|
|
{{{1.0, 0.0, 0.0},
|
|
{0.0, 1.0, 0.0},
|
|
{0.0, 0.0, 1.0}}},
|
|
{base + 1.0, base + 2.0, base + 3.0, base + 4.0,
|
|
base + 5.0, base + 6.0, base + 7.0, base + 8.0},
|
|
{base + 11.0, base + 12.0, base + 13.0, base + 14.0,
|
|
base + 15.0, base + 16.0, base + 17.0, base + 18.0},
|
|
{{{fesa::ShellSectionPosition::bottom,
|
|
-1.0,
|
|
{base + 21.0, base + 22.0, base + 23.0}},
|
|
{fesa::ShellSectionPosition::middle,
|
|
0.0,
|
|
{base + 24.0, base + 25.0, base + 26.0}},
|
|
{fesa::ShellSectionPosition::top,
|
|
1.0,
|
|
{base + 27.0, base + 28.0, base + 29.0}}}}});
|
|
}
|
|
candidate.physicalStrainEnergy = 123.5;
|
|
candidate.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
|
|
candidate.verificationMetrics = {1.0e-13, 2.0e-13, 3.0e-13};
|
|
const fesa::Status commit = state->commitShellResults(
|
|
{0U}, std::move(candidate));
|
|
if (!commit.IsOk()) {
|
|
throw std::runtime_error{"Shell writer fixture state commit failed."};
|
|
}
|
|
return {std::move(domain), std::move(dofs), std::move(state)};
|
|
}
|
|
|
|
Hdf5Handle openFile(const std::filesystem::path& path) {
|
|
const hid_t file = H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
|
|
if (file < 0) {
|
|
throw std::runtime_error{"Unable to open test HDF5 output."};
|
|
}
|
|
return Hdf5Handle{file, H5Fclose};
|
|
}
|
|
|
|
Hdf5Handle openDataset(const hid_t file, const std::string& path) {
|
|
const hid_t dataset = H5Dopen2(file, path.c_str(), H5P_DEFAULT);
|
|
if (dataset < 0) {
|
|
throw std::runtime_error{"Unable to open expected HDF5 dataset: " + path};
|
|
}
|
|
return Hdf5Handle{dataset, H5Dclose};
|
|
}
|
|
|
|
std::vector<hsize_t> datasetDimensions(
|
|
const hid_t file, const std::string& path) {
|
|
const auto dataset = openDataset(file, path);
|
|
Hdf5Handle space{H5Dget_space(dataset.get()), H5Sclose};
|
|
if (space.get() < 0) {
|
|
throw std::runtime_error{"Unable to inspect HDF5 dataspace."};
|
|
}
|
|
const int rank = H5Sget_simple_extent_ndims(space.get());
|
|
if (rank < 0) {
|
|
throw std::runtime_error{"Unable to inspect HDF5 rank."};
|
|
}
|
|
std::vector<hsize_t> dimensions(static_cast<std::size_t>(rank));
|
|
if (rank > 0 &&
|
|
H5Sget_simple_extent_dims(space.get(), dimensions.data(), nullptr) < 0) {
|
|
throw std::runtime_error{"Unable to inspect HDF5 dimensions."};
|
|
}
|
|
return dimensions;
|
|
}
|
|
|
|
std::vector<double> readDoubleDataset(
|
|
const hid_t file, const std::string& path) {
|
|
const auto dimensions = datasetDimensions(file, path);
|
|
std::size_t valueCount = 1U;
|
|
for (const hsize_t dimension : dimensions) {
|
|
valueCount *= static_cast<std::size_t>(dimension);
|
|
}
|
|
const auto dataset = openDataset(file, path);
|
|
std::vector<double> values(valueCount);
|
|
if (!values.empty() &&
|
|
H5Dread(dataset.get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, values.data()) < 0) {
|
|
throw std::runtime_error{"Unable to read numeric HDF5 dataset."};
|
|
}
|
|
return values;
|
|
}
|
|
|
|
std::vector<std::uint8_t> readUint8Dataset(
|
|
const hid_t file, const std::string& path) {
|
|
const auto dimensions = datasetDimensions(file, path);
|
|
std::size_t valueCount = 1U;
|
|
for (const hsize_t dimension : dimensions) {
|
|
valueCount *= static_cast<std::size_t>(dimension);
|
|
}
|
|
const auto dataset = openDataset(file, path);
|
|
Hdf5Handle type{H5Dget_type(dataset.get()), H5Tclose};
|
|
if (type.get() < 0 || H5Tget_class(type.get()) != H5T_INTEGER ||
|
|
H5Tget_size(type.get()) != sizeof(std::uint8_t) ||
|
|
H5Tget_sign(type.get()) != H5T_SGN_NONE ||
|
|
H5Tequal(type.get(), H5T_STD_U8LE) <= 0) {
|
|
throw std::runtime_error{"Expected a portable uint8 HDF5 dataset."};
|
|
}
|
|
std::vector<std::uint8_t> values(valueCount);
|
|
if (!values.empty() &&
|
|
H5Dread(dataset.get(), H5T_NATIVE_UINT8, H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, values.data()) < 0) {
|
|
throw std::runtime_error{"Unable to read uint8 HDF5 dataset."};
|
|
}
|
|
return values;
|
|
}
|
|
|
|
std::string readStringAttribute(const hid_t object, const char* name) {
|
|
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
|
Hdf5Handle type{H5Aget_type(attribute.get()), H5Tclose};
|
|
if (attribute.get() < 0 || type.get() < 0 ||
|
|
H5Tget_class(type.get()) != H5T_STRING ||
|
|
H5Tis_variable_str(type.get()) <= 0 ||
|
|
H5Tget_cset(type.get()) != H5T_CSET_UTF8) {
|
|
throw std::runtime_error{"Expected a variable-length UTF-8 attribute."};
|
|
}
|
|
char* raw = nullptr;
|
|
if (H5Aread(attribute.get(), type.get(), &raw) < 0 || raw == nullptr) {
|
|
throw std::runtime_error{"Unable to read UTF-8 HDF5 attribute."};
|
|
}
|
|
const std::string value{raw};
|
|
(void)H5free_memory(raw);
|
|
return value;
|
|
}
|
|
|
|
std::uint64_t readUint64Attribute(const hid_t object, const char* name) {
|
|
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
|
Hdf5Handle type{H5Aget_type(attribute.get()), H5Tclose};
|
|
if (attribute.get() < 0 || type.get() < 0 ||
|
|
H5Tget_class(type.get()) != H5T_INTEGER ||
|
|
H5Tget_size(type.get()) != sizeof(std::uint64_t) ||
|
|
H5Tget_sign(type.get()) != H5T_SGN_NONE ||
|
|
H5Tequal(type.get(), H5T_STD_U64LE) <= 0) {
|
|
throw std::runtime_error{"Expected a portable uint64 HDF5 attribute."};
|
|
}
|
|
std::uint64_t value = 0U;
|
|
if (H5Aread(attribute.get(), H5T_NATIVE_UINT64, &value) < 0) {
|
|
throw std::runtime_error{"Unable to read uint64 HDF5 attribute."};
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void expectPortableCompoundMember(
|
|
const hid_t compoundType,
|
|
const unsigned index,
|
|
const std::string& name) {
|
|
Hdf5Handle memberType{
|
|
H5Tget_member_type(compoundType, index), H5Tclose};
|
|
ASSERT_GE(memberType.get(), 0);
|
|
const bool isUint64 =
|
|
name == "internal_node_id" || name == "internal_element_id" ||
|
|
name == "gauss_point_index" || name == "section_point_index" ||
|
|
name == "line";
|
|
const bool isFloat64 = name == "x1" || name == "x2" || name == "S11";
|
|
const bool isString =
|
|
name == "instance_name" || name == "source_label" ||
|
|
name == "source" || name == "severity" || name == "code" ||
|
|
name == "file" || name == "keyword" ||
|
|
name == "entity_identity" || name == "message";
|
|
if (isUint64) {
|
|
EXPECT_EQ(H5Tget_class(memberType.get()), H5T_INTEGER);
|
|
EXPECT_EQ(H5Tget_size(memberType.get()), sizeof(std::uint64_t));
|
|
EXPECT_EQ(H5Tget_sign(memberType.get()), H5T_SGN_NONE);
|
|
EXPECT_GT(H5Tequal(memberType.get(), H5T_STD_U64LE), 0);
|
|
return;
|
|
}
|
|
if (isFloat64) {
|
|
EXPECT_EQ(H5Tget_class(memberType.get()), H5T_FLOAT);
|
|
EXPECT_EQ(H5Tget_size(memberType.get()), sizeof(double));
|
|
EXPECT_GT(H5Tequal(memberType.get(), H5T_IEEE_F64LE), 0);
|
|
return;
|
|
}
|
|
if (isString) {
|
|
EXPECT_EQ(H5Tget_class(memberType.get()), H5T_STRING);
|
|
EXPECT_GT(H5Tis_variable_str(memberType.get()), 0);
|
|
EXPECT_EQ(H5Tget_cset(memberType.get()), H5T_CSET_UTF8);
|
|
return;
|
|
}
|
|
|
|
ASSERT_EQ(H5Tget_class(memberType.get()), H5T_ARRAY);
|
|
const int rank = H5Tget_array_ndims(memberType.get());
|
|
ASSERT_GT(rank, 0);
|
|
std::vector<hsize_t> dimensions(static_cast<std::size_t>(rank));
|
|
ASSERT_GE(H5Tget_array_dims2(memberType.get(), dimensions.data()), 0);
|
|
Hdf5Handle baseType{H5Tget_super(memberType.get()), H5Tclose};
|
|
ASSERT_GE(baseType.get(), 0);
|
|
if (name == "node_internal_ids") {
|
|
EXPECT_EQ(dimensions, std::vector<hsize_t>({2U}));
|
|
EXPECT_GT(H5Tequal(baseType.get(), H5T_STD_U64LE), 0);
|
|
} else if (name == "coordinates") {
|
|
EXPECT_EQ(dimensions, std::vector<hsize_t>({3U}));
|
|
EXPECT_GT(H5Tequal(baseType.get(), H5T_IEEE_F64LE), 0);
|
|
} else {
|
|
EXPECT_EQ(name, "local_axes");
|
|
EXPECT_EQ(dimensions, std::vector<hsize_t>({3U, 3U}));
|
|
EXPECT_GT(H5Tequal(baseType.get(), H5T_IEEE_F64LE), 0);
|
|
}
|
|
}
|
|
|
|
void expectCompoundMembers(
|
|
const hid_t file,
|
|
const std::string& path,
|
|
const std::vector<std::string>& expectedNames) {
|
|
const auto dataset = openDataset(file, path);
|
|
Hdf5Handle type{H5Dget_type(dataset.get()), H5Tclose};
|
|
ASSERT_EQ(H5Tget_class(type.get()), H5T_COMPOUND);
|
|
ASSERT_EQ(
|
|
H5Tget_nmembers(type.get()), static_cast<int>(expectedNames.size()));
|
|
for (std::size_t index = 0U; index < expectedNames.size(); ++index) {
|
|
char* rawName = H5Tget_member_name(type.get(),
|
|
static_cast<unsigned>(index));
|
|
ASSERT_NE(rawName, nullptr);
|
|
const std::string actualName{rawName};
|
|
(void)H5free_memory(rawName);
|
|
EXPECT_EQ(actualName, expectedNames[index]);
|
|
expectPortableCompoundMember(
|
|
type.get(), static_cast<unsigned>(index), expectedNames[index]);
|
|
}
|
|
}
|
|
|
|
void expectCompoundMemberNames(
|
|
const hid_t file,
|
|
const std::string& path,
|
|
const std::vector<std::string>& expectedNames) {
|
|
const auto dataset = openDataset(file, path);
|
|
Hdf5Handle type{H5Dget_type(dataset.get()), H5Tclose};
|
|
ASSERT_EQ(H5Tget_class(type.get()), H5T_COMPOUND);
|
|
ASSERT_EQ(
|
|
H5Tget_nmembers(type.get()), static_cast<int>(expectedNames.size()));
|
|
for (std::size_t index = 0U; index < expectedNames.size(); ++index) {
|
|
char* rawName = H5Tget_member_name(
|
|
type.get(), static_cast<unsigned>(index));
|
|
ASSERT_NE(rawName, nullptr);
|
|
const std::string actualName{rawName};
|
|
(void)H5free_memory(rawName);
|
|
EXPECT_EQ(actualName, expectedNames[index]);
|
|
}
|
|
}
|
|
|
|
void expectNumericDataset(
|
|
const hid_t file,
|
|
const std::string& path,
|
|
const std::vector<hsize_t>& dimensions,
|
|
const std::string& components,
|
|
const std::string& units,
|
|
const std::string& coordinateSystem,
|
|
const std::string& location) {
|
|
EXPECT_EQ(datasetDimensions(file, path), dimensions);
|
|
const auto dataset = openDataset(file, path);
|
|
Hdf5Handle type{H5Dget_type(dataset.get()), H5Tclose};
|
|
ASSERT_EQ(H5Tget_class(type.get()), H5T_FLOAT);
|
|
EXPECT_EQ(H5Tget_size(type.get()), 8U);
|
|
EXPECT_GT(H5Tequal(type.get(), H5T_IEEE_F64LE), 0);
|
|
EXPECT_EQ(readStringAttribute(dataset.get(), "component_names"), components);
|
|
EXPECT_EQ(
|
|
readStringAttribute(dataset.get(), "component_unit_dimensions"), units);
|
|
EXPECT_EQ(
|
|
readStringAttribute(dataset.get(), "coordinate_system"), coordinateSystem);
|
|
EXPECT_EQ(readStringAttribute(dataset.get(), "location"), location);
|
|
EXPECT_EQ(readStringAttribute(dataset.get(), "step_name"), "Step-1");
|
|
EXPECT_EQ(readUint64Attribute(dataset.get(), "frame_index"), 0U);
|
|
}
|
|
|
|
Hdf5Handle makeUtf8StringType() {
|
|
Hdf5Handle type{H5Tcopy(H5T_C_S1), H5Tclose};
|
|
if (type.get() < 0 || H5Tset_size(type.get(), H5T_VARIABLE) < 0 ||
|
|
H5Tset_cset(type.get(), H5T_CSET_UTF8) < 0) {
|
|
throw std::runtime_error{"Unable to create a test UTF-8 memory type."};
|
|
}
|
|
return type;
|
|
}
|
|
|
|
struct NodeReadRow {
|
|
std::uint64_t internalNodeId;
|
|
char* instanceName;
|
|
char* sourceLabel;
|
|
double coordinates[3];
|
|
};
|
|
|
|
std::vector<NodeReadRow> readNodeRows(const hid_t file) {
|
|
const auto dataset = openDataset(file, "/model/nodes");
|
|
Hdf5Handle space{H5Dget_space(dataset.get()), H5Sclose};
|
|
auto stringType = makeUtf8StringType();
|
|
const hsize_t coordinateDimensions[] = {3U};
|
|
Hdf5Handle coordinatesType{
|
|
H5Tarray_create2(H5T_NATIVE_DOUBLE, 1, coordinateDimensions), H5Tclose};
|
|
Hdf5Handle memoryType{H5Tcreate(H5T_COMPOUND, sizeof(NodeReadRow)), H5Tclose};
|
|
if (H5Tinsert(memoryType.get(), "internal_node_id",
|
|
HOFFSET(NodeReadRow, internalNodeId), H5T_NATIVE_UINT64) < 0 ||
|
|
H5Tinsert(memoryType.get(), "instance_name",
|
|
HOFFSET(NodeReadRow, instanceName), stringType.get()) < 0 ||
|
|
H5Tinsert(memoryType.get(), "source_label",
|
|
HOFFSET(NodeReadRow, sourceLabel), stringType.get()) < 0 ||
|
|
H5Tinsert(memoryType.get(), "coordinates",
|
|
HOFFSET(NodeReadRow, coordinates), coordinatesType.get()) < 0) {
|
|
throw std::runtime_error{"Unable to create the node memory type."};
|
|
}
|
|
std::vector<NodeReadRow> rows(2U);
|
|
if (H5Dread(dataset.get(), memoryType.get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, rows.data()) < 0) {
|
|
throw std::runtime_error{"Unable to read node rows."};
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
void reclaimNodeRows(const hid_t file, std::vector<NodeReadRow>& rows) {
|
|
const auto dataset = openDataset(file, "/model/nodes");
|
|
Hdf5Handle space{H5Dget_space(dataset.get()), H5Sclose};
|
|
auto stringType = makeUtf8StringType();
|
|
const hsize_t coordinateDimensions[] = {3U};
|
|
Hdf5Handle coordinatesType{
|
|
H5Tarray_create2(H5T_NATIVE_DOUBLE, 1, coordinateDimensions), H5Tclose};
|
|
Hdf5Handle memoryType{H5Tcreate(H5T_COMPOUND, sizeof(NodeReadRow)), H5Tclose};
|
|
(void)H5Tinsert(memoryType.get(), "internal_node_id",
|
|
HOFFSET(NodeReadRow, internalNodeId), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "instance_name",
|
|
HOFFSET(NodeReadRow, instanceName), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "source_label",
|
|
HOFFSET(NodeReadRow, sourceLabel), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "coordinates",
|
|
HOFFSET(NodeReadRow, coordinates), coordinatesType.get());
|
|
(void)H5Dvlen_reclaim(
|
|
memoryType.get(), space.get(), H5P_DEFAULT, rows.data());
|
|
}
|
|
|
|
struct ElementReadRow {
|
|
std::uint64_t internalElementId;
|
|
char* instanceName;
|
|
char* sourceLabel;
|
|
std::uint64_t nodeInternalIds[2];
|
|
double localAxes[9];
|
|
};
|
|
|
|
std::vector<ElementReadRow> readElementRows(const hid_t file) {
|
|
const auto dataset = openDataset(file, "/model/elements");
|
|
auto stringType = makeUtf8StringType();
|
|
const hsize_t nodeDimensions[] = {2U};
|
|
const hsize_t axesDimensions[] = {3U, 3U};
|
|
Hdf5Handle nodeType{
|
|
H5Tarray_create2(H5T_NATIVE_UINT64, 1, nodeDimensions), H5Tclose};
|
|
Hdf5Handle axesType{
|
|
H5Tarray_create2(H5T_NATIVE_DOUBLE, 2, axesDimensions), H5Tclose};
|
|
Hdf5Handle memoryType{
|
|
H5Tcreate(H5T_COMPOUND, sizeof(ElementReadRow)), H5Tclose};
|
|
(void)H5Tinsert(memoryType.get(), "internal_element_id",
|
|
HOFFSET(ElementReadRow, internalElementId), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "instance_name",
|
|
HOFFSET(ElementReadRow, instanceName), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "source_label",
|
|
HOFFSET(ElementReadRow, sourceLabel), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "node_internal_ids",
|
|
HOFFSET(ElementReadRow, nodeInternalIds), nodeType.get());
|
|
(void)H5Tinsert(memoryType.get(), "local_axes",
|
|
HOFFSET(ElementReadRow, localAxes), axesType.get());
|
|
std::vector<ElementReadRow> rows(1U);
|
|
if (H5Dread(dataset.get(), memoryType.get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, rows.data()) < 0) {
|
|
throw std::runtime_error{"Unable to read element rows."};
|
|
}
|
|
Hdf5Handle space{H5Dget_space(dataset.get()), H5Sclose};
|
|
EXPECT_EQ(rows[0U].internalElementId, 0U);
|
|
EXPECT_STREQ(rows[0U].instanceName, u8"Beam-\u03b1");
|
|
EXPECT_STREQ(rows[0U].sourceLabel, "303");
|
|
EXPECT_EQ(rows[0U].nodeInternalIds[0U], 0U);
|
|
EXPECT_EQ(rows[0U].nodeInternalIds[1U], 1U);
|
|
const std::array<double, 9> expectedAxes = {
|
|
0.6, 0.8, 0.0,
|
|
0.0, 0.0, 1.0,
|
|
0.8, -0.6, 0.0};
|
|
for (std::size_t index = 0U; index < expectedAxes.size(); ++index) {
|
|
EXPECT_NEAR(rows[0U].localAxes[index], expectedAxes[index], 1.0e-15);
|
|
}
|
|
(void)H5Dvlen_reclaim(
|
|
memoryType.get(), space.get(), H5P_DEFAULT, rows.data());
|
|
return rows;
|
|
}
|
|
|
|
struct StressReadRow {
|
|
std::uint64_t internalElementId;
|
|
std::uint64_t gaussPointIndex;
|
|
std::uint64_t sectionPointIndex;
|
|
double x1;
|
|
double x2;
|
|
char* source;
|
|
double s11;
|
|
};
|
|
|
|
std::vector<StressReadRow> readStressRows(const hid_t file) {
|
|
const std::string path = std::string{kStepRoot} + "/element/stress_s11";
|
|
const auto dataset = openDataset(file, path);
|
|
const auto dimensions = datasetDimensions(file, path);
|
|
auto stringType = makeUtf8StringType();
|
|
Hdf5Handle memoryType{
|
|
H5Tcreate(H5T_COMPOUND, sizeof(StressReadRow)), H5Tclose};
|
|
(void)H5Tinsert(memoryType.get(), "internal_element_id",
|
|
HOFFSET(StressReadRow, internalElementId), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "gauss_point_index",
|
|
HOFFSET(StressReadRow, gaussPointIndex), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "section_point_index",
|
|
HOFFSET(StressReadRow, sectionPointIndex), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "x1",
|
|
HOFFSET(StressReadRow, x1), H5T_NATIVE_DOUBLE);
|
|
(void)H5Tinsert(memoryType.get(), "x2",
|
|
HOFFSET(StressReadRow, x2), H5T_NATIVE_DOUBLE);
|
|
(void)H5Tinsert(memoryType.get(), "source",
|
|
HOFFSET(StressReadRow, source), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "S11",
|
|
HOFFSET(StressReadRow, s11), H5T_NATIVE_DOUBLE);
|
|
std::vector<StressReadRow> rows(
|
|
dimensions.empty() ? 0U : static_cast<std::size_t>(dimensions[0U]));
|
|
if (!rows.empty() &&
|
|
H5Dread(dataset.get(), memoryType.get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, rows.data()) < 0) {
|
|
throw std::runtime_error{"Unable to read stress rows."};
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
void reclaimStressRows(const hid_t file, std::vector<StressReadRow>& rows) {
|
|
const std::string path = std::string{kStepRoot} + "/element/stress_s11";
|
|
const auto dataset = openDataset(file, path);
|
|
Hdf5Handle space{H5Dget_space(dataset.get()), H5Sclose};
|
|
auto stringType = makeUtf8StringType();
|
|
Hdf5Handle memoryType{
|
|
H5Tcreate(H5T_COMPOUND, sizeof(StressReadRow)), H5Tclose};
|
|
(void)H5Tinsert(memoryType.get(), "internal_element_id",
|
|
HOFFSET(StressReadRow, internalElementId), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "gauss_point_index",
|
|
HOFFSET(StressReadRow, gaussPointIndex), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "section_point_index",
|
|
HOFFSET(StressReadRow, sectionPointIndex), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "x1", HOFFSET(StressReadRow, x1), H5T_NATIVE_DOUBLE);
|
|
(void)H5Tinsert(memoryType.get(), "x2", HOFFSET(StressReadRow, x2), H5T_NATIVE_DOUBLE);
|
|
(void)H5Tinsert(memoryType.get(), "source",
|
|
HOFFSET(StressReadRow, source), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "S11", HOFFSET(StressReadRow, s11), H5T_NATIVE_DOUBLE);
|
|
if (!rows.empty()) {
|
|
(void)H5Dvlen_reclaim(
|
|
memoryType.get(), space.get(), H5P_DEFAULT, rows.data());
|
|
}
|
|
}
|
|
|
|
struct DiagnosticReadRow {
|
|
char* severity;
|
|
char* code;
|
|
char* file;
|
|
std::uint64_t line;
|
|
char* keyword;
|
|
char* entityIdentity;
|
|
char* message;
|
|
};
|
|
|
|
std::vector<DiagnosticReadRow> readDiagnosticRows(const hid_t file) {
|
|
const auto dataset = openDataset(file, "/diagnostics");
|
|
const auto dimensions = datasetDimensions(file, "/diagnostics");
|
|
auto stringType = makeUtf8StringType();
|
|
Hdf5Handle memoryType{
|
|
H5Tcreate(H5T_COMPOUND, sizeof(DiagnosticReadRow)), H5Tclose};
|
|
(void)H5Tinsert(memoryType.get(), "severity",
|
|
HOFFSET(DiagnosticReadRow, severity), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "code",
|
|
HOFFSET(DiagnosticReadRow, code), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "file",
|
|
HOFFSET(DiagnosticReadRow, file), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "line",
|
|
HOFFSET(DiagnosticReadRow, line), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "keyword",
|
|
HOFFSET(DiagnosticReadRow, keyword), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "entity_identity",
|
|
HOFFSET(DiagnosticReadRow, entityIdentity), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "message",
|
|
HOFFSET(DiagnosticReadRow, message), stringType.get());
|
|
std::vector<DiagnosticReadRow> rows(
|
|
dimensions.empty() ? 0U : static_cast<std::size_t>(dimensions[0U]));
|
|
if (!rows.empty() &&
|
|
H5Dread(dataset.get(), memoryType.get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, rows.data()) < 0) {
|
|
throw std::runtime_error{"Unable to read diagnostic rows."};
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
void reclaimDiagnosticRows(
|
|
const hid_t file, std::vector<DiagnosticReadRow>& rows) {
|
|
const auto dataset = openDataset(file, "/diagnostics");
|
|
Hdf5Handle space{H5Dget_space(dataset.get()), H5Sclose};
|
|
auto stringType = makeUtf8StringType();
|
|
Hdf5Handle memoryType{
|
|
H5Tcreate(H5T_COMPOUND, sizeof(DiagnosticReadRow)), H5Tclose};
|
|
(void)H5Tinsert(memoryType.get(), "severity",
|
|
HOFFSET(DiagnosticReadRow, severity), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "code",
|
|
HOFFSET(DiagnosticReadRow, code), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "file",
|
|
HOFFSET(DiagnosticReadRow, file), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "line",
|
|
HOFFSET(DiagnosticReadRow, line), H5T_NATIVE_UINT64);
|
|
(void)H5Tinsert(memoryType.get(), "keyword",
|
|
HOFFSET(DiagnosticReadRow, keyword), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "entity_identity",
|
|
HOFFSET(DiagnosticReadRow, entityIdentity), stringType.get());
|
|
(void)H5Tinsert(memoryType.get(), "message",
|
|
HOFFSET(DiagnosticReadRow, message), stringType.get());
|
|
if (!rows.empty()) {
|
|
(void)H5Dvlen_reclaim(
|
|
memoryType.get(), space.get(), H5P_DEFAULT, rows.data());
|
|
}
|
|
}
|
|
|
|
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 atomicity sentinel bytes."};
|
|
}
|
|
}
|
|
|
|
std::size_t entryCount(const std::filesystem::path& directory) {
|
|
return static_cast<std::size_t>(
|
|
std::distance(std::filesystem::directory_iterator{directory},
|
|
std::filesystem::directory_iterator{}));
|
|
}
|
|
|
|
void expectOutputFailure(
|
|
const fesa::Status& status, const std::string& expectedCode) {
|
|
ASSERT_FALSE(status.IsOk());
|
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
|
|
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
|
EXPECT_EQ(status.Diagnostics()[0U].severity, fesa::Severity::kError);
|
|
EXPECT_EQ(status.Diagnostics()[0U].code, expectedCode);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(Hdf5ResultsWriter, WritesExactSchemaShapesAttributesAndIdentity) {
|
|
TempDirectory directory{"schema"};
|
|
const auto source = directory.path() / "model.inp";
|
|
auto fixture = makeFixture(source);
|
|
const auto output = directory.path() / "results.h5";
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
|
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
|
|
|
const auto file = openFile(output);
|
|
for (const char* path : {
|
|
"/metadata",
|
|
"/model/nodes",
|
|
"/model/elements",
|
|
"/steps/Step-1/frames/0/nodal/displacement",
|
|
"/steps/Step-1/frames/0/nodal/reaction",
|
|
"/steps/Step-1/frames/0/element/end_force_local",
|
|
"/steps/Step-1/frames/0/element/section_resultant",
|
|
"/steps/Step-1/frames/0/element/generalized_strain",
|
|
"/steps/Step-1/frames/0/element/generalized_resultant",
|
|
"/steps/Step-1/frames/0/element/stress_s11",
|
|
"/diagnostics"}) {
|
|
EXPECT_GT(H5Lexists(file.get(), path, H5P_DEFAULT), 0) << path;
|
|
}
|
|
|
|
Hdf5Handle metadata{
|
|
H5Gopen2(file.get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
|
ASSERT_GE(metadata.get(), 0);
|
|
EXPECT_EQ(readUint64Attribute(metadata.get(), "schema_version"), 0U);
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "feature_id"),
|
|
"linear-static-3d-euler-beam");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "solver_version"),
|
|
std::string{fesa::SolverVersion()});
|
|
const std::string normalizedSource =
|
|
std::filesystem::absolute(source).lexically_normal().generic_u8string();
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "source_input_identity"),
|
|
"path=" + normalizedSource +
|
|
";content_identity=fnv1a64:0123456789abcdef");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "unit_system_label"),
|
|
"user-consistent-unspecified");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "coordinate_convention"),
|
|
"global-cartesian; beam-local=(t,n1,t-cross-n1)");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "element_formulation"),
|
|
"B33-3D-Euler-Bernoulli");
|
|
EXPECT_EQ(readStringAttribute(metadata.get(), "step_name"), "Step-1");
|
|
EXPECT_EQ(readUint64Attribute(metadata.get(), "frame_index"), 0U);
|
|
|
|
EXPECT_EQ(datasetDimensions(file.get(), "/model/nodes"),
|
|
std::vector<hsize_t>({2U}));
|
|
expectCompoundMembers(
|
|
file.get(), "/model/nodes",
|
|
{"internal_node_id", "instance_name", "source_label", "coordinates"});
|
|
const auto nodesDataset = openDataset(file.get(), "/model/nodes");
|
|
EXPECT_EQ(
|
|
readStringAttribute(nodesDataset.get(), "coordinate_system"),
|
|
"global-cartesian");
|
|
EXPECT_EQ(readStringAttribute(nodesDataset.get(), "units_label"), "length");
|
|
auto nodes = readNodeRows(file.get());
|
|
ASSERT_EQ(nodes.size(), 2U);
|
|
EXPECT_EQ(nodes[0U].internalNodeId, 0U);
|
|
EXPECT_STREQ(nodes[0U].instanceName, u8"Beam-\u03b1");
|
|
EXPECT_STREQ(nodes[0U].sourceLabel, "101");
|
|
EXPECT_DOUBLE_EQ(nodes[1U].coordinates[0U], 3.0);
|
|
EXPECT_DOUBLE_EQ(nodes[1U].coordinates[1U], 4.0);
|
|
reclaimNodeRows(file.get(), nodes);
|
|
|
|
EXPECT_EQ(datasetDimensions(file.get(), "/model/elements"),
|
|
std::vector<hsize_t>({1U}));
|
|
expectCompoundMembers(
|
|
file.get(), "/model/elements",
|
|
{"internal_element_id", "instance_name", "source_label",
|
|
"node_internal_ids", "local_axes"});
|
|
const auto elementsDataset = openDataset(file.get(), "/model/elements");
|
|
EXPECT_EQ(
|
|
readStringAttribute(elementsDataset.get(), "formulation"),
|
|
"B33-3D-Euler-Bernoulli");
|
|
(void)readElementRows(file.get());
|
|
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/nodal/displacement", {2U, 6U},
|
|
"UX,UY,UZ,URX,URY,URZ",
|
|
"length,length,length,radian,radian,radian",
|
|
"global-cartesian", "nodal");
|
|
const auto displacement = readDoubleDataset(
|
|
file.get(), std::string{kStepRoot} + "/nodal/displacement");
|
|
ASSERT_EQ(displacement.size(), 12U);
|
|
EXPECT_DOUBLE_EQ(displacement.front(), 0.25);
|
|
EXPECT_DOUBLE_EQ(displacement.back(), 11.25);
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/nodal/reaction", {2U, 6U},
|
|
"RF1,RF2,RF3,RM1,RM2,RM3",
|
|
"force,force,force,force*length,force*length,force*length",
|
|
"global-cartesian", "nodal");
|
|
const auto reaction = readDoubleDataset(
|
|
file.get(), std::string{kStepRoot} + "/nodal/reaction");
|
|
ASSERT_EQ(reaction.size(), 12U);
|
|
EXPECT_DOUBLE_EQ(reaction.front(), 100.0);
|
|
EXPECT_DOUBLE_EQ(reaction.back(), 111.0);
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/end_force_local",
|
|
{1U, 2U, 6U}, "FX,FY,FZ,MX,MY,MZ",
|
|
"force,force,force,force*length,force*length,force*length",
|
|
"beam-local", "endpoint-outward-action");
|
|
const auto endForce = readDoubleDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/end_force_local");
|
|
ASSERT_EQ(endForce.size(), 12U);
|
|
EXPECT_DOUBLE_EQ(endForce.front(), 1.0);
|
|
EXPECT_DOUBLE_EQ(endForce.back(), 12.0);
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/section_resultant",
|
|
{1U, 2U, 4U}, "N,T,My,Mz",
|
|
"force,force*length,force*length,force*length",
|
|
"beam-local", "endpoint-positive-local-x-section-cut");
|
|
const auto sectionResultant = readDoubleDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/section_resultant");
|
|
ASSERT_EQ(sectionResultant.size(), 8U);
|
|
EXPECT_DOUBLE_EQ(sectionResultant.front(), 11.0);
|
|
EXPECT_DOUBLE_EQ(sectionResultant.back(), 18.0);
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/generalized_strain",
|
|
{1U, 2U, 4U}, "epsilon0,kappa_x,kappa_y,kappa_z",
|
|
"1,1/length,1/length,1/length",
|
|
"beam-local", "integration-point");
|
|
const auto generalizedStrain = readDoubleDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/generalized_strain");
|
|
ASSERT_EQ(generalizedStrain.size(), 8U);
|
|
EXPECT_DOUBLE_EQ(generalizedStrain.front(), 0.01);
|
|
EXPECT_DOUBLE_EQ(generalizedStrain.back(), 0.08);
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/generalized_resultant",
|
|
{1U, 2U, 4U}, "N,T,My,Mz",
|
|
"force,force*length,force*length,force*length",
|
|
"beam-local", "integration-point");
|
|
const auto generalizedResultant = readDoubleDataset(
|
|
file.get(), std::string{kStepRoot} + "/element/generalized_resultant");
|
|
ASSERT_EQ(generalizedResultant.size(), 8U);
|
|
EXPECT_DOUBLE_EQ(generalizedResultant.front(), 21.0);
|
|
EXPECT_DOUBLE_EQ(generalizedResultant.back(), 28.0);
|
|
|
|
const std::string stressPath = std::string{kStepRoot} + "/element/stress_s11";
|
|
EXPECT_EQ(datasetDimensions(file.get(), stressPath),
|
|
std::vector<hsize_t>({4U}));
|
|
expectCompoundMembers(
|
|
file.get(), stressPath,
|
|
{"internal_element_id", "gauss_point_index", "section_point_index",
|
|
"x1", "x2", "source", "S11"});
|
|
const auto stressDataset = openDataset(file.get(), stressPath);
|
|
EXPECT_EQ(readStringAttribute(stressDataset.get(), "component_names"), "S11");
|
|
EXPECT_EQ(
|
|
readStringAttribute(stressDataset.get(), "component_unit_dimensions"),
|
|
"force/length^2");
|
|
EXPECT_EQ(
|
|
readStringAttribute(stressDataset.get(), "coordinate_system"),
|
|
"beam-local");
|
|
EXPECT_EQ(readStringAttribute(stressDataset.get(), "location"), "section-point");
|
|
EXPECT_EQ(readStringAttribute(stressDataset.get(), "step_name"), "Step-1");
|
|
EXPECT_EQ(readUint64Attribute(stressDataset.get(), "frame_index"), 0U);
|
|
auto stressRows = readStressRows(file.get());
|
|
ASSERT_EQ(stressRows.size(), 4U);
|
|
EXPECT_EQ(stressRows[0U].internalElementId, 0U);
|
|
EXPECT_EQ(stressRows[0U].gaussPointIndex, 1U);
|
|
EXPECT_EQ(stressRows[0U].sectionPointIndex, 1U);
|
|
EXPECT_DOUBLE_EQ(stressRows[0U].x1, -0.1);
|
|
EXPECT_DOUBLE_EQ(stressRows[0U].x2, 0.2);
|
|
EXPECT_STREQ(stressRows[0U].source, "input");
|
|
EXPECT_DOUBLE_EQ(stressRows[3U].s11, 34.0);
|
|
reclaimStressRows(file.get(), stressRows);
|
|
|
|
EXPECT_EQ(datasetDimensions(file.get(), "/diagnostics"),
|
|
std::vector<hsize_t>({0U}));
|
|
expectCompoundMembers(
|
|
file.get(), "/diagnostics",
|
|
{"severity", "code", "file", "line", "keyword",
|
|
"entity_identity", "message"});
|
|
EXPECT_EQ(
|
|
H5Lexists(file.get(),
|
|
"/steps/Step-1/frames/0/element/transverse_shear_stress",
|
|
H5P_DEFAULT),
|
|
0);
|
|
}
|
|
|
|
TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
|
|
TempDirectory directory{"mandatory"};
|
|
auto fixture = makeFixture(directory.path() / "request-model.inp");
|
|
const fesa::Diagnostic ignoredRequest{
|
|
fesa::Severity::kWarning,
|
|
"ignored-output-request",
|
|
{fixture.domain->SourcePath(), 70U},
|
|
"*OUTPUT",
|
|
"FIELD",
|
|
"Abaqus output requests do not filter FESA mandatory results."};
|
|
const auto output = directory.path() / "results.h5";
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
ASSERT_TRUE(
|
|
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
|
|
.IsOk());
|
|
const auto file = openFile(output);
|
|
for (const char* suffix : {
|
|
"/nodal/displacement",
|
|
"/nodal/reaction",
|
|
"/element/end_force_local",
|
|
"/element/section_resultant",
|
|
"/element/generalized_strain",
|
|
"/element/generalized_resultant",
|
|
"/element/stress_s11"}) {
|
|
const std::string path = std::string{kStepRoot} + suffix;
|
|
EXPECT_GT(H5Lexists(file.get(), path.c_str(), H5P_DEFAULT), 0) << path;
|
|
}
|
|
EXPECT_EQ(datasetDimensions(file.get(), "/diagnostics"),
|
|
std::vector<hsize_t>({1U}));
|
|
}
|
|
|
|
TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
|
|
TempDirectory directory{"warnings"};
|
|
auto fixture = makeFixture(directory.path() / "centroid.inp", true);
|
|
std::vector<fesa::Diagnostic> diagnostics = {
|
|
{fesa::Severity::kWarning,
|
|
"ignored-output-request",
|
|
{fixture.domain->SourcePath(), 80U},
|
|
"*OUTPUT",
|
|
"FIELD",
|
|
"Ignored output request."},
|
|
{fesa::Severity::kWarning,
|
|
"ignored-keyword",
|
|
{fixture.domain->SourcePath(), 20U},
|
|
"*PREPRINT",
|
|
"",
|
|
"Ignored generator control."}};
|
|
const auto output = directory.path() / "results.h5";
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, diagnostics).IsOk());
|
|
const auto file = openFile(output);
|
|
auto stressRows = readStressRows(file.get());
|
|
ASSERT_EQ(stressRows.size(), 2U);
|
|
for (std::size_t index = 0U; index < stressRows.size(); ++index) {
|
|
EXPECT_EQ(stressRows[index].internalElementId, 0U);
|
|
EXPECT_EQ(stressRows[index].gaussPointIndex, index + 1U);
|
|
EXPECT_EQ(stressRows[index].sectionPointIndex, 0U);
|
|
EXPECT_DOUBLE_EQ(stressRows[index].x1, 0.0);
|
|
EXPECT_DOUBLE_EQ(stressRows[index].x2, 0.0);
|
|
EXPECT_STREQ(stressRows[index].source, "fesa-default");
|
|
}
|
|
reclaimStressRows(file.get(), stressRows);
|
|
|
|
auto rows = readDiagnosticRows(file.get());
|
|
ASSERT_EQ(rows.size(), 2U);
|
|
EXPECT_STREQ(rows[0U].severity, "warning");
|
|
EXPECT_STREQ(rows[0U].code, "ignored-keyword");
|
|
EXPECT_STREQ(
|
|
rows[0U].file,
|
|
std::filesystem::absolute(fixture.domain->SourcePath())
|
|
.lexically_normal()
|
|
.generic_u8string()
|
|
.c_str());
|
|
EXPECT_EQ(rows[0U].line, 20U);
|
|
EXPECT_STREQ(rows[0U].keyword, "*PREPRINT");
|
|
EXPECT_STREQ(rows[0U].entityIdentity, "");
|
|
EXPECT_STREQ(rows[0U].message, "Ignored generator control.");
|
|
EXPECT_STREQ(rows[1U].code, "ignored-output-request");
|
|
EXPECT_EQ(rows[1U].line, 80U);
|
|
reclaimDiagnosticRows(file.get(), rows);
|
|
}
|
|
|
|
TEST(Hdf5ResultsWriter, FailureLeavesNoPartialAndPreservesExistingFinal) {
|
|
TempDirectory directory{"failure"};
|
|
auto fixture = makeFixture(directory.path() / "failure.inp");
|
|
fesa::Hdf5ResultsWriter writer;
|
|
|
|
fixture.state->displacement()[0U] =
|
|
std::numeric_limits<double>::quiet_NaN();
|
|
const auto invalidOutput = directory.path() / "invalid-results.h5";
|
|
expectOutputFailure(
|
|
writer.write(invalidOutput, *fixture.domain, *fixture.state, {}),
|
|
"invalid-result-state");
|
|
EXPECT_FALSE(std::filesystem::exists(invalidOutput));
|
|
EXPECT_EQ(entryCount(directory.path()), 0U);
|
|
fixture.state->displacement()[0U] = 0.25;
|
|
|
|
const auto final = directory.path() / "results.h5";
|
|
const std::vector<char> sentinel = {'p', 'r', 'e', 'v', 'i', 'o', 'u', 's'};
|
|
writeBytes(final, sentinel);
|
|
WinHandle lock{CreateFileW(
|
|
final.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
nullptr)};
|
|
ASSERT_NE(lock.get(), INVALID_HANDLE_VALUE);
|
|
|
|
expectOutputFailure(
|
|
writer.write(final, *fixture.domain, *fixture.state, {}),
|
|
"hdf5-finalization-failure");
|
|
EXPECT_EQ(readBytes(final), sentinel);
|
|
EXPECT_EQ(entryCount(directory.path()), 1U);
|
|
}
|
|
|
|
TEST(Hdf5ResultsWriter, SuccessfullyReplacesExistingFinal) {
|
|
TempDirectory directory{"replace"};
|
|
auto fixture = makeFixture(directory.path() / "replace.inp");
|
|
const auto final = directory.path() / "results.h5";
|
|
writeBytes(final, {'o', 'l', 'd'});
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
ASSERT_TRUE(writer.write(final, *fixture.domain, *fixture.state, {}).IsOk());
|
|
EXPECT_GT(H5Fis_hdf5(final.string().c_str()), 0);
|
|
EXPECT_EQ(entryCount(directory.path()), 1U);
|
|
const auto file = openFile(final);
|
|
Hdf5Handle metadata{
|
|
H5Gopen2(file.get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
|
ASSERT_GE(metadata.get(), 0);
|
|
EXPECT_EQ(readUint64Attribute(metadata.get(), "schema_version"), 0U);
|
|
}
|
|
|
|
// MITC4-H5-001
|
|
TEST(Hdf5ResultsWriter, WritesExactShellMetadataAndModelIdentity) {
|
|
TempDirectory directory{"shell-model"};
|
|
const auto source = directory.path() / "shell.inp";
|
|
auto fixture = makeShellFixture(source);
|
|
const auto output = directory.path() / "results.h5";
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
|
const auto file = openFile(output);
|
|
|
|
Hdf5Handle metadata{
|
|
H5Gopen2(file.get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
|
ASSERT_GE(metadata.get(), 0);
|
|
EXPECT_EQ(readUint64Attribute(metadata.get(), "schema_version"), 0U);
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "feature_id"),
|
|
"linear-static-mitc4-shell");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "coordinate_convention"),
|
|
"global-cartesian; shell-local=(e1,e2,e3); positive-thickness=+zeta");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "internal_formulation"),
|
|
"FESA-MITC4");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metadata.get(), "integration_rule"),
|
|
"2x2x2-gauss; mitc4-edge-midpoint-shear");
|
|
|
|
EXPECT_EQ(
|
|
datasetDimensions(file.get(), "/model/elements"),
|
|
std::vector<hsize_t>({1U}));
|
|
expectCompoundMemberNames(
|
|
file.get(),
|
|
"/model/elements",
|
|
{"internal_element_id", "instance_name", "source_label",
|
|
"source_element_type", "internal_formulation", "node_internal_ids",
|
|
"shell_section_internal_id", "material_internal_id"});
|
|
const auto elements = openDataset(file.get(), "/model/elements");
|
|
EXPECT_EQ(
|
|
readStringAttribute(elements.get(), "formulation"), "FESA-MITC4");
|
|
|
|
EXPECT_EQ(
|
|
datasetDimensions(file.get(), "/model/shell/nodal_director"),
|
|
std::vector<hsize_t>({4U, 3U}));
|
|
EXPECT_EQ(
|
|
readDoubleDataset(file.get(), "/model/shell/nodal_director"),
|
|
std::vector<double>({0.0, 0.0, 1.0,
|
|
0.0, 0.0, 1.0,
|
|
0.0, 0.0, 1.0,
|
|
0.0, 0.0, 1.0}));
|
|
EXPECT_EQ(
|
|
datasetDimensions(file.get(), "/model/shell/nodal_frame"),
|
|
std::vector<hsize_t>({4U, 3U, 3U}));
|
|
expectCompoundMemberNames(
|
|
file.get(), "/model/shell/materials",
|
|
{"internal_material_id", "name", "E", "nu"});
|
|
expectCompoundMemberNames(
|
|
file.get(), "/model/shell/sections",
|
|
{"internal_section_id", "source_file", "source_line", "source_elset",
|
|
"material_internal_id", "thickness"});
|
|
|
|
EXPECT_EQ(
|
|
datasetDimensions(file.get(), "/model/nodal_constraint_mask"),
|
|
std::vector<hsize_t>({4U, 6U}));
|
|
const auto mask = readUint8Dataset(file.get(), "/model/nodal_constraint_mask");
|
|
ASSERT_EQ(mask.size(), 24U);
|
|
EXPECT_EQ(mask[0U], 1U);
|
|
EXPECT_EQ(mask[1U], 1U);
|
|
EXPECT_EQ(mask[2U], 1U);
|
|
EXPECT_EQ(mask[3U], 1U);
|
|
EXPECT_EQ(mask[4U], 0U);
|
|
EXPECT_EQ(mask[5U], 0U);
|
|
const auto prescribed = readDoubleDataset(
|
|
file.get(), "/model/prescribed_displacement");
|
|
ASSERT_EQ(prescribed.size(), 24U);
|
|
EXPECT_DOUBLE_EQ(prescribed[0U], 0.0);
|
|
EXPECT_DOUBLE_EQ(prescribed[3U], 0.125);
|
|
EXPECT_DOUBLE_EQ(prescribed[4U], 0.0);
|
|
EXPECT_EQ(
|
|
readDoubleDataset(file.get(), "/model/shell/section_positions"),
|
|
std::vector<double>({-1.0, 0.0, 1.0}));
|
|
const auto locations = readDoubleDataset(
|
|
file.get(), "/model/shell/midsurface_locations");
|
|
ASSERT_EQ(locations.size(), 8U);
|
|
const double gauss = 1.0 / std::sqrt(3.0);
|
|
EXPECT_DOUBLE_EQ(locations[0U], -gauss);
|
|
EXPECT_DOUBLE_EQ(locations[1U], -gauss);
|
|
EXPECT_DOUBLE_EQ(locations[6U], -gauss);
|
|
EXPECT_DOUBLE_EQ(locations[7U], gauss);
|
|
}
|
|
|
|
// MITC4-H5-002
|
|
TEST(Hdf5ResultsWriter, WritesExactMandatoryShellResultInventory) {
|
|
TempDirectory directory{"shell-results"};
|
|
auto fixture = makeShellFixture(directory.path() / "shell.inp");
|
|
const auto output = directory.path() / "results.h5";
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
|
const auto file = openFile(output);
|
|
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
|
|
expectNumericDataset(
|
|
file.get(), shellRoot + "/local_frame", {1U, 4U, 3U, 3U},
|
|
"X,Y,Z", "1,1,1", "global-cartesian", "shell-local-frame");
|
|
expectNumericDataset(
|
|
file.get(), shellRoot + "/generalized_strain", {1U, 4U, 8U},
|
|
"E11,E22,G12,K11,K22,K12,G13,G23",
|
|
"1,1,1,1/length,1/length,1/length,1,1",
|
|
"shell-local", "midsurface");
|
|
expectNumericDataset(
|
|
file.get(), shellRoot + "/section_resultant", {1U, 4U, 8U},
|
|
"N11,N22,N12,M11,M22,M12,Q13,Q23",
|
|
"force/length,force/length,force/length,force,force,force,force/length,force/length",
|
|
"shell-local", "midsurface");
|
|
expectNumericDataset(
|
|
file.get(), shellRoot + "/stress", {1U, 4U, 3U, 3U},
|
|
"S11,S22,S12",
|
|
"force/length^2,force/length^2,force/length^2",
|
|
"shell-local", "section-position");
|
|
|
|
const auto strain = readDoubleDataset(file.get(), shellRoot + "/generalized_strain");
|
|
ASSERT_EQ(strain.size(), 32U);
|
|
EXPECT_DOUBLE_EQ(strain.front(), 101.0);
|
|
EXPECT_DOUBLE_EQ(strain.back(), 408.0);
|
|
const auto stress = readDoubleDataset(file.get(), shellRoot + "/stress");
|
|
ASSERT_EQ(stress.size(), 36U);
|
|
EXPECT_DOUBLE_EQ(stress.front(), 121.0);
|
|
EXPECT_DOUBLE_EQ(stress.back(), 429.0);
|
|
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/global/energy", {1U},
|
|
"PHYSICAL_STRAIN_ENERGY", "force*length", "global", "global");
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/global/equilibrium", {6U},
|
|
"FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3",
|
|
"force,force,force,force*length,force*length,force*length",
|
|
"global-cartesian", "global-origin");
|
|
expectNumericDataset(
|
|
file.get(), std::string{kStepRoot} + "/global/verification_metrics", {3U},
|
|
"FREE_RESIDUAL_NORMALIZED,FORCE_BALANCE_NORMALIZED,MOMENT_BALANCE_NORMALIZED",
|
|
"1,1,1", "global", "verification");
|
|
const auto metrics = openDataset(
|
|
file.get(), std::string{kStepRoot} + "/global/verification_metrics");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metrics.get(), "metric_definition_ids"),
|
|
"free-residual-l2-over-max-free-force-l2,force-balance-l2-over-max-force-sum,moment-balance-l2-over-max-moment-sum");
|
|
EXPECT_EQ(
|
|
readStringAttribute(metrics.get(), "acceptance_thresholds"),
|
|
"1e-10,1e-10,1e-10");
|
|
}
|
|
|
|
// MITC4-H5-003
|
|
TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPaths) {
|
|
TempDirectory directory{"shell-mandatory"};
|
|
auto fixture = makeShellFixture(directory.path() / "shell.inp");
|
|
const fesa::Diagnostic ignoredRequest{
|
|
fesa::Severity::kWarning,
|
|
"ignored-output-request",
|
|
{fixture.domain->SourcePath(), 80U},
|
|
"*ELEMENT OUTPUT",
|
|
"S",
|
|
"Output requests cannot filter mandatory shell results."};
|
|
const auto output = directory.path() / "results.h5";
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
ASSERT_TRUE(
|
|
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
|
|
.IsOk());
|
|
const auto file = openFile(output);
|
|
for (const char* suffix : {
|
|
"/element/shell/local_frame",
|
|
"/element/shell/generalized_strain",
|
|
"/element/shell/section_resultant",
|
|
"/element/shell/stress",
|
|
"/global/energy",
|
|
"/global/equilibrium",
|
|
"/global/verification_metrics"}) {
|
|
const std::string path = std::string{kStepRoot} + suffix;
|
|
EXPECT_GT(H5Lexists(file.get(), path.c_str(), H5P_DEFAULT), 0) << path;
|
|
}
|
|
for (const char* forbidden : {
|
|
"/steps/Step-1/frames/0/element/shell/drilling",
|
|
"/steps/Step-1/frames/0/element/shell/drilling_energy",
|
|
"/steps/Step-1/frames/0/element/shell/S33",
|
|
"/steps/Step-1/frames/0/element/shell/S13",
|
|
"/steps/Step-1/frames/0/element/shell/S23"}) {
|
|
EXPECT_EQ(H5Lexists(file.get(), forbidden, H5P_DEFAULT), 0) << forbidden;
|
|
}
|
|
EXPECT_EQ(datasetDimensions(file.get(), "/diagnostics"),
|
|
std::vector<hsize_t>({1U}));
|
|
}
|
|
|
|
// MITC4-H5-004
|
|
TEST(Hdf5ResultsWriter, InvalidShellInventoryPreservesExistingFinal) {
|
|
TempDirectory directory{"shell-atomic"};
|
|
auto fixture = makeShellFixture(directory.path() / "shell.inp");
|
|
auto invalidState = fesa::AnalysisState::create(
|
|
*fixture.dofs, {"Step-1", 0U});
|
|
const auto final = directory.path() / "results.h5";
|
|
const std::vector<char> sentinel = {'s', 'h', 'e', 'l', 'l'};
|
|
writeBytes(final, sentinel);
|
|
|
|
fesa::Hdf5ResultsWriter writer;
|
|
expectOutputFailure(
|
|
writer.write(final, *fixture.domain, invalidState, {}),
|
|
"invalid-result-rows");
|
|
EXPECT_EQ(readBytes(final), sentinel);
|
|
EXPECT_EQ(entryCount(directory.path()), 1U);
|
|
}
|