22c9d4435c
Implement MITC4 mass, constrained whitening, MKL FEAST, modal HDF5, and deterministic Value comparison. MSVC Debug builds; 394 of 395 tests pass. Original-model all-c residual and the documented S3 static contract conflict remain unresolved; this is an implementation checkpoint, not feature acceptance.
828 lines
39 KiB
C++
828 lines
39 KiB
C++
#include "io/hdf5/eigen_hdf5_self_check.h"
|
|
|
|
#include <hdf5.h>
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <numeric>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fesa/build_info.h"
|
|
#include "io/hdf5/hdf5_primitives.h"
|
|
#include "io/hdf5/hdf5_raii.h"
|
|
|
|
namespace fesa::hdf5_internal {
|
|
namespace {
|
|
|
|
Status Failure(const std::string& message) {
|
|
return OutputFailure("invalid-eigen-hdf5-candidate", message);
|
|
}
|
|
|
|
bool HasPath(hid_t file, const char* path) {
|
|
return H5Lexists(file, path, H5P_DEFAULT) > 0;
|
|
}
|
|
|
|
bool HasNoStaticResults(hid_t file) {
|
|
for (const char* path : {"/steps/Step-1/frames", "/steps/Step-1/global",
|
|
"/steps/Step-1/element"}) {
|
|
if (HasPath(file, path)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ReadUint64Attribute(hid_t object, const char* name,
|
|
std::uint64_t expected) {
|
|
if (H5Aexists(object, name) <= 0) return false;
|
|
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
|
if (attribute.Get() < 0) return false;
|
|
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
|
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
|
|
if (type.Get() < 0 || H5Tequal(type.Get(), H5T_STD_U64LE) <= 0 ||
|
|
space.Get() < 0 || H5Sget_simple_extent_type(space.Get()) != H5S_SCALAR)
|
|
return false;
|
|
std::uint64_t value = 0U;
|
|
return H5Aread(attribute.Get(), H5T_NATIVE_UINT64, &value) >= 0 &&
|
|
value == expected;
|
|
}
|
|
|
|
bool ReadStringAttribute(hid_t object, const char* name, const char* expected) {
|
|
if (H5Aexists(object, name) <= 0) return false;
|
|
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
|
if (attribute.Get() < 0) return false;
|
|
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
|
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
|
|
if (type.Get() < 0 || H5Tget_class(type.Get()) != H5T_STRING ||
|
|
H5Tis_variable_str(type.Get()) <= 0 ||
|
|
H5Tget_cset(type.Get()) != H5T_CSET_UTF8 || space.Get() < 0 ||
|
|
H5Sget_simple_extent_type(space.Get()) != H5S_SCALAR)
|
|
return false;
|
|
char* value = nullptr;
|
|
if (H5Aread(attribute.Get(), type.Get(), &value) < 0 || value == nullptr)
|
|
return false;
|
|
const bool matches = std::string{value} == expected;
|
|
H5free_memory(value);
|
|
return matches;
|
|
}
|
|
|
|
bool DatasetShape(hid_t file, const char* path,
|
|
const std::vector<hsize_t>& expected_dimensions) {
|
|
Hdf5Handle dataset{H5Dopen2(file, path, H5P_DEFAULT), H5Dclose};
|
|
if (dataset.Get() < 0) return false;
|
|
Hdf5Handle space{H5Dget_space(dataset.Get()), H5Sclose};
|
|
if (space.Get() < 0 || H5Sget_simple_extent_ndims(space.Get()) !=
|
|
static_cast<int>(expected_dimensions.size()))
|
|
return false;
|
|
std::vector<hsize_t> dimensions(expected_dimensions.size());
|
|
if (H5Sget_simple_extent_dims(space.Get(), dimensions.data(), nullptr) < 0)
|
|
return false;
|
|
return dimensions == expected_dimensions;
|
|
}
|
|
|
|
bool VerifyVectors(hid_t file, const Domain& domain,
|
|
const EigenAnalysisState& state) {
|
|
const std::size_t count = state.vectors.Rows() * state.vectors.Columns();
|
|
if (!DatasetShape(file, "/steps/Step-1/eigen/vectors",
|
|
{static_cast<hsize_t>(state.modes.size()),
|
|
static_cast<hsize_t>(domain.Nodes().size()), 6U}))
|
|
return false;
|
|
std::vector<double> values(count);
|
|
Hdf5Handle dataset{H5Dopen2(file, "/steps/Step-1/eigen/vectors", H5P_DEFAULT),
|
|
H5Dclose};
|
|
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
|
|
if (dataset.Get() < 0 || H5Tequal(type.Get(), H5T_IEEE_F64LE) <= 0 ||
|
|
(count != 0U && H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL,
|
|
H5S_ALL, H5P_DEFAULT, values.data()) < 0))
|
|
return false;
|
|
for (std::size_t row = 0U; row < state.vectors.Rows(); ++row) {
|
|
for (std::size_t column = 0U; column < state.vectors.Columns(); ++column) {
|
|
const double value = values[row * state.vectors.Columns() + column];
|
|
if (!std::isfinite(value) || value != state.vectors(row, column))
|
|
return false;
|
|
if (state.constraint_mask[column] != 0U && value != 0.0) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <class T, class Rows, class Getter>
|
|
bool CheckMember(hid_t file, const char* path, const char* name,
|
|
hid_t file_type, hid_t memory_type, const Rows& rows,
|
|
Getter get) {
|
|
if (!DatasetShape(file, path, {static_cast<hsize_t>(rows.size())}))
|
|
return false;
|
|
Hdf5Handle dataset{H5Dopen2(file, path, H5P_DEFAULT), H5Dclose};
|
|
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
|
|
if (type.Get() < 0 || H5Tget_class(type.Get()) != H5T_COMPOUND) return false;
|
|
const int member = H5Tget_member_index(type.Get(), name);
|
|
if (member < 0) return false;
|
|
Hdf5Handle actual_type{
|
|
H5Tget_member_type(type.Get(), static_cast<unsigned>(member)), H5Tclose};
|
|
if (H5Tequal(actual_type.Get(), file_type) <= 0) return false;
|
|
Hdf5Handle memory{H5Tcreate(H5T_COMPOUND, sizeof(T)), H5Tclose};
|
|
if (memory.Get() < 0 || H5Tinsert(memory.Get(), name, 0U, memory_type) < 0)
|
|
return false;
|
|
std::vector<T> values(rows.size());
|
|
if (!values.empty() && H5Dread(dataset.Get(), memory.Get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, values.data()) < 0)
|
|
return false;
|
|
for (std::size_t i = 0; i < values.size(); ++i)
|
|
if (values[i] != static_cast<T>(get(rows[i]))) return false;
|
|
return true;
|
|
}
|
|
|
|
template <class Rows, class Getter>
|
|
bool CheckTextMember(hid_t file, const char* path, const char* name,
|
|
const Rows& rows, Getter get) {
|
|
if (!DatasetShape(file, path, {static_cast<hsize_t>(rows.size())}))
|
|
return false;
|
|
Hdf5Handle dataset{H5Dopen2(file, path, H5P_DEFAULT), H5Dclose};
|
|
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
|
|
if (type.Get() < 0 || H5Tget_class(type.Get()) != H5T_COMPOUND) return false;
|
|
const int member = H5Tget_member_index(type.Get(), name);
|
|
if (member < 0) return false;
|
|
Hdf5Handle member_type{
|
|
H5Tget_member_type(type.Get(), static_cast<unsigned>(member)), H5Tclose};
|
|
if (H5Tget_class(member_type.Get()) != H5T_STRING ||
|
|
H5Tis_variable_str(member_type.Get()) <= 0 ||
|
|
H5Tget_cset(member_type.Get()) != H5T_CSET_UTF8)
|
|
return false;
|
|
Hdf5Handle memory{H5Tcreate(H5T_COMPOUND, sizeof(char*)), H5Tclose};
|
|
if (H5Tinsert(memory.Get(), name, 0U, member_type.Get()) < 0) return false;
|
|
std::vector<char*> values(rows.size(), nullptr);
|
|
if (!values.empty() && H5Dread(dataset.Get(), memory.Get(), H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, values.data()) < 0)
|
|
return false;
|
|
bool matches = true;
|
|
for (std::size_t i = 0; i < values.size(); ++i) {
|
|
if (values[i] == nullptr || std::string(values[i]) != get(rows[i]))
|
|
matches = false;
|
|
if (values[i] != nullptr && H5free_memory(values[i]) < 0) matches = false;
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
template <class T>
|
|
bool CheckScalar(hid_t object, const char* name, hid_t file_type,
|
|
hid_t memory_type, T expected) {
|
|
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
|
if (attribute.Get() < 0) return false;
|
|
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
|
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
|
|
if (H5Tequal(type.Get(), file_type) <= 0 ||
|
|
H5Sget_simple_extent_type(space.Get()) != H5S_SCALAR)
|
|
return false;
|
|
T value{};
|
|
return H5Aread(attribute.Get(), memory_type, &value) >= 0 &&
|
|
value == expected;
|
|
}
|
|
|
|
template <class T, class Values>
|
|
bool CheckArray(hid_t file, const char* path, hid_t file_type,
|
|
hid_t memory_type, const std::vector<hsize_t>& shape,
|
|
const Values& expected) {
|
|
if (!DatasetShape(file, path, shape)) return false;
|
|
Hdf5Handle dataset{H5Dopen2(file, path, H5P_DEFAULT), H5Dclose};
|
|
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
|
|
if (H5Tequal(type.Get(), file_type) <= 0) return false;
|
|
std::vector<T> actual(expected.size());
|
|
if (!actual.empty() && H5Dread(dataset.Get(), memory_type, H5S_ALL, H5S_ALL,
|
|
H5P_DEFAULT, actual.data()) < 0)
|
|
return false;
|
|
for (std::size_t i = 0U; i < actual.size(); ++i)
|
|
if (actual[i] != expected[i]) return false;
|
|
return true;
|
|
}
|
|
|
|
bool CheckTextAttributes(
|
|
hid_t object,
|
|
std::initializer_list<std::pair<const char*, std::string>> attributes) {
|
|
for (const auto& attribute : attributes)
|
|
if (!ReadStringAttribute(object, attribute.first, attribute.second.c_str()))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool CheckAttributes(hid_t file, const Domain& domain,
|
|
const EigenAnalysisState& state) {
|
|
Hdf5Handle metadata{H5Gopen2(file, "/metadata", H5P_DEFAULT), H5Gclose};
|
|
if (!CheckTextAttributes(
|
|
metadata.Get(),
|
|
{{"solver_version", std::string{SolverVersion()}},
|
|
{"source_input_identity", SourceInputIdentity(domain)},
|
|
{"unit_system_label", "user-consistent-unspecified"},
|
|
{"coordinate_convention",
|
|
"global-cartesian; shell-local=initial-reference; "
|
|
"positive-thickness=+zeta"},
|
|
{"internal_formulation", "FESA-MITC4"},
|
|
{"integration_rule", "2x2x2-gauss; mitc4-edge-midpoint-shear"},
|
|
{"mass_integration_rule", "3x3x3-gauss"},
|
|
{"stabilization_rule", "initial-physical-rotary-trace-mean"},
|
|
{"mass_variant", "derived-lumped"},
|
|
{"mass_origin", "consistent-moment-row-sum"},
|
|
{"normalization", "mass"},
|
|
{"eigensolver_backend", "MKL FEAST"},
|
|
{"eigensolver_routine", "dfeast_scsrgv"},
|
|
{"backend_problem", "scaled-whitened-lumped-with-identity-mass"},
|
|
{"numerical_contract", "mitc4-shell-feast-eigen2:r1"}}))
|
|
return false;
|
|
const auto& source = *domain.Frequency();
|
|
Hdf5Handle step{H5Gopen2(file, "/steps/Step-1", H5P_DEFAULT), H5Gclose};
|
|
if (!CheckTextAttributes(
|
|
step.Get(),
|
|
{{"source_step_name", source.source_step_name_raw},
|
|
{"source_step_name_canonical", source.source_step_name_canonical},
|
|
{"source_eigensolver", "LANCZOS"},
|
|
{"source_sim", "YES"},
|
|
{"source_acoustic_coupling", "ON"},
|
|
{"source_normalization", "MASS"},
|
|
{"frame_indexing", "mode-rank-one-based"}}) ||
|
|
!ReadUint64Attribute(step.Get(), "step_source_line",
|
|
source.step_location.line) ||
|
|
!ReadUint64Attribute(step.Get(), "frequency_source_line",
|
|
source.frequency_location.line) ||
|
|
!ReadUint64Attribute(step.Get(), "frequency_data_line",
|
|
source.data_location.line) ||
|
|
!CheckScalar<std::uint8_t>(step.Get(), "source_nlgeom", H5T_STD_U8LE,
|
|
H5T_NATIVE_UINT8, 0U) ||
|
|
!CheckScalar<std::uint8_t>(
|
|
step.Get(), "source_perturbation_present", H5T_STD_U8LE,
|
|
H5T_NATIVE_UINT8,
|
|
static_cast<std::uint8_t>(source.perturbation_explicit)))
|
|
return false;
|
|
Hdf5Handle presence{
|
|
H5Aopen(step.Get(), "source_option_presence", H5P_DEFAULT), H5Aclose};
|
|
Hdf5Handle presence_type{H5Aget_type(presence.Get()), H5Tclose};
|
|
Hdf5Handle presence_space{H5Aget_space(presence.Get()), H5Sclose};
|
|
hsize_t extent = 0U;
|
|
std::array<std::uint8_t, 4> actual_presence{};
|
|
const std::array<std::uint8_t, 4> expected_presence{
|
|
static_cast<std::uint8_t>(source.eigensolver_explicit),
|
|
static_cast<std::uint8_t>(source.sim_explicit),
|
|
static_cast<std::uint8_t>(source.acoustic_coupling_explicit),
|
|
static_cast<std::uint8_t>(source.normalization_explicit)};
|
|
if (presence.Get() < 0 || H5Tequal(presence_type.Get(), H5T_STD_U8LE) <= 0 ||
|
|
H5Sget_simple_extent_ndims(presence_space.Get()) != 1 ||
|
|
H5Sget_simple_extent_dims(presence_space.Get(), &extent, nullptr) < 0 ||
|
|
extent != 4U ||
|
|
H5Aread(presence.Get(), H5T_NATIVE_UINT8, actual_presence.data()) < 0 ||
|
|
actual_presence != expected_presence)
|
|
return false;
|
|
for (const char* leaf : {"modes", "frequencies", "vectors"}) {
|
|
const std::string path = std::string{"/steps/Step-1/eigen/"} + leaf;
|
|
Hdf5Handle dataset{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
|
|
if (!ReadUint64Attribute(dataset.Get(), "eigen_schema_version", 1U) ||
|
|
!CheckTextAttributes(dataset.Get(),
|
|
{{"source_step_name", source.source_step_name_raw},
|
|
{"domain", "mode"}}))
|
|
return false;
|
|
if (std::string{leaf} == "vectors") {
|
|
if (!CheckTextAttributes(
|
|
dataset.Get(),
|
|
{{"component_names", "UX,UY,UZ,URX,URY,URZ"},
|
|
{"component_unit_dimensions",
|
|
"mass^-1/2,mass^-1/2,mass^-1/2,mass^-1/2*length^-1,mass^-1/"
|
|
"2*length^-1,mass^-1/2*length^-1"},
|
|
{"coordinate_system", "global-cartesian"},
|
|
{"location", "nodal-modal-vector"},
|
|
{"normalization", "mass"},
|
|
{"mass_variant", "derived-lumped"},
|
|
{"interpretation",
|
|
"normalized-mode-not-response-displacement"}}))
|
|
return false;
|
|
} else {
|
|
const bool modes = std::string{leaf} == "modes";
|
|
if (!CheckTextAttributes(
|
|
dataset.Get(),
|
|
{{"quantity", modes ? "eigenvalue" : "cycle-frequency"},
|
|
{"component_names", modes ? "Value" : "Freq"},
|
|
{"component_unit_dimensions", modes ? "time^-2" : "time^-1"},
|
|
{"coordinate_system", "scalar"},
|
|
{"location", "modal-frame"}}))
|
|
return false;
|
|
if (!modes && !ReadStringAttribute(dataset.Get(), "unavailable_rule",
|
|
"negative-raw-eigenvalue"))
|
|
return false;
|
|
}
|
|
}
|
|
Hdf5Handle verification{
|
|
H5Gopen2(file, "/steps/Step-1/eigen/verification", H5P_DEFAULT),
|
|
H5Gclose};
|
|
for (const auto& metric :
|
|
std::initializer_list<std::pair<const char*, double>>{
|
|
{"characteristic_length", state.characteristic_length},
|
|
{"operator_scale", state.backend.operator_scale},
|
|
{"eta_gram", state.eta_gram},
|
|
{"max_eta_orth", state.max_eta_orth},
|
|
{"eta_mz", state.eta_mz},
|
|
{"eta_kz", state.eta_kz},
|
|
{"eta_pz", state.eta_pz}})
|
|
if (!CheckScalar<double>(verification.Get(), metric.first, H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, metric.second))
|
|
return false;
|
|
if (!ReadStringAttribute(verification.Get(), "validation_scope",
|
|
"all-computed-c-columns") ||
|
|
!CheckScalar<std::uint8_t>(
|
|
verification.Get(), "null_checks_applicable", H5T_STD_U8LE,
|
|
H5T_NATIVE_UINT8,
|
|
static_cast<std::uint8_t>(state.null_checks_applicable)))
|
|
return false;
|
|
Hdf5Handle backend{H5Gopen2(file, "/steps/Step-1/eigen/backend", H5P_DEFAULT),
|
|
H5Gclose};
|
|
const auto& b = state.backend;
|
|
if (!CheckTextAttributes(
|
|
backend.Get(),
|
|
{{"runtime_version", b.runtime_version},
|
|
{"integer_abi", b.integer_abi},
|
|
{"upper_search_termination", b.upper_search_termination}}) ||
|
|
!ReadUint64Attribute(backend.Get(), "thread_count", b.thread_count) ||
|
|
!ReadUint64Attribute(backend.Get(), "seed", b.seed) ||
|
|
!ReadUint64Attribute(backend.Get(), "final_count",
|
|
state.eigenpair_checks.size()))
|
|
return false;
|
|
for (const auto& bound :
|
|
std::initializer_list<std::pair<const char*, double>>{
|
|
{"enclosure_lower", b.enclosure_lower},
|
|
{"enclosure_upper", b.enclosure_upper},
|
|
{"solve_lower", b.solve_lower},
|
|
{"solve_upper", b.solve_upper}})
|
|
if (!CheckScalar<double>(backend.Get(), bound.first, H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, bound.second))
|
|
return false;
|
|
std::size_t counts = 0U, solves = 0U, final_ordinal = 0U;
|
|
for (const auto& call : b.backend_calls) {
|
|
if (call.kind == "count") ++counts;
|
|
if (call.kind == "solve") {
|
|
++solves;
|
|
final_ordinal = call.ordinal;
|
|
}
|
|
}
|
|
return ReadUint64Attribute(backend.Get(), "count_calls", counts) &&
|
|
ReadUint64Attribute(backend.Get(), "solve_calls", solves) &&
|
|
ReadUint64Attribute(backend.Get(), "final_solve_ordinal",
|
|
final_ordinal) &&
|
|
CheckArray<std::int64_t>(file, "/steps/Step-1/eigen/backend/base_fpm",
|
|
H5T_STD_I64LE, H5T_NATIVE_INT64, {128U},
|
|
b.base_fpm) &&
|
|
CheckArray<std::uint8_t>(
|
|
file, "/model/nodal_constraint_mask", H5T_STD_U8LE,
|
|
H5T_NATIVE_UINT8,
|
|
{static_cast<hsize_t>(domain.Nodes().size()), 6U},
|
|
state.constraint_mask) &&
|
|
CheckArray<double>(file, "/model/prescribed_displacement",
|
|
H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE,
|
|
{static_cast<hsize_t>(domain.Nodes().size()), 6U},
|
|
state.prescribed_displacement);
|
|
}
|
|
|
|
bool CheckModelRows(hid_t file, const Domain& domain) {
|
|
std::vector<std::size_t> indices(domain.Nodes().size());
|
|
std::iota(indices.begin(), indices.end(), 0U);
|
|
const char* path = "/model/nodes";
|
|
const hsize_t three = 3U, four = 4U;
|
|
Hdf5Handle coordinates_file{H5Tarray_create2(H5T_IEEE_F64LE, 1, &three),
|
|
H5Tclose};
|
|
Hdf5Handle coordinates_memory{H5Tarray_create2(H5T_NATIVE_DOUBLE, 1, &three),
|
|
H5Tclose};
|
|
if (!CheckMember<std::uint64_t>(file, path, "internal_node_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, indices,
|
|
[](auto i) { return i; }) ||
|
|
!CheckTextMember(
|
|
file, path, "instance_name", domain.Nodes(),
|
|
[](const auto& row) { return row.source_id.instance_name; }) ||
|
|
!CheckTextMember(
|
|
file, path, "source_label", domain.Nodes(),
|
|
[](const auto& row) { return row.source_id.source_label_text; }) ||
|
|
!CheckMember<std::array<double, 3>>(
|
|
file, path, "coordinates", coordinates_file.Get(),
|
|
coordinates_memory.Get(), domain.Nodes(),
|
|
[](const auto& row) { return row.coordinates; }))
|
|
return false;
|
|
indices.resize(domain.ShellElements().Size());
|
|
std::iota(indices.begin(), indices.end(), 0U);
|
|
Hdf5Handle nodes_file{H5Tarray_create2(H5T_STD_U64LE, 1, &four), H5Tclose};
|
|
Hdf5Handle nodes_memory{H5Tarray_create2(H5T_NATIVE_UINT64, 1, &four),
|
|
H5Tclose};
|
|
path = "/model/elements";
|
|
if (!CheckMember<std::uint64_t>(file, path, "internal_element_id",
|
|
H5T_STD_U64LE, H5T_NATIVE_UINT64, indices,
|
|
[](auto i) { return i; }) ||
|
|
!CheckTextMember(
|
|
file, path, "instance_name", indices,
|
|
[&](auto i) {
|
|
return domain.ShellElements()[i].source_id.instance_name;
|
|
}) ||
|
|
!CheckTextMember(
|
|
file, path, "source_label", indices,
|
|
[&](auto i) {
|
|
return domain.ShellElements()[i].source_id.source_label_text;
|
|
}) ||
|
|
!CheckTextMember(file, path, "source_element_type", indices,
|
|
[&](auto i) {
|
|
return ShellSourceTypeName(
|
|
domain.ShellElements()[i].source_type);
|
|
}) ||
|
|
!CheckTextMember(file, path, "internal_formulation", indices,
|
|
[](auto) { return std::string{"FESA-MITC4"}; }) ||
|
|
!CheckMember<std::uint64_t>(
|
|
file, path, "material_internal_id", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
indices,
|
|
[&](auto i) { return domain.ShellElements()[i].material_index; }) ||
|
|
!CheckMember<std::uint64_t>(
|
|
file, path, "shell_section_internal_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, indices,
|
|
[&](auto i) { return domain.ShellElements()[i].section_index; }) ||
|
|
!CheckMember<std::array<std::uint64_t, 4>>(
|
|
file, path, "node_internal_ids", nodes_file.Get(), nodes_memory.Get(),
|
|
indices, [&](auto i) {
|
|
const auto& nodes = domain.ShellElements()[i].node_indices;
|
|
return std::array<std::uint64_t, 4>{nodes[0], nodes[1], nodes[2],
|
|
nodes[3]};
|
|
}))
|
|
return false;
|
|
indices.resize(domain.LinearElasticMaterials().Size());
|
|
std::iota(indices.begin(), indices.end(), 0U);
|
|
path = "/model/shell/materials";
|
|
if (!CheckMember<std::uint64_t>(file, path, "internal_material_id",
|
|
H5T_STD_U64LE, H5T_NATIVE_UINT64, indices,
|
|
[](auto i) { return i; }) ||
|
|
!CheckTextMember(
|
|
file, path, "name", indices,
|
|
[&](auto i) { return domain.LinearElasticMaterials()[i].name; }) ||
|
|
!CheckMember<double>(
|
|
file, path, "E", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE, indices,
|
|
[&](auto i) {
|
|
return domain.LinearElasticMaterials()[i].youngs_modulus;
|
|
}) ||
|
|
!CheckMember<double>(
|
|
file, path, "nu", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE, indices,
|
|
[&](auto i) {
|
|
return domain.LinearElasticMaterials()[i].poisson_ratio;
|
|
}))
|
|
return false;
|
|
indices.resize(domain.ShellSections().Size());
|
|
std::iota(indices.begin(), indices.end(), 0U);
|
|
path = "/model/shell/sections";
|
|
if (!CheckMember<std::uint64_t>(file, path, "internal_section_id",
|
|
H5T_STD_U64LE, H5T_NATIVE_UINT64, indices,
|
|
[](auto i) { return i; }) ||
|
|
!CheckMember<std::uint64_t>(
|
|
file, path, "material_internal_id", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
indices,
|
|
[&](auto i) { return domain.ShellSections()[i].material_index; }) ||
|
|
!CheckMember<std::uint64_t>(
|
|
file, path, "source_line", H5T_STD_U64LE, H5T_NATIVE_UINT64, indices,
|
|
[&](auto i) { return domain.ShellSections()[i].location.line; }) ||
|
|
!CheckTextMember(file, path, "source_file", indices,
|
|
[&](auto i) {
|
|
return NormalizedPathString(
|
|
domain.ShellSections()[i].location.file);
|
|
}) ||
|
|
!CheckTextMember(
|
|
file, path, "source_elset", indices,
|
|
[&](auto i) { return domain.ShellSections()[i].name; }) ||
|
|
!CheckMember<double>(
|
|
file, path, "thickness", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE, indices,
|
|
[&](auto i) { return domain.ShellSections()[i].thickness; }))
|
|
return false;
|
|
std::vector<double> directors, frames;
|
|
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
|
directors.insert(directors.end(), frame.director.begin(),
|
|
frame.director.end());
|
|
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
|
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
|
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
|
}
|
|
return CheckArray<double>(file, "/model/shell/nodal_director", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE,
|
|
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
|
|
directors) &&
|
|
CheckArray<double>(
|
|
file, "/model/shell/nodal_frame", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE,
|
|
{static_cast<hsize_t>(domain.Nodes().size()), 3U, 3U}, frames);
|
|
}
|
|
|
|
bool CheckModalRows(hid_t file, const Domain& domain,
|
|
const EigenAnalysisState& state,
|
|
const std::vector<Diagnostic>& diagnostics) {
|
|
const char* path = "/steps/Step-1/eigen/modes";
|
|
if (!CheckMember<std::uint64_t>(file, path, "mode_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, state.modes,
|
|
[](const auto& row) { return row.mode_id; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "eigenvalue", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.modes,
|
|
[](const auto& row) { return row.eigenvalue; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "classification", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.modes, [](const auto& row) { return row.classification; }))
|
|
return false;
|
|
if (!CheckMember<std::uint64_t>(
|
|
file, path, "cluster_id", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
state.modes, [](const auto& row) { return row.cluster_id; }))
|
|
return false;
|
|
std::vector<EigenMode> frequencies;
|
|
for (const auto& mode : state.modes)
|
|
if (mode.frequency) frequencies.push_back(mode);
|
|
path = "/steps/Step-1/eigen/frequencies";
|
|
if (!CheckMember<std::uint64_t>(file, path, "mode_id", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64, frequencies,
|
|
[](const auto& row) { return row.mode_id; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "frequency", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, frequencies,
|
|
[](const auto& row) { return *row.frequency; }))
|
|
return false;
|
|
path = "/steps/Step-1/eigen/verification/nodal_reduction";
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "free_count", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.nodal_reduction,
|
|
[](const auto& row) { return row.free_count; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "null_count", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.nodal_reduction,
|
|
[](const auto& row) { return row.null_count; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "positive_count", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.nodal_reduction,
|
|
[](const auto& row) { return row.positive_count; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "rcond_available", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.nodal_reduction,
|
|
[](const auto& row) { return row.rcond_available; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "rcond_inf", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.nodal_reduction,
|
|
[](const auto& row) { return row.rcond_inf; }))
|
|
return false;
|
|
path = "/steps/Step-1/eigen/verification/eigenpair_checks";
|
|
if (!CheckMember<std::uint64_t>(
|
|
file, path, "sorted_rank", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
state.eigenpair_checks,
|
|
[](const auto& row) { return row.sorted_rank; }))
|
|
return false;
|
|
if (!CheckMember<std::uint64_t>(
|
|
file, path, "solver_column", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
state.eigenpair_checks,
|
|
[](const auto& row) { return row.solver_column; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "eigenvalue", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.eigenvalue; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "modal_mass", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.modal_mass; }))
|
|
return false;
|
|
if (!CheckMember<double>(
|
|
file, path, "rayleigh_quotient", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE,
|
|
state.eigenpair_checks,
|
|
[](const auto& row) { return row.rayleigh_quotient; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "eta_r", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.eta_r; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "eta_w", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.eta_w; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "rho_b", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.rho_b; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "eta_norm", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.eta_norm; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "eta_energy", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.eta_energy; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "backend_res_available", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.eigenpair_checks,
|
|
[](const auto& row) { return row.backend_res_available; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "backend_res", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.eigenpair_checks,
|
|
[](const auto& row) { return row.backend_res; }))
|
|
return false;
|
|
path = "/steps/Step-1/eigen/backend/calls";
|
|
if (!CheckMember<std::uint64_t>(file, path, "ordinal", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64,
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.ordinal; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "lower", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.backend.backend_calls,
|
|
[](const auto& row) { return row.lower; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "upper", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.backend.backend_calls,
|
|
[](const auto& row) { return row.upper; }))
|
|
return false;
|
|
if (!CheckMember<std::uint64_t>(
|
|
file, path, "allocated_m0", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.allocated_m0; }))
|
|
return false;
|
|
if (!CheckMember<std::int64_t>(file, path, "info", H5T_STD_I64LE,
|
|
H5T_NATIVE_INT64, state.backend.backend_calls,
|
|
[](const auto& row) { return row.info; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "count_available", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.count_available; }))
|
|
return false;
|
|
if (!CheckMember<std::uint64_t>(file, path, "count", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64,
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.count; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "loop_available", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.loop_available; }))
|
|
return false;
|
|
if (!CheckMember<std::uint64_t>(file, path, "loop", H5T_STD_U64LE,
|
|
H5T_NATIVE_UINT64,
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.loop; }))
|
|
return false;
|
|
if (!CheckMember<std::uint8_t>(
|
|
file, path, "epsout_available", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.epsout_available; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "epsout", H5T_IEEE_F64LE,
|
|
H5T_NATIVE_DOUBLE, state.backend.backend_calls,
|
|
[](const auto& row) { return row.epsout; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "kind", state.backend.backend_calls,
|
|
[](const auto& row) { return row.kind; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "purpose", state.backend.backend_calls,
|
|
[](const auto& row) { return row.purpose; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "outcome", state.backend.backend_calls,
|
|
[](const auto& row) { return row.outcome; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "decision_reason",
|
|
state.backend.backend_calls,
|
|
[](const auto& row) { return row.decision_reason; }))
|
|
return false;
|
|
path = "/model/shell/densities";
|
|
if (!CheckMember<std::uint64_t>(
|
|
file, path, "material_internal_id", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
domain.Densities(),
|
|
[](const auto& row) { return row.material_index; }))
|
|
return false;
|
|
if (!CheckMember<double>(file, path, "rho", H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE,
|
|
domain.Densities(),
|
|
[](const auto& row) { return row.rho; }))
|
|
return false;
|
|
if (!CheckMember<std::uint64_t>(
|
|
file, path, "source_line", H5T_STD_U64LE, H5T_NATIVE_UINT64,
|
|
domain.Densities(),
|
|
[](const auto& row) { return row.location.line; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "source_file", domain.Densities(),
|
|
[](const auto& row) {
|
|
return NormalizedPathString(row.location.file);
|
|
}))
|
|
return false;
|
|
path = "/diagnostics";
|
|
if (!CheckMember<std::uint64_t>(
|
|
file, path, "line", H5T_STD_U64LE, H5T_NATIVE_UINT64, diagnostics,
|
|
[](const auto& row) { return row.location.line; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "severity", diagnostics,
|
|
[](const auto&) { return std::string{"warning"}; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "file", diagnostics, [](const auto& row) {
|
|
return NormalizedPathString(row.location.file);
|
|
}))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "code", diagnostics,
|
|
[](const auto& row) { return row.code; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "keyword", diagnostics,
|
|
[](const auto& row) { return row.keyword; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "entity_identity", diagnostics,
|
|
[](const auto& row) { return row.entity_identity; }))
|
|
return false;
|
|
if (!CheckTextMember(file, path, "message", diagnostics,
|
|
[](const auto& row) { return row.message; }))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status SelfCheckEigenFile(const std::filesystem::path& path,
|
|
const Domain& domain, const EigenAnalysisState& state,
|
|
const std::vector<Diagnostic>& diagnostics) {
|
|
try {
|
|
Hdf5ErrorSilencer silence;
|
|
Hdf5Handle file{RequireHdf5Id(H5Fopen(path.string().c_str(), H5F_ACC_RDONLY,
|
|
H5P_DEFAULT),
|
|
"Unable to reopen eigen candidate."),
|
|
H5Fclose};
|
|
for (const char* required :
|
|
{"/metadata", "/model/nodes", "/model/elements",
|
|
"/model/shell/densities", "/diagnostics", "/steps/Step-1/eigen/modes",
|
|
"/steps/Step-1/eigen/frequencies", "/steps/Step-1/eigen/vectors",
|
|
"/steps/Step-1/eigen/verification/nodal_reduction",
|
|
"/steps/Step-1/eigen/verification/eigenpair_checks",
|
|
"/steps/Step-1/eigen/backend/base_fpm",
|
|
"/steps/Step-1/eigen/backend/calls"}) {
|
|
if (!HasPath(file.Get(), required))
|
|
return Failure("Mandatory eigen path is missing.");
|
|
}
|
|
if (!HasNoStaticResults(file.Get()))
|
|
return Failure("Eigen output contains static result paths.");
|
|
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
|
H5Gclose};
|
|
Hdf5Handle step{H5Gopen2(file.Get(), "/steps/Step-1", H5P_DEFAULT),
|
|
H5Gclose};
|
|
if (metadata.Get() < 0 || step.Get() < 0 ||
|
|
!ReadUint64Attribute(metadata.Get(), "schema_version", 0U) ||
|
|
!ReadUint64Attribute(metadata.Get(), "eigen_schema_version", 1U) ||
|
|
!ReadStringAttribute(metadata.Get(), "feature_id",
|
|
"mitc4-shell-feast-eigen2") ||
|
|
!ReadStringAttribute(metadata.Get(), "analysis_procedure",
|
|
"linear-frequency") ||
|
|
!ReadUint64Attribute(step.Get(), "requested_mode_count",
|
|
state.modes.size()) ||
|
|
!ReadUint64Attribute(step.Get(), "returned_mode_count",
|
|
state.modes.size()) ||
|
|
!ReadUint64Attribute(step.Get(), "full_dof_count",
|
|
state.full_dof_count) ||
|
|
!ReadUint64Attribute(step.Get(), "free_dof_count",
|
|
state.free_dof_count) ||
|
|
!ReadUint64Attribute(step.Get(), "mass_null_dimension",
|
|
state.mass_null_dimension) ||
|
|
!ReadUint64Attribute(step.Get(), "finite_dimension",
|
|
state.finite_dimension) ||
|
|
!ReadUint64Attribute(step.Get(), "validated_eigenpair_count",
|
|
state.eigenpair_checks.size())) {
|
|
return Failure("Eigen metadata or step inventory differs from state.");
|
|
}
|
|
if (!DatasetShape(file.Get(), "/steps/Step-1/eigen/modes",
|
|
{static_cast<hsize_t>(state.modes.size())}) ||
|
|
!DatasetShape(file.Get(), "/steps/Step-1/eigen/frequencies",
|
|
{static_cast<hsize_t>([&state] {
|
|
std::size_t count = 0U;
|
|
for (const auto& mode : state.modes)
|
|
if (mode.frequency) ++count;
|
|
return count;
|
|
}())}) ||
|
|
!DatasetShape(file.Get(),
|
|
"/steps/Step-1/eigen/verification/nodal_reduction",
|
|
{static_cast<hsize_t>(domain.Nodes().size())}) ||
|
|
!DatasetShape(file.Get(),
|
|
"/steps/Step-1/eigen/verification/eigenpair_checks",
|
|
{static_cast<hsize_t>(state.eigenpair_checks.size())}) ||
|
|
!DatasetShape(file.Get(), "/steps/Step-1/eigen/backend/base_fpm",
|
|
{128U}) ||
|
|
!DatasetShape(
|
|
file.Get(), "/steps/Step-1/eigen/backend/calls",
|
|
{static_cast<hsize_t>(state.backend.backend_calls.size())}) ||
|
|
!VerifyVectors(file.Get(), domain, state)) {
|
|
return Failure("Eigen dataset shape or vector data differs from state.");
|
|
}
|
|
if (!CheckModalRows(file.Get(), domain, state, diagnostics))
|
|
return Failure(
|
|
"Compound type, source identity or value differs from candidate "
|
|
"state.");
|
|
if (!CheckAttributes(file.Get(), domain, state))
|
|
return Failure(
|
|
"Mandatory attributes, controls or boundary data differ from "
|
|
"candidate state.");
|
|
if (!CheckModelRows(file.Get(), domain))
|
|
return Failure(
|
|
"Model source identities, connectivity or physical data differ from "
|
|
"Domain.");
|
|
if (metadata.CloseChecked() < 0 || step.CloseChecked() < 0)
|
|
return Failure("Unable to close candidate groups.");
|
|
if (file.CloseChecked() < 0)
|
|
return Failure("Unable to close checked eigen candidate.");
|
|
return Status::Ok();
|
|
} catch (const Hdf5Failure& failure) {
|
|
return Failure(failure.what());
|
|
}
|
|
}
|
|
|
|
} // namespace fesa::hdf5_internal
|