998 lines
48 KiB
C++
998 lines
48 KiB
C++
#include "fesa/io/hdf5/dynamic_hdf5_results_writer.h"
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fesa/analysis/analysis_model.h"
|
|
#include "fesa/build_info.h"
|
|
#include "fesa/core/diagnostic.h"
|
|
#include "fesa/fem/dof_manager.h"
|
|
#include "io/hdf5/hdf5_atomic_file.h"
|
|
#include "io/hdf5/hdf5_model_writer.h"
|
|
#include "io/hdf5/hdf5_primitives.h"
|
|
#include "io/hdf5/hdf5_raii.h"
|
|
|
|
namespace fesa {
|
|
namespace {
|
|
|
|
using hdf5_internal::Hdf5Failure;
|
|
using hdf5_internal::Hdf5Handle;
|
|
using hdf5_internal::CopyVectorValues;
|
|
using hdf5_internal::FlattenNodalTriads;
|
|
using hdf5_internal::FlattenNodalDirectors;
|
|
using hdf5_internal::SpatialReactionValues;
|
|
|
|
struct AcceptedFrameRow {
|
|
std::uint64_t frame_index;
|
|
std::uint64_t increment_index;
|
|
double time;
|
|
std::uint64_t iteration_count;
|
|
std::uint8_t is_initial;
|
|
};
|
|
|
|
struct DiagnosticRow {
|
|
const char* severity;
|
|
const char* code;
|
|
const char* file;
|
|
std::uint64_t line;
|
|
const char* keyword;
|
|
const char* entity_identity;
|
|
const char* message;
|
|
};
|
|
|
|
struct DensityRow {
|
|
std::uint64_t internal_material_id;
|
|
double rho;
|
|
};
|
|
|
|
/// @brief Rejects a candidate before its final path can be replaced.
|
|
void Check(const bool condition, const char* message) {
|
|
if (!condition) throw Hdf5Failure{message};
|
|
}
|
|
|
|
/// @brief Checks scalar type, rank and value of one numeric attribute.
|
|
template <typename Value>
|
|
void CheckAttribute(const hid_t object, const char* name, const hid_t file_type,
|
|
const hid_t memory_type, const Value expected) {
|
|
Hdf5Handle attribute{
|
|
hdf5_internal::RequireHdf5Id(H5Aopen(object, name, H5P_DEFAULT),
|
|
"Missing numeric attribute."),
|
|
H5Aclose};
|
|
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
|
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
|
|
Check(H5Tequal(type.Get(), file_type) > 0 &&
|
|
H5Sget_simple_extent_type(space.Get()) == H5S_SCALAR,
|
|
"Invalid numeric attribute schema.");
|
|
Value actual{};
|
|
hdf5_internal::RequireHdf5(H5Aread(attribute.Get(), memory_type, &actual),
|
|
"Unable to read numeric attribute.");
|
|
Check(actual == expected, "Numeric attribute value mismatch.");
|
|
}
|
|
|
|
/// @brief Checks UTF-8 variable-length scalar attribute storage and value.
|
|
void CheckString(const hid_t object, const char* name,
|
|
const std::string& expected) {
|
|
Hdf5Handle attribute{
|
|
hdf5_internal::RequireHdf5Id(H5Aopen(object, name, H5P_DEFAULT),
|
|
"Missing string attribute."),
|
|
H5Aclose};
|
|
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
|
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
|
|
Check(H5Tget_class(type.Get()) == H5T_STRING &&
|
|
H5Tis_variable_str(type.Get()) > 0 &&
|
|
H5Tget_cset(type.Get()) == H5T_CSET_UTF8 &&
|
|
H5Sget_simple_extent_type(space.Get()) == H5S_SCALAR,
|
|
"Invalid string attribute schema.");
|
|
char* actual = nullptr;
|
|
hdf5_internal::RequireHdf5(H5Aread(attribute.Get(), type.Get(), &actual),
|
|
"Unable to read string attribute.");
|
|
const bool equal = actual != nullptr && expected == actual;
|
|
(void)H5free_memory(actual);
|
|
Check(equal, "String attribute value mismatch.");
|
|
}
|
|
|
|
/// @brief Opens one mandatory contiguous, uncompressed dataset of exact shape.
|
|
Hdf5Handle OpenDataset(const hid_t file, const std::string& path,
|
|
const std::vector<hsize_t>& dimensions) {
|
|
Hdf5Handle dataset{
|
|
hdf5_internal::RequireHdf5Id(H5Dopen2(file, path.c_str(), H5P_DEFAULT),
|
|
"Missing dynamic dataset."),
|
|
H5Dclose};
|
|
Hdf5Handle space{H5Dget_space(dataset.Get()), H5Sclose};
|
|
Check(H5Sget_simple_extent_ndims(space.Get()) ==
|
|
static_cast<int>(dimensions.size()),
|
|
"Dataset rank mismatch.");
|
|
std::vector<hsize_t> actual(dimensions.size());
|
|
hdf5_internal::RequireHdf5(
|
|
H5Sget_simple_extent_dims(space.Get(), actual.data(), nullptr),
|
|
"Unable to read dataset dimensions.");
|
|
Check(actual == dimensions, "Dataset shape mismatch.");
|
|
Hdf5Handle properties{H5Dget_create_plist(dataset.Get()), H5Pclose};
|
|
Check(H5Pget_layout(properties.Get()) == H5D_CONTIGUOUS &&
|
|
H5Pget_nfilters(properties.Get()) == 0,
|
|
"Dataset must be contiguous and uncompressed.");
|
|
return dataset;
|
|
}
|
|
|
|
/// @brief Compares binary64 readback to the complete finite accepted values.
|
|
Hdf5Handle CheckDoubles(const hid_t file, const std::string& path,
|
|
const std::vector<hsize_t>& dimensions,
|
|
const std::vector<double>& expected) {
|
|
auto dataset = OpenDataset(file, path, dimensions);
|
|
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
|
|
Check(H5Tequal(type.Get(), H5T_IEEE_F64LE) > 0,
|
|
"Dataset must use little-endian binary64.");
|
|
std::vector<double> actual(expected.size());
|
|
hdf5_internal::RequireHdf5(H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL,
|
|
H5S_ALL, H5P_DEFAULT, actual.data()),
|
|
"Unable to read dynamic values.");
|
|
for (std::size_t index = 0U; index < actual.size(); ++index) {
|
|
Check(std::isfinite(actual[index]) && actual[index] == expected[index],
|
|
"Dataset value mismatch or nonfinite value.");
|
|
}
|
|
return dataset;
|
|
}
|
|
|
|
/// @brief Validates ordered compound members without accepting extra fields.
|
|
void CheckMembers(const hid_t dataset, const std::vector<std::string>& names) {
|
|
Hdf5Handle type{H5Dget_type(dataset), H5Tclose};
|
|
Check(H5Tget_class(type.Get()) == H5T_COMPOUND &&
|
|
H5Tget_nmembers(type.Get()) == static_cast<int>(names.size()),
|
|
"Compound field inventory mismatch.");
|
|
for (std::size_t index = 0U; index < names.size(); ++index) {
|
|
char* name = H5Tget_member_name(type.Get(), static_cast<unsigned>(index));
|
|
const bool equal = name != nullptr && names[index] == name;
|
|
(void)H5free_memory(name);
|
|
Check(equal, "Compound field order mismatch.");
|
|
}
|
|
}
|
|
|
|
/// @brief Compares one fixed-width compound column with its source inventory.
|
|
template <typename Value>
|
|
void CheckColumn(const hid_t dataset, const char* name, const hid_t file_type,
|
|
const hid_t memory_type, const std::vector<Value>& expected) {
|
|
Hdf5Handle type{H5Dget_type(dataset), H5Tclose};
|
|
const int index = H5Tget_member_index(type.Get(), name);
|
|
Check(index >= 0, "Missing compound column.");
|
|
Hdf5Handle member{
|
|
H5Tget_member_type(type.Get(), static_cast<unsigned>(index)), H5Tclose};
|
|
Check(H5Tequal(member.Get(), file_type) > 0,
|
|
"Compound column dtype mismatch.");
|
|
Hdf5Handle memory{H5Tcreate(H5T_COMPOUND, sizeof(Value)), H5Tclose};
|
|
hdf5_internal::RequireHdf5(H5Tinsert(memory.Get(), name, 0U, memory_type),
|
|
"Unable to create readback column type.");
|
|
std::vector<Value> actual(expected.size());
|
|
if (!actual.empty()) {
|
|
hdf5_internal::RequireHdf5(H5Dread(dataset, memory.Get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, actual.data()),
|
|
"Unable to read compound column.");
|
|
}
|
|
Check(actual == expected, "Compound column value mismatch.");
|
|
}
|
|
|
|
/// @brief Compares and reclaims a UTF-8 variable-length compound column.
|
|
void CheckStringColumn(const hid_t dataset, const char* name,
|
|
const std::vector<std::string>& expected) {
|
|
Hdf5Handle type{H5Dget_type(dataset), H5Tclose};
|
|
const int index = H5Tget_member_index(type.Get(), name);
|
|
Check(index >= 0, "Missing compound string column.");
|
|
Hdf5Handle member{
|
|
H5Tget_member_type(type.Get(), static_cast<unsigned>(index)), H5Tclose};
|
|
Check(H5Tget_class(member.Get()) == H5T_STRING &&
|
|
H5Tis_variable_str(member.Get()) > 0 &&
|
|
H5Tget_cset(member.Get()) == H5T_CSET_UTF8,
|
|
"Invalid compound string dtype.");
|
|
Hdf5Handle memory{H5Tcreate(H5T_COMPOUND, sizeof(char*)), H5Tclose};
|
|
hdf5_internal::RequireHdf5(H5Tinsert(memory.Get(), name, 0U, member.Get()),
|
|
"Unable to create readback string type.");
|
|
std::vector<char*> actual(expected.size(), nullptr);
|
|
if (actual.empty()) return;
|
|
hdf5_internal::RequireHdf5(H5Dread(dataset, memory.Get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, actual.data()),
|
|
"Unable to read compound strings.");
|
|
bool equal = true;
|
|
for (std::size_t row = 0U; row < actual.size(); ++row) {
|
|
equal = equal && actual[row] != nullptr && expected[row] == actual[row];
|
|
}
|
|
Hdf5Handle space{H5Dget_space(dataset), H5Sclose};
|
|
hdf5_internal::RequireHdf5(
|
|
H5Dvlen_reclaim(memory.Get(), space.Get(), H5P_DEFAULT, actual.data()),
|
|
"Unable to reclaim compound strings.");
|
|
Check(equal, "Compound string value mismatch.");
|
|
}
|
|
|
|
/// @brief Writes the stable material-index density table.
|
|
void WriteDensity(const hid_t file, const Domain& domain) {
|
|
std::vector<DensityRow> rows;
|
|
for (const auto& density : domain.Densities()) {
|
|
rows.push_back({density.material_index, density.rho});
|
|
}
|
|
Hdf5Handle type{H5Tcreate(H5T_COMPOUND, sizeof(DensityRow)), H5Tclose};
|
|
hdf5_internal::RequireHdf5(
|
|
H5Tinsert(type.Get(), "internal_material_id",
|
|
HOFFSET(DensityRow, internal_material_id), H5T_STD_U64LE),
|
|
"Unable to define density identity.");
|
|
hdf5_internal::RequireHdf5(
|
|
H5Tinsert(type.Get(), "rho", HOFFSET(DensityRow, rho), H5T_IEEE_F64LE),
|
|
"Unable to define density value.");
|
|
(void)hdf5_internal::WriteCompoundDataset(file, "/model/material_density",
|
|
rows.size(), type.Get(), type.Get(),
|
|
rows.data());
|
|
}
|
|
|
|
/// @brief Reports an output failure without exposing backend exceptions.
|
|
Status Failure(const char* code, const std::string& message) {
|
|
return hdf5_internal::OutputFailure(code, message);
|
|
}
|
|
|
|
/// @brief Removes inherited metadata before applying the dynamic schema.
|
|
void DeleteAttribute(const hid_t object, const char* name) {
|
|
if (H5Aexists(object, name) > 0) {
|
|
hdf5_internal::RequireHdf5(H5Adelete(object, name),
|
|
"Unable to replace metadata attribute.");
|
|
}
|
|
}
|
|
|
|
/// @brief Maps a validated frame's integration semantics to its schema.
|
|
std::uint64_t SchemaVersion(const DynamicAcceptedFrameState& frame) {
|
|
return frame.time_integration_formulation ==
|
|
DynamicTimeIntegrationFormulation::kReducedCoordinateHht
|
|
? 2U
|
|
: 1U;
|
|
}
|
|
|
|
/// @brief Returns the explicit integration discriminator without aliases.
|
|
const char* IntegrationFormulation(const DynamicAcceptedFrameState& frame) {
|
|
return SchemaVersion(frame) == 2U
|
|
? "reduced-coordinate-HHT-with-physical-rate-reconstruction"
|
|
: "full-coordinate-HHT";
|
|
}
|
|
|
|
/// @brief Writes the dynamic procedure identity and fixed time schedule.
|
|
void WriteMetadata(const hid_t file, const Domain& domain,
|
|
const DynamicAcceptedFrameState& frame) {
|
|
Hdf5Handle metadata{
|
|
hdf5_internal::RequireHdf5Id(H5Gopen2(file, "/metadata", H5P_DEFAULT),
|
|
"The model metadata group is missing."),
|
|
H5Gclose};
|
|
for (const char* name :
|
|
{"feature_id", "analysis_procedure", "schema_version",
|
|
"mass_formulation", "internal_formulation", "initial_state",
|
|
"acceleration_convention", "step_name", "source_step_name",
|
|
"unit_system_label", "coordinate_convention", "integration_rule",
|
|
"frame_index", "time_integration_formulation"}) {
|
|
DeleteAttribute(metadata.Get(), name);
|
|
}
|
|
const DynamicStepDefinition* const step = domain.Dynamic();
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "feature_id",
|
|
"hht-implicit-dynamics");
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "analysis_procedure",
|
|
"hht-implicit-dynamics");
|
|
hdf5_internal::WriteUint64Attribute(metadata.Get(), "schema_version",
|
|
SchemaVersion(frame));
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(),
|
|
"time_integration_formulation",
|
|
IntegrationFormulation(frame));
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "mass_formulation",
|
|
"nodal-lumped-kinetic");
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "internal_formulation",
|
|
"FESA-MITC4-total-lagrangian");
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "initial_state",
|
|
"zero-q-v-step-load-0+");
|
|
hdf5_internal::WriteStringAttribute(
|
|
metadata.Get(), "acceleration_convention",
|
|
"physical-endpoint-and-auxiliary-separated");
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "source_step_name",
|
|
step->source_step_name_raw);
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "step_name", "Step-1");
|
|
hdf5_internal::WriteStringAttribute(metadata.Get(), "unit_system_label",
|
|
"consistent-input-units");
|
|
hdf5_internal::WriteDoubleAttribute(metadata.Get(), "alpha", step->alpha);
|
|
hdf5_internal::WriteDoubleAttribute(metadata.Get(), "beta", step->beta);
|
|
hdf5_internal::WriteDoubleAttribute(metadata.Get(), "gamma", step->gamma);
|
|
hdf5_internal::WriteDoubleAttribute(metadata.Get(), "initial_increment",
|
|
step->initial_increment);
|
|
hdf5_internal::WriteDoubleAttribute(metadata.Get(), "time_period",
|
|
step->time_period);
|
|
hdf5_internal::WriteUint64Attribute(metadata.Get(), "increment_count",
|
|
step->increment_count);
|
|
hdf5_internal::WriteUint64Attribute(metadata.Get(), "maximum_increments",
|
|
step->maximum_increments);
|
|
(void)domain;
|
|
}
|
|
|
|
/// @brief Records exact accepted endpoint identity on a result dataset.
|
|
void WriteFrameAttributes(const hid_t dataset,
|
|
const DynamicAcceptedFrameState& frame) {
|
|
hdf5_internal::WriteUint64Attribute(dataset, "schema_version",
|
|
SchemaVersion(frame));
|
|
hdf5_internal::WriteStringAttribute(dataset, "step_name", "Step-1");
|
|
hdf5_internal::WriteUint64Attribute(dataset, "frame_index",
|
|
frame.frame_index);
|
|
hdf5_internal::WriteUint64Attribute(dataset, "increment_index",
|
|
frame.increment_index);
|
|
hdf5_internal::WriteDoubleAttribute(dataset, "time", frame.time);
|
|
hdf5_internal::WriteUint8Attribute(dataset, "is_initial",
|
|
frame.is_initial ? 1U : 0U);
|
|
}
|
|
|
|
/// @brief Writes or validates a result against the explicit schema plan.
|
|
void ProcessDataset(const hid_t file, const std::string& path,
|
|
const std::vector<hsize_t>& dimensions,
|
|
const std::vector<double>& values, const char* names,
|
|
const char* units, const char* coordinates,
|
|
const char* location,
|
|
const DynamicAcceptedFrameState& frame,
|
|
const std::string& source_step, const bool readback) {
|
|
Hdf5Handle dataset =
|
|
readback ? CheckDoubles(file, path, dimensions, values)
|
|
: hdf5_internal::WriteDoubleValues(file, path, dimensions,
|
|
values.data(), values.size());
|
|
const auto string_attribute = [&](const char* name,
|
|
const std::string& value) {
|
|
if (readback)
|
|
CheckString(dataset.Get(), name, value);
|
|
else
|
|
hdf5_internal::WriteStringAttribute(dataset.Get(), name, value);
|
|
};
|
|
string_attribute("component_names", names);
|
|
string_attribute("component_unit_dimensions", units);
|
|
string_attribute("coordinate_system", coordinates);
|
|
string_attribute("location", location);
|
|
string_attribute("source_step_name", source_step);
|
|
if (readback) {
|
|
CheckAttribute(dataset.Get(), "schema_version", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, SchemaVersion(frame));
|
|
CheckString(dataset.Get(), "step_name", "Step-1");
|
|
CheckAttribute(dataset.Get(), "frame_index", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, std::uint64_t{frame.frame_index});
|
|
CheckAttribute(dataset.Get(), "increment_index", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, std::uint64_t{frame.increment_index});
|
|
CheckAttribute(dataset.Get(), "time", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE,
|
|
frame.time);
|
|
CheckAttribute(dataset.Get(), "is_initial", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
static_cast<std::uint8_t>(frame.is_initial));
|
|
} else {
|
|
WriteFrameAttributes(dataset.Get(), frame);
|
|
}
|
|
if (path.find("/nodal/") != std::string::npos) {
|
|
string_attribute("node_identity_dataset", "/model/nodes");
|
|
}
|
|
const std::string leaf = path.substr(path.find_last_of('/') + 1U);
|
|
if (leaf == "displacement" || leaf == "velocity" || leaf == "acceleration" ||
|
|
leaf == "algorithmic_acceleration") {
|
|
string_attribute("rotation_representation", "total-rotation-vector-chart");
|
|
string_attribute(
|
|
"quantity_semantics",
|
|
leaf == "displacement" ? "global-translation-and-total-rotation-vector"
|
|
: leaf == "velocity" ? "global-translation-rate-and-chart-rate"
|
|
: leaf == "acceleration" ? "physical-endpoint-chart-acceleration"
|
|
: SchemaVersion(frame) == 2U
|
|
? "full-chart-HHT-filter-combination-not-predictor-driver"
|
|
: "auxiliary-HHT-chart-acceleration");
|
|
}
|
|
if (leaf == "triad") string_attribute("axis_names", "A,B,D");
|
|
if (path.find("/element/shell/") != std::string::npos) {
|
|
string_attribute("element_identity_dataset", "/model/elements");
|
|
string_attribute("source_element_type_dataset",
|
|
"/model/elements.source_element_type");
|
|
string_attribute("internal_formulation", "FESA-MITC4-total-lagrangian");
|
|
string_attribute("midsurface_location_dataset",
|
|
"/model/shell/midsurface_locations");
|
|
if (leaf == "local_frame") {
|
|
string_attribute("axis_names", "E1,E2,E3");
|
|
} else {
|
|
string_attribute(
|
|
"local_frame_dataset",
|
|
path.substr(0U, path.find_last_of('/') + 1U) + "local_frame");
|
|
}
|
|
if (leaf == "stress") {
|
|
string_attribute("section_position_dataset",
|
|
"/model/shell/section_positions");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @brief Flattens physical recovery in element and Gauss order.
|
|
std::vector<double> FlattenShell(const DynamicAcceptedFrameState& frame,
|
|
const int quantity) {
|
|
std::vector<double> values;
|
|
for (const ShellResultRow& row : frame.shell_results) {
|
|
if (quantity == 0)
|
|
for (const auto& a : row.local_frame)
|
|
values.insert(values.end(), a.begin(), a.end());
|
|
if (quantity == 1)
|
|
values.insert(values.end(), row.generalized_strain.begin(),
|
|
row.generalized_strain.end());
|
|
if (quantity == 2)
|
|
values.insert(values.end(), row.section_resultant.begin(),
|
|
row.section_resultant.end());
|
|
if (quantity == 3)
|
|
for (const auto& s : row.stress)
|
|
values.insert(values.end(), s.components.begin(), s.components.end());
|
|
}
|
|
return values;
|
|
}
|
|
|
|
/// @brief Writes or reads back all mandatory accepted-endpoint quantities.
|
|
void WriteFrame(const hid_t file, const Domain& domain,
|
|
const DynamicAcceptedFrameState& frame,
|
|
const bool readback = false) {
|
|
const hsize_t nodes = static_cast<hsize_t>(domain.Nodes().size());
|
|
const hsize_t elements = static_cast<hsize_t>(domain.ShellElements().Size());
|
|
const std::string root =
|
|
"/steps/Step-1/frames/" + std::to_string(frame.frame_index);
|
|
if (!readback) {
|
|
(void)hdf5_internal::CreateGroup(file, (root + "/nodal").c_str());
|
|
(void)hdf5_internal::CreateGroup(file, (root + "/element/shell").c_str());
|
|
(void)hdf5_internal::CreateGroup(file, (root + "/global").c_str());
|
|
}
|
|
const auto WriteDataset = [&](const hid_t target, const std::string& path,
|
|
const std::vector<hsize_t>& dimensions,
|
|
const std::vector<double>& values,
|
|
const char* names, const char* units,
|
|
const char* coordinates, const char* location,
|
|
const DynamicAcceptedFrameState& endpoint) {
|
|
ProcessDataset(target, path, dimensions, values, names, units, coordinates,
|
|
location, endpoint, domain.Dynamic()->source_step_name_raw,
|
|
readback);
|
|
};
|
|
constexpr const char* kGeneralized = "F1,F2,F3,MPHI1,MPHI2,MPHI3";
|
|
constexpr const char* kGeneralizedUnits =
|
|
"force,force,force,force*length,force*length,force*length";
|
|
const std::vector<hsize_t> nodal{nodes, 6U};
|
|
WriteDataset(file, root + "/nodal/displacement", nodal,
|
|
CopyVectorValues(frame.generalized_coordinates), "UX,UY,UZ,URX,URY,URZ",
|
|
"length,length,length,1,1,1", "global-cartesian", "nodal",
|
|
frame);
|
|
WriteDataset(file, root + "/nodal/velocity", nodal, CopyVectorValues(frame.velocity),
|
|
"V1,V2,V3,PHIDOT1,PHIDOT2,PHIDOT3",
|
|
"length/time,length/time,length/time,1/time,1/time,1/time",
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(
|
|
file, root + "/nodal/acceleration", nodal, CopyVectorValues(frame.acceleration),
|
|
"A1,A2,A3,PHIDDOT1,PHIDDOT2,PHIDDOT3",
|
|
"length/time^2,length/time^2,length/time^2,1/time^2,1/time^2,1/time^2",
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(
|
|
file, root + "/nodal/algorithmic_acceleration", nodal,
|
|
CopyVectorValues(frame.algorithmic_acceleration), "AUX1,AUX2,AUX3,AUX4,AUX5,AUX6",
|
|
"length/time^2,length/time^2,length/time^2,1/time^2,1/time^2,1/time^2",
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/angular_velocity", {nodes, 3U},
|
|
CopyVectorValues(frame.angular_velocity), "OMEGA1,OMEGA2,OMEGA3",
|
|
"1/time,1/time,1/time", "global-cartesian", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/angular_acceleration", {nodes, 3U},
|
|
CopyVectorValues(frame.angular_acceleration),
|
|
"OMEGADOT1,OMEGADOT2,OMEGADOT3", "1/time^2,1/time^2,1/time^2",
|
|
"global-cartesian", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/triad", {nodes, 3U, 3U},
|
|
FlattenNodalTriads(frame.nodal_triad), "X,Y,Z", "1,1,1", "global-cartesian",
|
|
"nodal", frame);
|
|
WriteDataset(file, root + "/nodal/director", {nodes, 3U},
|
|
FlattenNodalDirectors(frame.nodal_director), "D1,D2,D3", "1,1,1", "global-cartesian",
|
|
"nodal", frame);
|
|
WriteDataset(file, root + "/nodal/generalized_inertia_force", nodal,
|
|
CopyVectorValues(frame.inertia_force), kGeneralized, kGeneralizedUnits,
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/generalized_internal_force", nodal,
|
|
CopyVectorValues(frame.internal_force), kGeneralized, kGeneralizedUnits,
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/generalized_external_force", nodal,
|
|
CopyVectorValues(frame.external_force), kGeneralized, kGeneralizedUnits,
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/generalized_residual", nodal,
|
|
CopyVectorValues(frame.residual), kGeneralized, kGeneralizedUnits,
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/generalized_reaction", nodal,
|
|
CopyVectorValues(frame.reaction), kGeneralized, kGeneralizedUnits,
|
|
"fixed-initial-global-chart", "nodal", frame);
|
|
WriteDataset(file, root + "/nodal/reaction", nodal,
|
|
SpatialReactionValues(frame.generalized_coordinates,
|
|
frame.reaction,
|
|
frame.nodal_internal_order.size()),
|
|
"RF1,RF2,RF3,RM1,RM2,RM3", kGeneralizedUnits, "global-cartesian",
|
|
"nodal", frame);
|
|
const std::string shell = root + "/element/shell/";
|
|
WriteDataset(file, shell + "local_frame", {elements, 4U, 3U, 3U},
|
|
FlattenShell(frame, 0), "X,Y,Z", "1,1,1", "global-cartesian",
|
|
"shell-local-frame", frame);
|
|
WriteDataset(file, shell + "generalized_strain", {elements, 4U, 8U},
|
|
FlattenShell(frame, 1), "E11,E22,G12,K11,K22,K12,G13,G23",
|
|
"1,1,1,1/length,1/length,1/length,1,1", "shell-local",
|
|
"midsurface", frame);
|
|
WriteDataset(file, shell + "section_resultant", {elements, 4U, 8U},
|
|
FlattenShell(frame, 2), "N11,N22,N12,M11,M22,M12,Q13,Q23",
|
|
"force/length,force/length,force/length,force,force,force,force/"
|
|
"length,force/length",
|
|
"shell-local", "midsurface", frame);
|
|
WriteDataset(file, shell + "stress", {elements, 4U, 3U, 3U},
|
|
FlattenShell(frame, 3), "S11,S22,S12",
|
|
"force/length^2,force/length^2,force/length^2", "shell-local",
|
|
"section-position", frame);
|
|
WriteDataset(
|
|
file, root + "/global/energy", {5U},
|
|
{frame.kinetic_energy, frame.physical_strain_energy,
|
|
frame.drilling_energy, frame.external_work, frame.energy_defect},
|
|
"KINETIC_ENERGY,PHYSICAL_STRAIN_ENERGY,DRILLING_ENERGY,EXTERNAL_WORK,"
|
|
"ENERGY_DEFECT",
|
|
"force*length,force*length,force*length,force*length,force*length",
|
|
"global", "global", frame);
|
|
WriteDataset(file, root + "/global/equilibrium", {6U},
|
|
std::vector<double>{frame.global_equilibrium.begin(),
|
|
frame.global_equilibrium.end()},
|
|
"FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3",
|
|
kGeneralizedUnits, "global-cartesian", "global-balance-error",
|
|
frame);
|
|
}
|
|
|
|
/// @brief Writes ordinal and exact-time accepted frame identities.
|
|
void WriteAcceptedFrames(const hid_t file,
|
|
const std::vector<DynamicAcceptedFrameState>& frames) {
|
|
std::vector<AcceptedFrameRow> rows;
|
|
rows.reserve(frames.size());
|
|
for (const auto& frame : frames)
|
|
rows.push_back({frame.frame_index, frame.increment_index, frame.time,
|
|
frame.iteration_count,
|
|
static_cast<std::uint8_t>(frame.is_initial)});
|
|
Hdf5Handle type{hdf5_internal::RequireHdf5Id(
|
|
H5Tcreate(H5T_COMPOUND, sizeof(AcceptedFrameRow)),
|
|
"Unable to create accepted-frame type."),
|
|
H5Tclose};
|
|
hdf5_internal::RequireHdf5(
|
|
H5Tinsert(type.Get(), "frame_index",
|
|
HOFFSET(AcceptedFrameRow, frame_index), H5T_STD_U64LE),
|
|
"Unable to define accepted-frame field.");
|
|
hdf5_internal::RequireHdf5(
|
|
H5Tinsert(type.Get(), "increment_index",
|
|
HOFFSET(AcceptedFrameRow, increment_index), H5T_STD_U64LE),
|
|
"Unable to define accepted-frame field.");
|
|
hdf5_internal::RequireHdf5(
|
|
H5Tinsert(type.Get(), "time", HOFFSET(AcceptedFrameRow, time),
|
|
H5T_IEEE_F64LE),
|
|
"Unable to define accepted-frame field.");
|
|
hdf5_internal::RequireHdf5(
|
|
H5Tinsert(type.Get(), "iteration_count",
|
|
HOFFSET(AcceptedFrameRow, iteration_count), H5T_STD_U64LE),
|
|
"Unable to define accepted-frame field.");
|
|
hdf5_internal::RequireHdf5(
|
|
H5Tinsert(type.Get(), "is_initial", HOFFSET(AcceptedFrameRow, is_initial),
|
|
H5T_STD_U8LE),
|
|
"Unable to define accepted-frame field.");
|
|
(void)hdf5_internal::WriteCompoundDataset(
|
|
file, "/steps/Step-1/accepted_frames", rows.size(), type.Get(),
|
|
type.Get(), rows.data());
|
|
}
|
|
|
|
/// @brief Writes sorted diagnostics with dynamic schema field names.
|
|
void WriteDiagnostics(const hid_t file,
|
|
const std::vector<Diagnostic>& input_diagnostics) {
|
|
std::vector<Diagnostic> diagnostics = input_diagnostics;
|
|
SortDiagnostics(diagnostics);
|
|
std::vector<std::string> files;
|
|
std::vector<DiagnosticRow> rows;
|
|
files.reserve(diagnostics.size());
|
|
rows.reserve(diagnostics.size());
|
|
for (const Diagnostic& diagnostic : diagnostics) {
|
|
files.push_back(
|
|
hdf5_internal::NormalizedPathString(diagnostic.location.file));
|
|
}
|
|
for (std::size_t index = 0U; index < diagnostics.size(); ++index) {
|
|
const Diagnostic& diagnostic = diagnostics[index];
|
|
rows.push_back(
|
|
{diagnostic.severity == Severity::kWarning ? "warning" : "error",
|
|
diagnostic.code.c_str(), files[index].c_str(),
|
|
static_cast<std::uint64_t>(diagnostic.location.line),
|
|
diagnostic.keyword.c_str(), diagnostic.entity_identity.c_str(),
|
|
diagnostic.message.c_str()});
|
|
}
|
|
auto string_type = hdf5_internal::MakeUtf8StringType();
|
|
Hdf5Handle type{hdf5_internal::RequireHdf5Id(
|
|
H5Tcreate(H5T_COMPOUND, sizeof(DiagnosticRow)),
|
|
"Unable to create dynamic diagnostic type."),
|
|
H5Tclose};
|
|
const auto add = [&](const char* name, const std::size_t offset,
|
|
const hid_t value_type) {
|
|
hdf5_internal::RequireHdf5(H5Tinsert(type.Get(), name, offset, value_type),
|
|
"Unable to define dynamic diagnostic field.");
|
|
};
|
|
add("severity", HOFFSET(DiagnosticRow, severity), string_type.Get());
|
|
add("code", HOFFSET(DiagnosticRow, code), string_type.Get());
|
|
add("source_file", HOFFSET(DiagnosticRow, file), string_type.Get());
|
|
add("source_line", HOFFSET(DiagnosticRow, line), H5T_STD_U64LE);
|
|
add("keyword", HOFFSET(DiagnosticRow, keyword), string_type.Get());
|
|
add("entity_identity", HOFFSET(DiagnosticRow, entity_identity),
|
|
string_type.Get());
|
|
add("message", HOFFSET(DiagnosticRow, message), string_type.Get());
|
|
(void)hdf5_internal::WriteCompoundDataset(
|
|
file, "/diagnostics", rows.size(), type.Get(), type.Get(), rows.data());
|
|
}
|
|
|
|
/// @brief Checks every source-model field written by the shared model writer.
|
|
void CheckModel(const hid_t file, const Domain& domain,
|
|
const hdf5_internal::WriterModelData& model) {
|
|
const hsize_t count = static_cast<hsize_t>(domain.Nodes().size());
|
|
auto nodes = OpenDataset(file, "/model/nodes", {count});
|
|
CheckMembers(nodes.Get(), {"internal_node_id", "instance_name",
|
|
"source_label", "coordinates"});
|
|
std::vector<std::uint64_t> ids;
|
|
std::vector<std::string> instances;
|
|
std::vector<std::string> labels;
|
|
std::vector<std::array<double, 3>> coordinates;
|
|
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
|
|
ids.push_back(index);
|
|
instances.push_back(domain.Nodes()[index].source_id.instance_name);
|
|
labels.push_back(domain.Nodes()[index].source_id.source_label_text);
|
|
coordinates.push_back(domain.Nodes()[index].coordinates);
|
|
}
|
|
const hsize_t three = 3U;
|
|
Hdf5Handle triple_file{H5Tarray_create2(H5T_IEEE_F64LE, 1, &three), H5Tclose};
|
|
Hdf5Handle triple_memory{H5Tarray_create2(H5T_NATIVE_DOUBLE, 1, &three),
|
|
H5Tclose};
|
|
CheckColumn(nodes.Get(), "internal_node_id", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
ids);
|
|
CheckStringColumn(nodes.Get(), "instance_name", instances);
|
|
CheckStringColumn(nodes.Get(), "source_label", labels);
|
|
CheckColumn(nodes.Get(), "coordinates", triple_file.Get(),
|
|
triple_memory.Get(), coordinates);
|
|
CheckString(nodes.Get(), "coordinate_system", "global-cartesian");
|
|
CheckString(nodes.Get(), "units_label", "length");
|
|
|
|
auto elements = OpenDataset(file, "/model/elements",
|
|
{static_cast<hsize_t>(domain.Elements().Size())});
|
|
CheckMembers(
|
|
elements.Get(),
|
|
{"internal_element_id", "instance_name", "source_label",
|
|
"source_element_type", "internal_formulation", "node_internal_ids",
|
|
"shell_section_internal_id", "material_internal_id"});
|
|
ids.clear();
|
|
instances.clear();
|
|
labels.clear();
|
|
std::vector<std::string> source_types;
|
|
std::vector<std::string> formulations;
|
|
std::vector<std::uint64_t> sections;
|
|
std::vector<std::uint64_t> materials;
|
|
std::vector<std::array<std::uint64_t, 4>> connectivity;
|
|
for (std::size_t index = 0U; index < domain.ShellElements().Size(); ++index) {
|
|
const auto& element = domain.ShellElements()[index];
|
|
ids.push_back(index);
|
|
instances.push_back(element.source_id.instance_name);
|
|
labels.push_back(element.source_id.source_label_text);
|
|
source_types.push_back(
|
|
hdf5_internal::ShellSourceTypeName(element.source_type));
|
|
formulations.push_back("FESA-MITC4");
|
|
sections.push_back(element.section_index);
|
|
materials.push_back(element.material_index);
|
|
connectivity.push_back({element.node_indices[0U], element.node_indices[1U],
|
|
element.node_indices[2U],
|
|
element.node_indices[3U]});
|
|
}
|
|
const hsize_t four = 4U;
|
|
Hdf5Handle nodes_file{H5Tarray_create2(H5T_STD_U64LE, 1, &four), H5Tclose};
|
|
Hdf5Handle nodes_memory{H5Tarray_create2(H5T_NATIVE_UINT64, 1, &four),
|
|
H5Tclose};
|
|
CheckColumn(elements.Get(), "internal_element_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, ids);
|
|
CheckStringColumn(elements.Get(), "instance_name", instances);
|
|
CheckStringColumn(elements.Get(), "source_label", labels);
|
|
CheckStringColumn(elements.Get(), "source_element_type", source_types);
|
|
CheckStringColumn(elements.Get(), "internal_formulation", formulations);
|
|
CheckColumn(elements.Get(), "node_internal_ids", nodes_file.Get(),
|
|
nodes_memory.Get(), connectivity);
|
|
CheckColumn(elements.Get(), "shell_section_internal_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, sections);
|
|
CheckColumn(elements.Get(), "material_internal_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, materials);
|
|
|
|
auto material_table =
|
|
OpenDataset(file, "/model/shell/materials",
|
|
{static_cast<hsize_t>(domain.Materials().Size())});
|
|
CheckMembers(material_table.Get(),
|
|
{"internal_material_id", "name", "E", "nu"});
|
|
ids.clear();
|
|
labels.clear();
|
|
std::vector<double> youngs;
|
|
std::vector<double> poisson;
|
|
for (std::size_t index = 0U; index < domain.Materials().Size(); ++index) {
|
|
const auto& material = domain.LinearElasticMaterials()[index];
|
|
ids.push_back(index);
|
|
labels.push_back(material.name);
|
|
youngs.push_back(material.youngs_modulus);
|
|
poisson.push_back(material.poisson_ratio);
|
|
}
|
|
CheckColumn(material_table.Get(), "internal_material_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, ids);
|
|
CheckStringColumn(material_table.Get(), "name", labels);
|
|
CheckColumn(material_table.Get(), "E", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE,
|
|
youngs);
|
|
CheckColumn(material_table.Get(), "nu", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE,
|
|
poisson);
|
|
auto density_table =
|
|
OpenDataset(file, "/model/material_density",
|
|
{static_cast<hsize_t>(domain.Materials().Size())});
|
|
CheckMembers(density_table.Get(), {"internal_material_id", "rho"});
|
|
std::vector<double> densities;
|
|
for (const auto& density : domain.Densities())
|
|
densities.push_back(density.rho);
|
|
CheckColumn(density_table.Get(), "internal_material_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, ids);
|
|
CheckColumn(density_table.Get(), "rho", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE,
|
|
densities);
|
|
|
|
auto section_table =
|
|
OpenDataset(file, "/model/shell/sections",
|
|
{static_cast<hsize_t>(domain.ShellSections().Size())});
|
|
CheckMembers(section_table.Get(),
|
|
{"internal_section_id", "source_file", "source_line",
|
|
"source_elset", "material_internal_id", "thickness"});
|
|
ids.clear();
|
|
labels.clear();
|
|
materials.clear();
|
|
std::vector<std::string> files;
|
|
std::vector<std::uint64_t> lines;
|
|
std::vector<double> thickness;
|
|
for (std::size_t index = 0U; index < domain.ShellSections().Size(); ++index) {
|
|
const auto& section = domain.ShellSections()[index];
|
|
ids.push_back(index);
|
|
files.push_back(hdf5_internal::NormalizedPathString(section.location.file));
|
|
lines.push_back(section.location.line);
|
|
labels.push_back(section.name);
|
|
materials.push_back(section.material_index);
|
|
thickness.push_back(section.thickness);
|
|
}
|
|
CheckColumn(section_table.Get(), "internal_section_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, ids);
|
|
CheckStringColumn(section_table.Get(), "source_file", files);
|
|
CheckColumn(section_table.Get(), "source_line", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, lines);
|
|
CheckStringColumn(section_table.Get(), "source_elset", labels);
|
|
CheckColumn(section_table.Get(), "material_internal_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, materials);
|
|
CheckColumn(section_table.Get(), "thickness", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, thickness);
|
|
|
|
auto mask = OpenDataset(file, "/model/nodal_constraint_mask", {count, 6U});
|
|
Hdf5Handle mask_type{H5Dget_type(mask.Get()), H5Tclose};
|
|
Check(H5Tequal(mask_type.Get(), H5T_STD_U8LE) > 0,
|
|
"Constraint dtype mismatch.");
|
|
std::vector<std::uint8_t> actual_mask(model.constraint_mask.size());
|
|
hdf5_internal::RequireHdf5(H5Dread(mask.Get(), H5T_NATIVE_UINT8, H5S_ALL,
|
|
H5S_ALL, H5P_DEFAULT, actual_mask.data()),
|
|
"Unable to read constraint mask.");
|
|
Check(actual_mask == model.constraint_mask, "Constraint mask mismatch.");
|
|
(void)CheckDoubles(file, "/model/prescribed_displacement", {count, 6U},
|
|
model.prescribed_displacement);
|
|
std::vector<double> directors;
|
|
std::vector<double> frames;
|
|
hdf5_internal::AppendShellReferenceFrames(domain, directors, frames);
|
|
(void)CheckDoubles(file, "/model/shell/nodal_director", {count, 3U},
|
|
directors);
|
|
(void)CheckDoubles(file, "/model/shell/nodal_frame", {count, 3U, 3U}, frames);
|
|
const double gauss = 1.0 / std::sqrt(3.0);
|
|
(void)CheckDoubles(
|
|
file, "/model/shell/midsurface_locations", {4U, 2U},
|
|
{-gauss, -gauss, gauss, -gauss, gauss, gauss, -gauss, gauss});
|
|
(void)CheckDoubles(file, "/model/shell/section_positions", {3U},
|
|
{-1.0, 0.0, 1.0});
|
|
}
|
|
|
|
/// @brief Checks complete accepted-frame table and diagnostic identities.
|
|
void CheckTables(const hid_t file, const DynamicAnalysisState& state,
|
|
std::vector<Diagnostic> diagnostics) {
|
|
auto table =
|
|
OpenDataset(file, "/steps/Step-1/accepted_frames",
|
|
{static_cast<hsize_t>(state.AcceptedFrames().size())});
|
|
CheckMembers(table.Get(), {"frame_index", "increment_index", "time",
|
|
"iteration_count", "is_initial"});
|
|
std::vector<std::uint64_t> frames;
|
|
std::vector<std::uint64_t> increments;
|
|
std::vector<std::uint64_t> iterations;
|
|
std::vector<double> times;
|
|
std::vector<std::uint8_t> initial;
|
|
for (const auto& frame : state.AcceptedFrames()) {
|
|
frames.push_back(frame.frame_index);
|
|
increments.push_back(frame.increment_index);
|
|
iterations.push_back(frame.iteration_count);
|
|
times.push_back(frame.time);
|
|
initial.push_back(static_cast<std::uint8_t>(frame.is_initial));
|
|
}
|
|
CheckColumn(table.Get(), "frame_index", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
frames);
|
|
CheckColumn(table.Get(), "increment_index", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
increments);
|
|
CheckColumn(table.Get(), "iteration_count", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
iterations);
|
|
CheckColumn(table.Get(), "time", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE, times);
|
|
CheckColumn(table.Get(), "is_initial", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
initial);
|
|
SortDiagnostics(diagnostics);
|
|
auto diagnostic_table = OpenDataset(
|
|
file, "/diagnostics", {static_cast<hsize_t>(diagnostics.size())});
|
|
CheckMembers(diagnostic_table.Get(),
|
|
{"severity", "code", "source_file", "source_line", "keyword",
|
|
"entity_identity", "message"});
|
|
std::vector<std::string> severity;
|
|
std::vector<std::string> codes;
|
|
std::vector<std::string> files;
|
|
std::vector<std::uint64_t> lines;
|
|
std::vector<std::string> keywords;
|
|
std::vector<std::string> identities;
|
|
std::vector<std::string> messages;
|
|
for (const auto& diagnostic : diagnostics) {
|
|
severity.push_back(diagnostic.severity == Severity::kWarning ? "warning"
|
|
: "error");
|
|
codes.push_back(diagnostic.code);
|
|
files.push_back(
|
|
hdf5_internal::NormalizedPathString(diagnostic.location.file));
|
|
lines.push_back(diagnostic.location.line);
|
|
keywords.push_back(diagnostic.keyword);
|
|
identities.push_back(diagnostic.entity_identity);
|
|
messages.push_back(diagnostic.message);
|
|
}
|
|
CheckStringColumn(diagnostic_table.Get(), "severity", severity);
|
|
CheckStringColumn(diagnostic_table.Get(), "code", codes);
|
|
CheckStringColumn(diagnostic_table.Get(), "source_file", files);
|
|
CheckColumn(diagnostic_table.Get(), "source_line", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, lines);
|
|
CheckStringColumn(diagnostic_table.Get(), "keyword", keywords);
|
|
CheckStringColumn(diagnostic_table.Get(), "entity_identity", identities);
|
|
CheckStringColumn(diagnostic_table.Get(), "message", messages);
|
|
}
|
|
|
|
/// @brief Reopens and verifies the complete dynamic publication candidate.
|
|
Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
|
const DynamicAnalysisState& state,
|
|
const hdf5_internal::WriterModelData& model,
|
|
const std::vector<Diagnostic>& diagnostics) {
|
|
try {
|
|
Hdf5Handle file{
|
|
hdf5_internal::RequireHdf5Id(
|
|
H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
|
|
"Unable to reopen dynamic candidate."),
|
|
H5Fclose};
|
|
Hdf5Handle metadata{hdf5_internal::RequireHdf5Id(
|
|
H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
|
"The dynamic metadata group is missing."),
|
|
H5Gclose};
|
|
const auto& step = *domain.Dynamic();
|
|
CheckString(metadata.Get(), "feature_id", "hht-implicit-dynamics");
|
|
CheckString(metadata.Get(), "analysis_procedure", "hht-implicit-dynamics");
|
|
CheckString(metadata.Get(), "mass_formulation", "nodal-lumped-kinetic");
|
|
CheckString(metadata.Get(), "internal_formulation",
|
|
"FESA-MITC4-total-lagrangian");
|
|
CheckString(metadata.Get(), "initial_state", "zero-q-v-step-load-0+");
|
|
CheckString(metadata.Get(), "acceleration_convention",
|
|
"physical-endpoint-and-auxiliary-separated");
|
|
CheckString(metadata.Get(), "source_step_name", step.source_step_name_raw);
|
|
CheckString(metadata.Get(), "step_name", "Step-1");
|
|
CheckString(metadata.Get(), "unit_system_label", "consistent-input-units");
|
|
CheckString(metadata.Get(), "solver_version", std::string{SolverVersion()});
|
|
CheckString(metadata.Get(), "source_input_identity",
|
|
hdf5_internal::SourceInputIdentity(domain));
|
|
CheckAttribute(metadata.Get(), "schema_version", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64,
|
|
SchemaVersion(state.AcceptedFrames().front()));
|
|
CheckString(metadata.Get(), "time_integration_formulation",
|
|
IntegrationFormulation(state.AcceptedFrames().front()));
|
|
CheckAttribute(metadata.Get(), "increment_count", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, std::uint64_t{step.increment_count});
|
|
CheckAttribute(metadata.Get(), "maximum_increments", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, std::uint64_t{step.maximum_increments});
|
|
for (const auto& entry : std::vector<std::pair<const char*, double>>{
|
|
{"alpha", step.alpha},
|
|
{"beta", step.beta},
|
|
{"gamma", step.gamma},
|
|
{"initial_increment", step.initial_increment},
|
|
{"time_period", step.time_period}}) {
|
|
CheckAttribute(metadata.Get(), entry.first, H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, entry.second);
|
|
}
|
|
CheckModel(file.Get(), domain, model);
|
|
CheckTables(file.Get(), state, diagnostics);
|
|
Hdf5Handle frames{H5Gopen2(file.Get(), "/steps/Step-1/frames", H5P_DEFAULT),
|
|
H5Gclose};
|
|
H5G_info_t information{};
|
|
hdf5_internal::RequireHdf5(H5Gget_info(frames.Get(), &information),
|
|
"Unable to inspect accepted frame group.");
|
|
Check(information.nlinks == state.AcceptedFrames().size(),
|
|
"Unexpected frame group inventory.");
|
|
for (const auto& frame : state.AcceptedFrames()) {
|
|
WriteFrame(file.Get(), domain, frame, true);
|
|
}
|
|
return Status::Ok();
|
|
} catch (const std::exception& error) {
|
|
return Failure("dynamic-output-schema", error.what());
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status DynamicHdf5ResultsWriter::Write(
|
|
const std::filesystem::path& output_path, const Domain& domain,
|
|
const DynamicAnalysisState& state,
|
|
const std::vector<Diagnostic>& diagnostics) {
|
|
if (output_path.empty() || output_path.filename().empty() ||
|
|
!state.IsInitialized() || state.AcceptedFrames().empty() ||
|
|
domain.Dynamic() == nullptr)
|
|
return Failure("dynamic-output-schema",
|
|
"Dynamic output requires a named path, initialized state, "
|
|
"and accepted frames.");
|
|
try {
|
|
hdf5_internal::Hdf5ErrorSilencer silence;
|
|
const auto& step = *domain.Dynamic();
|
|
Check(state.AcceptedFrames().size() ==
|
|
static_cast<std::size_t>(step.increment_count) + 1U &&
|
|
state.AcceptedFrames().back().time == step.time_period,
|
|
"Dynamic output requires the complete accepted history.");
|
|
auto analysis_model = AnalysisModel::Create(domain);
|
|
Check(analysis_model.HasValue(), "Dynamic model inventory is invalid.");
|
|
auto dofs_result = DofManager::Create(analysis_model.Value());
|
|
Check(dofs_result.HasValue(), "Dynamic constraint inventory is invalid.");
|
|
const auto& dofs = dofs_result.Value();
|
|
auto validated_state = DynamicAnalysisState::Create(domain, dofs);
|
|
Check(validated_state.HasValue(),
|
|
"Dynamic schedule or inventory is invalid.");
|
|
for (const auto& frame : state.AcceptedFrames()) {
|
|
Check(validated_state.Value().CommitAcceptedFrame(frame).IsOk(),
|
|
"Dynamic frame does not match the output Domain.");
|
|
}
|
|
Check(
|
|
domain.Densities().size() == domain.Materials().Size() &&
|
|
domain.LinearElasticMaterials().Size() == domain.Materials().Size(),
|
|
"Material and density inventories differ.");
|
|
for (std::size_t index = 0U; index < domain.Densities().size(); ++index) {
|
|
const auto& density = domain.Densities()[index];
|
|
Check(density.material_index == index && std::isfinite(density.rho) &&
|
|
density.rho > 0.0,
|
|
"Material density identity or value is invalid.");
|
|
}
|
|
hdf5_internal::WriterModelData model{};
|
|
model.constraint_mask.assign(dofs.FullDofCount(), 0U);
|
|
model.prescribed_displacement.assign(dofs.FullDofCount(), 0.0);
|
|
for (const std::size_t dof : dofs.ConstrainedDofs()) {
|
|
model.constraint_mask[dof] = 1U;
|
|
}
|
|
const std::filesystem::path candidate_path =
|
|
hdf5_internal::MakeCandidatePath(output_path);
|
|
hdf5_internal::CandidateFileGuard cleanup{candidate_path};
|
|
Hdf5Handle file{hdf5_internal::RequireHdf5Id(
|
|
H5Fcreate(candidate_path.string().c_str(), H5F_ACC_EXCL,
|
|
H5P_DEFAULT, H5P_DEFAULT),
|
|
"Unable to create dynamic candidate."),
|
|
H5Fclose};
|
|
(void)hdf5_internal::CreateGroup(file.Get(), "/model/shell");
|
|
hdf5_internal::WriteModel(file.Get(), domain, model);
|
|
WriteMetadata(file.Get(), domain, state.AcceptedFrames().front());
|
|
WriteDensity(file.Get(), domain);
|
|
(void)hdf5_internal::CreateGroup(file.Get(), "/steps");
|
|
(void)hdf5_internal::CreateGroup(file.Get(), "/steps/Step-1");
|
|
WriteAcceptedFrames(file.Get(), state.AcceptedFrames());
|
|
for (const auto& frame : state.AcceptedFrames()) {
|
|
WriteFrame(file.Get(), domain, frame);
|
|
}
|
|
WriteDiagnostics(file.Get(), diagnostics);
|
|
hdf5_internal::RequireHdf5(H5Fflush(file.Get(), H5F_SCOPE_GLOBAL),
|
|
"Unable to flush dynamic candidate.");
|
|
hdf5_internal::RequireHdf5(file.CloseChecked(),
|
|
"Unable to close dynamic candidate.");
|
|
const Status check =
|
|
SelfCheck(candidate_path, domain, state, model, diagnostics);
|
|
const Status finalization = hdf5_internal::AtomicFinalizeValidatedCandidate(
|
|
candidate_path, output_path, check);
|
|
if (finalization.IsOk()) cleanup.Release();
|
|
return finalization;
|
|
} catch (const Hdf5Failure& error) {
|
|
return Failure("dynamic-output-schema", error.what());
|
|
} catch (const std::exception& error) {
|
|
return Failure("hdf5-write-failure", error.what());
|
|
}
|
|
}
|
|
|
|
} // namespace fesa
|