feat(nonlinear-static-newton-raphson): step 9 - nonlinear-hdf5-schema-v1-writer
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef FESA_IO_HDF5_NONLINEAR_HDF5_RESULTS_WRITER_H_
|
||||||
|
#define FESA_IO_HDF5_NONLINEAR_HDF5_RESULTS_WRITER_H_
|
||||||
|
|
||||||
|
#include "fesa/results/nonlinear_results_writer.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Writes authoritative schema-v1 nonlinear HDF5 results atomically.
|
||||||
|
/// @note HDF5 and platform types remain private to the implementation.
|
||||||
|
class NonlinearHdf5ResultsWriter final : public NonlinearResultsWriter {
|
||||||
|
public:
|
||||||
|
/// @brief Writes and self-checks committed nonlinear frames before publish.
|
||||||
|
/// @return Success only after atomic replacement or structured output failure.
|
||||||
|
/// @note Factor-zero state remains metadata and is not an accepted frame.
|
||||||
|
Status Write(const std::filesystem::path& output_path,
|
||||||
|
const Domain& domain, const NonlinearAnalysisState& state,
|
||||||
|
const std::vector<Diagnostic>& diagnostics) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_IO_HDF5_NONLINEAR_HDF5_RESULTS_WRITER_H_
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef FESA_RESULTS_NONLINEAR_RESULTS_WRITER_H_
|
||||||
|
#define FESA_RESULTS_NONLINEAR_RESULTS_WRITER_H_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/nonlinear_analysis_state.h"
|
||||||
|
#include "fesa/core/diagnostic.h"
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Isolates nonlinear authoritative result storage from solver code.
|
||||||
|
class NonlinearResultsWriter {
|
||||||
|
public:
|
||||||
|
virtual ~NonlinearResultsWriter() = default;
|
||||||
|
|
||||||
|
/// @brief Writes complete accepted nonlinear frames.
|
||||||
|
/// @param output_path Final authoritative path in the model input directory.
|
||||||
|
/// @param domain Immutable source and semantic model identity.
|
||||||
|
/// @param state Committed state containing every accepted increment frame.
|
||||||
|
/// @param diagnostics Deterministically ordered run diagnostics.
|
||||||
|
/// @return Success only after backend-specific finalization completes.
|
||||||
|
virtual Status Write(const std::filesystem::path& output_path,
|
||||||
|
const Domain& domain,
|
||||||
|
const NonlinearAnalysisState& state,
|
||||||
|
const std::vector<Diagnostic>& diagnostics) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_RESULTS_NONLINEAR_RESULTS_WRITER_H_
|
||||||
@@ -33,6 +33,7 @@ add_library(
|
|||||||
io/abaqus/topology_mapper.cpp
|
io/abaqus/topology_mapper.cpp
|
||||||
io/hdf5/hdf5_atomic_file.cpp
|
io/hdf5/hdf5_atomic_file.cpp
|
||||||
io/hdf5/hdf5_model_writer.cpp
|
io/hdf5/hdf5_model_writer.cpp
|
||||||
|
io/hdf5/nonlinear_hdf5_results_writer.cpp
|
||||||
io/hdf5/hdf5_primitives.cpp
|
io/hdf5/hdf5_primitives.cpp
|
||||||
io/hdf5/hdf5_result_writer.cpp
|
io/hdf5/hdf5_result_writer.cpp
|
||||||
io/hdf5/hdf5_results_writer.cpp
|
io/hdf5/hdf5_results_writer.cpp
|
||||||
|
|||||||
@@ -138,6 +138,34 @@ void WriteUint64Attribute(const hid_t object, const char* name,
|
|||||||
"Unable to write an HDF5 integer attribute.");
|
"Unable to write an HDF5 integer attribute.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WriteUint8Attribute(const hid_t object, const char* name,
|
||||||
|
const std::uint8_t value) {
|
||||||
|
Hdf5Handle space{RequireHdf5Id(H5Screate(H5S_SCALAR),
|
||||||
|
"Unable to create an attribute space."),
|
||||||
|
H5Sclose};
|
||||||
|
Hdf5Handle attribute{
|
||||||
|
RequireHdf5Id(H5Acreate2(object, name, H5T_STD_U8LE, space.Get(),
|
||||||
|
H5P_DEFAULT, H5P_DEFAULT),
|
||||||
|
"Unable to create an HDF5 uint8 attribute."),
|
||||||
|
H5Aclose};
|
||||||
|
RequireHdf5(H5Awrite(attribute.Get(), H5T_NATIVE_UINT8, &value),
|
||||||
|
"Unable to write an HDF5 uint8 attribute.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteDoubleAttribute(const hid_t object, const char* name,
|
||||||
|
const double value) {
|
||||||
|
Hdf5Handle space{RequireHdf5Id(H5Screate(H5S_SCALAR),
|
||||||
|
"Unable to create an attribute space."),
|
||||||
|
H5Sclose};
|
||||||
|
Hdf5Handle attribute{
|
||||||
|
RequireHdf5Id(H5Acreate2(object, name, H5T_IEEE_F64LE, space.Get(),
|
||||||
|
H5P_DEFAULT, H5P_DEFAULT),
|
||||||
|
"Unable to create an HDF5 float64 attribute."),
|
||||||
|
H5Aclose};
|
||||||
|
RequireHdf5(H5Awrite(attribute.Get(), H5T_NATIVE_DOUBLE, &value),
|
||||||
|
"Unable to write an HDF5 float64 attribute.");
|
||||||
|
}
|
||||||
|
|
||||||
Hdf5Handle CreateGroup(const hid_t file, const char* path) {
|
Hdf5Handle CreateGroup(const hid_t file, const char* path) {
|
||||||
Hdf5Handle link_properties{RequireHdf5Id(H5Pcreate(H5P_LINK_CREATE),
|
Hdf5Handle link_properties{RequireHdf5Id(H5Pcreate(H5P_LINK_CREATE),
|
||||||
"Unable to create link properties."),
|
"Unable to create link properties."),
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ Hdf5Handle MakeUtf8StringType();
|
|||||||
void WriteStringAttribute(hid_t object, const char* name,
|
void WriteStringAttribute(hid_t object, const char* name,
|
||||||
const std::string& value);
|
const std::string& value);
|
||||||
void WriteUint64Attribute(hid_t object, const char* name, std::uint64_t value);
|
void WriteUint64Attribute(hid_t object, const char* name, std::uint64_t value);
|
||||||
|
void WriteUint8Attribute(hid_t object, const char* name, std::uint8_t value);
|
||||||
|
void WriteDoubleAttribute(hid_t object, const char* name, double value);
|
||||||
Hdf5Handle CreateGroup(hid_t file, const char* path);
|
Hdf5Handle CreateGroup(hid_t file, const char* path);
|
||||||
Hdf5Handle CreateDatasetSpace(const std::vector<hsize_t>& dimensions);
|
Hdf5Handle CreateDatasetSpace(const std::vector<hsize_t>& dimensions);
|
||||||
void WriteResultAttributes(hid_t dataset, const std::string& component_names,
|
void WriteResultAttributes(hid_t dataset, const std::string& component_names,
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ void RequireStringAttribute(const hid_t object, const char* name,
|
|||||||
RequireHdf5(H5free_memory(raw), "Unable to release an HDF5 string value.");
|
RequireHdf5(H5free_memory(raw), "Unable to release an HDF5 string value.");
|
||||||
}
|
}
|
||||||
if (actual != expected) {
|
if (actual != expected) {
|
||||||
throw Hdf5Failure{"An HDF5 string attribute has the wrong value."};
|
throw Hdf5Failure{"HDF5 string attribute '" + std::string{name} +
|
||||||
|
"' mismatch: expected '" + expected + "', got '" +
|
||||||
|
actual + "'."};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,9 +101,13 @@ void RequirePortableCompoundMember(const hid_t compound_type,
|
|||||||
name == "internal_material_id" || name == "internal_section_id" ||
|
name == "internal_material_id" || name == "internal_section_id" ||
|
||||||
name == "shell_section_internal_id" || name == "material_internal_id" ||
|
name == "shell_section_internal_id" || name == "material_internal_id" ||
|
||||||
name == "source_line" || name == "gauss_point_index" ||
|
name == "source_line" || name == "gauss_point_index" ||
|
||||||
name == "section_point_index" || name == "line";
|
name == "section_point_index" || name == "line" ||
|
||||||
|
name == "frame_index" || name == "increment_index" ||
|
||||||
|
name == "iteration_count";
|
||||||
const bool is_float64 = name == "x1" || name == "x2" || name == "S11" ||
|
const bool is_float64 = name == "x1" || name == "x2" || name == "S11" ||
|
||||||
name == "E" || name == "nu" || name == "thickness";
|
name == "E" || name == "nu" || name == "thickness" ||
|
||||||
|
name == "load_factor";
|
||||||
|
const bool is_uint8 = name == "is_factor_one";
|
||||||
const bool is_string =
|
const bool is_string =
|
||||||
name == "instance_name" || name == "source_label" ||
|
name == "instance_name" || name == "source_label" ||
|
||||||
name == "source_element_type" || name == "internal_formulation" ||
|
name == "source_element_type" || name == "internal_formulation" ||
|
||||||
@@ -126,6 +132,15 @@ void RequirePortableCompoundMember(const hid_t compound_type,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (is_uint8) {
|
||||||
|
if (H5Tget_class(member_type.Get()) != H5T_INTEGER ||
|
||||||
|
H5Tget_size(member_type.Get()) != sizeof(std::uint8_t) ||
|
||||||
|
H5Tget_sign(member_type.Get()) != H5T_SGN_NONE ||
|
||||||
|
H5Tequal(member_type.Get(), H5T_STD_U8LE) <= 0) {
|
||||||
|
throw Hdf5Failure{"A compound HDF5 field is not portable uint8."};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (is_string) {
|
if (is_string) {
|
||||||
if (H5Tget_class(member_type.Get()) != H5T_STRING ||
|
if (H5Tget_class(member_type.Get()) != H5T_STRING ||
|
||||||
H5Tis_variable_str(member_type.Get()) <= 0 ||
|
H5Tis_variable_str(member_type.Get()) <= 0 ||
|
||||||
@@ -342,13 +357,159 @@ void RequireCompoundDataset(const hid_t file, const char* path,
|
|||||||
RequireHdf5(H5free_memory(raw_name),
|
RequireHdf5(H5free_memory(raw_name),
|
||||||
"Unable to release a compound field name.");
|
"Unable to release a compound field name.");
|
||||||
if (actual_name != expected_members[index]) {
|
if (actual_name != expected_members[index]) {
|
||||||
throw Hdf5Failure{"A compound HDF5 field has the wrong identity."};
|
throw Hdf5Failure{"Compound HDF5 field identity mismatch: expected '" +
|
||||||
|
std::string{expected_members[index]} + "', got '" +
|
||||||
|
actual_name + "'."};
|
||||||
}
|
}
|
||||||
RequirePortableCompoundMember(type.Get(), static_cast<unsigned>(index),
|
RequirePortableCompoundMember(type.Get(), static_cast<unsigned>(index),
|
||||||
expected_members[index]);
|
expected_members[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RequireUint8Attribute(const hid_t object, const char* name,
|
||||||
|
const std::uint8_t expected) {
|
||||||
|
Hdf5Handle attribute{
|
||||||
|
RequireHdf5Id(H5Aopen(object, name, H5P_DEFAULT),
|
||||||
|
"A mandatory HDF5 uint8 attribute is missing."),
|
||||||
|
H5Aclose};
|
||||||
|
Hdf5Handle type{
|
||||||
|
RequireHdf5Id(H5Aget_type(attribute.Get()),
|
||||||
|
"Unable to inspect an HDF5 uint8 attribute type."),
|
||||||
|
H5Tclose};
|
||||||
|
if (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 Hdf5Failure{"An HDF5 attribute is not portable uint8."};
|
||||||
|
}
|
||||||
|
std::uint8_t actual = 0U;
|
||||||
|
RequireHdf5(H5Aread(attribute.Get(), H5T_NATIVE_UINT8, &actual),
|
||||||
|
"Unable to read an HDF5 uint8 attribute.");
|
||||||
|
if (actual != expected) {
|
||||||
|
throw Hdf5Failure{"An HDF5 uint8 attribute has the wrong value."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequireDoubleAttribute(const hid_t object, const char* name,
|
||||||
|
const double expected) {
|
||||||
|
Hdf5Handle attribute{
|
||||||
|
RequireHdf5Id(H5Aopen(object, name, H5P_DEFAULT),
|
||||||
|
"A mandatory HDF5 float64 attribute is missing."),
|
||||||
|
H5Aclose};
|
||||||
|
Hdf5Handle type{
|
||||||
|
RequireHdf5Id(H5Aget_type(attribute.Get()),
|
||||||
|
"Unable to inspect an HDF5 float64 attribute type."),
|
||||||
|
H5Tclose};
|
||||||
|
if (H5Tget_class(type.Get()) != H5T_FLOAT ||
|
||||||
|
H5Tget_size(type.Get()) != sizeof(double) ||
|
||||||
|
H5Tequal(type.Get(), H5T_IEEE_F64LE) <= 0) {
|
||||||
|
throw Hdf5Failure{"An HDF5 attribute is not portable float64."};
|
||||||
|
}
|
||||||
|
double actual = 0.0;
|
||||||
|
RequireHdf5(H5Aread(attribute.Get(), H5T_NATIVE_DOUBLE, &actual),
|
||||||
|
"Unable to read an HDF5 float64 attribute.");
|
||||||
|
if (actual != expected) {
|
||||||
|
throw Hdf5Failure{"An HDF5 float64 attribute has the wrong value."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequireSchemaV1NumericDataset(
|
||||||
|
const hid_t file, const char* path,
|
||||||
|
const std::vector<hsize_t>& expected_dimensions,
|
||||||
|
const char* component_names, const char* component_units,
|
||||||
|
const char* coordinate_system, const char* location,
|
||||||
|
const AcceptedFrameState* frame) {
|
||||||
|
auto dataset = OpenDatasetForCheck(file, path);
|
||||||
|
if (CheckedDimensions(dataset.Get()) != expected_dimensions) {
|
||||||
|
throw Hdf5Failure{"A schema-v1 numeric dataset has the wrong shape."};
|
||||||
|
}
|
||||||
|
Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()),
|
||||||
|
"Unable to inspect a schema-v1 dataset type."),
|
||||||
|
H5Tclose};
|
||||||
|
if (H5Tget_class(type.Get()) != H5T_FLOAT ||
|
||||||
|
H5Tget_size(type.Get()) != sizeof(double) ||
|
||||||
|
H5Tequal(type.Get(), H5T_IEEE_F64LE) <= 0) {
|
||||||
|
throw Hdf5Failure{"A schema-v1 numeric dataset is not float64."};
|
||||||
|
}
|
||||||
|
RequireUint64Attribute(dataset.Get(), "schema_version", 1U);
|
||||||
|
RequireStringAttribute(dataset.Get(), "component_names", component_names);
|
||||||
|
RequireStringAttribute(dataset.Get(), "component_unit_dimensions",
|
||||||
|
component_units);
|
||||||
|
RequireStringAttribute(dataset.Get(), "coordinate_system",
|
||||||
|
coordinate_system);
|
||||||
|
RequireStringAttribute(dataset.Get(), "location", location);
|
||||||
|
if (frame != nullptr) {
|
||||||
|
RequireStringAttribute(dataset.Get(), "step_name", "Step-1");
|
||||||
|
RequireUint64Attribute(dataset.Get(), "frame_index", frame->frame_index);
|
||||||
|
RequireUint64Attribute(dataset.Get(), "increment_index",
|
||||||
|
frame->increment_index);
|
||||||
|
RequireDoubleAttribute(dataset.Get(), "load_factor", frame->load_factor);
|
||||||
|
RequireUint8Attribute(dataset.Get(), "is_factor_one",
|
||||||
|
frame->is_factor_one ? 1U : 0U);
|
||||||
|
}
|
||||||
|
std::size_t value_count = 1U;
|
||||||
|
for (const hsize_t dimension : expected_dimensions) {
|
||||||
|
std::size_t next = 0U;
|
||||||
|
if (!SizeProductFits(value_count, static_cast<std::size_t>(dimension),
|
||||||
|
next)) {
|
||||||
|
throw Hdf5Failure{"A schema-v1 numeric shape overflows size_t."};
|
||||||
|
}
|
||||||
|
value_count = next;
|
||||||
|
}
|
||||||
|
std::vector<double> values(value_count);
|
||||||
|
if (!values.empty()) {
|
||||||
|
RequireHdf5(H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||||
|
H5P_DEFAULT, values.data()),
|
||||||
|
"Unable to read a schema-v1 numeric dataset.");
|
||||||
|
}
|
||||||
|
if (!std::all_of(values.begin(), values.end(),
|
||||||
|
[](const double value) { return std::isfinite(value); })) {
|
||||||
|
throw Hdf5Failure{"A schema-v1 numeric dataset contains a nonfinite value."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequireSchemaV1MaskDataset(const hid_t file,
|
||||||
|
const std::vector<hsize_t>& dimensions) {
|
||||||
|
auto dataset = OpenDatasetForCheck(file, "/model/nodal_constraint_mask");
|
||||||
|
if (CheckedDimensions(dataset.Get()) != dimensions) {
|
||||||
|
throw Hdf5Failure{"The schema-v1 constraint mask has the wrong shape."};
|
||||||
|
}
|
||||||
|
Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()),
|
||||||
|
"Unable to inspect the constraint mask type."),
|
||||||
|
H5Tclose};
|
||||||
|
if (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 Hdf5Failure{"The schema-v1 constraint mask is not uint8."};
|
||||||
|
}
|
||||||
|
RequireUint64Attribute(dataset.Get(), "schema_version", 1U);
|
||||||
|
RequireStringAttribute(dataset.Get(), "component_names", "UX,UY,UZ,URX,URY,URZ");
|
||||||
|
RequireStringAttribute(dataset.Get(), "component_unit_dimensions",
|
||||||
|
"1,1,1,1,1,1");
|
||||||
|
RequireStringAttribute(dataset.Get(), "coordinate_system", "global-cartesian");
|
||||||
|
RequireStringAttribute(dataset.Get(), "location", "nodal-constraint");
|
||||||
|
RequireStringAttribute(dataset.Get(), "value_meaning", "0=free,1=constrained");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequireSchemaV1ShellIdentity(const hid_t file, const char* path,
|
||||||
|
const bool has_section_positions,
|
||||||
|
const std::string& frame_root) {
|
||||||
|
auto dataset = OpenDatasetForCheck(file, path);
|
||||||
|
RequireStringAttribute(dataset.Get(), "source_element_type_dataset",
|
||||||
|
"/model/elements.source_element_type");
|
||||||
|
RequireStringAttribute(dataset.Get(), "internal_formulation",
|
||||||
|
"FESA-MITC4-total-lagrangian");
|
||||||
|
RequireStringAttribute(dataset.Get(), "midsurface_location_dataset",
|
||||||
|
"/model/shell/midsurface_locations");
|
||||||
|
RequireStringAttribute(dataset.Get(), "local_frame_dataset",
|
||||||
|
frame_root + "/element/shell/local_frame");
|
||||||
|
if (has_section_positions) {
|
||||||
|
RequireStringAttribute(dataset.Get(), "section_position_dataset",
|
||||||
|
"/model/shell/section_positions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Reopens the closed candidate and verifies its required external
|
/// @brief Reopens the closed candidate and verifies its required external
|
||||||
/// schema.
|
/// schema.
|
||||||
void SelfCheckFile(const std::filesystem::path& path, const Domain& domain,
|
void SelfCheckFile(const std::filesystem::path& path, const Domain& domain,
|
||||||
@@ -594,6 +755,175 @@ void SelfCheckFile(const std::filesystem::path& path, const Domain& domain,
|
|||||||
"Unable to close the read-only HDF5 schema self-check handle.");
|
"Unable to close the read-only HDF5 schema self-check handle.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NonlinearSelfCheckFile(const std::filesystem::path& path,
|
||||||
|
const Domain& domain,
|
||||||
|
const NonlinearAnalysisState& state,
|
||||||
|
const std::size_t diagnostic_count) {
|
||||||
|
if (H5Fis_hdf5(path.string().c_str()) <= 0) {
|
||||||
|
throw Hdf5Failure{"The nonlinear candidate is not an HDF5 file."};
|
||||||
|
}
|
||||||
|
Hdf5Handle file{
|
||||||
|
RequireHdf5Id(H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||||
|
"Unable to reopen nonlinear candidate read-only."),
|
||||||
|
H5Fclose};
|
||||||
|
Hdf5Handle metadata{
|
||||||
|
RequireHdf5Id(H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
||||||
|
"The nonlinear metadata group is missing."),
|
||||||
|
H5Gclose};
|
||||||
|
RequireUint64Attribute(metadata.Get(), "schema_version", 1U);
|
||||||
|
RequireStringAttribute(metadata.Get(), "feature_id",
|
||||||
|
"nonlinear-static-newton-raphson");
|
||||||
|
RequireStringAttribute(metadata.Get(), "solver_version",
|
||||||
|
std::string{SolverVersion()});
|
||||||
|
RequireStringAttribute(metadata.Get(), "source_input_identity",
|
||||||
|
SourceInputIdentity(domain));
|
||||||
|
RequireStringAttribute(metadata.Get(), "unit_system_label",
|
||||||
|
"user-consistent-unspecified");
|
||||||
|
RequireStringAttribute(
|
||||||
|
metadata.Get(), "coordinate_convention",
|
||||||
|
"global-cartesian; shell-local=fixed-initial-reference; "
|
||||||
|
"positive-thickness=+zeta");
|
||||||
|
RequireStringAttribute(metadata.Get(), "analysis_procedure",
|
||||||
|
"nonlinear-static-full-newton");
|
||||||
|
RequireStringAttribute(metadata.Get(), "internal_formulation",
|
||||||
|
"FESA-MITC4-total-lagrangian");
|
||||||
|
RequireStringAttribute(metadata.Get(), "integration_rule",
|
||||||
|
"2x2x2-gauss; mitc4-edge-midpoint-shear");
|
||||||
|
RequireStringAttribute(metadata.Get(), "step_name", "Step-1");
|
||||||
|
RequireStringAttribute(metadata.Get(), "frame_indexing",
|
||||||
|
"accepted-increment-order-zero-based");
|
||||||
|
RequireUint8Attribute(metadata.Get(), "factor_zero_is_frame", 0U);
|
||||||
|
RequireDoubleAttribute(metadata.Get(), "factor_zero_load_factor", 0.0);
|
||||||
|
|
||||||
|
const hsize_t node_count = static_cast<hsize_t>(domain.Nodes().size());
|
||||||
|
const hsize_t element_count =
|
||||||
|
static_cast<hsize_t>(domain.ShellElements().Size());
|
||||||
|
const std::vector<hsize_t> nodal_dimensions{node_count, kDofsPerNode};
|
||||||
|
RequireCompoundDataset(file.Get(), "/model/nodes", node_count,
|
||||||
|
{"internal_node_id", "instance_name", "source_label",
|
||||||
|
"coordinates"});
|
||||||
|
RequireCompoundDataset(
|
||||||
|
file.Get(), "/model/elements", element_count,
|
||||||
|
{"internal_element_id", "instance_name", "source_label",
|
||||||
|
"source_element_type", "internal_formulation", "node_internal_ids",
|
||||||
|
"shell_section_internal_id", "material_internal_id"});
|
||||||
|
RequireCompoundDataset(file.Get(), "/model/shell/materials",
|
||||||
|
static_cast<hsize_t>(domain.LinearElasticMaterials().Size()),
|
||||||
|
{"internal_material_id", "name", "E", "nu"});
|
||||||
|
RequireCompoundDataset(
|
||||||
|
file.Get(), "/model/shell/sections",
|
||||||
|
static_cast<hsize_t>(domain.ShellSections().Size()),
|
||||||
|
{"internal_section_id", "source_file", "source_line", "source_elset",
|
||||||
|
"material_internal_id", "thickness"});
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), "/model/shell/nodal_director", {node_count, 3U}, "D1,D2,D3",
|
||||||
|
"1,1,1", "global-cartesian", "nodal-initial-director", nullptr);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), "/model/shell/nodal_frame", {node_count, 3U, 3U}, "X,Y,Z",
|
||||||
|
"1,1,1", "global-cartesian", "nodal-initial-frame", nullptr);
|
||||||
|
RequireSchemaV1MaskDataset(file.Get(), nodal_dimensions);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), "/model/prescribed_displacement_start", nodal_dimensions,
|
||||||
|
"UX,UY,UZ,URX,URY,URZ", "length,length,length,1,1,1",
|
||||||
|
"global-cartesian", "nodal-prescribed-start", nullptr);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), "/model/prescribed_displacement_target", nodal_dimensions,
|
||||||
|
"UX,UY,UZ,URX,URY,URZ", "length,length,length,1,1,1",
|
||||||
|
"global-cartesian", "nodal-prescribed-target", nullptr);
|
||||||
|
RequireSchemaV1NumericDataset(file.Get(), "/model/shell/midsurface_locations",
|
||||||
|
{4U, 2U}, "XI,ETA", "1,1", "shell-natural",
|
||||||
|
"midsurface-location", nullptr);
|
||||||
|
RequireSchemaV1NumericDataset(file.Get(), "/model/shell/section_positions",
|
||||||
|
{3U}, "ZETA", "1", "shell-natural",
|
||||||
|
"section-position", nullptr);
|
||||||
|
|
||||||
|
const auto& frames = state.AcceptedFrames();
|
||||||
|
RequireCompoundDataset(file.Get(), "/steps/Step-1/accepted_frames",
|
||||||
|
static_cast<hsize_t>(frames.size()),
|
||||||
|
{"frame_index", "increment_index", "load_factor",
|
||||||
|
"iteration_count", "is_factor_one"});
|
||||||
|
if (H5Lexists(file.Get(), "/steps/Step-1/trial_history", H5P_DEFAULT) != 0) {
|
||||||
|
throw Hdf5Failure{"Schema v1 must not contain nonlinear trial history."};
|
||||||
|
}
|
||||||
|
for (const auto& frame : frames) {
|
||||||
|
const std::string root = "/steps/Step-1/frames/" +
|
||||||
|
std::to_string(frame.frame_index);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/nodal/displacement").c_str(), nodal_dimensions,
|
||||||
|
"UX,UY,UZ,URX,URY,URZ", "length,length,length,1,1,1",
|
||||||
|
"global-cartesian", "nodal", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/nodal/triad").c_str(), {node_count, 3U, 3U},
|
||||||
|
"X,Y,Z", "1,1,1", "global-cartesian", "nodal-triad", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/nodal/director").c_str(), {node_count, 3U},
|
||||||
|
"D1,D2,D3", "1,1,1", "global-cartesian", "nodal-director", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/nodal/generalized_internal_force").c_str(),
|
||||||
|
nodal_dimensions, "FI1,FI2,FI3,MI_PHI1,MI_PHI2,MI_PHI3",
|
||||||
|
"force,force,force,force*length,force*length,force*length",
|
||||||
|
"global-cartesian", "nodal", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/nodal/generalized_external_force").c_str(),
|
||||||
|
nodal_dimensions, "FE1,FE2,FE3,ME_PHI1,ME_PHI2,ME_PHI3",
|
||||||
|
"force,force,force,force*length,force*length,force*length",
|
||||||
|
"global-cartesian", "nodal", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/nodal/generalized_residual").c_str(),
|
||||||
|
nodal_dimensions, "RU1,RU2,RU3,RPHI1,RPHI2,RPHI3",
|
||||||
|
"force,force,force,force*length,force*length,force*length",
|
||||||
|
"global-cartesian", "nodal", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/nodal/reaction").c_str(), nodal_dimensions,
|
||||||
|
"RF1,RF2,RF3,RM1,RM2,RM3",
|
||||||
|
"force,force,force,force*length,force*length,force*length",
|
||||||
|
"global-cartesian", "nodal", &frame);
|
||||||
|
const std::string shell_root = root + "/element/shell";
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (shell_root + "/local_frame").c_str(),
|
||||||
|
{element_count, 4U, 3U, 3U}, "X,Y,Z", "1,1,1", "global-cartesian",
|
||||||
|
"shell-local-frame", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (shell_root + "/generalized_strain").c_str(),
|
||||||
|
{element_count, 4U, 8U}, "E11,E22,G12,K11,K22,K12,G13,G23",
|
||||||
|
"1,1,1,1/length,1/length,1/length,1,1", "shell-local", "midsurface",
|
||||||
|
&frame);
|
||||||
|
RequireSchemaV1ShellIdentity(file.Get(),
|
||||||
|
(shell_root + "/generalized_strain").c_str(),
|
||||||
|
false, root);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (shell_root + "/section_resultant").c_str(),
|
||||||
|
{element_count, 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", &frame);
|
||||||
|
RequireSchemaV1ShellIdentity(file.Get(),
|
||||||
|
(shell_root + "/section_resultant").c_str(),
|
||||||
|
false, root);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (shell_root + "/stress").c_str(),
|
||||||
|
{element_count, 4U, 3U, 3U}, "S11,S22,S12",
|
||||||
|
"force/length^2,force/length^2,force/length^2", "shell-local",
|
||||||
|
"section-position", &frame);
|
||||||
|
RequireSchemaV1ShellIdentity(file.Get(), (shell_root + "/stress").c_str(),
|
||||||
|
true, root);
|
||||||
|
RequireSchemaV1NumericDataset(file.Get(), (root + "/global/energy").c_str(),
|
||||||
|
{1U}, "PHYSICAL_STRAIN_ENERGY", "force*length",
|
||||||
|
"global", "global", &frame);
|
||||||
|
RequireSchemaV1NumericDataset(
|
||||||
|
file.Get(), (root + "/global/equilibrium").c_str(), {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", &frame);
|
||||||
|
}
|
||||||
|
RequireCompoundDataset(file.Get(), "/diagnostics",
|
||||||
|
static_cast<hsize_t>(diagnostic_count),
|
||||||
|
{"severity", "code", "file", "line", "keyword",
|
||||||
|
"entity_identity", "message"});
|
||||||
|
RequireHdf5(file.CloseChecked(),
|
||||||
|
"Unable to close nonlinear read-only self-check file.");
|
||||||
|
}
|
||||||
|
|
||||||
Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||||
const AnalysisState& state, const std::size_t diagnostic_count,
|
const AnalysisState& state, const std::size_t diagnostic_count,
|
||||||
const WriterModelData& model_data) {
|
const WriterModelData& model_data) {
|
||||||
@@ -607,4 +937,18 @@ Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status NonlinearSelfCheck(const std::filesystem::path& path,
|
||||||
|
const Domain& domain,
|
||||||
|
const NonlinearAnalysisState& state,
|
||||||
|
const std::size_t diagnostic_count) {
|
||||||
|
try {
|
||||||
|
NonlinearSelfCheckFile(path, domain, state, diagnostic_count);
|
||||||
|
return Status::Ok();
|
||||||
|
} catch (const Hdf5Failure& failure) {
|
||||||
|
return OutputFailure("hdf5-write-failure", failure.what());
|
||||||
|
} catch (const std::exception& failure) {
|
||||||
|
return OutputFailure("hdf5-write-failure", failure.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace fesa::hdf5_internal
|
} // namespace fesa::hdf5_internal
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_state.h"
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
#include "fesa/analysis/nonlinear_analysis_state.h"
|
||||||
#include "fesa/core/status.h"
|
#include "fesa/core/status.h"
|
||||||
#include "fesa/model/domain.h"
|
#include "fesa/model/domain.h"
|
||||||
#include "io/hdf5/hdf5_primitives.h"
|
#include "io/hdf5/hdf5_primitives.h"
|
||||||
@@ -16,6 +17,12 @@ Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
|||||||
const AnalysisState& state, std::size_t diagnostic_count,
|
const AnalysisState& state, std::size_t diagnostic_count,
|
||||||
const WriterModelData& model_data);
|
const WriterModelData& model_data);
|
||||||
|
|
||||||
|
/// @brief Reopens and validates the complete closed schema-v1 candidate.
|
||||||
|
Status NonlinearSelfCheck(const std::filesystem::path& path,
|
||||||
|
const Domain& domain,
|
||||||
|
const NonlinearAnalysisState& state,
|
||||||
|
std::size_t diagnostic_count);
|
||||||
|
|
||||||
} // namespace fesa::hdf5_internal
|
} // namespace fesa::hdf5_internal
|
||||||
|
|
||||||
#endif // FESA_IO_HDF5_HDF5_SELF_CHECK_H_
|
#endif // FESA_IO_HDF5_HDF5_SELF_CHECK_H_
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ add_executable(
|
|||||||
unit/io/abaqus/input_reader_test.cpp
|
unit/io/abaqus/input_reader_test.cpp
|
||||||
unit/io/abaqus/input_syntax_test.cpp
|
unit/io/abaqus/input_syntax_test.cpp
|
||||||
unit/io/hdf5/hdf5_results_writer_test.cpp
|
unit/io/hdf5/hdf5_results_writer_test.cpp
|
||||||
|
unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp
|
||||||
unit/io/hdf5/hdf5_writer_components_test.cpp
|
unit/io/hdf5/hdf5_writer_components_test.cpp
|
||||||
unit/loads/load_test.cpp
|
unit/loads/load_test.cpp
|
||||||
unit/model/domain_test.cpp
|
unit/model/domain_test.cpp
|
||||||
|
|||||||
@@ -0,0 +1,591 @@
|
|||||||
|
#include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <hdf5.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/analysis/nonlinear_analysis_state.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
#include "fesa/results/nonlinear_results_writer.h"
|
||||||
|
#include "io/hdf5/hdf5_raii.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using fesa::hdf5_internal::Hdf5Handle;
|
||||||
|
|
||||||
|
constexpr std::array<std::array<double, 3>, 3> kIdentityTriad{
|
||||||
|
std::array<double, 3>{1.0, 0.0, 0.0},
|
||||||
|
std::array<double, 3>{0.0, 1.0, 0.0},
|
||||||
|
std::array<double, 3>{0.0, 0.0, 1.0}};
|
||||||
|
|
||||||
|
class TempDirectory {
|
||||||
|
public:
|
||||||
|
explicit TempDirectory(const std::string& leaf)
|
||||||
|
: path_{std::filesystem::temp_directory_path() /
|
||||||
|
("fesa-nonlinear-hdf5-" + leaf)} {
|
||||||
|
std::error_code error;
|
||||||
|
std::filesystem::remove_all(path_, error);
|
||||||
|
if (!std::filesystem::create_directory(path_)) {
|
||||||
|
throw std::runtime_error{"Unable to create nonlinear HDF5 test directory."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~TempDirectory() {
|
||||||
|
std::error_code error;
|
||||||
|
std::filesystem::remove_all(path_, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& Path() const noexcept { return path_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::filesystem::path path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WinHandle {
|
||||||
|
public:
|
||||||
|
explicit WinHandle(const HANDLE handle) : handle_{handle} {}
|
||||||
|
WinHandle(const WinHandle&) = delete;
|
||||||
|
WinHandle& operator=(const WinHandle&) = delete;
|
||||||
|
~WinHandle() {
|
||||||
|
if (handle_ != INVALID_HANDLE_VALUE) {
|
||||||
|
(void)CloseHandle(handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE Get() const noexcept { return handle_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE handle_{INVALID_HANDLE_VALUE};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Fixture {
|
||||||
|
std::unique_ptr<fesa::Domain> domain;
|
||||||
|
std::unique_ptr<fesa::DofManager> dofs;
|
||||||
|
fesa::NonlinearAnalysisState state;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AcceptedFrameRow {
|
||||||
|
std::uint64_t frame_index;
|
||||||
|
std::uint64_t increment_index;
|
||||||
|
double load_factor;
|
||||||
|
std::uint64_t iteration_count;
|
||||||
|
std::uint8_t is_factor_one;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiagnosticRow {
|
||||||
|
char* severity;
|
||||||
|
char* code;
|
||||||
|
char* file;
|
||||||
|
std::uint64_t line;
|
||||||
|
char* keyword;
|
||||||
|
char* entity_identity;
|
||||||
|
char* message;
|
||||||
|
};
|
||||||
|
|
||||||
|
fesa::ModelDefinition MakeDefinition(const std::filesystem::path& source) {
|
||||||
|
fesa::ModelDefinition definition{};
|
||||||
|
definition.source_path = source;
|
||||||
|
definition.source_content_identity = "nonlinear-hdf5-fixture";
|
||||||
|
definition.nodes = {
|
||||||
|
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 1U}},
|
||||||
|
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 2U}},
|
||||||
|
{{"Shell-1", 30, "30"}, {1.0, 1.0, 0.0}, {source, 3U}},
|
||||||
|
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 4U}}};
|
||||||
|
definition.materials = {{"Steel", 210.0e9, 0.3, {source, 5U}}};
|
||||||
|
definition.shell_sections = {{"Shell", 0.1, 0U, {source, 6U}}};
|
||||||
|
definition.shell_elements = {{{"Shell-1", 1, "1"},
|
||||||
|
fesa::ShellSourceElementType::kS4,
|
||||||
|
{0U, 1U, 2U, 3U},
|
||||||
|
0U,
|
||||||
|
0U,
|
||||||
|
{source, 7U}}};
|
||||||
|
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.steps = {{"Step-1",
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
fesa::StaticProcedureKind::kNonlinearFullNewton,
|
||||||
|
true,
|
||||||
|
0.5,
|
||||||
|
1.0,
|
||||||
|
0.001,
|
||||||
|
1.0,
|
||||||
|
{source, 8U}}};
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
fesa::NonlinearStateBaselineInput MakeBaseline(const fesa::Domain& domain) {
|
||||||
|
fesa::NonlinearStateBaselineInput baseline{};
|
||||||
|
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||||
|
baseline.node_baselines.push_back(
|
||||||
|
{static_cast<fesa::EntityIndex>(node), domain.Nodes()[node].source_id,
|
||||||
|
kIdentityTriad, {0.0, 0.0, 1.0}});
|
||||||
|
}
|
||||||
|
return baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
fesa::ShellResultRow MakeShellRow(const fesa::ShellMidsurfaceLocation location,
|
||||||
|
const std::array<double, 2> coordinates,
|
||||||
|
const double seed) {
|
||||||
|
return {0U,
|
||||||
|
location,
|
||||||
|
coordinates,
|
||||||
|
kIdentityTriad,
|
||||||
|
{seed, seed + 1.0, seed + 2.0, seed + 3.0, seed + 4.0,
|
||||||
|
seed + 5.0, seed + 6.0, seed + 7.0},
|
||||||
|
{seed + 10.0, seed + 11.0, seed + 12.0, seed + 13.0,
|
||||||
|
seed + 14.0, seed + 15.0, seed + 16.0, seed + 17.0},
|
||||||
|
{{{fesa::ShellSectionPosition::kBottom,
|
||||||
|
-1.0,
|
||||||
|
{seed + 20.0, seed + 21.0, seed + 22.0}},
|
||||||
|
{fesa::ShellSectionPosition::kMiddle,
|
||||||
|
0.0,
|
||||||
|
{seed + 23.0, seed + 24.0, seed + 25.0}},
|
||||||
|
{fesa::ShellSectionPosition::kTop,
|
||||||
|
1.0,
|
||||||
|
{seed + 26.0, seed + 27.0, seed + 28.0}}}}};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<fesa::ShellResultRow> MakeShellRows(const double seed) {
|
||||||
|
const double gauss = 1.0 / std::sqrt(3.0);
|
||||||
|
return {
|
||||||
|
MakeShellRow(fesa::ShellMidsurfaceLocation::kGp1, {-gauss, -gauss},
|
||||||
|
seed),
|
||||||
|
MakeShellRow(fesa::ShellMidsurfaceLocation::kGp2, {gauss, -gauss},
|
||||||
|
seed + 30.0),
|
||||||
|
MakeShellRow(fesa::ShellMidsurfaceLocation::kGp3, {gauss, gauss},
|
||||||
|
seed + 60.0),
|
||||||
|
MakeShellRow(fesa::ShellMidsurfaceLocation::kGp4, {-gauss, gauss},
|
||||||
|
seed + 90.0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
fesa::AcceptedFrameState MakeFrame(const fesa::NonlinearTrialState& committed,
|
||||||
|
const std::uint32_t frame_index,
|
||||||
|
const std::uint32_t increment_index,
|
||||||
|
const double load_factor) {
|
||||||
|
fesa::AcceptedFrameState frame{
|
||||||
|
frame_index,
|
||||||
|
increment_index,
|
||||||
|
load_factor,
|
||||||
|
2U,
|
||||||
|
load_factor == 1.0,
|
||||||
|
committed.nodal_internal_order,
|
||||||
|
committed.nodal_source_ids,
|
||||||
|
committed.generalized_coordinates,
|
||||||
|
committed.displacement,
|
||||||
|
committed.rotation_vector,
|
||||||
|
committed.nodal_triad,
|
||||||
|
committed.nodal_director,
|
||||||
|
committed.internal_force,
|
||||||
|
committed.external_force,
|
||||||
|
committed.residual,
|
||||||
|
committed.reaction,
|
||||||
|
MakeShellRows(100.0 * load_factor),
|
||||||
|
20.0 * load_factor,
|
||||||
|
{load_factor, 2.0 * load_factor, 3.0 * load_factor,
|
||||||
|
4.0 * load_factor, 5.0 * load_factor, 6.0 * load_factor}};
|
||||||
|
for (std::size_t node = 0U; node < frame.nodal_internal_order.size(); ++node) {
|
||||||
|
const std::size_t offset = 6U * node;
|
||||||
|
frame.displacement[offset] = load_factor * static_cast<double>(node + 1U);
|
||||||
|
frame.displacement[offset + 1U] = -frame.displacement[offset];
|
||||||
|
frame.displacement[offset + 2U] = 0.5 * frame.displacement[offset];
|
||||||
|
frame.rotation_vector[offset + 5U] = 0.1 * load_factor;
|
||||||
|
for (std::size_t component = 0U; component < 6U; ++component) {
|
||||||
|
frame.generalized_coordinates[offset + component] =
|
||||||
|
component < 3U ? frame.displacement[offset + component]
|
||||||
|
: frame.rotation_vector[offset + component];
|
||||||
|
frame.internal_force[offset + component] =
|
||||||
|
10.0 * static_cast<double>(component + 1U);
|
||||||
|
frame.external_force[offset + component] = 0.0;
|
||||||
|
frame.residual[offset + component] = frame.internal_force[offset + component];
|
||||||
|
frame.reaction[offset + component] = frame.residual[offset + component];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fixture MakeFixture(const std::filesystem::path& source) {
|
||||||
|
auto domain_result = fesa::Domain::Create(MakeDefinition(source));
|
||||||
|
if (!domain_result.HasValue()) {
|
||||||
|
throw std::runtime_error{"Unable to construct nonlinear HDF5 Domain."};
|
||||||
|
}
|
||||||
|
auto domain = std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
|
||||||
|
auto model_result = fesa::AnalysisModel::Create(*domain);
|
||||||
|
if (!model_result.HasValue()) {
|
||||||
|
throw std::runtime_error{"Unable to construct nonlinear HDF5 model."};
|
||||||
|
}
|
||||||
|
auto dofs_result = fesa::DofManager::Create(model_result.Value());
|
||||||
|
if (!dofs_result.HasValue()) {
|
||||||
|
throw std::runtime_error{"Unable to construct nonlinear HDF5 DOFs."};
|
||||||
|
}
|
||||||
|
auto dofs = std::make_unique<fesa::DofManager>(std::move(dofs_result.Value()));
|
||||||
|
auto state_result =
|
||||||
|
fesa::NonlinearAnalysisState::Create(*domain, *dofs, MakeBaseline(*domain));
|
||||||
|
if (!state_result.HasValue()) {
|
||||||
|
throw std::runtime_error{"Unable to construct nonlinear HDF5 state."};
|
||||||
|
}
|
||||||
|
fesa::NonlinearAnalysisState state = std::move(state_result.Value());
|
||||||
|
if (!state.CommitAcceptedFrame(
|
||||||
|
MakeFrame(state.CommittedState(), 0U, 1U, 0.5))
|
||||||
|
.IsOk() ||
|
||||||
|
!state.CommitAcceptedFrame(
|
||||||
|
MakeFrame(state.CommittedState(), 1U, 2U, 1.0))
|
||||||
|
.IsOk()) {
|
||||||
|
throw std::runtime_error{"Unable to commit nonlinear HDF5 test frames."};
|
||||||
|
}
|
||||||
|
return {std::move(domain), std::move(dofs), std::move(state)};
|
||||||
|
}
|
||||||
|
|
||||||
|
Hdf5Handle OpenFile(const std::filesystem::path& path) {
|
||||||
|
return {H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT), H5Fclose};
|
||||||
|
}
|
||||||
|
|
||||||
|
Hdf5Handle OpenDataset(const hid_t file, const std::string& path) {
|
||||||
|
return {H5Dopen2(file, path.c_str(), H5P_DEFAULT), 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};
|
||||||
|
const int rank = H5Sget_simple_extent_ndims(space.Get());
|
||||||
|
std::vector<hsize_t> dimensions(static_cast<std::size_t>(rank));
|
||||||
|
if (rank > 0) {
|
||||||
|
EXPECT_GE(H5Sget_simple_extent_dims(space.Get(), dimensions.data(), nullptr),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
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};
|
||||||
|
char* raw = nullptr;
|
||||||
|
EXPECT_GE(H5Aread(attribute.Get(), type.Get(), &raw), 0);
|
||||||
|
const std::string value = raw == nullptr ? std::string{} : std::string{raw};
|
||||||
|
if (raw != nullptr) {
|
||||||
|
EXPECT_GE(H5free_memory(raw), 0);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t ReadUint64Attribute(const hid_t object, const char* name) {
|
||||||
|
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
||||||
|
std::uint64_t value = 0U;
|
||||||
|
EXPECT_GE(H5Aread(attribute.Get(), H5T_NATIVE_UINT64, &value), 0);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ReadDoubleAttribute(const hid_t object, const char* name) {
|
||||||
|
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
||||||
|
double value = 0.0;
|
||||||
|
EXPECT_GE(H5Aread(attribute.Get(), H5T_NATIVE_DOUBLE, &value), 0);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> ReadDoubleDataset(const hid_t file,
|
||||||
|
const std::string& path) {
|
||||||
|
const auto dimensions = DatasetDimensions(file, path);
|
||||||
|
std::size_t count = 1U;
|
||||||
|
for (const hsize_t dimension : dimensions) {
|
||||||
|
count *= static_cast<std::size_t>(dimension);
|
||||||
|
}
|
||||||
|
std::vector<double> values(count);
|
||||||
|
const auto dataset = OpenDataset(file, path);
|
||||||
|
if (!values.empty()) {
|
||||||
|
EXPECT_GE(H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||||
|
H5P_DEFAULT, values.data()),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hdf5Handle AcceptedFrameMemoryType() {
|
||||||
|
Hdf5Handle type{H5Tcreate(H5T_COMPOUND, sizeof(AcceptedFrameRow)), H5Tclose};
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "frame_index",
|
||||||
|
HOFFSET(AcceptedFrameRow, frame_index),
|
||||||
|
H5T_NATIVE_UINT64),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "increment_index",
|
||||||
|
HOFFSET(AcceptedFrameRow, increment_index),
|
||||||
|
H5T_NATIVE_UINT64),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "load_factor",
|
||||||
|
HOFFSET(AcceptedFrameRow, load_factor), H5T_NATIVE_DOUBLE),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "iteration_count",
|
||||||
|
HOFFSET(AcceptedFrameRow, iteration_count),
|
||||||
|
H5T_NATIVE_UINT64),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "is_factor_one",
|
||||||
|
HOFFSET(AcceptedFrameRow, is_factor_one),
|
||||||
|
H5T_NATIVE_UINT8),
|
||||||
|
0);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AcceptedFrameRow> ReadAcceptedFrames(const hid_t file) {
|
||||||
|
const auto dataset =
|
||||||
|
OpenDataset(file, "/steps/Step-1/accepted_frames");
|
||||||
|
const auto dimensions = DatasetDimensions(file, "/steps/Step-1/accepted_frames");
|
||||||
|
std::vector<AcceptedFrameRow> rows(static_cast<std::size_t>(dimensions[0U]));
|
||||||
|
auto type = AcceptedFrameMemoryType();
|
||||||
|
EXPECT_GE(H5Dread(dataset.Get(), type.Get(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
|
||||||
|
rows.data()),
|
||||||
|
0);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hdf5Handle DiagnosticMemoryType() {
|
||||||
|
Hdf5Handle string_type{H5Tcopy(H5T_C_S1), H5Tclose};
|
||||||
|
EXPECT_GE(H5Tset_size(string_type.Get(), H5T_VARIABLE), 0);
|
||||||
|
EXPECT_GE(H5Tset_cset(string_type.Get(), H5T_CSET_UTF8), 0);
|
||||||
|
Hdf5Handle type{H5Tcreate(H5T_COMPOUND, sizeof(DiagnosticRow)), H5Tclose};
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "severity", HOFFSET(DiagnosticRow, severity),
|
||||||
|
string_type.Get()),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "code", HOFFSET(DiagnosticRow, code),
|
||||||
|
string_type.Get()),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "file", HOFFSET(DiagnosticRow, file),
|
||||||
|
string_type.Get()),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "line", HOFFSET(DiagnosticRow, line),
|
||||||
|
H5T_NATIVE_UINT64),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "keyword", HOFFSET(DiagnosticRow, keyword),
|
||||||
|
string_type.Get()),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "entity_identity",
|
||||||
|
HOFFSET(DiagnosticRow, entity_identity), string_type.Get()),
|
||||||
|
0);
|
||||||
|
EXPECT_GE(H5Tinsert(type.Get(), "message", HOFFSET(DiagnosticRow, message),
|
||||||
|
string_type.Get()),
|
||||||
|
0);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DiagnosticRow> ReadDiagnostics(const hid_t file) {
|
||||||
|
const auto dataset = OpenDataset(file, "/diagnostics");
|
||||||
|
const auto dimensions = DatasetDimensions(file, "/diagnostics");
|
||||||
|
std::vector<DiagnosticRow> rows(static_cast<std::size_t>(dimensions[0U]));
|
||||||
|
auto type = DiagnosticMemoryType();
|
||||||
|
EXPECT_GE(H5Dread(dataset.Get(), type.Get(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
|
||||||
|
rows.data()),
|
||||||
|
0);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReclaimDiagnostics(const std::vector<DiagnosticRow>& rows) {
|
||||||
|
for (const auto& row : rows) {
|
||||||
|
for (char* value : {row.severity, row.code, row.file, row.keyword,
|
||||||
|
row.entity_identity, row.message}) {
|
||||||
|
if (value != nullptr) {
|
||||||
|
EXPECT_GE(H5free_memory(value), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 nonlinear HDF5 test bytes."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// NSNR-HDF5-001 through NSNR-HDF5-006, NSNR-HDF5-008 through NSNR-HDF5-010.
|
||||||
|
TEST(NonlinearHdf5ResultsWriter,
|
||||||
|
WritesSchemaV1ModelAndCommittedAcceptedFramesOnly) {
|
||||||
|
TempDirectory directory{"schema"};
|
||||||
|
auto fixture = MakeFixture(directory.Path() / "model.inp");
|
||||||
|
const auto output = directory.Path() / "model_results.h5";
|
||||||
|
|
||||||
|
fesa::NonlinearHdf5ResultsWriter writer;
|
||||||
|
const auto status = writer.Write(output, *fixture.domain, fixture.state, {});
|
||||||
|
ASSERT_TRUE(status.IsOk()) << status.Diagnostics().at(0U).message;
|
||||||
|
|
||||||
|
const auto file = OpenFile(output);
|
||||||
|
ASSERT_GE(file.Get(), 0);
|
||||||
|
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||||
|
ASSERT_GE(metadata.Get(), 0);
|
||||||
|
EXPECT_EQ(ReadUint64Attribute(metadata.Get(), "schema_version"), 1U);
|
||||||
|
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "feature_id"),
|
||||||
|
"nonlinear-static-newton-raphson");
|
||||||
|
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "analysis_procedure"),
|
||||||
|
"nonlinear-static-full-newton");
|
||||||
|
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "internal_formulation"),
|
||||||
|
"FESA-MITC4-total-lagrangian");
|
||||||
|
EXPECT_EQ(ReadStringAttribute(metadata.Get(), "integration_rule"),
|
||||||
|
"2x2x2-gauss; mitc4-edge-midpoint-shear");
|
||||||
|
EXPECT_EQ(ReadUint64Attribute(metadata.Get(), "factor_zero_is_frame"), 0U);
|
||||||
|
EXPECT_DOUBLE_EQ(ReadDoubleAttribute(metadata.Get(), "factor_zero_load_factor"),
|
||||||
|
0.0);
|
||||||
|
|
||||||
|
for (const char* path :
|
||||||
|
{"/model/nodes", "/model/elements", "/model/shell/materials",
|
||||||
|
"/model/shell/sections", "/model/shell/nodal_director",
|
||||||
|
"/model/shell/nodal_frame", "/model/nodal_constraint_mask",
|
||||||
|
"/model/prescribed_displacement_start",
|
||||||
|
"/model/prescribed_displacement_target",
|
||||||
|
"/model/shell/midsurface_locations", "/model/shell/section_positions"}) {
|
||||||
|
EXPECT_GT(H5Lexists(file.Get(), path, H5P_DEFAULT), 0) << path;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(DatasetDimensions(file.Get(), "/steps/Step-1/accepted_frames"),
|
||||||
|
std::vector<hsize_t>({2U}));
|
||||||
|
const auto frames = ReadAcceptedFrames(file.Get());
|
||||||
|
ASSERT_EQ(frames.size(), 2U);
|
||||||
|
EXPECT_EQ(frames[0U].frame_index, 0U);
|
||||||
|
EXPECT_EQ(frames[0U].increment_index, 1U);
|
||||||
|
EXPECT_DOUBLE_EQ(frames[0U].load_factor, 0.5);
|
||||||
|
EXPECT_EQ(frames[0U].is_factor_one, 0U);
|
||||||
|
EXPECT_EQ(frames[1U].frame_index, 1U);
|
||||||
|
EXPECT_EQ(frames[1U].increment_index, 2U);
|
||||||
|
EXPECT_DOUBLE_EQ(frames[1U].load_factor, 1.0);
|
||||||
|
EXPECT_EQ(frames[1U].is_factor_one, 1U);
|
||||||
|
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/frames/2", H5P_DEFAULT), 0);
|
||||||
|
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/trial_history", H5P_DEFAULT),
|
||||||
|
0);
|
||||||
|
|
||||||
|
const std::string root = "/steps/Step-1/frames/1";
|
||||||
|
for (const std::string& suffix :
|
||||||
|
{"/nodal/displacement", "/nodal/triad", "/nodal/director",
|
||||||
|
"/nodal/generalized_internal_force", "/nodal/generalized_external_force",
|
||||||
|
"/nodal/generalized_residual", "/nodal/reaction",
|
||||||
|
"/element/shell/local_frame", "/element/shell/generalized_strain",
|
||||||
|
"/element/shell/section_resultant", "/element/shell/stress",
|
||||||
|
"/global/energy", "/global/equilibrium"}) {
|
||||||
|
EXPECT_GT(H5Lexists(file.Get(), (root + suffix).c_str(), H5P_DEFAULT), 0)
|
||||||
|
<< suffix;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(DatasetDimensions(file.Get(), root + "/nodal/displacement"),
|
||||||
|
std::vector<hsize_t>({4U, 6U}));
|
||||||
|
EXPECT_EQ(DatasetDimensions(file.Get(), root + "/nodal/triad"),
|
||||||
|
std::vector<hsize_t>({4U, 3U, 3U}));
|
||||||
|
EXPECT_EQ(DatasetDimensions(file.Get(), root + "/element/shell/local_frame"),
|
||||||
|
std::vector<hsize_t>({1U, 4U, 3U, 3U}));
|
||||||
|
EXPECT_EQ(DatasetDimensions(file.Get(), root + "/element/shell/stress"),
|
||||||
|
std::vector<hsize_t>({1U, 4U, 3U, 3U}));
|
||||||
|
const auto displacement = ReadDoubleDataset(file.Get(), root + "/nodal/displacement");
|
||||||
|
ASSERT_EQ(displacement.size(), 24U);
|
||||||
|
EXPECT_DOUBLE_EQ(displacement[0U], 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(displacement[1U], -1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(displacement[2U], 0.5);
|
||||||
|
EXPECT_DOUBLE_EQ(displacement[5U], 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NSNR-HDF5-007.
|
||||||
|
TEST(NonlinearHdf5ResultsWriter, ProjectsRotationalReactionToSpatialMoment) {
|
||||||
|
TempDirectory directory{"reaction"};
|
||||||
|
auto fixture = MakeFixture(directory.Path() / "reaction.inp");
|
||||||
|
const auto output = directory.Path() / "reaction_results.h5";
|
||||||
|
|
||||||
|
fesa::NonlinearHdf5ResultsWriter writer;
|
||||||
|
ASSERT_TRUE(writer.Write(output, *fixture.domain, fixture.state, {}).IsOk());
|
||||||
|
|
||||||
|
const auto file = OpenFile(output);
|
||||||
|
const auto reaction = ReadDoubleDataset(
|
||||||
|
file.Get(), "/steps/Step-1/frames/1/nodal/reaction");
|
||||||
|
ASSERT_EQ(reaction.size(), 24U);
|
||||||
|
const double theta = 0.1;
|
||||||
|
const double quadratic =
|
||||||
|
(1.0 - 0.5 * theta / std::tan(0.5 * theta)) / (theta * theta);
|
||||||
|
EXPECT_NEAR(reaction[3U],
|
||||||
|
(1.0 - quadratic * theta * theta) * 40.0 -
|
||||||
|
0.5 * theta * 50.0,
|
||||||
|
1.0e-12);
|
||||||
|
EXPECT_NEAR(reaction[4U],
|
||||||
|
0.5 * theta * 40.0 +
|
||||||
|
(1.0 - quadratic * theta * theta) * 50.0,
|
||||||
|
1.0e-12);
|
||||||
|
EXPECT_NEAR(reaction[5U], 60.0, 1.0e-12);
|
||||||
|
EXPECT_GT(quadratic, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NSNR-HDF5-012 and NSNR-HDF5-013.
|
||||||
|
TEST(NonlinearHdf5ResultsWriter, StoresSortedDiagnosticsThroughBackendNeutralBoundary) {
|
||||||
|
static_assert(std::is_base_of_v<fesa::NonlinearResultsWriter,
|
||||||
|
fesa::NonlinearHdf5ResultsWriter>);
|
||||||
|
TempDirectory directory{"diagnostics"};
|
||||||
|
auto fixture = MakeFixture(directory.Path() / "diagnostics.inp");
|
||||||
|
const std::vector<fesa::Diagnostic> diagnostics{
|
||||||
|
{fesa::Severity::kWarning,
|
||||||
|
"later",
|
||||||
|
{fixture.domain->SourcePath(), 20U},
|
||||||
|
"*OUTPUT",
|
||||||
|
"",
|
||||||
|
"Later warning."},
|
||||||
|
{fesa::Severity::kWarning,
|
||||||
|
"earlier",
|
||||||
|
{fixture.domain->SourcePath(), 10U},
|
||||||
|
"*PREPRINT",
|
||||||
|
"",
|
||||||
|
"Earlier warning."}};
|
||||||
|
const auto output = directory.Path() / "diagnostics_results.h5";
|
||||||
|
|
||||||
|
fesa::NonlinearHdf5ResultsWriter writer;
|
||||||
|
ASSERT_TRUE(writer.Write(output, *fixture.domain, fixture.state, diagnostics)
|
||||||
|
.IsOk());
|
||||||
|
const auto file = OpenFile(output);
|
||||||
|
const auto rows = ReadDiagnostics(file.Get());
|
||||||
|
ASSERT_EQ(rows.size(), 2U);
|
||||||
|
EXPECT_STREQ(rows[0U].code, "earlier");
|
||||||
|
EXPECT_EQ(rows[0U].line, 10U);
|
||||||
|
EXPECT_STREQ(rows[1U].code, "later");
|
||||||
|
EXPECT_EQ(rows[1U].line, 20U);
|
||||||
|
ReclaimDiagnostics(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NSNR-HDF5-011.
|
||||||
|
TEST(NonlinearHdf5ResultsWriter, CandidateFinalizationFailurePreservesPriorFinal) {
|
||||||
|
TempDirectory directory{"atomic"};
|
||||||
|
auto fixture = MakeFixture(directory.Path() / "atomic.inp");
|
||||||
|
const auto output = directory.Path() / "atomic_results.h5";
|
||||||
|
const std::vector<char> sentinel{'p', 'r', 'i', 'o', 'r'};
|
||||||
|
WriteBytes(output, sentinel);
|
||||||
|
WinHandle lock{CreateFileW(output.c_str(), GENERIC_READ,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
|
||||||
|
ASSERT_NE(lock.Get(), INVALID_HANDLE_VALUE);
|
||||||
|
|
||||||
|
fesa::NonlinearHdf5ResultsWriter writer;
|
||||||
|
const auto status = writer.Write(output, *fixture.domain, fixture.state, {});
|
||||||
|
|
||||||
|
ASSERT_FALSE(status.IsOk());
|
||||||
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
|
||||||
|
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||||
|
EXPECT_EQ(status.Diagnostics()[0U].code, "hdf5-finalization-failure");
|
||||||
|
EXPECT_EQ(ReadBytes(output), sentinel);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user