feat: add eigen2 frequency pipeline and fail-closed validation

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.
This commit is contained in:
KOKO\Mimi
2026-09-09 03:14:30 +09:00
parent e74ac247db
commit 22c9d4435c
39 changed files with 7240 additions and 541 deletions
+5
View File
@@ -3,6 +3,8 @@ add_library(
STATIC
analysis/analysis_model.cpp
analysis/analysis_state.cpp
analysis/eigen_reduction.cpp
analysis/linear_frequency_procedure.cpp
analysis/linear_static_analysis.cpp
analysis/linear_static_procedure.cpp
analysis/newton_iteration_log.cpp
@@ -42,6 +44,8 @@ add_library(
io/hdf5/hdf5_primitives.cpp
io/hdf5/hdf5_result_writer.cpp
io/hdf5/hdf5_results_writer.cpp
io/hdf5/eigen_hdf5_results_writer.cpp
io/hdf5/eigen_hdf5_self_check.cpp
io/hdf5/hdf5_self_check.cpp
loads/concentrated_nodal_load.cpp
loads/shell_surface_pressure.cpp
@@ -61,6 +65,7 @@ add_library(
results/recovery_candidate.cpp
results/result_recovery.cpp
results/shell_result_recovery.cpp
solvers/eigen/mkl_feast_eigen_solver.cpp
solvers/linear/mkl_pardiso_solver.cpp
solvers/linear/mkl_pardiso_tangent_solver.cpp
)
+411
View File
@@ -0,0 +1,411 @@
#include "analysis/eigen_reduction.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.h"
namespace fesa::eigen_internal {
namespace {
constexpr double kAlgebraLimit = 1e-12;
constexpr double kConditionLimit = 0x1p-32;
Status Failure(const std::string& code, const std::size_t node,
const std::string& message) {
return Status::Failure(FailureCategory::kSolver, {{Severity::kError,
code,
{},
"FREQUENCY",
std::to_string(node),
message}});
}
double Norm(const Matrix& matrix) {
double norm = 0.0;
for (std::size_t i = 0; i < matrix.Rows(); ++i) {
for (std::size_t j = 0; j < matrix.Columns(); ++j) {
norm = std::hypot(norm, matrix(i, j));
}
}
return norm;
}
double Ratio(const double numerator, const double denominator) {
if (denominator > 0.0 && std::isfinite(denominator)) {
return numerator / denominator;
}
return numerator == 0.0 && denominator == 0.0
? 0.0
: (std::numeric_limits<double>::infinity)();
}
double Scale(const std::size_t component, const double length) {
return component < 3U ? length : 1.0;
}
Matrix ScaledMass(const Matrix& mass, const double length) {
Matrix scaled(6U, 6U);
for (std::size_t i = 0; i < 6U; ++i) {
for (std::size_t j = 0; j < 6U; ++j) {
scaled(i, j) = Scale(i, length) * mass(i, j) * Scale(j, length);
}
}
return scaled;
}
Result<NodalLift> MakeLift(const Matrix& mass,
const ShellNodeInitialFrame& frame,
const std::vector<std::uint8_t>& mask,
const double length, const std::size_t offset) {
const std::size_t node = frame.node_index;
std::vector<std::size_t> translations;
std::vector<std::size_t> rotations;
bool has_null = true;
for (std::size_t c = 0; c < 6U; ++c) {
if (mask[6U * node + c] == 0U) {
(c < 3U ? translations : rotations).push_back(c);
} else if (c >= 3U && frame.director[c - 3U] != 0.0) {
has_null = false;
}
}
has_null = has_null && !rotations.empty();
const std::size_t free = translations.size() + rotations.size();
const std::size_t positive = free - static_cast<std::size_t>(has_null);
NodalLift result;
result.offset = offset;
result.record = {static_cast<std::uint8_t>(free),
static_cast<std::uint8_t>(has_null),
static_cast<std::uint8_t>(positive), positive != 0U, 0.0};
result.positive_basis = Matrix(6U, positive);
auto& basis = result.positive_basis;
std::size_t column = 0U;
for (const auto c : translations) basis(c, column++) = 1.0;
if (!has_null) {
for (const auto c : rotations) basis(c, column++) = 1.0;
} else {
for (std::size_t c = 0; c < 3U; ++c) {
result.null_vector[c + 3U] = frame.director[c];
}
if (rotations.size() == 3U) {
for (std::size_t c = 0; c < 3U; ++c) {
basis(c + 3U, column) = frame.tangent_a[c];
basis(c + 3U, column + 1U) = frame.tangent_b[c];
}
} else if (rotations.size() == 2U) {
const auto a = rotations[0];
const auto b = rotations[1];
const double norm =
std::hypot(frame.director[a - 3U], frame.director[b - 3U]);
basis(a, column) = -frame.director[b - 3U] / norm;
basis(b, column) = frame.director[a - 3U] / norm;
const auto pivot =
std::abs(basis(a, column)) >= std::abs(basis(b, column)) ? a : b;
if (basis(pivot, column) < 0.0) {
basis(a, column) = -basis(a, column);
basis(b, column) = -basis(b, column);
}
}
}
result.scaled_lift = Matrix(6U, positive);
if (positive == 0U) return Result<NodalLift>::Success(std::move(result));
const Matrix scaled = ScaledMass(mass, length);
Matrix mp(positive, positive);
double scale = 0.0;
double basis_error = 0.0;
for (std::size_t i = 0; i < positive; ++i) {
for (std::size_t j = 0; j < positive; ++j) {
double gram = 0.0;
for (std::size_t a = 0; a < 6U; ++a) {
gram += basis(a, i) * basis(a, j);
for (std::size_t b = 0; b < 6U; ++b) {
mp(i, j) += basis(a, i) * scaled(a, b) * basis(b, j);
}
}
basis_error = std::hypot(basis_error, gram - (i == j ? 1.0 : 0.0));
scale = (std::max)(scale, std::abs(mp(i, j)));
}
}
if (!(basis_error <= kAlgebraLimit) || !(scale > 0.0) ||
!std::isfinite(scale)) {
return Result<NodalLift>::Failure(Failure("invalid-eigen-mass-space", node,
"Invalid positive mass basis."));
}
Matrix lower(positive, positive);
for (std::size_t i = 0; i < positive; ++i) {
for (std::size_t j = 0; j <= i; ++j) {
double value = mp(i, j) / scale;
for (std::size_t k = 0; k < j; ++k) value -= lower(i, k) * lower(j, k);
if (i == j) {
if (!(value > 0.0) || !std::isfinite(value)) {
return Result<NodalLift>::Failure(
Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable."));
}
lower(i, j) = std::sqrt(value);
} else {
lower(i, j) = value / lower(j, j);
}
}
}
Matrix inverse_lower(positive, positive);
for (std::size_t j = 0; j < positive; ++j) {
for (std::size_t i = 0; i < positive; ++i) {
double value = i == j ? 1.0 : 0.0;
for (std::size_t k = 0; k < i; ++k)
value -= lower(i, k) * inverse_lower(k, j);
inverse_lower(i, j) = value / lower(i, i);
}
}
double norm_mp = 0.0;
double norm_inverse = 0.0;
double reconstruction = 0.0;
double scaled_mass_norm = 0.0;
double rebuilt_mass_norm = 0.0;
for (std::size_t i = 0; i < positive; ++i) {
double row_mp = 0.0;
double row_inverse = 0.0;
for (std::size_t j = 0; j < positive; ++j) {
double inverse = 0.0;
double rebuilt = 0.0;
for (std::size_t k = 0; k < positive; ++k) {
inverse += inverse_lower(k, i) * inverse_lower(k, j);
rebuilt += lower(i, k) * lower(j, k);
}
row_inverse += std::abs(inverse);
row_mp += std::abs(mp(i, j) / scale);
reconstruction = std::hypot(reconstruction, rebuilt - mp(i, j) / scale);
scaled_mass_norm = std::hypot(scaled_mass_norm, mp(i, j) / scale);
rebuilt_mass_norm = std::hypot(rebuilt_mass_norm, rebuilt);
}
norm_mp = (std::max)(norm_mp, row_mp);
norm_inverse = (std::max)(norm_inverse, row_inverse);
}
result.record.rcond_inf = (1.0 / norm_mp) / norm_inverse;
if (!(result.record.rcond_inf >= kConditionLimit) ||
!(Ratio(reconstruction, scaled_mass_norm + rebuilt_mass_norm) <=
kAlgebraLimit)) {
return Result<NodalLift>::Failure(Failure(
"eigen-mass-conditioning", node,
"Nodal mass fails rcond_inf >= 2^-32 or Cholesky reconstruction."));
}
const double root_scale = std::sqrt(scale);
for (std::size_t c = 0; c < 6U; ++c) {
for (std::size_t j = 0; j < positive; ++j) {
for (std::size_t k = 0; k < positive; ++k) {
result.scaled_lift(c, j) +=
basis(c, k) * inverse_lower(j, k) / root_scale;
}
if (!std::isfinite(result.scaled_lift(c, j))) {
return Result<NodalLift>::Failure(Failure(
"eigen-mass-conditioning", node, "Nodal whitening is nonfinite."));
}
}
}
return Result<NodalLift>::Success(std::move(result));
}
} // namespace
Result<EigenReduction> BuildEigenReduction(
const SparseMatrix& stiffness, const std::vector<Matrix>& mass,
const std::vector<ShellNodeInitialFrame>& frames,
const std::vector<std::uint8_t>& mask, const std::vector<double>& drilling,
const double length) {
const std::size_t count = mass.size();
const Status valid = stiffness.Validate();
if (!valid.IsOk()) return Result<EigenReduction>::Failure(valid);
if (count > (std::numeric_limits<std::size_t>::max)() / 6U ||
stiffness.Rows() != 6U * count || stiffness.Columns() != 6U * count ||
frames.size() != count || drilling.size() != count ||
mask.size() != 6U * count || !(length > 0.0) || !std::isfinite(length)) {
return Result<EigenReduction>::Failure(Failure(
"invalid-eigen-mass-space", 0U, "Incompatible mass-space inventory."));
}
std::vector<NodalLift> lifts;
std::size_t dimension = 0U;
std::size_t free_count = 0U;
std::size_t null_count = 0U;
double mass_norm = 0.0;
double mass_null_error = 0.0;
double null_norm = 0.0;
double expected_kz_norm = 0.0;
double pz_norm = 0.0;
for (std::size_t node = 0; node < count; ++node) {
if (mass[node].Rows() != 6U || mass[node].Columns() != 6U ||
frames[node].node_index != node || !(drilling[node] > 0.0) ||
!std::isfinite(drilling[node])) {
return Result<EigenReduction>::Failure(
Failure("invalid-eigen-mass-space", node,
"Invalid nodal mass or drill inventory."));
}
const auto scaled = ScaledMass(mass[node], length);
double symmetry = 0.0;
double director_norm = 0.0;
for (const auto value : frames[node].director)
director_norm = std::hypot(director_norm, value);
for (std::size_t a = 0; a < 6U; ++a) {
if (mask[6U * node + a] > 1U) {
return Result<EigenReduction>::Failure(
Failure("invalid-eigen-mass-space", node,
"Constraint mask must be binary."));
}
for (std::size_t b = 0; b < 6U; ++b) {
if (!std::isfinite(scaled(a, b))) {
return Result<EigenReduction>::Failure(
Failure("invalid-eigen-mass-space", node, "Mass is nonfinite."));
}
symmetry = std::hypot(symmetry, scaled(a, b) - scaled(b, a));
if (mask[6U * node + a] == 0U && mask[6U * node + b] == 0U) {
mass_norm = std::hypot(mass_norm, scaled(a, b));
}
}
}
if (!(std::abs(director_norm - 1.0) <= kAlgebraLimit) ||
!(Ratio(symmetry, Norm(scaled)) <= kAlgebraLimit)) {
return Result<EigenReduction>::Failure(
Failure("invalid-eigen-mass-space", node,
"Invalid director or mass symmetry."));
}
auto lift = MakeLift(mass[node], frames[node], mask, length, dimension);
if (!lift.HasValue())
return Result<EigenReduction>::Failure(lift.GetStatus());
dimension += lift.Value().record.positive_count;
free_count += lift.Value().record.free_count;
null_count += lift.Value().record.null_count;
const auto& z = lift.Value().null_vector;
for (std::size_t a = 0; a < 6U; ++a) {
null_norm = std::hypot(null_norm, z[a]);
expected_kz_norm = std::hypot(expected_kz_norm, drilling[node] * z[a]);
if (mask[6U * node + a] != 0U) continue;
double action = 0.0;
for (std::size_t b = 0; b < 6U; ++b) action += scaled(a, b) * z[b];
mass_null_error = std::hypot(mass_null_error, action);
}
for (std::size_t j = 0; j < lift.Value().positive_basis.Columns(); ++j) {
double dot = 0.0;
for (std::size_t a = 0; a < 6U; ++a)
dot += lift.Value().positive_basis(a, j) * z[a];
pz_norm = std::hypot(pz_norm, dot);
}
lifts.push_back(std::move(lift.Value()));
}
if (dimension == 0U) {
return Result<EigenReduction>::Failure(Status::Failure(
FailureCategory::kModel, {{Severity::kError,
"empty-eigenproblem",
{},
"FREQUENCY",
{},
"No finite mass coordinates remain."}}));
}
double stiffness_norm = 0.0;
double kz_error = 0.0;
const auto& offsets = stiffness.RowOffsets();
const auto& columns = stiffness.ColumnIndices();
const auto& values = stiffness.Values();
SparsePattern pattern;
pattern.row_offsets.push_back(0U);
std::vector<CooContribution> contributions;
for (std::size_t node = 0; node < count; ++node) {
std::map<std::size_t, Matrix> blocks;
for (std::size_t a = 0; a < 6U; ++a) {
const auto row = node * 6U + a;
if (mask[row] != 0U) continue;
std::map<std::size_t, double> null_action;
if (lifts[node].record.null_count != 0U) {
null_action[node] = -drilling[node] * lifts[node].null_vector[a];
}
for (std::size_t pos = offsets[row]; pos < offsets[row + 1U]; ++pos) {
const auto col = columns[pos];
if (mask[col] != 0U) continue;
const auto other = col / 6U;
const auto b = col % 6U;
const double value = Scale(a, length) * values[pos] * Scale(b, length);
if (!std::isfinite(value)) {
return Result<EigenReduction>::Failure(
Failure("invalid-eigen-mass-space", node,
"Scaled stiffness is nonfinite."));
}
stiffness_norm = std::hypot(stiffness_norm, value);
null_action[other] += value * lifts[other].null_vector[b];
auto block = blocks.try_emplace(other, 6U, 6U).first;
block->second(a, b) = value;
}
for (const auto& item : null_action)
kz_error = std::hypot(kz_error, item.second);
}
const auto& left = lifts[node];
for (std::size_t i = 0; i < left.scaled_lift.Columns(); ++i) {
for (const auto& entry : blocks) {
const auto& right = lifts[entry.first];
for (std::size_t j = 0; j < right.scaled_lift.Columns(); ++j) {
double value = 0.0;
for (std::size_t a = 0; a < 6U; ++a) {
for (std::size_t b = 0; b < 6U; ++b) {
value += left.scaled_lift(a, i) * entry.second(a, b) *
right.scaled_lift(b, j);
}
}
pattern.column_indices.push_back(right.offset + j);
contributions.push_back({left.offset + i, right.offset + j, value,
node, pattern.column_indices.size() - 1U});
}
}
pattern.row_offsets.push_back(pattern.column_indices.size());
}
}
const double eta_mz = Ratio(mass_null_error, mass_norm * null_norm);
const double eta_kz =
Ratio(kz_error, stiffness_norm * null_norm + expected_kz_norm);
if (!(eta_mz <= kAlgebraLimit) || !(eta_kz <= kAlgebraLimit) ||
!(pz_norm <= kAlgebraLimit)) {
return Result<EigenReduction>::Failure(Failure(
"invalid-eigen-mass-space", 0U,
"Original constrained MZ, KZ or PZ violates the analytic null split."));
}
auto reduced = SparseMatrix::FromCoo(dimension, dimension,
std::move(contributions), pattern);
if (!reduced.HasValue())
return Result<EigenReduction>::Failure(reduced.GetStatus());
return Result<EigenReduction>::Success(
{std::move(reduced.Value()), std::move(lifts), free_count, null_count,
length, eta_mz, eta_kz, pz_norm});
}
Vector RestoreScaledMode(const EigenReduction& reduction, const Matrix& vectors,
const std::size_t column) {
Vector result(reduction.nodes.size() * 6U);
for (std::size_t node = 0; node < reduction.nodes.size(); ++node) {
const auto& lift = reduction.nodes[node];
for (std::size_t a = 0; a < 6U; ++a) {
for (std::size_t j = 0; j < lift.scaled_lift.Columns(); ++j) {
result[6U * node + a] +=
lift.scaled_lift(a, j) * vectors(lift.offset + j, column);
}
}
}
return result;
}
Vector MultiplyNodalMass(const std::vector<Matrix>& mass,
const Vector& vector) {
Vector result(vector.Size());
for (std::size_t node = 0; node < mass.size(); ++node) {
for (std::size_t a = 0; a < 6U; ++a) {
for (std::size_t b = 0; b < 6U; ++b) {
result[6U * node + a] += mass[node](a, b) * vector[6U * node + b];
}
}
}
return result;
}
} // namespace fesa::eigen_internal
+57
View File
@@ -0,0 +1,57 @@
#ifndef FESA_ANALYSIS_EIGEN_REDUCTION_H_
#define FESA_ANALYSIS_EIGEN_REDUCTION_H_
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "fesa/analysis/eigen_analysis_state.h"
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/model_types.h"
namespace fesa::eigen_internal {
/// @brief Stores one constraint-first nodal lift from whitened coordinates.
struct NodalLift {
Matrix positive_basis{6U, 0U};
Matrix scaled_lift{6U, 0U};
std::array<double, 6> null_vector{};
std::size_t offset{0U};
EigenNodalReductionRecord record{};
};
/// @brief Preserves the original sparse pencil and its checked nodal reduction.
struct EigenReduction {
SparseMatrix whitened_operator;
std::vector<NodalLift> nodes;
std::size_t free_count;
std::size_t null_count;
double characteristic_length;
double eta_mz;
double eta_kz;
double eta_pz;
};
/// @brief Applies actual component constraints before analytic null removal.
Result<EigenReduction> BuildEigenReduction(
const SparseMatrix& full_stiffness, const std::vector<Matrix>& nodal_mass,
const std::vector<ShellNodeInitialFrame>& frames,
const std::vector<std::uint8_t>& constraint_mask,
const std::vector<double>& nodal_drilling_stiffness,
double characteristic_length);
/// @brief Restores scaled global coordinates with constrained entries zero.
Vector RestoreScaledMode(const EigenReduction& reduction,
const Matrix& whitened_vectors, std::size_t column);
/// @brief Applies the assembled nodal mass in physical global coordinates.
Vector MultiplyNodalMass(const std::vector<Matrix>& nodal_mass,
const Vector& vector);
} // namespace fesa::eigen_internal
#endif // FESA_ANALYSIS_EIGEN_REDUCTION_H_
@@ -0,0 +1,427 @@
#include "fesa/analysis/linear_frequency_procedure.h"
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <limits>
#include <memory>
#include <new>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "analysis/eigen_reduction.h"
#include "fesa/analysis/analysis_model.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/sparse_assembler.h"
#include "fesa/elements/element_factory.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h"
namespace fesa {
namespace {
Status Failure(const std::string& code, const std::string& identity,
const std::string& message) {
return Status::Failure(
FailureCategory::kSolver,
{{Severity::kError, code, {}, "FREQUENCY", identity, message}});
}
double Dot(const Vector& left, const Vector& right) {
double result = 0.0;
for (std::size_t index = 0U; index < left.Size(); ++index) {
result += left[index] * right[index];
}
return result;
}
double Norm(const Vector& vector) {
double result = 0.0;
for (std::size_t i = 0; i < vector.Size(); ++i)
result = std::hypot(result, vector[i]);
return result;
}
double Ratio(double numerator, double denominator) {
if (std::isfinite(numerator) && std::isfinite(denominator) &&
denominator > 0.0)
return numerator / denominator;
return numerator == 0.0 && denominator == 0.0
? 0.0
: (std::numeric_limits<double>::infinity)();
}
double D(std::size_t dof, double length) {
return dof % 6U < 3U ? length : 1.0;
}
Status MetricFailure(const char* metric, double value, std::size_t column) {
std::ostringstream message;
message << std::setprecision(17) << metric << "=" << value << "; limit=1e-10";
return Failure("eigenpair-validation-failed",
"solver-column=" + std::to_string(column), message.str());
}
double FrobeniusNorm(const SparseMatrix& matrix) {
double norm = 0.0;
for (const double value : matrix.Values()) norm = std::hypot(norm, value);
return norm;
}
double MaximumEdgeLength(const Domain& domain) {
double maximum = 0.0;
for (std::size_t shell = 0U; shell < domain.ShellElements().Size(); ++shell) {
const auto& nodes = domain.ShellElements()[shell].NodeIndices();
for (std::size_t edge = 0U; edge < 4U; ++edge) {
const auto& first = domain.Nodes()[nodes[edge]].coordinates;
const auto& second = domain.Nodes()[nodes[(edge + 1U) % 4U]].coordinates;
maximum =
(std::max)(maximum, std::hypot(std::hypot(first[0U] - second[0U],
first[1U] - second[1U]),
first[2U] - second[2U]));
}
}
return maximum;
}
Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
const FrequencyStepDefinition* frequency = domain.Frequency();
if (frequency == nullptr || domain.Steps().Size() != 1U) {
return Result<EigenAnalysisState>::Failure(
Failure("invalid-frequency-procedure", "step",
"The domain has no sole frequency step."));
}
if (frequency->requested_mode_count <= 0) {
return Result<EigenAnalysisState>::Failure(
Failure("invalid-frequency-request", "requested-mode-count",
"Frequency requested mode count must be positive."));
}
Result<AnalysisModel> model = AnalysisModel::Create(domain);
if (!model.HasValue())
return Result<EigenAnalysisState>::Failure(model.GetStatus());
const ElementFactory factory;
std::vector<std::unique_ptr<Element>> owned;
ElementView view;
owned.reserve(model.Value().ActiveElements().size());
view.reserve(model.Value().ActiveElements().size());
for (const EntityIndex index : model.Value().ActiveElements()) {
auto element = factory.Create(domain.Elements()[index], domain);
if (!element.HasValue())
return Result<EigenAnalysisState>::Failure(element.GetStatus());
if (dynamic_cast<const Mitc4Shell*>(element.Value().get()) == nullptr) {
return Result<EigenAnalysisState>::Failure(Failure(
"unsupported-frequency-element", std::to_string(index),
"The frequency procedure supports MITC4 shell elements only."));
}
view.emplace_back(*element.Value());
owned.push_back(std::move(element.Value()));
}
DofManager dofs;
const Status dof_status = dofs.Build(model.Value(), view);
if (!dof_status.IsOk())
return Result<EigenAnalysisState>::Failure(dof_status);
const SerialParallelFor serial;
auto stiffness = SparseAssembler::Assemble(view, dofs, serial);
if (!stiffness.HasValue())
return Result<EigenAnalysisState>::Failure(stiffness.GetStatus());
std::vector<double> density(domain.Materials().Size(), 0.0);
for (const auto& item : domain.Densities()) {
if (item.material_index >= density.size() ||
density[item.material_index] != 0.0 || !(item.rho > 0.0) ||
!std::isfinite(item.rho)) {
return Result<EigenAnalysisState>::Failure(Failure(
"invalid-material-density", std::to_string(item.material_index),
"Invalid or duplicate material density."));
}
density[item.material_index] = item.rho;
}
for (std::size_t i = 0; i < dofs.PrescribedValues().Size(); ++i) {
if (dofs.PrescribedValues()[i] != 0.0) {
return Result<EigenAnalysisState>::Failure(
Status::Failure(FailureCategory::kInput,
{{Severity::kError,
"unsupported-eigen-boundary",
frequency->frequency_location,
"BOUNDARY",
{},
"Frequency requires homogeneous constraints."}}));
}
}
std::vector<Matrix> nodal_mass(domain.Nodes().size(), Matrix(6U, 6U));
std::vector<double> drilling(domain.Nodes().size(), 0.0);
for (std::size_t shell = 0U; shell < domain.ShellElements().Size(); ++shell) {
const auto& definition = domain.ShellElements()[shell];
if (!(density[definition.MaterialIndex()] > 0.0)) {
return Result<EigenAnalysisState>::Failure(
Failure("missing-frequency-density",
std::to_string(definition.MaterialIndex()),
"Every active shell material requires a positive density."));
}
const auto* element = dynamic_cast<const Mitc4Shell*>(owned[shell].get());
auto mass = element->Mass(density[definition.MaterialIndex()]);
auto shell_stiffness = element->Stiffness();
if (!mass.HasValue())
return Result<EigenAnalysisState>::Failure(mass.GetStatus());
if (!shell_stiffness.HasValue())
return Result<EigenAnalysisState>::Failure(shell_stiffness.GetStatus());
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
const std::size_t node = definition.NodeIndices()[local_node];
drilling[node] += shell_stiffness.Value().drilling_stiffness;
for (std::size_t a = 0U; a < 6U; ++a) {
for (std::size_t b = 0U; b < 6U; ++b) {
nodal_mass[node](a, b) += mass.Value().lumped_global24(
6U * local_node + a, 6U * local_node + b);
}
}
}
}
const double length = MaximumEdgeLength(domain);
std::vector<std::uint8_t> mask(dofs.FullDofCount(), 0U);
for (const std::size_t index : dofs.ConstrainedDofs()) mask[index] = 1U;
auto reduction = eigen_internal::BuildEigenReduction(
stiffness.Value(), nodal_mass, domain.ShellNodeInitialFrames(), mask,
drilling, length);
if (!reduction.HasValue())
return Result<EigenAnalysisState>::Failure(reduction.GetStatus());
const std::size_t request =
static_cast<std::size_t>(frequency->requested_mode_count);
if (request > reduction.Value().whitened_operator.Rows()) {
return Result<EigenAnalysisState>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, "insufficient-finite-modes",
frequency->frequency_location, "FREQUENCY", std::to_string(request),
"Requested modes exceed finite dimension " +
std::to_string(reduction.Value().whitened_operator.Rows())}}));
}
double zero_scale = (std::numeric_limits<double>::infinity)();
for (std::size_t e = 0; e < domain.ShellElements().Size(); ++e) {
const auto material_id = domain.ShellElements()[e].MaterialIndex();
const auto* material = dynamic_cast<const IsotropicLinearElasticMaterial*>(
&domain.Materials()[material_id]);
if (material == nullptr)
return Result<EigenAnalysisState>::Failure(
Failure("invalid-eigenvalue-result", "material",
"Unsupported frequency material."));
zero_scale = (std::min)(zero_scale,
(material->YoungsModulus() / density[material_id]) /
length / length);
}
auto backend = MklFeastEigenSolver{}.Solve(
reduction.Value().whitened_operator, request, zero_scale);
if (!backend.HasValue())
return Result<EigenAnalysisState>::Failure(backend.GetStatus());
EigenAnalysisState state(std::move(backend.Value()));
state.full_dof_count = dofs.FullDofCount();
state.free_dof_count = reduction.Value().free_count;
state.mass_null_dimension = reduction.Value().null_count;
state.finite_dimension = reduction.Value().whitened_operator.Rows();
state.characteristic_length = length;
state.eta_mz = reduction.Value().eta_mz;
state.eta_kz = reduction.Value().eta_kz;
state.eta_pz = reduction.Value().eta_pz;
state.null_checks_applicable = state.mass_null_dimension != 0U;
state.constraint_mask = mask;
state.prescribed_displacement.assign(state.full_dof_count, 0.0);
for (const auto& node : reduction.Value().nodes)
state.nodal_reduction.push_back(node.record);
// The controls use the actual constrained, length-scaled pencil, including
// positive partial-BC drilling stiffness. Full constrained rows are
// reactions.
const SparseMatrix& k = stiffness.Value();
const SparseMatrix& a = reduction.Value().whitened_operator;
double stiffness_norm = 0.0;
for (const auto row : dofs.FreeDofs()) {
for (std::size_t entry = k.RowOffsets()[row];
entry < k.RowOffsets()[row + 1U]; ++entry) {
const auto col = k.ColumnIndices()[entry];
if (!mask[col])
stiffness_norm =
std::hypot(stiffness_norm,
D(row, length) * k.Values()[entry] * D(col, length));
}
}
double mass_norm = 0.0;
for (std::size_t node = 0; node < nodal_mass.size(); ++node) {
for (std::size_t row = 0; row < 6U; ++row) {
for (std::size_t col = 0; col < 6U; ++col) {
if (!mask[6U * node + row] && !mask[6U * node + col]) {
mass_norm = std::hypot(
mass_norm,
D(row, length) * nodal_mass[node](row, col) * D(col, length));
}
}
}
}
const double a_norm = FrobeniusNorm(a);
state.vectors = Matrix(request, state.full_dof_count);
const std::size_t count = state.backend.eigenvalues.size();
std::vector<Vector> all_modes;
std::vector<Vector> all_mass_times;
all_modes.reserve(count);
all_mass_times.reserve(count);
std::size_t cluster = 1U;
for (std::size_t column = 0; column < count; ++column) {
const double lambda = state.backend.eigenvalues[column];
const double mu = lambda / state.backend.operator_scale;
if (column != 0U && mu - state.backend.eigenvalues[column - 1U] /
state.backend.operator_scale >
1e-8)
++cluster;
Vector y = eigen_internal::RestoreScaledMode(
reduction.Value(), state.backend.eigenvectors, column);
Vector physical(y.Size());
std::size_t pivot = 0U;
for (std::size_t dof = 0; dof < y.Size(); ++dof) {
physical[dof] = D(dof, length) * y[dof];
if (std::abs(y[dof]) > std::abs(y[pivot])) pivot = dof;
}
Vector mass_times = eigen_internal::MultiplyNodalMass(nodal_mass, physical);
const double initial_mass = Dot(physical, mass_times);
if (!(initial_mass > 0.0) || !std::isfinite(initial_mass))
return Result<EigenAnalysisState>::Failure(
MetricFailure("initial_modal_mass", initial_mass,
state.backend.column_ids[column]));
const double normalization =
(y[pivot] < 0.0 ? -1.0 : 1.0) / std::sqrt(initial_mass);
for (std::size_t dof = 0; dof < physical.Size(); ++dof) {
physical[dof] *= normalization;
if (!std::isfinite(physical[dof]) ||
(mask[dof] && physical[dof] != 0.0)) {
return Result<EigenAnalysisState>::Failure(
Failure("invalid-eigenvalue-result", std::to_string(column),
"Recovered mode is nonfinite or violates constraints."));
}
y[dof] = physical[dof] / D(dof, length);
}
// All stored physical metrics are recomputed from the final normalized phi.
mass_times = eigen_internal::MultiplyNodalMass(nodal_mass, physical);
const double modal_mass = Dot(physical, mass_times);
Vector ky = k.Multiply(physical);
Vector residual(physical.Size());
for (std::size_t dof = 0; dof < physical.Size(); ++dof) {
ky[dof] = mask[dof] ? 0.0 : D(dof, length) * ky[dof];
residual[dof] =
mask[dof] ? 0.0 : ky[dof] - lambda * D(dof, length) * mass_times[dof];
}
const double energy = Dot(y, ky);
const double y_norm = Norm(y);
const double pencil_scale = stiffness_norm + std::abs(lambda) * mass_norm;
const double eta_r = Ratio(Norm(residual), pencil_scale * y_norm);
const double eta_norm = std::abs(modal_mass - 1.0);
const double eta_energy = Ratio(std::abs(energy - lambda * modal_mass),
(pencil_scale * y_norm) * y_norm);
const double rayleigh = energy / modal_mass;
Vector x(a.Rows());
for (std::size_t row = 0; row < x.Size(); ++row)
x[row] = state.backend.eigenvectors(row, column) * normalization;
Vector ax = a.Multiply(x);
Vector whitened_residual = ax;
whitened_residual.Axpy(-lambda, x);
const double x_norm = Norm(x);
const double eta_w =
Ratio(Norm(whitened_residual), (a_norm + std::abs(lambda)) * x_norm);
for (std::size_t row = 0; row < ax.Size(); ++row)
ax[row] = ax[row] / state.backend.operator_scale - mu * x[row];
const double rho_b = Ratio(Norm(ax), x_norm);
for (const auto& metric :
std::initializer_list<std::pair<const char*, double>>{
{"eta_r", eta_r},
{"eta_w", eta_w},
{"rho_b", rho_b},
{"eta_norm", eta_norm},
{"eta_energy", eta_energy}}) {
if (!std::isfinite(metric.second) || metric.second > 1e-10)
return Result<EigenAnalysisState>::Failure(MetricFailure(
metric.first, metric.second, state.backend.column_ids[column]));
}
if (!std::isfinite(rayleigh))
return Result<EigenAnalysisState>::Failure(
Failure("invalid-eigenvalue-result", std::to_string(column),
"Nonfinite Rayleigh quotient."));
state.eigenpair_checks.push_back(
{column + 1U, state.backend.column_ids[column], lambda, modal_mass,
rayleigh, eta_r, eta_w, rho_b, eta_norm, eta_energy,
state.backend.backend_res_available[column],
state.backend.backend_res[column]});
if (column < request) {
std::optional<double> frequency_value;
if (lambda >= 0.0) {
frequency_value = std::sqrt(lambda) / (2.0 * std::acos(-1.0));
if (!std::isfinite(*frequency_value) ||
(lambda > 0.0 && *frequency_value == 0.0))
return Result<EigenAnalysisState>::Failure(
Failure("invalid-eigenvalue-result", std::to_string(column),
"Cycle frequency conversion failed."));
}
state.modes.push_back(
{column + 1U, lambda,
static_cast<std::uint8_t>(std::abs(mu) <= 1e-9 ? 0U : 1U), cluster,
frequency_value});
for (std::size_t dof = 0; dof < physical.Size(); ++dof)
state.vectors(column, dof) = physical[dof];
}
all_modes.push_back(std::move(physical));
all_mass_times.push_back(std::move(mass_times));
}
double gram_norm = 0.0;
for (std::size_t left = 0; left < count; ++left) {
for (std::size_t right = 0; right < count; ++right) {
const double gram = Dot(all_modes[left], all_mass_times[right]);
gram_norm = std::hypot(gram_norm, gram - (left == right ? 1.0 : 0.0));
if (left != right)
state.max_eta_orth =
(std::max)(state.max_eta_orth,
Ratio(std::abs(gram),
std::sqrt(
state.eigenpair_checks[left].modal_mass *
state.eigenpair_checks[right].modal_mass)));
}
}
state.eta_gram = gram_norm / std::sqrt(static_cast<double>(count));
if (!std::isfinite(state.eta_gram) || state.eta_gram > 1e-10 ||
!std::isfinite(state.max_eta_orth) || state.max_eta_orth > 1e-10) {
return Result<EigenAnalysisState>::Failure(
MetricFailure("eta_gram", state.eta_gram, 0U));
}
state.backend.eigenvectors = Matrix(0U, 0U);
return Result<EigenAnalysisState>::Success(std::move(state));
}
} // namespace
Result<EigenAnalysisState> LinearFrequencyProcedureExecutor::Execute(
const Domain& domain) const {
try {
auto result = ExecuteCandidate(domain);
if (!result.HasValue()) {
auto diagnostics = result.GetStatus().Diagnostics();
for (auto& diagnostic : diagnostics) {
if (diagnostic.location.file.empty())
diagnostic.location = domain.Frequency() != nullptr
? domain.Frequency()->frequency_location
: SourceLocation{domain.SourcePath(), 0U};
}
return Result<EigenAnalysisState>::Failure(Status::Failure(
result.GetStatus().Category().value_or(FailureCategory::kSolver),
std::move(diagnostics)));
}
return result;
} catch (const std::bad_alloc&) {
return Result<EigenAnalysisState>::Failure(
Failure("eigen-allocation-failed", "modal-workspace",
"Allocation failed after dimension checks; requested modes "
"were not reduced."));
} catch (const std::length_error&) {
return Result<EigenAnalysisState>::Failure(
Failure("eigen-index-overflow", "modal-workspace",
"Requested workspace exceeds container capacity."));
}
}
} // namespace fesa
@@ -26,6 +26,16 @@ LinearStaticProcedureExecutor::LinearStaticProcedureExecutor(
Result<LinearStaticExecutionResult> LinearStaticProcedureExecutor::Execute(
const Domain& domain) const {
if (domain.Frequency() != nullptr) {
return Result<LinearStaticExecutionResult>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
"invalid-analysis-procedure",
domain.Frequency()->frequency_location,
"FREQUENCY",
{},
"Linear-static execution cannot consume a frequency step."}}));
}
Result<AnalysisModel> model = AnalysisModel::Create(domain);
if (!model.HasValue()) {
return Result<LinearStaticExecutionResult>::Failure(model.GetStatus());
+25 -3
View File
@@ -2,12 +2,14 @@
#include <utility>
#include "fesa/analysis/linear_frequency_procedure.h"
#include "fesa/analysis/linear_static_procedure.h"
#include "fesa/analysis/newton_iteration_log.h"
#include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/app/output_paths.h"
#include "fesa/io/abaqus/domain_mapper.h"
#include "fesa/io/abaqus/input_reader.h"
#include "fesa/results/eigen_results_writer.h"
#include "fesa/results/nonlinear_results_writer.h"
#include "fesa/results/results_writer.h"
@@ -34,12 +36,26 @@ Result<PreparedAnalysisInput> PrepareAnalysisInput(
Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
const Domain& domain, LinearStaticProcedureExecutor& linear_executor,
NonlinearStaticProcedureExecutor& nonlinear_executor,
const LinearFrequencyProcedureExecutor& frequency_executor,
NewtonIterationLogWriter& log) {
if (domain.Steps().Size() != 1U) {
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError, "invalid-analysis-step-count", {}, "*STEP", {},
"Prepared analysis requires exactly one static step."}}));
{{Severity::kError,
"invalid-analysis-step-count",
{},
"*STEP",
{},
"Prepared analysis requires exactly one analysis step."}}));
}
if (domain.Steps()[0U].IsFrequency()) {
auto eigen = frequency_executor.Execute(domain);
if (!eigen.HasValue()) {
return Result<PreparedAnalysisOutcome>::Failure(eigen.GetStatus());
}
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(eigen.Value())});
}
if (domain.Steps()[0U].IsNonlinearStatic()) {
@@ -64,7 +80,13 @@ Status FinalizePreparedAnalysisResult(
const DerivedOutputPaths& paths, const Domain& domain,
const PreparedAnalysisOutcome& outcome,
const std::vector<Diagnostic>& diagnostics, ResultsWriter& linear_writer,
NonlinearResultsWriter& nonlinear_writer) {
NonlinearResultsWriter& nonlinear_writer,
EigenResultsWriter& eigen_writer) {
if (std::holds_alternative<EigenAnalysisState>(outcome)) {
return eigen_writer.Write(paths.results_path, domain,
std::get<EigenAnalysisState>(outcome),
diagnostics);
}
if (std::holds_alternative<LinearStaticExecutionResult>(outcome)) {
return linear_writer.Write(
paths.results_path, domain,
+12 -6
View File
@@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include "fesa/analysis/linear_frequency_procedure.h"
#include "fesa/analysis/linear_static_procedure.h"
#include "fesa/analysis/newton_iteration_log.h"
#include "fesa/analysis/nonlinear_static_procedure.h"
@@ -12,6 +13,7 @@
#include "fesa/app/output_paths.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/core/diagnostic.h"
#include "fesa/io/hdf5/eigen_hdf5_results_writer.h"
#include "fesa/io/hdf5/hdf5_results_writer.h"
#include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
@@ -63,8 +65,8 @@ int ExitCodeFor(const Status& status) {
} // namespace
int FesaApplication::Run(const std::vector<std::string>& arguments) {
const auto paths = DeriveSingleInputOutputPaths(
arguments, std::filesystem::current_path());
const auto paths =
DeriveSingleInputOutputPaths(arguments, std::filesystem::current_path());
if (!paths.HasValue()) {
WriteDiagnostics(paths.GetStatus().Diagnostics());
return kUsageExitCode;
@@ -81,6 +83,7 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
MklPardisoTangentSolver tangent_solver;
Hdf5ResultsWriter linear_writer;
NonlinearHdf5ResultsWriter nonlinear_writer;
EigenHdf5ResultsWriter eigen_writer;
NewtonIterationLogWriter log;
Status status = log.Open(paths.Value().log_path);
@@ -96,9 +99,11 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
} else {
LinearStaticProcedureExecutor linear_executor(parallel_for, linear_solver);
NonlinearStaticProcedureExecutor nonlinear_executor(parallel_for,
tangent_solver);
Result<PreparedAnalysisOutcome> outcome = ExecutePreparedAnalysis(
prepared.Value().domain, linear_executor, nonlinear_executor, log);
tangent_solver);
const LinearFrequencyProcedureExecutor frequency_executor;
Result<PreparedAnalysisOutcome> outcome =
ExecutePreparedAnalysis(prepared.Value().domain, linear_executor,
nonlinear_executor, frequency_executor, log);
if (!outcome.HasValue()) {
status = outcome.GetStatus();
} else {
@@ -109,7 +114,8 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
}
status = FinalizePreparedAnalysisResult(
paths.Value(), prepared.Value().domain, outcome.Value(),
prepared.Value().diagnostics, linear_writer, nonlinear_writer);
prepared.Value().diagnostics, linear_writer, nonlinear_writer,
eigen_writer);
if (status.IsOk()) {
return kSuccessExitCode;
}
+178 -9
View File
@@ -279,6 +279,27 @@ Result<Mitc4Stiffness> StiffnessFailure(const SourceLocation& location,
"*ELEMENT", identity, std::move(message)}}));
}
Result<Mitc4Mass> MassFailure(const SourceLocation& location,
const std::string& identity,
std::string message) {
return Result<Mitc4Mass>::Failure(
Status::Failure(FailureCategory::kModel,
{{Severity::kError, "invalid-shell-mass", location,
"*ELEMENT", identity, std::move(message)}}));
}
/// @brief Returns S=-[director]_cross for theta cross director.
Matrix DirectorSkew(const Vector3& director) {
Matrix result{3U, 3U};
result(0U, 1U) = director.Z();
result(0U, 2U) = -director.Y();
result(1U, 0U) = -director.Z();
result(1U, 2U) = director.X();
result(2U, 0U) = director.Y();
result(2U, 1U) = -director.X();
return result;
}
Result<Mitc4PhysicalRecovery> RecoveryFailure(const SourceLocation& location,
const std::string& identity,
std::string message) {
@@ -625,24 +646,29 @@ Result<Mitc4Stiffness> Mitc4Shell::Stiffness() const {
}
}
double drilling_reference = (std::numeric_limits<double>::max)();
bool has_drilling_reference = false;
double maximum_rotary_diagonal = 0.0;
std::array<double, 8> rotary_diagonals{};
std::size_t rotary_index = 0U;
for (std::size_t node = 0U; node < kNodeCount; ++node) {
const std::size_t offset = node * kPhysicalDofsPerNode;
for (std::size_t rotation = 3U; rotation < 5U; ++rotation) {
const double diagonal =
physical_local(offset + rotation, offset + rotation);
if (std::isfinite(diagonal) && diagonal > 0.0) {
drilling_reference = (std::min)(drilling_reference, diagonal);
has_drilling_reference = true;
if (!std::isfinite(diagonal) || diagonal < 0.0) {
return StiffnessFailure(
source_location_, identity_,
"MITC4 drilling stabilization requires finite nonnegative "
"physical tangent-rotation diagonals.");
}
rotary_diagonals[rotary_index++] = diagonal;
maximum_rotary_diagonal = (std::max)(maximum_rotary_diagonal, diagonal);
}
}
if (!has_drilling_reference) {
if (!(maximum_rotary_diagonal > 0.0)) {
return StiffnessFailure(
source_location_, identity_,
"MITC4 drilling stabilization requires a finite positive physical "
"tangent-rotation diagonal.");
"MITC4 drilling stabilization requires a positive physical "
"tangent-rotation diagonal inventory.");
}
if (!IsFinite(physical_local)) {
return StiffnessFailure(
@@ -650,7 +676,12 @@ Result<Mitc4Stiffness> Mitc4Shell::Stiffness() const {
"MITC4 physical stiffness must contain only finite values.");
}
const double drilling_stiffness = 1.0e-3 * drilling_reference;
double normalized_diagonal_sum = 0.0;
for (const double diagonal : rotary_diagonals) {
normalized_diagonal_sum += diagonal / maximum_rotary_diagonal;
}
const double drilling_stiffness =
1.0e-3 * maximum_rotary_diagonal * (normalized_diagonal_sum / 8.0);
if (!std::isfinite(drilling_stiffness) || !(drilling_stiffness > 0.0)) {
return StiffnessFailure(
source_location_, identity_,
@@ -685,6 +716,144 @@ Result<Mitc4Stiffness> Mitc4Shell::Stiffness() const {
drilling_stiffness});
}
Result<Mitc4Mass> Mitc4Shell::Mass(const double density) const {
if (!std::isfinite(density) || !(density > 0.0)) {
return MassFailure(source_location_, identity_,
"MITC4 mass requires finite density > 0.");
}
std::array<Matrix, 3> moments{Matrix{kNodeCount, kNodeCount},
Matrix{kNodeCount, kNodeCount},
Matrix{kNodeCount, kNodeCount}};
constexpr double kGaussCoordinate = 0.77459666924148337704;
constexpr std::array<double, 3> kGaussPoints{-kGaussCoordinate, 0.0,
kGaussCoordinate};
constexpr std::array<double, 3> kGaussWeights{5.0 / 9.0, 8.0 / 9.0,
5.0 / 9.0};
for (std::size_t xi_index = 0U; xi_index < kGaussPoints.size(); ++xi_index) {
for (std::size_t eta_index = 0U; eta_index < kGaussPoints.size();
++eta_index) {
const Mitc4ShapeFunctions shape =
ShapeFunctions(kGaussPoints[xi_index], kGaussPoints[eta_index]);
for (std::size_t zeta_index = 0U; zeta_index < kGaussPoints.size();
++zeta_index) {
GeometryData geometry{};
const double zeta = kGaussPoints[zeta_index];
if (!EvaluateGeometry(kGaussPoints[xi_index], kGaussPoints[eta_index],
zeta, geometry)) {
return MassFailure(source_location_, identity_,
"MITC4 mass quadrature geometry is invalid.");
}
const double z = 0.5 * thickness_ * zeta;
const double weight =
density * geometry.jacobian * kGaussWeights[xi_index] *
kGaussWeights[eta_index] * kGaussWeights[zeta_index];
if (!std::isfinite(z) || !std::isfinite(weight)) {
return MassFailure(
source_location_, identity_,
"MITC4 mass quadrature contribution is nonfinite.");
}
const std::array<double, 3> z_powers{1.0, z, z * z};
for (std::size_t row = 0U; row < kNodeCount; ++row) {
for (std::size_t column = row; column < kNodeCount; ++column) {
const double shape_product =
shape.values[row] * shape.values[column];
for (std::size_t moment = 0U; moment < moments.size(); ++moment) {
const double contribution =
weight * shape_product * z_powers[moment];
moments[moment](row, column) += contribution;
if (row != column) {
moments[moment](column, row) += contribution;
}
}
}
}
}
}
}
for (const Matrix& moment : moments) {
if (!IsFinite(moment)) {
return MassFailure(source_location_, identity_,
"MITC4 consistent moments must be finite.");
}
}
Matrix consistent{kGlobalDofCount, kGlobalDofCount};
for (std::size_t row_node = 0U; row_node < kNodeCount; ++row_node) {
const Matrix row_skew = DirectorSkew(directors_[row_node]);
for (std::size_t column_node = 0U; column_node < kNodeCount;
++column_node) {
const Matrix column_skew = DirectorSkew(directors_[column_node]);
for (std::size_t row = 0U; row < 3U; ++row) {
for (std::size_t column = 0U; column < 3U; ++column) {
const std::size_t global_row = kGlobalDofsPerNode * row_node + row;
const std::size_t global_column =
kGlobalDofsPerNode * column_node + column;
consistent(global_row, global_column) =
moments[0U](row_node, column_node) * (row == column ? 1.0 : 0.0);
consistent(global_row, global_column + 3U) =
moments[1U](row_node, column_node) * column_skew(row, column);
consistent(global_row + 3U, global_column) =
moments[1U](row_node, column_node) * row_skew(column, row);
double rotary = 0.0;
for (std::size_t axis = 0U; axis < 3U; ++axis) {
rotary += row_skew(axis, row) * column_skew(axis, column);
}
consistent(global_row + 3U, global_column + 3U) =
moments[2U](row_node, column_node) * rotary;
}
}
}
}
Matrix lumped{kGlobalDofCount, kGlobalDofCount};
std::array<std::array<double, 3>, kNodeCount> nodal_moments{};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
double mass = 0.0;
double first_moment = 0.0;
double second_moment = 0.0;
for (std::size_t column = 0U; column < kNodeCount; ++column) {
mass += moments[0U](node, column);
first_moment += moments[1U](node, column);
second_moment += moments[2U](node, column);
}
const double centered_inertia =
second_moment - first_moment * (first_moment / mass);
if (!std::isfinite(mass) || !std::isfinite(first_moment) ||
!std::isfinite(second_moment) || !std::isfinite(centered_inertia) ||
!(mass > 0.0) || !(second_moment > 0.0) || !(centered_inertia > 0.0)) {
return MassFailure(
source_location_, identity_,
"MITC4 nodal mass moments must be finite and positive.");
}
nodal_moments[node] = {mass, first_moment, second_moment};
const Matrix skew = DirectorSkew(directors_[node]);
const std::size_t offset = kGlobalDofsPerNode * node;
for (std::size_t row = 0U; row < 3U; ++row) {
for (std::size_t column = 0U; column < 3U; ++column) {
lumped(offset + row, offset + column) =
mass * (row == column ? 1.0 : 0.0);
lumped(offset + row, offset + column + 3U) =
first_moment * skew(row, column);
lumped(offset + row + 3U, offset + column) =
first_moment * skew(column, row);
double projector = row == column ? 1.0 : 0.0;
projector -= directors_[node][row] * directors_[node][column];
lumped(offset + row + 3U, offset + column + 3U) =
second_moment * projector;
}
}
}
if (!IsFinite(consistent) || !IsFinite(lumped)) {
return MassFailure(
source_location_, identity_,
"MITC4 transformed mass must contain only finite values.");
}
return Result<Mitc4Mass>::Success(
Mitc4Mass{std::move(moments), std::move(consistent), std::move(lumped),
std::move(nodal_moments)});
}
Result<Mitc4PhysicalRecovery> Mitc4Shell::RecoverPhysical(
const Vector& global_element_displacement24) const {
if (global_element_displacement24.Size() != kGlobalDofCount) {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,974 @@
#include "fesa/io/hdf5/eigen_hdf5_results_writer.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <exception>
#include <limits>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/build_info.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/model/domain.h"
#include "io/hdf5/eigen_hdf5_self_check.h"
#include "io/hdf5/hdf5_atomic_file.h"
#include "io/hdf5/hdf5_model_writer.h"
#include "io/hdf5/hdf5_primitives.h"
#include "io/hdf5/hdf5_raii.h"
namespace fesa {
namespace {
constexpr const char* kEigenRoot = "/steps/Step-1/eigen";
void WriteQuantityAttributes(hid_t dataset, const std::string& components,
const std::string& units,
const std::string& coordinates,
const std::string& location) {
hdf5_internal::WriteStringAttribute(dataset, "component_names", components);
hdf5_internal::WriteStringAttribute(dataset, "component_unit_dimensions",
units);
hdf5_internal::WriteStringAttribute(dataset, "coordinate_system",
coordinates);
hdf5_internal::WriteStringAttribute(dataset, "location", location);
}
void WriteModalAttributes(hid_t dataset, const std::string& source_step) {
hdf5_internal::WriteUint64Attribute(dataset, "eigen_schema_version", 1U);
hdf5_internal::WriteStringAttribute(dataset, "source_step_name", source_step);
hdf5_internal::WriteStringAttribute(dataset, "domain", "mode");
}
bool IsFinite(const Matrix& matrix) {
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
if (!std::isfinite(matrix(row, column))) {
return false;
}
}
}
return true;
}
Status Validate(const Domain& domain, const EigenAnalysisState& state,
const std::vector<Diagnostic>& diagnostics) {
const FrequencyStepDefinition* frequency = domain.Frequency();
if (frequency == nullptr || !hdf5_internal::IsShellDomain(domain)) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Eigen HDF5 output requires one shell frequency domain.");
}
std::set<EntityIndex> density_materials;
EntityIndex previous_density_material = 0U;
bool first_density = true;
for (const MaterialDensityDefinition& density : domain.Densities()) {
if (density.material_index >= domain.Materials().Size() ||
!std::isfinite(density.rho) || !(density.rho > 0.0) ||
(!first_density &&
density.material_index <= previous_density_material)) {
return hdf5_internal::OutputFailure("invalid-eigen-result-state",
"Eigen material density inventory "
"must be finite and source ordered.");
}
density_materials.insert(density.material_index);
previous_density_material = density.material_index;
first_density = false;
}
for (std::size_t index = 0U; index < domain.ShellElements().Size(); ++index) {
if (density_materials.count(
domain.ShellElements()[index].MaterialIndex()) == 0U) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Every active shell material requires one positive density.");
}
}
const std::size_t node_count = domain.Nodes().size();
if (node_count > (std::numeric_limits<std::size_t>::max)() / 6U ||
state.full_dof_count != 6U * node_count ||
state.free_dof_count !=
state.mass_null_dimension + state.finite_dimension ||
state.modes.size() !=
static_cast<std::size_t>(frequency->requested_mode_count) ||
state.vectors.Rows() != state.modes.size() ||
state.vectors.Columns() != 6U * node_count ||
state.nodal_reduction.size() != node_count ||
state.constraint_mask.size() != 6U * node_count ||
state.prescribed_displacement.size() != 6U * node_count ||
state.eigenpair_checks.size() < state.modes.size() ||
state.eigenpair_checks.size() > state.finite_dimension ||
!IsFinite(state.vectors) || !std::isfinite(state.characteristic_length) ||
!(state.characteristic_length > 0.0) ||
!std::isfinite(state.backend.operator_scale) ||
!(state.backend.operator_scale > 0.0)) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Eigen state does not satisfy the durable result inventory.");
}
const auto metric_ok = [](double value, double limit) {
return std::isfinite(value) && value >= 0.0 && value <= limit;
};
const auto& backend = state.backend;
if (backend.base_fpm.size() != 128U || backend.backend_calls.empty() ||
backend.backend_calls.size() > 512U || backend.thread_count != 1U ||
backend.seed != 0x4D495443344D4645ULL ||
backend.runtime_version.empty() ||
(backend.integer_abi != "LP64" && backend.integer_abi != "ILP64") ||
backend.requested_modes != state.modes.size() ||
!metric_ok(backend.gram_error, 1e-10) ||
!std::isfinite(backend.enclosure_lower) ||
!std::isfinite(backend.enclosure_upper) ||
!std::isfinite(backend.solve_lower) ||
!std::isfinite(backend.solve_upper) ||
!(backend.enclosure_lower < backend.enclosure_upper) ||
backend.solve_lower != backend.enclosure_lower ||
!(backend.solve_lower < backend.solve_upper) ||
backend.solve_upper > backend.enclosure_upper) {
return hdf5_internal::OutputFailure("invalid-eigen-result-state",
"Backend inventory is invalid.");
}
for (const auto& control :
std::array<std::pair<std::size_t, std::int64_t>, 11>{{{0U, 0},
{1U, 16},
{2U, 12},
{3U, 20},
{4U, 1},
{5U, 1},
{13U, 0},
{26U, 1},
{27U, 1},
{49U, 0},
{63U, 0}}}) {
if (backend.base_fpm[control.first] != control.second)
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Backend controls differ from the numerical contract.");
}
std::size_t solve_calls = 0U;
bool enclosed = false;
for (std::size_t i = 0U; i < backend.backend_calls.size(); ++i) {
const auto& call = backend.backend_calls[i];
const bool count = call.kind == "count", solve = call.kind == "solve";
if (solve) ++solve_calls;
if (call.ordinal != i || (!count && !solve) || !std::isfinite(call.lower) ||
!std::isfinite(call.upper) || !(call.lower < call.upper) ||
call.allocated_m0 == 0U || call.allocated_m0 > state.finite_dimension ||
(count && call.allocated_m0 != state.finite_dimension) ||
(call.count_available && call.count > call.allocated_m0) ||
(!call.count_available && call.count != 0U) ||
(call.loop_available && call.loop < 0) ||
(!call.loop_available && call.loop != 0) ||
(call.epsout_available && !std::isfinite(call.epsout)) ||
(!call.epsout_available && call.epsout != 0.0) ||
(call.outcome != "accepted" && call.outcome != "retry" &&
call.outcome != "optional-abandoned") ||
(call.outcome == "accepted" &&
(call.info != 0 || !call.count_available)) ||
call.decision_reason.empty() ||
(enclosed && call.lower != backend.enclosure_lower)) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Backend call history is inconsistent.");
}
if (call.purpose == "total-enclosure" && call.outcome == "accepted" &&
call.count == state.finite_dimension)
enclosed = true;
}
const auto& last_call = backend.backend_calls.back();
if (!enclosed || solve_calls == 0U || solve_calls > 3U ||
last_call.kind != "solve" || last_call.outcome != "accepted" ||
last_call.count != state.eigenpair_checks.size() ||
!last_call.loop_available || !last_call.epsout_available ||
last_call.lower != backend.solve_lower ||
last_call.upper != backend.solve_upper) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state", "Final all-c solve evidence is invalid.");
}
if (!metric_ok(state.eta_gram, 1e-10) ||
!metric_ok(state.max_eta_orth, 1e-10) ||
!metric_ok(state.eta_mz, 1e-12) || !metric_ok(state.eta_kz, 1e-12) ||
!metric_ok(state.eta_pz, 1e-12) ||
state.null_checks_applicable != (state.mass_null_dimension != 0U) ||
(!state.null_checks_applicable &&
(state.eta_mz != 0.0 || state.eta_kz != 0.0 || state.eta_pz != 0.0))) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state", "Global modal verification is invalid.");
}
const auto model = AnalysisModel::Create(domain);
if (!model.HasValue()) return model.GetStatus();
const auto dofs = DofManager::Create(model.Value());
if (!dofs.HasValue()) return dofs.GetStatus();
for (std::size_t i = 0U; i < state.full_dof_count; ++i) {
const auto constrained =
static_cast<std::uint8_t>(!dofs.Value().FreeEquation(i).has_value());
if (state.constraint_mask[i] != constrained)
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state", "Constraint mask differs from Domain.");
if (constrained != 0U) {
for (std::size_t mode = 0U; mode < state.vectors.Rows(); ++mode)
if (state.vectors(mode, i) != 0.0)
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Constrained modal component is nonzero.");
}
}
std::size_t cluster = 1U;
for (std::size_t index = 0U; index < state.modes.size(); ++index) {
const EigenMode& mode = state.modes[index];
const double mu = mode.eigenvalue / state.backend.operator_scale;
if (index > 0U &&
mu - state.modes[index - 1U].eigenvalue / state.backend.operator_scale >
1e-8)
++cluster;
if (mode.mode_id != index + 1U || !std::isfinite(mode.eigenvalue) ||
!std::isfinite(mu) || mu < -1e-9 || mode.cluster_id != cluster ||
mode.classification != static_cast<std::uint8_t>(std::abs(mu) > 1e-9) ||
(index > 0U && mode.eigenvalue < state.modes[index - 1U].eigenvalue) ||
(mode.eigenvalue < 0.0 && mode.frequency.has_value()) ||
(mode.eigenvalue >= 0.0 && !mode.frequency.has_value()) ||
(mode.frequency &&
(!std::isfinite(*mode.frequency) || *mode.frequency < 0.0 ||
(mode.eigenvalue > 0.0 && *mode.frequency == 0.0) ||
*mode.frequency !=
(mode.eigenvalue == 0.0
? 0.0
: std::sqrt(mode.eigenvalue) / (2.0 * std::acos(-1.0)))))) {
return hdf5_internal::OutputFailure("invalid-eigen-result-state",
"Eigen mode inventory is invalid.");
}
}
std::size_t free_count = 0U;
std::size_t null_count = 0U;
std::size_t positive_count = 0U;
for (const EigenNodalReductionRecord& record : state.nodal_reduction) {
if (record.free_count != record.null_count + record.positive_count ||
record.free_count > 6U || record.null_count > 1U ||
record.rcond_available != (record.positive_count != 0U) ||
(record.rcond_available &&
(!std::isfinite(record.rcond_inf) ||
record.rcond_inf < std::ldexp(1.0, -32) || record.rcond_inf > 1.0)) ||
(!record.rcond_available && record.rcond_inf != 0.0)) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Nodal mass-reduction evidence is invalid.");
}
free_count += record.free_count;
null_count += record.null_count;
positive_count += record.positive_count;
}
if (free_count != state.free_dof_count ||
null_count != state.mass_null_dimension ||
positive_count != state.finite_dimension) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Nodal mass-reduction counts do not match state dimensions.");
}
std::set<std::size_t> columns;
for (std::size_t index = 0U; index < state.eigenpair_checks.size(); ++index) {
const EigenpairCheck& check = state.eigenpair_checks[index];
if (check.sorted_rank != index + 1U || !std::isfinite(check.eigenvalue) ||
check.solver_column >= state.eigenpair_checks.size() ||
!columns.insert(check.solver_column).second ||
(index < state.modes.size() &&
check.eigenvalue != state.modes[index].eigenvalue) ||
(index > 0U &&
check.eigenvalue < state.eigenpair_checks[index - 1U].eigenvalue) ||
!std::isfinite(check.modal_mass) ||
std::abs(check.modal_mass - 1.0) > 1e-10 ||
!std::isfinite(check.rayleigh_quotient) ||
!std::isfinite(check.eta_r) || !std::isfinite(check.eta_w) ||
!std::isfinite(check.rho_b) || !std::isfinite(check.eta_norm) ||
!metric_ok(check.eta_r, 1e-10) || !metric_ok(check.eta_w, 1e-10) ||
!metric_ok(check.rho_b, 1e-10) || !metric_ok(check.eta_norm, 1e-10) ||
!metric_ok(check.eta_energy, 1e-10) ||
(check.backend_res_available && !std::isfinite(check.backend_res)) ||
(!check.backend_res_available && check.backend_res != 0.0)) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Eigenpair verification evidence is invalid.");
}
}
for (std::uint8_t value : state.constraint_mask) {
if (value > 1U) {
return hdf5_internal::OutputFailure("invalid-eigen-result-state",
"Constraint mask must be binary.");
}
}
for (double value : state.prescribed_displacement) {
if (value != 0.0) {
return hdf5_internal::OutputFailure(
"invalid-eigen-result-state",
"Eigen prescribed displacements must be exact zero.");
}
}
for (const Diagnostic& diagnostic : diagnostics) {
if (diagnostic.severity != Severity::kWarning) {
return hdf5_internal::OutputFailure(
"invalid-eigen-diagnostics",
"Successful eigen output stores warnings only.");
}
}
return Status::Ok();
}
void ReplaceStringAttribute(hid_t object, const char* name,
const std::string& value) {
if (H5Aexists(object, name) > 0) {
hdf5_internal::RequireHdf5(H5Adelete(object, name),
"Unable to replace HDF5 metadata attribute.");
}
hdf5_internal::WriteStringAttribute(object, name, value);
}
void WriteEigenMetadata(hid_t file) {
hdf5_internal::Hdf5Handle metadata{
hdf5_internal::RequireHdf5Id(H5Gopen2(file, "/metadata", H5P_DEFAULT),
"Unable to open metadata."),
H5Gclose};
ReplaceStringAttribute(metadata.Get(), "feature_id",
"mitc4-shell-feast-eigen2");
ReplaceStringAttribute(metadata.Get(), "analysis_procedure",
"linear-frequency");
ReplaceStringAttribute(metadata.Get(), "coordinate_convention",
"global-cartesian; shell-local=initial-reference; "
"positive-thickness=+zeta");
hdf5_internal::WriteUint64Attribute(metadata.Get(), "eigen_schema_version",
1U);
hdf5_internal::WriteStringAttribute(metadata.Get(), "mass_integration_rule",
"3x3x3-gauss");
hdf5_internal::WriteStringAttribute(metadata.Get(), "stabilization_rule",
"initial-physical-rotary-trace-mean");
hdf5_internal::WriteStringAttribute(metadata.Get(), "mass_variant",
"derived-lumped");
hdf5_internal::WriteStringAttribute(metadata.Get(), "mass_origin",
"consistent-moment-row-sum");
hdf5_internal::WriteStringAttribute(metadata.Get(), "normalization", "mass");
hdf5_internal::WriteStringAttribute(metadata.Get(), "eigensolver_backend",
"MKL FEAST");
hdf5_internal::WriteStringAttribute(metadata.Get(), "eigensolver_routine",
"dfeast_scsrgv");
hdf5_internal::WriteStringAttribute(
metadata.Get(), "backend_problem",
"scaled-whitened-lumped-with-identity-mass");
hdf5_internal::WriteStringAttribute(metadata.Get(), "numerical_contract",
"mitc4-shell-feast-eigen2:r1");
}
void WriteStep(hid_t file, const Domain& domain,
const EigenAnalysisState& state) {
const FrequencyStepDefinition& source = *domain.Frequency();
auto step = hdf5_internal::CreateGroup(file, "/steps/Step-1");
hdf5_internal::WriteStringAttribute(step.Get(), "source_step_name",
source.source_step_name_raw);
hdf5_internal::WriteStringAttribute(step.Get(), "source_step_name_canonical",
source.source_step_name_canonical);
hdf5_internal::WriteUint64Attribute(step.Get(), "step_source_line",
source.step_location.line);
hdf5_internal::WriteUint64Attribute(step.Get(), "frequency_source_line",
source.frequency_location.line);
hdf5_internal::WriteUint64Attribute(step.Get(), "frequency_data_line",
source.data_location.line);
hdf5_internal::WriteUint8Attribute(step.Get(), "source_nlgeom", 0U);
hdf5_internal::WriteUint8Attribute(
step.Get(), "source_perturbation_present",
static_cast<std::uint8_t>(source.perturbation_explicit));
const std::array<std::uint8_t, 4> 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)};
auto presence_space = hdf5_internal::CreateDatasetSpace({4U});
hdf5_internal::Hdf5Handle presence_attr{
hdf5_internal::RequireHdf5Id(
H5Acreate2(step.Get(), "source_option_presence", H5T_STD_U8LE,
presence_space.Get(), H5P_DEFAULT, H5P_DEFAULT),
"Unable to create source option presence."),
H5Aclose};
hdf5_internal::RequireHdf5(
H5Awrite(presence_attr.Get(), H5T_NATIVE_UINT8, presence.data()),
"Unable to write source option presence.");
hdf5_internal::WriteStringAttribute(step.Get(), "source_eigensolver",
"LANCZOS");
hdf5_internal::WriteStringAttribute(step.Get(), "source_sim", "YES");
hdf5_internal::WriteStringAttribute(step.Get(), "source_acoustic_coupling",
"ON");
hdf5_internal::WriteStringAttribute(step.Get(), "source_normalization",
"MASS");
hdf5_internal::WriteUint64Attribute(step.Get(), "requested_mode_count",
state.modes.size());
hdf5_internal::WriteUint64Attribute(step.Get(), "returned_mode_count",
state.modes.size());
hdf5_internal::WriteUint64Attribute(step.Get(), "full_dof_count",
state.full_dof_count);
hdf5_internal::WriteUint64Attribute(step.Get(), "free_dof_count",
state.free_dof_count);
hdf5_internal::WriteUint64Attribute(step.Get(), "mass_null_dimension",
state.mass_null_dimension);
hdf5_internal::WriteUint64Attribute(step.Get(), "finite_dimension",
state.finite_dimension);
hdf5_internal::WriteUint64Attribute(step.Get(), "validated_eigenpair_count",
state.eigenpair_checks.size());
hdf5_internal::WriteStringAttribute(step.Get(), "frame_indexing",
"mode-rank-one-based");
}
struct ModeRow {
std::uint64_t mode_id;
double eigenvalue;
std::uint8_t classification;
std::uint64_t cluster_id;
};
struct FrequencyRow {
std::uint64_t mode_id;
double frequency;
};
struct BackendCallRow {
std::uint64_t ordinal;
const char* kind;
const char* purpose;
double lower;
double upper;
std::uint64_t allocated_m0;
std::int64_t info;
std::uint8_t count_available;
std::uint64_t count;
std::uint8_t loop_available;
std::uint64_t loop;
std::uint8_t epsout_available;
double epsout;
const char* outcome;
const char* decision_reason;
};
struct DensityRow {
std::uint64_t material_internal_id;
double rho;
const char* source_file;
std::uint64_t source_line;
};
struct NodalReductionRow {
std::uint8_t free_count;
std::uint8_t null_count;
std::uint8_t positive_count;
std::uint8_t rcond_available;
double rcond_inf;
};
struct EigenpairCheckRow {
std::uint64_t sorted_rank;
std::uint64_t solver_column;
double eigenvalue;
double modal_mass;
double rayleigh_quotient;
double eta_r;
double eta_w;
double rho_b;
double eta_norm;
double eta_energy;
std::uint8_t backend_res_available;
double backend_res;
};
struct DiagnosticRow {
const char* severity;
const char* code;
const char* file;
std::uint64_t line;
const char* keyword;
const char* entity_identity;
const char* message;
};
template <typename Row>
hdf5_internal::Hdf5Handle MakeCompoundType() {
return hdf5_internal::Hdf5Handle{
hdf5_internal::RequireHdf5Id(H5Tcreate(H5T_COMPOUND, sizeof(Row)),
"Unable to create compound type."),
H5Tclose};
}
void WriteModes(hid_t file, const EigenAnalysisState& state,
const std::string& source_step) {
std::vector<ModeRow> modes;
std::vector<FrequencyRow> frequencies;
modes.reserve(state.modes.size());
for (const EigenMode& mode : state.modes) {
modes.push_back(
{mode.mode_id, mode.eigenvalue, mode.classification, mode.cluster_id});
if (mode.frequency) frequencies.push_back({mode.mode_id, *mode.frequency});
}
auto mode_file = MakeCompoundType<ModeRow>();
auto mode_memory = MakeCompoundType<ModeRow>();
for (hid_t type : {mode_file.Get(), mode_memory.Get()}) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, "mode_id", HOFFSET(ModeRow, mode_id), H5T_STD_U64LE),
"Unable to define mode ID.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "eigenvalue", HOFFSET(ModeRow, eigenvalue),
H5T_IEEE_F64LE),
"Unable to define eigenvalue.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "classification", HOFFSET(ModeRow, classification),
H5T_STD_U8LE),
"Unable to define classification.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "cluster_id", HOFFSET(ModeRow, cluster_id),
H5T_STD_U64LE),
"Unable to define cluster ID.");
}
auto dataset = hdf5_internal::WriteCompoundDataset(
file, "/steps/Step-1/eigen/modes", modes.size(), mode_file.Get(),
mode_memory.Get(), modes.data());
WriteQuantityAttributes(dataset.Get(), "Value", "time^-2", "scalar",
"modal-frame");
WriteModalAttributes(dataset.Get(), source_step);
hdf5_internal::WriteStringAttribute(dataset.Get(), "quantity", "eigenvalue");
auto frequency_file = MakeCompoundType<FrequencyRow>();
auto frequency_memory = MakeCompoundType<FrequencyRow>();
for (hid_t type : {frequency_file.Get(), frequency_memory.Get()}) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, "mode_id", HOFFSET(FrequencyRow, mode_id),
H5T_STD_U64LE),
"Unable to define frequency mode ID.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "frequency", HOFFSET(FrequencyRow, frequency),
H5T_IEEE_F64LE),
"Unable to define frequency.");
}
auto frequency_dataset = hdf5_internal::WriteCompoundDataset(
file, "/steps/Step-1/eigen/frequencies", frequencies.size(),
frequency_file.Get(), frequency_memory.Get(), frequencies.data());
WriteQuantityAttributes(frequency_dataset.Get(), "Freq", "time^-1", "scalar",
"modal-frame");
WriteModalAttributes(frequency_dataset.Get(), source_step);
hdf5_internal::WriteStringAttribute(frequency_dataset.Get(), "quantity",
"cycle-frequency");
hdf5_internal::WriteStringAttribute(
frequency_dataset.Get(), "unavailable_rule", "negative-raw-eigenvalue");
}
void WriteBaseFpm(hid_t file, const std::vector<std::int64_t>& values) {
const std::vector<hsize_t> dimensions{static_cast<hsize_t>(values.size())};
auto space = hdf5_internal::CreateDatasetSpace(dimensions);
hdf5_internal::Hdf5Handle dataset{
hdf5_internal::RequireHdf5Id(
H5Dcreate2(file, "/steps/Step-1/eigen/backend/base_fpm",
H5T_STD_I64LE, space.Get(), H5P_DEFAULT, H5P_DEFAULT,
H5P_DEFAULT),
"Unable to create eigen backend control dataset."),
H5Dclose};
if (!values.empty()) {
hdf5_internal::RequireHdf5(
H5Dwrite(dataset.Get(), H5T_NATIVE_INT64, H5S_ALL, H5S_ALL, H5P_DEFAULT,
values.data()),
"Unable to write eigen backend control dataset.");
}
WriteQuantityAttributes(dataset.Get(), "fpm", "1", "none",
"backend-parameter");
}
void WriteBackendCalls(hid_t file, const std::vector<FeastBackendCall>& calls) {
std::vector<BackendCallRow> rows;
rows.reserve(calls.size());
for (const FeastBackendCall& call : calls) {
rows.push_back({call.ordinal, call.kind.c_str(), call.purpose.c_str(),
call.lower, call.upper, call.allocated_m0, call.info,
static_cast<std::uint8_t>(call.count_available), call.count,
static_cast<std::uint8_t>(call.loop_available),
static_cast<std::uint64_t>(call.loop),
static_cast<std::uint8_t>(call.epsout_available),
call.epsout, call.outcome.c_str(),
call.decision_reason.c_str()});
}
auto string_type = hdf5_internal::MakeUtf8StringType();
auto file_type = MakeCompoundType<BackendCallRow>();
auto memory_type = MakeCompoundType<BackendCallRow>();
const auto add_fields = [&](hid_t type, hid_t u64, hid_t i64, hid_t f64) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, "ordinal", HOFFSET(BackendCallRow, ordinal), u64),
"Unable to define backend ordinal.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "kind", HOFFSET(BackendCallRow, kind),
string_type.Get()),
"Unable to define backend kind.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "purpose", HOFFSET(BackendCallRow, purpose),
string_type.Get()),
"Unable to define backend purpose.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "lower", HOFFSET(BackendCallRow, lower), f64),
"Unable to define backend lower.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "upper", HOFFSET(BackendCallRow, upper), f64),
"Unable to define backend upper.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "allocated_m0", HOFFSET(BackendCallRow, allocated_m0),
u64),
"Unable to define backend capacity.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "info", HOFFSET(BackendCallRow, info), i64),
"Unable to define backend info.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "count_available",
HOFFSET(BackendCallRow, count_available), H5T_STD_U8LE),
"Unable to define count flag.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "count", HOFFSET(BackendCallRow, count), u64),
"Unable to define count.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "loop_available",
HOFFSET(BackendCallRow, loop_available), H5T_STD_U8LE),
"Unable to define loop flag.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "loop", HOFFSET(BackendCallRow, loop), u64),
"Unable to define loop.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "epsout_available",
HOFFSET(BackendCallRow, epsout_available), H5T_STD_U8LE),
"Unable to define epsout flag.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "epsout", HOFFSET(BackendCallRow, epsout), f64),
"Unable to define epsout.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "outcome", HOFFSET(BackendCallRow, outcome),
string_type.Get()),
"Unable to define outcome.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "decision_reason",
HOFFSET(BackendCallRow, decision_reason), string_type.Get()),
"Unable to define decision reason.");
};
add_fields(file_type.Get(), H5T_STD_U64LE, H5T_STD_I64LE, H5T_IEEE_F64LE);
add_fields(memory_type.Get(), H5T_NATIVE_UINT64, H5T_NATIVE_INT64,
H5T_NATIVE_DOUBLE);
auto dataset = hdf5_internal::WriteCompoundDataset(
file, "/steps/Step-1/eigen/backend/calls", rows.size(), file_type.Get(),
memory_type.Get(), rows.data());
WriteQuantityAttributes(dataset.Get(), "lower,upper,epsout", "1,1,1",
"scalar", "backend-call");
}
void WriteBackendAttributes(hid_t file, const EigenAnalysisState& state) {
hdf5_internal::Hdf5Handle backend{
hdf5_internal::RequireHdf5Id(
H5Gopen2(file, "/steps/Step-1/eigen/backend", H5P_DEFAULT),
"Unable to open eigen backend group."),
H5Gclose};
const FeastEigenResult& result = state.backend;
hdf5_internal::WriteStringAttribute(backend.Get(), "runtime_version",
result.runtime_version);
hdf5_internal::WriteStringAttribute(backend.Get(), "integer_abi",
result.integer_abi);
hdf5_internal::WriteUint64Attribute(backend.Get(), "thread_count",
result.thread_count);
hdf5_internal::WriteUint64Attribute(backend.Get(), "seed", result.seed);
hdf5_internal::WriteDoubleAttribute(backend.Get(), "enclosure_lower",
result.enclosure_lower);
hdf5_internal::WriteDoubleAttribute(backend.Get(), "enclosure_upper",
result.enclosure_upper);
hdf5_internal::WriteDoubleAttribute(backend.Get(), "solve_lower",
result.solve_lower);
hdf5_internal::WriteDoubleAttribute(backend.Get(), "solve_upper",
result.solve_upper);
std::size_t count_calls = 0U;
std::size_t solve_calls = 0U;
std::size_t final_solve_ordinal = 0U;
for (const FeastBackendCall& call : result.backend_calls) {
if (call.kind == "count") ++count_calls;
if (call.kind == "solve") {
++solve_calls;
final_solve_ordinal = call.ordinal;
}
}
hdf5_internal::WriteUint64Attribute(backend.Get(), "count_calls",
count_calls);
hdf5_internal::WriteUint64Attribute(backend.Get(), "solve_calls",
solve_calls);
hdf5_internal::WriteUint64Attribute(backend.Get(), "final_solve_ordinal",
final_solve_ordinal);
hdf5_internal::WriteUint64Attribute(backend.Get(), "final_count",
state.eigenpair_checks.size());
hdf5_internal::WriteStringAttribute(backend.Get(), "upper_search_termination",
result.upper_search_termination);
}
void WriteDensities(hid_t file, const Domain& domain) {
std::vector<DensityRow> rows;
std::vector<std::string> source_paths;
rows.reserve(domain.Densities().size());
source_paths.reserve(domain.Densities().size());
for (const MaterialDensityDefinition& density : domain.Densities()) {
source_paths.push_back(
hdf5_internal::NormalizedPathString(density.location.file));
rows.push_back({density.material_index, density.rho,
source_paths.back().c_str(), density.location.line});
}
auto string_type = hdf5_internal::MakeUtf8StringType();
auto file_type = MakeCompoundType<DensityRow>();
auto memory_type = MakeCompoundType<DensityRow>();
const auto add_fields = [&](hid_t type, hid_t u64, hid_t f64) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, "material_internal_id",
HOFFSET(DensityRow, material_internal_id), u64),
"Unable to define density material ID.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "rho", HOFFSET(DensityRow, rho), f64),
"Unable to define density value.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "source_file", HOFFSET(DensityRow, source_file),
string_type.Get()),
"Unable to define density source file.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "source_line", HOFFSET(DensityRow, source_line), u64),
"Unable to define density source line.");
};
add_fields(file_type.Get(), H5T_STD_U64LE, H5T_IEEE_F64LE);
add_fields(memory_type.Get(), H5T_NATIVE_UINT64, H5T_NATIVE_DOUBLE);
auto dataset = hdf5_internal::WriteCompoundDataset(
file, "/model/shell/densities", rows.size(), file_type.Get(),
memory_type.Get(), rows.data());
WriteQuantityAttributes(dataset.Get(), "rho", "mass/length^3", "scalar",
"model-material");
}
void WriteVerification(hid_t file, const EigenAnalysisState& state) {
auto verification =
hdf5_internal::CreateGroup(file, "/steps/Step-1/eigen/verification");
hdf5_internal::WriteDoubleAttribute(
verification.Get(), "characteristic_length", state.characteristic_length);
hdf5_internal::WriteDoubleAttribute(verification.Get(), "operator_scale",
state.backend.operator_scale);
hdf5_internal::WriteStringAttribute(verification.Get(), "validation_scope",
"all-computed-c-columns");
hdf5_internal::WriteDoubleAttribute(verification.Get(), "eta_gram",
state.eta_gram);
hdf5_internal::WriteDoubleAttribute(verification.Get(), "max_eta_orth",
state.max_eta_orth);
hdf5_internal::WriteUint8Attribute(
verification.Get(), "null_checks_applicable",
static_cast<std::uint8_t>(state.null_checks_applicable));
hdf5_internal::WriteDoubleAttribute(verification.Get(), "eta_mz",
state.eta_mz);
hdf5_internal::WriteDoubleAttribute(verification.Get(), "eta_kz",
state.eta_kz);
hdf5_internal::WriteDoubleAttribute(verification.Get(), "eta_pz",
state.eta_pz);
std::vector<NodalReductionRow> reductions;
reductions.reserve(state.nodal_reduction.size());
for (const EigenNodalReductionRecord& record : state.nodal_reduction) {
reductions.push_back(
{record.free_count, record.null_count, record.positive_count,
static_cast<std::uint8_t>(record.rcond_available), record.rcond_inf});
}
auto reduction_file = MakeCompoundType<NodalReductionRow>();
auto reduction_memory = MakeCompoundType<NodalReductionRow>();
const auto add_reduction = [](hid_t type, hid_t f64) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, "free_count", HOFFSET(NodalReductionRow, free_count),
H5T_STD_U8LE),
"Unable to define free count.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "null_count", HOFFSET(NodalReductionRow, null_count),
H5T_STD_U8LE),
"Unable to define null count.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "positive_count",
HOFFSET(NodalReductionRow, positive_count), H5T_STD_U8LE),
"Unable to define positive count.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "rcond_available",
HOFFSET(NodalReductionRow, rcond_available), H5T_STD_U8LE),
"Unable to define rcond availability.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "rcond_inf", HOFFSET(NodalReductionRow, rcond_inf),
f64),
"Unable to define rcond.");
};
add_reduction(reduction_file.Get(), H5T_IEEE_F64LE);
add_reduction(reduction_memory.Get(), H5T_NATIVE_DOUBLE);
auto reduction_dataset = hdf5_internal::WriteCompoundDataset(
file, "/steps/Step-1/eigen/verification/nodal_reduction",
reductions.size(), reduction_file.Get(), reduction_memory.Get(),
reductions.data());
WriteQuantityAttributes(reduction_dataset.Get(), "rcond_inf", "1", "scalar",
"nodal-mass-reduction");
std::vector<EigenpairCheckRow> checks;
checks.reserve(state.eigenpair_checks.size());
for (const EigenpairCheck& check : state.eigenpair_checks) {
checks.push_back({check.sorted_rank, check.solver_column, check.eigenvalue,
check.modal_mass, check.rayleigh_quotient, check.eta_r,
check.eta_w, check.rho_b, check.eta_norm,
check.eta_energy,
static_cast<std::uint8_t>(check.backend_res_available),
check.backend_res});
}
auto check_file = MakeCompoundType<EigenpairCheckRow>();
auto check_memory = MakeCompoundType<EigenpairCheckRow>();
const auto add_check = [](hid_t type, hid_t u64, hid_t f64) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, "sorted_rank", HOFFSET(EigenpairCheckRow, sorted_rank),
u64),
"Unable to define sorted rank.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "solver_column",
HOFFSET(EigenpairCheckRow, solver_column), u64),
"Unable to define solver column.");
for (const auto& field : std::array<std::pair<const char*, std::size_t>, 8>{
{{"eigenvalue", HOFFSET(EigenpairCheckRow, eigenvalue)},
{"modal_mass", HOFFSET(EigenpairCheckRow, modal_mass)},
{"rayleigh_quotient",
HOFFSET(EigenpairCheckRow, rayleigh_quotient)},
{"eta_r", HOFFSET(EigenpairCheckRow, eta_r)},
{"eta_w", HOFFSET(EigenpairCheckRow, eta_w)},
{"rho_b", HOFFSET(EigenpairCheckRow, rho_b)},
{"eta_norm", HOFFSET(EigenpairCheckRow, eta_norm)},
{"eta_energy", HOFFSET(EigenpairCheckRow, eta_energy)}}}) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, field.first, field.second, f64),
"Unable to define eigenpair metric.");
}
hdf5_internal::RequireHdf5(
H5Tinsert(type, "backend_res_available",
HOFFSET(EigenpairCheckRow, backend_res_available),
H5T_STD_U8LE),
"Unable to define backend residual flag.");
hdf5_internal::RequireHdf5(
H5Tinsert(type, "backend_res", HOFFSET(EigenpairCheckRow, backend_res),
f64),
"Unable to define backend residual.");
};
add_check(check_file.Get(), H5T_STD_U64LE, H5T_IEEE_F64LE);
add_check(check_memory.Get(), H5T_NATIVE_UINT64, H5T_NATIVE_DOUBLE);
auto check_dataset = hdf5_internal::WriteCompoundDataset(
file, "/steps/Step-1/eigen/verification/eigenpair_checks", checks.size(),
check_file.Get(), check_memory.Get(), checks.data());
WriteQuantityAttributes(check_dataset.Get(),
"eigenvalue,modal_mass,rayleigh_quotient,eta_r,"
"eta_w,rho_b,eta_norm,eta_energy,backend_res",
"time^-2,1,time^-2,1,1,1,1,1,1", "scalar",
"eigenpair-verification");
}
void WriteDiagnostics(hid_t file, const std::vector<Diagnostic>& diagnostics) {
std::vector<std::string> files;
files.reserve(diagnostics.size());
std::vector<DiagnosticRow> rows;
rows.reserve(diagnostics.size());
for (const Diagnostic& diagnostic : diagnostics) {
files.push_back(
hdf5_internal::NormalizedPathString(diagnostic.location.file));
rows.push_back({"warning", diagnostic.code.c_str(), files.back().c_str(),
diagnostic.location.line, diagnostic.keyword.c_str(),
diagnostic.entity_identity.c_str(),
diagnostic.message.c_str()});
}
auto string_type = hdf5_internal::MakeUtf8StringType();
auto file_type = MakeCompoundType<DiagnosticRow>();
auto memory_type = MakeCompoundType<DiagnosticRow>();
const auto add_fields = [&](hid_t type, hid_t u64) {
for (const auto& field : std::array<std::pair<const char*, std::size_t>, 6>{
{{"severity", HOFFSET(DiagnosticRow, severity)},
{"code", HOFFSET(DiagnosticRow, code)},
{"file", HOFFSET(DiagnosticRow, file)},
{"keyword", HOFFSET(DiagnosticRow, keyword)},
{"entity_identity", HOFFSET(DiagnosticRow, entity_identity)},
{"message", HOFFSET(DiagnosticRow, message)}}}) {
hdf5_internal::RequireHdf5(
H5Tinsert(type, field.first, field.second, string_type.Get()),
"Unable to define diagnostic text field.");
}
hdf5_internal::RequireHdf5(
H5Tinsert(type, "line", HOFFSET(DiagnosticRow, line), u64),
"Unable to define diagnostic line.");
};
add_fields(file_type.Get(), H5T_STD_U64LE);
add_fields(memory_type.Get(), H5T_NATIVE_UINT64);
(void)hdf5_internal::WriteCompoundDataset(file, "/diagnostics", rows.size(),
file_type.Get(), memory_type.Get(),
rows.data());
}
} // namespace
Status EigenHdf5ResultsWriter::Write(
const std::filesystem::path& output_path, const Domain& domain,
const EigenAnalysisState& state,
const std::vector<Diagnostic>& diagnostics) {
const Status valid = Validate(domain, state, diagnostics);
if (!valid.IsOk()) return valid;
try {
hdf5_internal::Hdf5ErrorSilencer silence;
const auto candidate_path = hdf5_internal::MakeCandidatePath(output_path);
hdf5_internal::CandidateFileGuard cleanup{candidate_path};
hdf5_internal::Hdf5Handle file{
hdf5_internal::RequireHdf5Id(
H5Fcreate(candidate_path.string().c_str(), H5F_ACC_EXCL,
H5P_DEFAULT, H5P_DEFAULT),
"Unable to create eigen candidate."),
H5Fclose};
hdf5_internal::WriterModelData model{
{}, state.constraint_mask, state.prescribed_displacement};
(void)hdf5_internal::CreateGroup(file.Get(), "/model");
(void)hdf5_internal::CreateGroup(file.Get(), "/model/shell");
hdf5_internal::WriteModel(file.Get(), domain, model);
WriteDensities(file.Get(), domain);
WriteDiagnostics(file.Get(), diagnostics);
WriteEigenMetadata(file.Get());
WriteStep(file.Get(), domain, state);
(void)hdf5_internal::CreateGroup(file.Get(), kEigenRoot);
WriteModes(file.Get(), state, domain.Frequency()->source_step_name_raw);
std::vector<double> vector_values;
vector_values.reserve(state.vectors.Rows() * state.vectors.Columns());
for (std::size_t row = 0U; row < state.vectors.Rows(); ++row)
for (std::size_t column = 0U; column < state.vectors.Columns(); ++column)
vector_values.push_back(state.vectors(row, column));
hdf5_internal::WriteModelDoubleDataset(
file.Get(), "/steps/Step-1/eigen/vectors",
{static_cast<hsize_t>(state.modes.size()),
static_cast<hsize_t>(domain.Nodes().size()), 6U},
vector_values.data(), vector_values.size(), "UX,UY,UZ,URX,URY,URZ",
"mass^-1/2,mass^-1/2,mass^-1/2,mass^-1/2*length^-1,mass^-1/"
"2*length^-1,mass^-1/2*length^-1",
"global-cartesian", "nodal-modal-vector");
{
hdf5_internal::Hdf5Handle vectors{
hdf5_internal::RequireHdf5Id(
H5Dopen2(file.Get(), "/steps/Step-1/eigen/vectors", H5P_DEFAULT),
"Unable to open modal vectors."),
H5Dclose};
WriteModalAttributes(vectors.Get(),
domain.Frequency()->source_step_name_raw);
hdf5_internal::WriteStringAttribute(vectors.Get(), "normalization",
"mass");
hdf5_internal::WriteStringAttribute(vectors.Get(), "mass_variant",
"derived-lumped");
hdf5_internal::WriteStringAttribute(
vectors.Get(), "interpretation",
"normalized-mode-not-response-displacement");
}
WriteVerification(file.Get(), state);
(void)hdf5_internal::CreateGroup(file.Get(), "/steps/Step-1/eigen/backend");
WriteBackendAttributes(file.Get(), state);
WriteBaseFpm(file.Get(), state.backend.base_fpm);
WriteBackendCalls(file.Get(), state.backend.backend_calls);
hdf5_internal::RequireHdf5(H5Fflush(file.Get(), H5F_SCOPE_GLOBAL),
"Unable to flush eigen candidate.");
hdf5_internal::RequireHdf5(file.CloseChecked(),
"Unable to close eigen candidate.");
const Status checked = hdf5_internal::SelfCheckEigenFile(
candidate_path, domain, state, diagnostics);
const Status result = hdf5_internal::AtomicFinalizeValidatedCandidate(
candidate_path, output_path, checked);
if (result.IsOk()) cleanup.Release();
return result;
} catch (const hdf5_internal::Hdf5Failure& failure) {
return hdf5_internal::OutputFailure("hdf5-write-failure", failure.what());
} catch (const std::exception& failure) {
return hdf5_internal::OutputFailure("hdf5-write-failure", failure.what());
}
}
} // namespace fesa
+827
View File
@@ -0,0 +1,827 @@
#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
+24
View File
@@ -0,0 +1,24 @@
#ifndef FESA_IO_HDF5_EIGEN_HDF5_SELF_CHECK_H_
#define FESA_IO_HDF5_EIGEN_HDF5_SELF_CHECK_H_
#include <filesystem>
#include <vector>
#include "fesa/analysis/eigen_analysis_state.h"
#include "fesa/core/status.h"
#include "fesa/model/domain.h"
namespace fesa {
struct Diagnostic;
}
namespace fesa::hdf5_internal {
/// @brief Reopens and validates a closed authoritative eigen HDF5 candidate.
Status SelfCheckEigenFile(const std::filesystem::path& path,
const Domain& domain, const EigenAnalysisState& state,
const std::vector<Diagnostic>& diagnostics);
} // namespace fesa::hdf5_internal
#endif // FESA_IO_HDF5_EIGEN_HDF5_SELF_CHECK_H_
+50 -1
View File
@@ -44,6 +44,31 @@ StepDefinition::StepDefinition(StaticStepDefinition definition)
surface_pressure_loads_ = std::move(definition.surface_pressure_loads);
}
StepDefinition::StepDefinition(FrequencyStepDefinition definition)
: name_{definition.source_step_name_raw},
procedure_kind_{StaticProcedureKind::kLinearSmallDisplacement},
nlgeom_{false},
initial_increment_{0.0},
time_period_{0.0},
minimum_increment_{0.0},
maximum_increment_{0.0},
location_{definition.step_location},
frequency_{std::move(definition)} {
for (const auto& boundary : frequency_->boundaries) {
for (int source_dof = boundary.first_dof; source_dof <= boundary.last_dof;
++source_dof) {
const std::size_t source_order = boundary_conditions_.size();
auto owned = std::make_unique<PrescribedDisplacementBoundaryCondition>(
SourceTargetQuery{SourceEntityKind::kNode, "", boundary.target},
static_cast<DofComponent>(source_dof - 1), boundary.value,
source_order, boundary.location);
boundary_conditions_view_.push_back(std::cref(*owned));
prescribed_displacements_view_.Add(*owned);
boundary_conditions_.push_back(std::move(owned));
}
}
}
const std::string& StepDefinition::Name() const noexcept { return name_; }
const BoundaryConditionView& StepDefinition::BoundaryConditions()
@@ -76,6 +101,14 @@ bool StepDefinition::IsNonlinearStatic() const noexcept {
return procedure_kind_ == StaticProcedureKind::kNonlinearFullNewton;
}
bool StepDefinition::IsFrequency() const noexcept {
return frequency_.has_value();
}
const FrequencyStepDefinition* StepDefinition::Frequency() const noexcept {
return frequency_ ? &*frequency_ : nullptr;
}
bool StepDefinition::UsesNonlinearGeometry() const noexcept { return nlgeom_; }
double StepDefinition::InitialIncrement() const noexcept {
@@ -165,6 +198,15 @@ const DomainCollectionView<StepDefinition>& Domain::Steps() const noexcept {
return steps_view_;
}
const std::vector<MaterialDensityDefinition>& Domain::Densities()
const noexcept {
return definition_.densities;
}
const FrequencyStepDefinition* Domain::Frequency() const noexcept {
return steps_view_.Size() == 1U ? steps_view_[0U].Frequency() : nullptr;
}
const std::vector<Diagnostic>& Domain::Warnings() const noexcept {
return definition_.warnings;
}
@@ -227,7 +269,8 @@ Domain::Domain(ModelDefinition definition)
}
definition_.shell_elements.clear();
step_definitions_.reserve(definition_.steps.size());
step_definitions_.reserve(definition_.steps.size() +
(definition_.frequency_step ? 1U : 0U));
for (auto& step : definition_.steps) {
auto owned =
std::unique_ptr<StepDefinition>(new StepDefinition(std::move(step)));
@@ -235,6 +278,12 @@ Domain::Domain(ModelDefinition definition)
step_definitions_.push_back(std::move(owned));
}
definition_.steps.clear();
if (definition_.frequency_step) {
auto owned = std::unique_ptr<StepDefinition>(
new StepDefinition(std::move(*definition_.frequency_step)));
steps_view_.Add(*owned);
step_definitions_.push_back(std::move(owned));
}
}
} // namespace fesa
File diff suppressed because it is too large Load Diff