#include "io/hdf5/hdf5_self_check.h" #include #include #include #include #include #include #include #include #include "fesa/build_info.h" #include "io/hdf5/hdf5_raii.h" #include "io/hdf5/hdf5_primitives.h" #include "io/hdf5/mitc4_drilling_diagnostics.h" namespace fesa::hdf5_internal { Hdf5Handle OpenDatasetForCheck(const hid_t file, const char* path) { return Hdf5Handle{RequireHdf5Id(H5Dopen2(file, path, H5P_DEFAULT), "A mandatory HDF5 dataset is missing."), H5Dclose}; } std::vector CheckedDimensions(const hid_t dataset) { Hdf5Handle space{RequireHdf5Id(H5Dget_space(dataset), "Unable to inspect an HDF5 dataspace."), H5Sclose}; const int rank = H5Sget_simple_extent_ndims(space.Get()); if (rank < 0) { throw Hdf5Failure{"Unable to inspect an HDF5 dataset rank."}; } std::vector dimensions(static_cast(rank)); if (rank > 0) { RequireHdf5( H5Sget_simple_extent_dims(space.Get(), dimensions.data(), nullptr), "Unable to inspect HDF5 dataset dimensions."); } return dimensions; } void RequireStringAttribute(const hid_t object, const char* name, const std::string& expected) { Hdf5Handle attribute{ RequireHdf5Id(H5Aopen(object, name, H5P_DEFAULT), "A mandatory HDF5 string attribute is missing."), H5Aclose}; Hdf5Handle type{RequireHdf5Id(H5Aget_type(attribute.Get()), "Unable to inspect an HDF5 attribute type."), H5Tclose}; if (H5Tget_class(type.Get()) != H5T_STRING || H5Tis_variable_str(type.Get()) <= 0 || H5Tget_cset(type.Get()) != H5T_CSET_UTF8) { throw Hdf5Failure{"An HDF5 string attribute is not variable-length UTF-8."}; } char* raw = nullptr; RequireHdf5(H5Aread(attribute.Get(), type.Get(), &raw), "Unable to read an HDF5 string attribute."); const std::string actual = raw == nullptr ? std::string{} : std::string{raw}; if (raw != nullptr) { RequireHdf5(H5free_memory(raw), "Unable to release an HDF5 string value."); } if (actual != expected) { throw Hdf5Failure{"HDF5 string attribute '" + std::string{name} + "' mismatch: expected '" + expected + "', got '" + actual + "'."}; } } void RequireUint64Attribute(const hid_t object, const char* name, const std::uint64_t expected) { Hdf5Handle attribute{ RequireHdf5Id(H5Aopen(object, name, H5P_DEFAULT), "A mandatory HDF5 integer attribute is missing."), H5Aclose}; Hdf5Handle type{ RequireHdf5Id(H5Aget_type(attribute.Get()), "Unable to inspect an HDF5 integer attribute type."), H5Tclose}; if (H5Tget_class(type.Get()) != H5T_INTEGER || H5Tget_size(type.Get()) != sizeof(std::uint64_t) || H5Tget_sign(type.Get()) != H5T_SGN_NONE || H5Tequal(type.Get(), H5T_STD_U64LE) <= 0) { throw Hdf5Failure{"An HDF5 integer attribute is not portable uint64."}; } std::uint64_t actual = 0U; RequireHdf5(H5Aread(attribute.Get(), H5T_NATIVE_UINT64, &actual), "Unable to read an HDF5 integer attribute."); if (actual != expected) { throw Hdf5Failure{"An HDF5 integer attribute has the wrong value."}; } } void RequirePortableCompoundMember(const hid_t compound_type, const unsigned index, const std::string& name) { Hdf5Handle member_type{ RequireHdf5Id(H5Tget_member_type(compound_type, index), "Unable to inspect a compound HDF5 field type."), H5Tclose}; const bool is_uint64 = name == "internal_node_id" || name == "internal_element_id" || name == "internal_material_id" || name == "internal_section_id" || name == "shell_section_internal_id" || name == "material_internal_id" || name == "source_line" || name == "gauss_point_index" || 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" || name == "E" || name == "nu" || name == "thickness" || name == "load_factor"; const bool is_uint8 = name == "is_factor_one"; const bool is_string = name == "instance_name" || name == "source_label" || name == "source_element_type" || name == "internal_formulation" || name == "name" || name == "source_file" || name == "source_elset" || name == "source" || name == "severity" || name == "code" || name == "file" || name == "keyword" || name == "entity_identity" || name == "message"; if (is_uint64) { if (H5Tget_class(member_type.Get()) != H5T_INTEGER || H5Tget_size(member_type.Get()) != sizeof(std::uint64_t) || H5Tget_sign(member_type.Get()) != H5T_SGN_NONE || H5Tequal(member_type.Get(), H5T_STD_U64LE) <= 0) { throw Hdf5Failure{"A compound HDF5 field is not portable uint64."}; } return; } if (is_float64) { if (H5Tget_class(member_type.Get()) != H5T_FLOAT || H5Tget_size(member_type.Get()) != sizeof(double) || H5Tequal(member_type.Get(), H5T_IEEE_F64LE) <= 0) { throw Hdf5Failure{"A compound HDF5 field is not float64."}; } 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 (H5Tget_class(member_type.Get()) != H5T_STRING || H5Tis_variable_str(member_type.Get()) <= 0 || H5Tget_cset(member_type.Get()) != H5T_CSET_UTF8) { throw Hdf5Failure{"A compound HDF5 field is not UTF-8."}; } return; } if (H5Tget_class(member_type.Get()) != H5T_ARRAY) { throw Hdf5Failure{"A compound HDF5 array field has the wrong type."}; } const int rank = H5Tget_array_ndims(member_type.Get()); if (rank <= 0) { throw Hdf5Failure{"Unable to inspect a compound HDF5 array rank."}; } std::vector dimensions(static_cast(rank)); RequireHdf5(H5Tget_array_dims2(member_type.Get(), dimensions.data()), "Unable to inspect compound HDF5 array dimensions."); Hdf5Handle base_type{ RequireHdf5Id(H5Tget_super(member_type.Get()), "Unable to inspect a compound HDF5 array base type."), H5Tclose}; if (name == "node_internal_ids") { if ((dimensions != std::vector{2U} && dimensions != std::vector{4U}) || H5Tequal(base_type.Get(), H5T_STD_U64LE) <= 0) { throw Hdf5Failure{"Element connectivity is not uint64[2] or uint64[4]."}; } } else if (name == "coordinates") { if (dimensions != std::vector{3U} || H5Tequal(base_type.Get(), H5T_IEEE_F64LE) <= 0) { throw Hdf5Failure{"Node coordinates are not float64[3]."}; } } else if (name == "local_axes") { if (dimensions != std::vector{3U, 3U} || H5Tequal(base_type.Get(), H5T_IEEE_F64LE) <= 0) { throw Hdf5Failure{"Element local axes are not float64[3,3]."}; } } else { throw Hdf5Failure{"A compound HDF5 field has an unknown type contract."}; } } void RequireResultAttributes(const hid_t dataset, const char* component_names, const char* component_units, const char* coordinate_system, const char* location) { RequireStringAttribute(dataset, "component_names", component_names); RequireStringAttribute(dataset, "component_unit_dimensions", component_units); RequireStringAttribute(dataset, "coordinate_system", coordinate_system); RequireStringAttribute(dataset, "location", location); RequireStringAttribute(dataset, "step_name", kStepName); RequireUint64Attribute(dataset, "frame_index", 0U); } void RequireShellResultIdentity(const hid_t file, const char* path, const bool uses_section_positions) { auto dataset = OpenDatasetForCheck(file, path); RequireStringAttribute(dataset.Get(), "source_element_type_dataset", "/model/elements.source_element_type"); RequireStringAttribute(dataset.Get(), "internal_formulation", "FESA-MITC4"); RequireStringAttribute(dataset.Get(), "midsurface_location_dataset", "/model/shell/midsurface_locations"); RequireStringAttribute(dataset.Get(), "local_frame_dataset", "/steps/Step-1/frames/0/element/shell/local_frame"); if (uses_section_positions) { RequireStringAttribute(dataset.Get(), "section_position_dataset", "/model/shell/section_positions"); } } void RequireDoubleDataset(const hid_t file, const char* path, const std::vector& expected_dimensions, const char* component_names, const char* component_units, const char* coordinate_system, const char* location) { auto dataset = OpenDatasetForCheck(file, path); if (CheckedDimensions(dataset.Get()) != expected_dimensions) { throw Hdf5Failure{"A floating-point HDF5 dataset has the wrong shape."}; } Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()), "Unable to inspect an HDF5 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 result dataset is not IEEE-754 float64 little-endian."}; } RequireResultAttributes(dataset.Get(), component_names, component_units, coordinate_system, location); std::size_t value_count = 1U; for (const hsize_t dimension : expected_dimensions) { std::size_t next = 0U; if (!SizeProductFits(value_count, static_cast(dimension), next)) { throw Hdf5Failure{"A result dataset shape overflows size_t."}; } value_count = next; } std::vector 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 result dataset during self-check."); } if (!std::all_of(values.begin(), values.end(), [](const double value) { return std::isfinite(value); })) { throw Hdf5Failure{"A result dataset contains a nonfinite value."}; } } void RequireModelDoubleDataset( const hid_t file, const char* path, const std::vector& expected_dimensions, const char* component_names, const char* component_units, const char* coordinate_system, const char* location, const std::vector* expected_values = nullptr) { auto dataset = OpenDatasetForCheck(file, path); if (CheckedDimensions(dataset.Get()) != expected_dimensions) { throw Hdf5Failure{"A model floating-point dataset has the wrong shape."}; } Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()), "Unable to inspect a model 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 model dataset is not IEEE-754 float64 little-endian."}; } 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); std::size_t value_count = 1U; for (const hsize_t dimension : expected_dimensions) { std::size_t next = 0U; if (!SizeProductFits(value_count, static_cast(dimension), next)) { throw Hdf5Failure{"A model dataset shape overflows size_t."}; } value_count = next; } std::vector 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 model dataset during self-check."); } if (!std::all_of(values.begin(), values.end(), [](const double value) { return std::isfinite(value); })) { throw Hdf5Failure{"A model dataset contains a nonfinite value."}; } if (expected_values != nullptr && values != *expected_values) { throw Hdf5Failure{"A fixed model dataset has the wrong value order."}; } } void RequireUint8Dataset(const hid_t file, const char* path, const std::vector& expected_dimensions, const std::vector& expected_values) { auto dataset = OpenDatasetForCheck(file, path); if (CheckedDimensions(dataset.Get()) != expected_dimensions) { throw Hdf5Failure{"A uint8 HDF5 dataset has the wrong shape."}; } Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()), "Unable to inspect a uint8 dataset 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{"A constraint mask is not portable uint8."}; } std::vector values(expected_values.size()); if (!values.empty()) { RequireHdf5(H5Dread(dataset.Get(), H5T_NATIVE_UINT8, H5S_ALL, H5S_ALL, H5P_DEFAULT, values.data()), "Unable to read the constraint mask during self-check."); } if (values != expected_values) { throw Hdf5Failure{"The constraint mask has the wrong value order."}; } RequireStringAttribute(dataset.Get(), "component_names", "UX,UY,UZ,URX,URY,URZ"); RequireStringAttribute(dataset.Get(), "value_meaning", "0=free,1=constrained"); } void RequireCompoundDataset(const hid_t file, const char* path, const hsize_t expected_rows, const std::vector& expected_members) { auto dataset = OpenDatasetForCheck(file, path); if (CheckedDimensions(dataset.Get()) != std::vector{expected_rows}) { throw Hdf5Failure{"A compound HDF5 dataset has the wrong shape."}; } Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()), "Unable to inspect a compound type."), H5Tclose}; if (H5Tget_class(type.Get()) != H5T_COMPOUND || H5Tget_nmembers(type.Get()) != static_cast(expected_members.size())) { throw Hdf5Failure{"A compound HDF5 dataset has the wrong field count."}; } for (std::size_t index = 0U; index < expected_members.size(); ++index) { char* raw_name = H5Tget_member_name(type.Get(), static_cast(index)); if (raw_name == nullptr) { throw Hdf5Failure{"Unable to inspect a compound HDF5 field name."}; } const std::string actual_name{raw_name}; RequireHdf5(H5free_memory(raw_name), "Unable to release a compound field name."); if (actual_name != expected_members[index]) { throw Hdf5Failure{"Compound HDF5 field identity mismatch: expected '" + std::string{expected_members[index]} + "', got '" + actual_name + "'."}; } RequirePortableCompoundMember(type.Get(), static_cast(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& 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(dimension), next)) { throw Hdf5Failure{"A schema-v1 numeric shape overflows size_t."}; } value_count = next; } std::vector 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& 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 /// schema. void SelfCheckFile(const std::filesystem::path& path, const Domain& domain, const AnalysisState& state, const std::size_t diagnostic_count, const WriterModelData& model_data, herr_t (*close_reader)(hid_t)) { if (H5Fis_hdf5(path.string().c_str()) <= 0) { throw Hdf5Failure{"The temporary output is not an HDF5 file."}; } Hdf5Handle file{ RequireHdf5Id(H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT), "Unable to reopen the temporary HDF5 file read-only."), close_reader}; Hdf5Handle metadata{ RequireHdf5Id(H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), "The HDF5 metadata group is missing."), H5Gclose}; if (IsShellDomain(domain)) { const auto drilling = ReadDrillingDiagnostics(file.Get()); if (state.Mitc4Drilling()) { ValidateDrillingEvidence(state.Equilibrium(), state.Mitc4Drilling()); RequireDrilling(drilling.has_value() && drilling->formulation == Mitc4DrillingModeName(state.Mitc4Drilling()->mode) && drilling->frames.size() == 1U); CheckDrillingFrame(drilling->frames.front(), 0U, state.Equilibrium(), *state.Mitc4Drilling()); } else { RequireDrilling(!drilling.has_value()); } } RequireUint64Attribute(metadata.Get(), "schema_version", 0U); 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"); if (IsShellDomain(domain)) { RequireStringAttribute(metadata.Get(), "feature_id", "linear-static-mitc4-shell"); RequireStringAttribute( metadata.Get(), "coordinate_convention", "global-cartesian; shell-local=(e1,e2,e3); positive-thickness=+zeta"); RequireStringAttribute(metadata.Get(), "internal_formulation", "FESA-MITC4"); RequireStringAttribute(metadata.Get(), "integration_rule", "2x2x2-gauss; mitc4-edge-midpoint-shear"); } else { RequireStringAttribute(metadata.Get(), "feature_id", "linear-static-3d-euler-beam"); RequireStringAttribute(metadata.Get(), "coordinate_convention", "global-cartesian; beam-local=(t,n1,t-cross-n1)"); RequireStringAttribute(metadata.Get(), "element_formulation", "B33-3D-Euler-Bernoulli"); } RequireStringAttribute(metadata.Get(), "step_name", kStepName); RequireUint64Attribute(metadata.Get(), "frame_index", 0U); RequireCompoundDataset( file.Get(), "/model/nodes", static_cast(domain.Nodes().size()), {"internal_node_id", "instance_name", "source_label", "coordinates"}); auto nodes = OpenDatasetForCheck(file.Get(), "/model/nodes"); RequireStringAttribute(nodes.Get(), "coordinate_system", "global-cartesian"); RequireStringAttribute(nodes.Get(), "units_label", "length"); const std::vector nodal_dimensions = { static_cast(domain.Nodes().size()), kDofsPerNode}; RequireDoubleDataset(file.Get(), "/steps/Step-1/frames/0/nodal/displacement", nodal_dimensions, "UX,UY,UZ,URX,URY,URZ", "length,length,length,radian,radian,radian", "global-cartesian", "nodal"); RequireDoubleDataset( file.Get(), "/steps/Step-1/frames/0/nodal/reaction", nodal_dimensions, "RF1,RF2,RF3,RM1,RM2,RM3", "force,force,force,force*length,force*length,force*length", "global-cartesian", "nodal"); if (IsShellDomain(domain)) { RequireCompoundDataset( file.Get(), "/model/elements", static_cast(domain.ShellElements().Size()), {"internal_element_id", "instance_name", "source_label", "source_element_type", "internal_formulation", "node_internal_ids", "shell_section_internal_id", "material_internal_id"}); auto elements = OpenDatasetForCheck(file.Get(), "/model/elements"); RequireStringAttribute(elements.Get(), "formulation", "FESA-MITC4"); RequireCompoundDataset( file.Get(), "/model/shell/materials", static_cast(domain.LinearElasticMaterials().Size()), {"internal_material_id", "name", "E", "nu"}); RequireCompoundDataset( file.Get(), "/model/shell/sections", static_cast(domain.ShellSections().Size()), {"internal_section_id", "source_file", "source_line", "source_elset", "material_internal_id", "thickness"}); std::vector directors; std::vector frames; directors.reserve(domain.Nodes().size() * 3U); frames.reserve(domain.Nodes().size() * 9U); hdf5_internal::AppendShellReferenceFrames(domain, directors, frames); RequireModelDoubleDataset(file.Get(), "/model/shell/nodal_director", {static_cast(domain.Nodes().size()), 3U}, "D1,D2,D3", "1,1,1", "global-cartesian", "nodal", &directors); RequireModelDoubleDataset( file.Get(), "/model/shell/nodal_frame", {static_cast(domain.Nodes().size()), 3U, 3U}, "X,Y,Z", "1,1,1", "global-cartesian", "nodal-frame", &frames); auto nodal_frame = OpenDatasetForCheck(file.Get(), "/model/shell/nodal_frame"); RequireStringAttribute(nodal_frame.Get(), "axis_names", "A,B,D"); RequireUint8Dataset(file.Get(), "/model/nodal_constraint_mask", nodal_dimensions, model_data.constraint_mask); RequireModelDoubleDataset(file.Get(), "/model/prescribed_displacement", nodal_dimensions, "UX,UY,UZ,URX,URY,URZ", "length,length,length,radian,radian,radian", "global-cartesian", "nodal-prescribed-value", &model_data.prescribed_displacement); const double gauss = 1.0 / std::sqrt(3.0); const std::vector locations{-gauss, -gauss, gauss, -gauss, gauss, gauss, -gauss, gauss}; RequireModelDoubleDataset(file.Get(), "/model/shell/midsurface_locations", {4U, 2U}, "XI,ETA", "1,1", "shell-natural", "midsurface-location", &locations); const std::vector section_positions{-1.0, 0.0, 1.0}; RequireModelDoubleDataset(file.Get(), "/model/shell/section_positions", {3U}, "ZETA", "1", "shell-natural", "section-position", §ion_positions); auto positions = OpenDatasetForCheck(file.Get(), "/model/shell/section_positions"); RequireStringAttribute(positions.Get(), "position_names", "BOTTOM,MIDDLE,TOP"); const hsize_t element_count = static_cast(domain.ShellElements().Size()); const std::string shell_root = std::string{kStepRoot} + "/element/shell"; RequireDoubleDataset(file.Get(), (shell_root + "/local_frame").c_str(), {element_count, 4U, 3U, 3U}, "X,Y,Z", "1,1,1", "global-cartesian", "shell-local-frame"); auto local_frame = OpenDatasetForCheck(file.Get(), (shell_root + "/local_frame").c_str()); RequireStringAttribute(local_frame.Get(), "axis_names", "E1,E2,E3"); RequireStringAttribute(local_frame.Get(), "internal_formulation", "FESA-MITC4"); RequireStringAttribute(local_frame.Get(), "source_element_type_dataset", "/model/elements.source_element_type"); RequireStringAttribute(local_frame.Get(), "midsurface_location_dataset", "/model/shell/midsurface_locations"); RequireDoubleDataset( 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"); RequireShellResultIdentity( file.Get(), (shell_root + "/generalized_strain").c_str(), false); RequireDoubleDataset( 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"); RequireShellResultIdentity( file.Get(), (shell_root + "/section_resultant").c_str(), false); RequireDoubleDataset(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"); RequireShellResultIdentity(file.Get(), (shell_root + "/stress").c_str(), true); RequireDoubleDataset(file.Get(), "/steps/Step-1/frames/0/global/energy", {1U}, "PHYSICAL_STRAIN_ENERGY", "force*length", "global", "global"); RequireDoubleDataset( file.Get(), "/steps/Step-1/frames/0/global/equilibrium", {6U}, "FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3", "force,force,force,force*length,force*length,force*length", "global-cartesian", "global-origin"); RequireDoubleDataset( file.Get(), "/steps/Step-1/frames/0/global/verification_metrics", {3U}, "FREE_RESIDUAL_NORMALIZED,FORCE_BALANCE_NORMALIZED,MOMENT_BALANCE_" "NORMALIZED", "1,1,1", "global", "verification"); auto metrics = OpenDatasetForCheck( file.Get(), "/steps/Step-1/frames/0/global/verification_metrics"); RequireStringAttribute( metrics.Get(), "metric_definition_ids", "free-residual-l2-over-max-free-force-l2,force-balance-l2-over-max-" "force-sum,moment-balance-l2-over-max-moment-sum"); RequireStringAttribute(metrics.Get(), "acceptance_thresholds", "1e-10,1e-10,1e-10"); for (const char* forbidden : {"/steps/Step-1/frames/0/element/shell/drilling", "/steps/Step-1/frames/0/element/shell/drilling_energy", "/steps/Step-1/frames/0/element/shell/S33", "/steps/Step-1/frames/0/element/shell/S13", "/steps/Step-1/frames/0/element/shell/S23"}) { if (H5Lexists(file.Get(), forbidden, H5P_DEFAULT) != 0) { throw Hdf5Failure{"A forbidden shell result path exists."}; } } } else { RequireCompoundDataset(file.Get(), "/model/elements", static_cast(domain.BeamElements().Size()), {"internal_element_id", "instance_name", "source_label", "node_internal_ids", "local_axes"}); auto elements = OpenDatasetForCheck(file.Get(), "/model/elements"); RequireStringAttribute(elements.Get(), "formulation", "B33-3D-Euler-Bernoulli"); const std::vector end_dimensions = { static_cast(domain.BeamElements().Size()), kEndpointCount, kEndActionComponentCount}; const std::vector generalized_dimensions = { static_cast(domain.BeamElements().Size()), kGaussPointCount, kGeneralizedComponentCount}; RequireDoubleDataset( file.Get(), "/steps/Step-1/frames/0/element/end_force_local", end_dimensions, "FX,FY,FZ,MX,MY,MZ", "force,force,force,force*length,force*length,force*length", "beam-local", "endpoint-outward-action"); RequireDoubleDataset(file.Get(), "/steps/Step-1/frames/0/element/section_resultant", generalized_dimensions, "N,T,My,Mz", "force,force*length,force*length,force*length", "beam-local", "endpoint-positive-local-x-section-cut"); RequireDoubleDataset( file.Get(), "/steps/Step-1/frames/0/element/generalized_strain", generalized_dimensions, "epsilon0,kappa_x,kappa_y,kappa_z", "1,1/length,1/length,1/length", "beam-local", "integration-point"); RequireDoubleDataset(file.Get(), "/steps/Step-1/frames/0/element/generalized_resultant", generalized_dimensions, "N,T,My,Mz", "force,force*length,force*length,force*length", "beam-local", "integration-point"); RequireCompoundDataset( file.Get(), "/steps/Step-1/frames/0/element/stress_s11", static_cast(state.StressResults().size()), {"internal_element_id", "gauss_point_index", "section_point_index", "x1", "x2", "source", "S11"}); auto stress = OpenDatasetForCheck( file.Get(), "/steps/Step-1/frames/0/element/stress_s11"); RequireResultAttributes(stress.Get(), "S11", "force/length^2", "beam-local", "section-point"); } RequireCompoundDataset(file.Get(), "/diagnostics", static_cast(diagnostic_count), {"severity", "code", "file", "line", "keyword", "entity_identity", "message"}); RequireHdf5(file.CloseChecked(), "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, herr_t (*close_reader)(hid_t)) { 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."), close_reader}; Hdf5Handle metadata{ RequireHdf5Id(H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), "The nonlinear metadata group is missing."), H5Gclose}; RequireDrilling(!state.AcceptedFrames().empty()); const auto drilling = ReadDrillingDiagnostics(file.Get()); const auto& expected_drilling = state.AcceptedFrames().front().mitc4_drilling; if (expected_drilling) { RequireDrilling(drilling.has_value() && drilling->formulation == Mitc4DrillingModeName(expected_drilling->mode) && drilling->frames.size() == state.AcceptedFrames().size()); for (std::size_t i = 0; i < drilling->frames.size(); ++i) { const auto& frame = state.AcceptedFrames()[i]; ValidateDrillingEvidence(frame.global_equilibrium, frame.mitc4_drilling); RequireDrilling(frame.mitc4_drilling->mode == expected_drilling->mode); CheckDrillingFrame(drilling->frames[i], frame.frame_index, frame.global_equilibrium, *frame.mitc4_drilling); } } else { RequireDrilling(!drilling.has_value()); for (const auto& frame : state.AcceptedFrames()) RequireDrilling(!frame.mitc4_drilling); } 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(domain.Nodes().size()); const hsize_t element_count = static_cast(domain.ShellElements().Size()); const std::vector 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(domain.LinearElasticMaterials().Size()), {"internal_material_id", "name", "E", "nu"}); RequireCompoundDataset( file.Get(), "/model/shell/sections", static_cast(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(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-balance-error", &frame); } RequireCompoundDataset(file.Get(), "/diagnostics", static_cast(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, const AnalysisState& state, const std::size_t diagnostic_count, const WriterModelData& model_data, herr_t (*close_reader)(hid_t)) { try { SelfCheckFile(path, domain, state, diagnostic_count, model_data, close_reader); 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()); } } Status NonlinearSelfCheck(const std::filesystem::path& path, const Domain& domain, const NonlinearAnalysisState& state, const std::size_t diagnostic_count, herr_t (*close_reader)(hid_t)) { try { NonlinearSelfCheckFile(path, domain, state, diagnostic_count, close_reader); 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