feat(hex8): connect Solid CLI and atomic HDF5 output
This commit is contained in:
@@ -71,6 +71,8 @@ add_library(
|
||||
io/hdf5/hdf5_self_check.cpp
|
||||
io/hdf5/thermal_hdf5_results_writer.cpp
|
||||
io/hdf5/thermal_hdf5_self_check.cpp
|
||||
io/hdf5/solid_hdf5_results_writer.cpp
|
||||
io/hdf5/solid_hdf5_self_check.cpp
|
||||
loads/concentrated_nodal_load.cpp
|
||||
loads/shell_surface_pressure.cpp
|
||||
materials/isotropic_linear_elastic_material.cpp
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/solid_dynamic_analysis_state.h"
|
||||
#include "fesa/analysis/solid_static_state.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
@@ -320,6 +323,119 @@ Result<std::string> SerializeRecord(
|
||||
return Result<std::string>::Success(std::move(line));
|
||||
}
|
||||
|
||||
Result<std::string> JoinSolidFields(const std::vector<std::string>& fields) {
|
||||
std::string line;
|
||||
for (const auto& field : fields) {
|
||||
if (!line.empty()) line += '\t';
|
||||
line += field;
|
||||
}
|
||||
line += '\n';
|
||||
return Result<std::string>::Success(std::move(line));
|
||||
}
|
||||
Result<std::string> SerializeRecord(const SolidStaticIterationRecord& record,
|
||||
const std::string& step,
|
||||
const std::filesystem::path& path) {
|
||||
std::vector<std::string> fields;
|
||||
AppendField(&fields, "schema", "solid-static-log-v1");
|
||||
AppendField(&fields, "step", EscapeText(step));
|
||||
AppendField(&fields, "increment", std::to_string(record.increment_index));
|
||||
AppendField(&fields, "iteration", std::to_string(record.iteration));
|
||||
const auto factor = FormatFiniteNumber(record.load_factor, path);
|
||||
if (!factor.HasValue())
|
||||
return Result<std::string>::Failure(factor.GetStatus());
|
||||
AppendField(&fields, "load_factor", factor.Value());
|
||||
AppendField(&fields, "stage", EscapeText(record.stage));
|
||||
for (const auto& entry :
|
||||
std::array<std::pair<ChannelSpec, const NewtonChannelDecision*>, 2>{
|
||||
{{{"residual_force", "residual_force"}, &record.force_residual},
|
||||
{{"correction_translation", "correction_translation"},
|
||||
&record.translation_correction}}}) {
|
||||
const Status status = AppendChannelFields(
|
||||
&fields, entry.first, *entry.second, record.convergence_evaluated,
|
||||
"not-evaluated(" + EscapeText(record.failure_reason) + ")", path);
|
||||
if (!status.IsOk()) return Result<std::string>::Failure(status);
|
||||
}
|
||||
AppendField(&fields, "accepted", record.accepted ? "1" : "0");
|
||||
AppendField(&fields, "diagnostic_code", EscapeText(record.diagnostic_code));
|
||||
AppendField(&fields, "failure_reason", EscapeText(record.failure_reason));
|
||||
return JoinSolidFields(fields);
|
||||
}
|
||||
Result<std::string> SerializeRecord(const SolidDynamicIterationRecord& record,
|
||||
const std::filesystem::path& path) {
|
||||
std::vector<std::string> fields;
|
||||
AppendField(&fields, "schema", "solid-hht-log-v1");
|
||||
AppendField(&fields, "arithmetic",
|
||||
"normalized-high-low-physical-state;RN64-observables");
|
||||
AppendField(&fields, "increment", std::to_string(record.increment_index));
|
||||
AppendField(&fields, "iteration",
|
||||
std::to_string(record.evidence.iteration_index));
|
||||
AppendField(&fields, "stage", EscapeText(record.stage));
|
||||
AppendField(&fields, "outcome", EscapeText(record.outcome));
|
||||
AppendField(&fields, "failure_code", EscapeText(record.failure_code));
|
||||
const auto& e = record.evidence;
|
||||
for (const double metric :
|
||||
{e.residual_numerator, e.residual_scale, e.residual_metric,
|
||||
e.weighted_residual_numerator, e.recombined_residual_numerator,
|
||||
e.old_residual_numerator, e.weighted_equivalence_metric,
|
||||
e.update_metric}) {
|
||||
if (!std::isfinite(metric) || metric < 0)
|
||||
return Result<std::string>::Failure(LogOutputFailure(
|
||||
path, "A Solid HHT norm, scale or metric is invalid."));
|
||||
}
|
||||
for (const double metric : e.correction_metrics) {
|
||||
if (!std::isfinite(metric) || metric < 0)
|
||||
return Result<std::string>::Failure(
|
||||
LogOutputFailure(path, "A Solid HHT correction metric is invalid."));
|
||||
}
|
||||
std::vector<std::pair<std::string, double>> numbers{
|
||||
{"time", record.time},
|
||||
{"physical_residual_force_norm", e.residual_numerator},
|
||||
{"uncancelled_force_scale", e.residual_scale},
|
||||
{"physical_residual_max_ratio", e.residual_metric},
|
||||
{"original_weighted_force_norm", e.weighted_residual_numerator},
|
||||
{"solved_weighted_force_norm", e.recombined_residual_numerator},
|
||||
{"old_physical_force_norm", e.old_residual_numerator},
|
||||
{"weighted_equivalence_metric", e.weighted_equivalence_metric},
|
||||
{"update_metric", e.update_metric}};
|
||||
const std::array<const char*, 4> corrections{{"du_over_L", "h_dv_over_L",
|
||||
"h2_physical_da_over_L",
|
||||
"h2_algorithmic_dA_over_L"}};
|
||||
for (std::size_t i = 0; i < 4; ++i)
|
||||
numbers.emplace_back(corrections[i], e.correction_metrics[i]);
|
||||
const std::array<const char*, 5> terms{
|
||||
{"worst_r_force", "worst_Ma_force", "worst_internal_force",
|
||||
"worst_external_force", "worst_uncancelled_force_scale"}};
|
||||
for (std::size_t i = 0; i < 5; ++i)
|
||||
numbers.emplace_back(terms[i], e.worst_force_terms[i]);
|
||||
for (const auto& number : numbers) {
|
||||
const auto formatted = FormatFiniteNumber(number.second, path);
|
||||
if (!formatted.HasValue())
|
||||
return Result<std::string>::Failure(formatted.GetStatus());
|
||||
AppendField(&fields, number.first, formatted.Value());
|
||||
}
|
||||
const std::array<const char*, 4> weighted{
|
||||
{"worst_original_weighted_force", "worst_solved_weighted_force",
|
||||
"worst_old_physical_force", "worst_weighted_uncancelled_force_scale"}};
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
const auto formatted = FormatFiniteNumber(e.worst_weighted_terms[i], path);
|
||||
if (!formatted.HasValue())
|
||||
return Result<std::string>::Failure(formatted.GetStatus());
|
||||
AppendField(&fields, weighted[i], formatted.Value());
|
||||
}
|
||||
AppendField(&fields, "physical_residual_threshold", "1e-10");
|
||||
AppendField(&fields, "correction_threshold", "1e-10");
|
||||
AppendField(&fields, "update_threshold", "1e-12");
|
||||
AppendField(&fields, "weighted_equivalence_threshold", "1e-12");
|
||||
AppendField(&fields, "residual_unit", "force");
|
||||
AppendField(&fields, "correction_unit", "dimensionless");
|
||||
AppendField(&fields, "worst_full_dof", std::to_string(e.worst_full_dof));
|
||||
AppendField(&fields, "zero_scale_exact", e.zero_scale_exact ? "1" : "0");
|
||||
AppendField(&fields, "physical_residual_pass", e.residual_pass ? "1" : "0");
|
||||
AppendField(&fields, "correction_pass", e.correction_pass ? "1" : "0");
|
||||
AppendField(&fields, "update_pass", e.update_pass ? "1" : "0");
|
||||
return JoinSolidFields(fields);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status NewtonIterationLogWriter::Open(const std::filesystem::path& log_path) {
|
||||
@@ -379,6 +495,48 @@ Status NewtonIterationLogWriter::Write(
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status NewtonIterationLogWriter::Write(const SolidStaticIterationRecord& record,
|
||||
const std::string& step_name) {
|
||||
if (state_ != State::kOpen) {
|
||||
state_ = State::kFailed;
|
||||
return LogOutputFailure(log_path_, "The Newton log writer is not open.");
|
||||
}
|
||||
const auto line = SerializeRecord(record, step_name, log_path_);
|
||||
if (!line.HasValue()) {
|
||||
state_ = State::kFailed;
|
||||
return line.GetStatus();
|
||||
}
|
||||
stream_.write(line.Value().data(),
|
||||
static_cast<std::streamsize>(line.Value().size()));
|
||||
if (!stream_) {
|
||||
state_ = State::kFailed;
|
||||
return LogOutputFailure(log_path_,
|
||||
"The Solid Newton log record could not write.");
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status NewtonIterationLogWriter::Write(
|
||||
const SolidDynamicIterationRecord& record) {
|
||||
if (state_ != State::kOpen) {
|
||||
state_ = State::kFailed;
|
||||
return LogOutputFailure(log_path_, "The Newton log writer is not open.");
|
||||
}
|
||||
const auto line = SerializeRecord(record, log_path_);
|
||||
if (!line.HasValue()) {
|
||||
state_ = State::kFailed;
|
||||
return line.GetStatus();
|
||||
}
|
||||
stream_.write(line.Value().data(),
|
||||
static_cast<std::streamsize>(line.Value().size()));
|
||||
if (!stream_) {
|
||||
state_ = State::kFailed;
|
||||
return LogOutputFailure(log_path_,
|
||||
"The Solid Newton log record could not write.");
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status NewtonIterationLogWriter::Flush() {
|
||||
if (state_ != State::kOpen) {
|
||||
state_ = State::kFailed;
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/hht_dynamic_procedure.h"
|
||||
#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/analysis/solid_frequency_procedure.h"
|
||||
#include "fesa/app/output_paths.h"
|
||||
#include "fesa/io/abaqus/domain_mapper.h"
|
||||
#include "fesa/io/abaqus/input_reader.h"
|
||||
@@ -14,6 +16,7 @@
|
||||
#include "fesa/results/eigen_results_writer.h"
|
||||
#include "fesa/results/nonlinear_results_writer.h"
|
||||
#include "fesa/results/results_writer.h"
|
||||
#include "fesa/results/solid_results_writer.h"
|
||||
#include "fesa/results/thermal_results_writer.h"
|
||||
|
||||
namespace fesa {
|
||||
@@ -42,7 +45,10 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
|
||||
const LinearFrequencyProcedureExecutor& frequency_executor,
|
||||
NewtonIterationLogWriter& log,
|
||||
const HhtDynamicProcedureExecutor* dynamic_executor,
|
||||
const TransientHeatProcedureExecutor* thermal_executor) {
|
||||
const TransientHeatProcedureExecutor* thermal_executor,
|
||||
const SolidStaticProcedureExecutor* solid_static_executor,
|
||||
const SolidFrequencyProcedureExecutor* solid_frequency_executor,
|
||||
const SolidHhtDynamicProcedureExecutor* solid_dynamic_executor) {
|
||||
if (domain.ThermalStep() != nullptr) {
|
||||
if (thermal_executor == nullptr) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
@@ -72,6 +78,63 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
|
||||
"Prepared analysis requires exactly one analysis step."}}));
|
||||
}
|
||||
|
||||
if (domain.IsSolid()) {
|
||||
const auto missing = []() {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError,
|
||||
"missing-solid-executor",
|
||||
{},
|
||||
"",
|
||||
{},
|
||||
"The selected Solid execution adapter is required."}}));
|
||||
};
|
||||
if (domain.Dynamic() != nullptr) {
|
||||
if (solid_dynamic_executor == nullptr) return missing();
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
|
||||
auto dofs = DofManager::Create(model.Value());
|
||||
if (!dofs.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
|
||||
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
|
||||
if (!state.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
|
||||
const Status execution =
|
||||
solid_dynamic_executor->ExecuteInto(domain, state.Value());
|
||||
for (const auto& record : state.Value().IterationRecords()) {
|
||||
const Status written = log.Write(record);
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
|
||||
std::move(dofs.Value()), std::move(state.Value())}});
|
||||
}
|
||||
if (domain.Steps()[0U].IsFrequency()) {
|
||||
if (solid_frequency_executor == nullptr) return missing();
|
||||
auto result = solid_frequency_executor->Execute(domain);
|
||||
if (!result.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(result.Value())});
|
||||
}
|
||||
if (solid_static_executor == nullptr) return missing();
|
||||
SolidStaticState state;
|
||||
const Status execution = solid_static_executor->ExecuteInto(domain, state);
|
||||
for (const auto& record : state.IterationRecords()) {
|
||||
const Status written = log.Write(record, domain.Steps()[0U].Name());
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
|
||||
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
|
||||
}
|
||||
|
||||
if (domain.Dynamic() != nullptr) {
|
||||
if (dynamic_executor == nullptr)
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
@@ -120,8 +183,29 @@ Status FinalizePreparedAnalysisResult(
|
||||
const PreparedAnalysisOutcome& outcome,
|
||||
const std::vector<Diagnostic>& diagnostics, ResultsWriter& linear_writer,
|
||||
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer,
|
||||
DynamicResultsWriter* dynamic_writer,
|
||||
ThermalResultsWriter* thermal_writer) {
|
||||
DynamicResultsWriter* dynamic_writer, ThermalResultsWriter* thermal_writer,
|
||||
SolidResultsWriter* solid_writer) {
|
||||
if (std::holds_alternative<SolidStaticExecutionResult>(outcome) ||
|
||||
std::holds_alternative<SolidFrequencyState>(outcome) ||
|
||||
std::holds_alternative<SolidDynamicExecutionResult>(outcome)) {
|
||||
if (solid_writer == nullptr)
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
{{Severity::kError,
|
||||
"missing-solid-writer",
|
||||
{},
|
||||
"",
|
||||
{},
|
||||
"Solid result writer is required."}});
|
||||
if (const auto* result = std::get_if<SolidStaticExecutionResult>(&outcome))
|
||||
return solid_writer->Write(paths.results_path, domain, result->state,
|
||||
diagnostics);
|
||||
if (const auto* result = std::get_if<SolidFrequencyState>(&outcome))
|
||||
return solid_writer->Write(paths.results_path, domain, *result,
|
||||
diagnostics);
|
||||
return solid_writer->Write(
|
||||
paths.results_path, domain,
|
||||
std::get<SolidDynamicExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<ThermalExecutionResult>(outcome)) {
|
||||
if (thermal_writer == nullptr) {
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "fesa/analysis/newton_iteration_log.h"
|
||||
#include "fesa/analysis/nonlinear_static_procedure.h"
|
||||
#include "fesa/analysis/prepared_analysis.h"
|
||||
#include "fesa/analysis/solid_frequency_procedure.h"
|
||||
#include "fesa/app/output_paths.h"
|
||||
#include "fesa/assembly/parallel_for.h"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
@@ -18,6 +19,7 @@
|
||||
#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/io/hdf5/solid_hdf5_results_writer.h"
|
||||
#include "fesa/io/hdf5/thermal_hdf5_results_writer.h"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
|
||||
@@ -89,6 +91,7 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
|
||||
EigenHdf5ResultsWriter eigen_writer;
|
||||
DynamicHdf5ResultsWriter dynamic_writer;
|
||||
ThermalHdf5ResultsWriter thermal_writer;
|
||||
SolidHdf5ResultsWriter solid_writer;
|
||||
NewtonIterationLogWriter log;
|
||||
|
||||
Status status = log.Open(paths.Value().log_path);
|
||||
@@ -110,9 +113,15 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
|
||||
parallel_for, linear_solver, tangent_solver);
|
||||
const TransientHeatProcedureExecutor thermal_executor(parallel_for,
|
||||
linear_solver);
|
||||
const SolidStaticProcedureExecutor solid_static(parallel_for, linear_solver,
|
||||
tangent_solver);
|
||||
const SolidFrequencyProcedureExecutor solid_frequency;
|
||||
const SolidHhtDynamicProcedureExecutor solid_dynamic(
|
||||
parallel_for, linear_solver, tangent_solver);
|
||||
Result<PreparedAnalysisOutcome> outcome = ExecutePreparedAnalysis(
|
||||
prepared.Value().domain, linear_executor, nonlinear_executor,
|
||||
frequency_executor, log, &dynamic_executor, &thermal_executor);
|
||||
frequency_executor, log, &dynamic_executor, &thermal_executor,
|
||||
&solid_static, &solid_frequency, &solid_dynamic);
|
||||
if (!outcome.HasValue()) {
|
||||
status = outcome.GetStatus();
|
||||
} else {
|
||||
@@ -124,8 +133,10 @@ 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,
|
||||
eigen_writer, &dynamic_writer, &thermal_writer);
|
||||
eigen_writer, &dynamic_writer, &thermal_writer, &solid_writer);
|
||||
if (status.IsOk()) {
|
||||
if (prepared.Value().domain.IsSolid())
|
||||
WriteDiagnostics(prepared.Value().diagnostics);
|
||||
return kSuccessExitCode;
|
||||
}
|
||||
WriteDiagnostics(status.Diagnostics());
|
||||
|
||||
@@ -0,0 +1,763 @@
|
||||
#include "fesa/io/hdf5/solid_hdf5_results_writer.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/build_info.h"
|
||||
#include "fesa/elements/element_factory.h"
|
||||
#include "fesa/elements/hex8_element.h"
|
||||
#include "fesa/materials/isotropic_linear_elastic_material.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_result_writer.h"
|
||||
#include "io/hdf5/solid_hdf5_self_check.h"
|
||||
#include "math/high_low_arithmetic.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
namespace {
|
||||
void Check(bool condition, const char* message) {
|
||||
if (!condition) throw Hdf5Failure(message);
|
||||
}
|
||||
using U = std::vector<std::uint64_t>;
|
||||
using B = std::vector<std::uint8_t>;
|
||||
using D = std::vector<double>;
|
||||
using S = std::vector<std::string>;
|
||||
std::vector<double> Values(const Vector& vector) {
|
||||
D result;
|
||||
for (std::size_t i = 0; i < vector.Size(); ++i) result.push_back(vector[i]);
|
||||
return result;
|
||||
}
|
||||
void Parent(hid_t file, const std::string& path) {
|
||||
const auto parent = path.substr(0, path.find_last_of('/'));
|
||||
if (!parent.empty() && H5Lexists(file, parent.c_str(), H5P_DEFAULT) <= 0)
|
||||
(void)CreateGroup(file, parent.c_str());
|
||||
}
|
||||
void Table(hid_t file, const SolidTable& table) {
|
||||
Parent(file, table.path);
|
||||
std::vector<Hdf5Handle> native, filetypes;
|
||||
std::size_t size = 0;
|
||||
for (const auto& column : table.columns) {
|
||||
native.push_back(SolidColumnType(column, true));
|
||||
filetypes.push_back(SolidColumnType(column, false));
|
||||
size += H5Tget_size(native.back().Get());
|
||||
}
|
||||
Hdf5Handle ft{RequireHdf5Id(H5Tcreate(H5T_COMPOUND, size),
|
||||
"Solid file compound failed."),
|
||||
H5Tclose};
|
||||
Hdf5Handle mt{RequireHdf5Id(H5Tcreate(H5T_COMPOUND, size),
|
||||
"Solid memory compound failed."),
|
||||
H5Tclose};
|
||||
std::vector<unsigned char> bytes(table.rows * size);
|
||||
std::size_t offset = 0;
|
||||
for (std::size_t c = 0; c < table.columns.size(); ++c) {
|
||||
const auto& column = table.columns[c];
|
||||
RequireHdf5(
|
||||
H5Tinsert(ft.Get(), column.name.c_str(), offset, filetypes[c].Get()),
|
||||
"Solid file member failed.");
|
||||
RequireHdf5(
|
||||
H5Tinsert(mt.Get(), column.name.c_str(), offset, native[c].Get()),
|
||||
"Solid memory member failed.");
|
||||
std::visit(
|
||||
[&](const auto& values) {
|
||||
using T = typename std::decay_t<decltype(values)>::value_type;
|
||||
for (std::size_t row = 0; row < table.rows; ++row) {
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
const char* pointer = values[row].c_str();
|
||||
std::memcpy(bytes.data() + row * size + offset, &pointer,
|
||||
sizeof(pointer));
|
||||
} else
|
||||
std::memcpy(bytes.data() + row * size + offset,
|
||||
values.data() + row * column.width,
|
||||
column.width * sizeof(T));
|
||||
}
|
||||
},
|
||||
column.values);
|
||||
offset += H5Tget_size(native[c].Get());
|
||||
}
|
||||
auto dataset = WriteCompoundDataset(file, table.path.c_str(), table.rows,
|
||||
ft.Get(), mt.Get(), bytes.data());
|
||||
for (const auto& item : table.strings)
|
||||
WriteStringAttribute(dataset.Get(), item.first.c_str(), item.second);
|
||||
}
|
||||
SolidData Data(const std::string& path, std::vector<hsize_t> dimensions,
|
||||
D values, const char* names, const char* units,
|
||||
const char* location) {
|
||||
return {path,
|
||||
std::move(dimensions),
|
||||
std::move(values),
|
||||
{{"component_names", names},
|
||||
{"unit_dimensions", units},
|
||||
{"coordinate_system", "global-cartesian"},
|
||||
{"location", location},
|
||||
{"step_name", "Step-1"}},
|
||||
{},
|
||||
{}};
|
||||
}
|
||||
void FrameData(SolidPlan& plan, const Domain& domain, std::size_t frame,
|
||||
std::size_t increment, double coordinate, bool dynamic,
|
||||
const Vector& u, const Vector& rf, const Vector& r,
|
||||
const std::vector<SolidIntegrationPointRow>& points,
|
||||
double energy) {
|
||||
const std::string root = "/steps/Step-1/frames/" + std::to_string(frame);
|
||||
const hsize_t n = domain.Nodes().size(), e = domain.Elements().Size();
|
||||
const auto add = [&](SolidData value) {
|
||||
value.integers = {{"frame_index", frame}, {"increment_index", increment}};
|
||||
value.doubles = {{dynamic ? "time" : "load_factor", coordinate}};
|
||||
plan.datasets.push_back(std::move(value));
|
||||
};
|
||||
add(Data(root + "/nodal/displacement", {n, 3}, Values(u), "U1,U2,U3",
|
||||
"length", "nodal"));
|
||||
add(Data(root + "/nodal/reaction", {n, 3}, Values(rf), "RF1,RF2,RF3", "force",
|
||||
"nodal"));
|
||||
add(Data(root + "/nodal/residual", {n, 3}, Values(r), "R1,R2,R3", "force",
|
||||
"nodal"));
|
||||
D stress, strain;
|
||||
Check(points.size() == 8 * e, "Solid point count is invalid.");
|
||||
for (std::size_t i = 0; i < points.size(); ++i) {
|
||||
Check(points[i].element == i / 8 &&
|
||||
points[i].integration_point == static_cast<int>(i % 8 + 1),
|
||||
"Solid point identity is invalid.");
|
||||
stress.insert(stress.end(), points[i].cauchy_stress.begin(),
|
||||
points[i].cauchy_stress.end());
|
||||
strain.insert(strain.end(), points[i].strain.begin(),
|
||||
points[i].strain.end());
|
||||
}
|
||||
auto s =
|
||||
Data(root + "/solid/stress", {e, 8, 6}, std::move(stress),
|
||||
"S11,S22,S33,S12,S13,S23", "force/length^2", "integration-point");
|
||||
s.strings["stress_measure"] = "cauchy";
|
||||
add(std::move(s));
|
||||
auto eps =
|
||||
Data(root + "/solid/strain", {e, 8, 6}, std::move(strain),
|
||||
"E11,E22,E33,E12,E13,E23", "dimensionless", "integration-point");
|
||||
eps.strings["strain_measure"] = domain.Steps()[0].UsesNonlinearGeometry()
|
||||
? "green-lagrange"
|
||||
: "infinitesimal";
|
||||
eps.strings["shear_convention"] = "tensor";
|
||||
add(std::move(eps));
|
||||
auto en = Data(root + "/global/strain_energy", {}, D{energy}, "scalar",
|
||||
"force*length", "global");
|
||||
en.strings["definition"] = domain.Steps()[0].UsesNonlinearGeometry()
|
||||
? "sum(W*detJ0)"
|
||||
: "0.5*u^T*K0*u";
|
||||
add(std::move(en));
|
||||
}
|
||||
void Model(SolidPlan& plan, const Domain& domain, const DofManager& dofs) {
|
||||
SolidTable nodes{"/model/nodes",
|
||||
domain.Nodes().size(),
|
||||
{{"internal_node_id", 1, U{}},
|
||||
{"instance_name", 1, S{}},
|
||||
{"source_label", 1, S{}},
|
||||
{"coordinates", 3, D{}}}};
|
||||
for (std::size_t i = 0; i < domain.Nodes().size(); ++i) {
|
||||
const auto& node = domain.Nodes()[i];
|
||||
std::get<U>(nodes.columns[0].values).push_back(i);
|
||||
std::get<S>(nodes.columns[1].values)
|
||||
.push_back(node.source_id.instance_name);
|
||||
std::get<S>(nodes.columns[2].values)
|
||||
.push_back(node.source_id.source_label_text);
|
||||
auto& coords = std::get<D>(nodes.columns[3].values);
|
||||
coords.insert(coords.end(), node.coordinates.begin(),
|
||||
node.coordinates.end());
|
||||
}
|
||||
plan.tables.push_back(std::move(nodes));
|
||||
SolidTable elements{"/model/elements",
|
||||
domain.SolidElements().Size(),
|
||||
{{"internal_element_id", 1, U{}},
|
||||
{"instance_name", 1, S{}},
|
||||
{"source_label", 1, S{}},
|
||||
{"source_element_type", 1, S{}},
|
||||
{"internal_formulation", 1, S{}},
|
||||
{"node_internal_ids", 8, U{}},
|
||||
{"solid_section_internal_id", 1, U{}},
|
||||
{"material_internal_id", 1, U{}}}};
|
||||
for (std::size_t i = 0; i < domain.SolidElements().Size(); ++i) {
|
||||
const auto& element = domain.SolidElements()[i];
|
||||
std::get<U>(elements.columns[0].values).push_back(i);
|
||||
std::get<S>(elements.columns[1].values)
|
||||
.push_back(element.SourceId().instance_name);
|
||||
std::get<S>(elements.columns[2].values)
|
||||
.push_back(element.SourceId().source_label_text);
|
||||
std::get<S>(elements.columns[3].values)
|
||||
.push_back(std::string(element.SourceElementType()));
|
||||
std::get<S>(elements.columns[4].values).push_back("FESA-Hex8");
|
||||
auto& connectivity = std::get<U>(elements.columns[5].values);
|
||||
connectivity.insert(connectivity.end(), element.node_indices.begin(),
|
||||
element.node_indices.end());
|
||||
std::get<U>(elements.columns[6].values).push_back(element.section_index);
|
||||
std::get<U>(elements.columns[7].values).push_back(element.MaterialIndex());
|
||||
}
|
||||
plan.tables.push_back(std::move(elements));
|
||||
SolidTable materials{"/model/solid/materials",
|
||||
domain.Materials().Size(),
|
||||
{{"internal_material_id", 1, U{}},
|
||||
{"name", 1, S{}},
|
||||
{"youngs_modulus", 1, D{}},
|
||||
{"poisson_ratio", 1, D{}},
|
||||
{"density_present", 1, B{}},
|
||||
{"density", 1, D{}}}};
|
||||
for (std::size_t i = 0; i < domain.Materials().Size(); ++i) {
|
||||
const auto* material =
|
||||
dynamic_cast<const LinearElasticMaterial*>(&domain.Materials()[i]);
|
||||
Check(material != nullptr, "Solid material is not isotropic elastic.");
|
||||
bool present = false;
|
||||
double rho = 0;
|
||||
for (const auto& density : domain.Densities())
|
||||
if (density.material_index == i) {
|
||||
present = true;
|
||||
rho = density.rho;
|
||||
}
|
||||
std::get<U>(materials.columns[0].values).push_back(i);
|
||||
std::get<S>(materials.columns[1].values).push_back(material->Name());
|
||||
std::get<D>(materials.columns[2].values)
|
||||
.push_back(material->YoungsModulus());
|
||||
std::get<D>(materials.columns[3].values)
|
||||
.push_back(material->PoissonsRatio());
|
||||
std::get<B>(materials.columns[4].values).push_back(present ? 1 : 0);
|
||||
std::get<D>(materials.columns[5].values).push_back(rho);
|
||||
}
|
||||
plan.tables.push_back(std::move(materials));
|
||||
SolidTable sections{"/model/solid/sections",
|
||||
domain.SolidSections().Size(),
|
||||
{{"internal_section_id", 1, U{}},
|
||||
{"source_file", 1, S{}},
|
||||
{"source_line", 1, U{}},
|
||||
{"source_elset", 1, S{}},
|
||||
{"material_internal_id", 1, U{}}}};
|
||||
for (std::size_t i = 0; i < domain.SolidSections().Size(); ++i) {
|
||||
const auto& section = domain.SolidSections()[i];
|
||||
std::get<U>(sections.columns[0].values).push_back(i);
|
||||
std::get<S>(sections.columns[1].values)
|
||||
.push_back(section.Location().file.generic_u8string());
|
||||
std::get<U>(sections.columns[2].values).push_back(section.Location().line);
|
||||
std::get<S>(sections.columns[3].values).push_back(section.Name());
|
||||
std::get<U>(sections.columns[4].values).push_back(section.MaterialIndex());
|
||||
}
|
||||
plan.tables.push_back(std::move(sections));
|
||||
SolidTable points{"/model/solid_integration_points",
|
||||
8 * domain.Elements().Size(),
|
||||
{{"internal_element_id", 1, U{}},
|
||||
{"point_id", 1, B{}},
|
||||
{"natural_coordinates", 3, D{}},
|
||||
{"reference_coordinates", 3, D{}}}};
|
||||
ElementFactory factory;
|
||||
for (std::size_t i = 0; i < domain.Elements().Size(); ++i) {
|
||||
auto element = factory.Create(domain.Elements()[i], domain);
|
||||
Check(element.HasValue(), "Solid factory reference failed.");
|
||||
const auto* hex = dynamic_cast<const Hex8Element*>(element.Value().get());
|
||||
Check(hex != nullptr, "Solid kernel identity failed.");
|
||||
auto response = hex->EvaluateLinear(Vector{24});
|
||||
Check(response.HasValue(), "Solid reference recovery failed.");
|
||||
for (const auto& point : response.Value().points) {
|
||||
std::get<U>(points.columns[0].values).push_back(i);
|
||||
std::get<B>(points.columns[1].values)
|
||||
.push_back(static_cast<std::uint8_t>(point.integration_point));
|
||||
auto& natural = std::get<D>(points.columns[2].values);
|
||||
natural.insert(natural.end(), point.natural_coordinates.begin(),
|
||||
point.natural_coordinates.end());
|
||||
auto& position = std::get<D>(points.columns[3].values);
|
||||
position.insert(position.end(), point.reference_coordinates.begin(),
|
||||
point.reference_coordinates.end());
|
||||
}
|
||||
}
|
||||
plan.tables.push_back(std::move(points));
|
||||
plan.mask.assign(dofs.FullDofCount(), 0);
|
||||
for (const auto index : dofs.ConstrainedDofs()) plan.mask[index] = 1;
|
||||
}
|
||||
void Diagnostics(SolidPlan& plan, std::vector<Diagnostic> diagnostics) {
|
||||
SortDiagnostics(diagnostics);
|
||||
SolidTable table{"/diagnostics",
|
||||
diagnostics.size(),
|
||||
{{"severity", 1, S{}},
|
||||
{"code", 1, S{}},
|
||||
{"file", 1, S{}},
|
||||
{"line", 1, U{}},
|
||||
{"keyword", 1, S{}},
|
||||
{"entity_identity", 1, S{}},
|
||||
{"message", 1, S{}}}};
|
||||
for (const auto& d : diagnostics) {
|
||||
std::get<S>(table.columns[0].values)
|
||||
.push_back(d.severity == Severity::kWarning ? "warning" : "error");
|
||||
std::get<S>(table.columns[1].values).push_back(d.code);
|
||||
std::get<S>(table.columns[2].values)
|
||||
.push_back(d.location.file.generic_u8string());
|
||||
std::get<U>(table.columns[3].values).push_back(d.location.line);
|
||||
std::get<S>(table.columns[4].values).push_back(d.keyword);
|
||||
std::get<S>(table.columns[5].values).push_back(d.entity_identity);
|
||||
std::get<S>(table.columns[6].values).push_back(d.message);
|
||||
}
|
||||
plan.tables.push_back(std::move(table));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Hdf5Handle SolidColumnType(const SolidColumn& column, bool native) {
|
||||
Hdf5Handle result;
|
||||
if (std::holds_alternative<S>(column.values))
|
||||
result = MakeUtf8StringType();
|
||||
else {
|
||||
hid_t type = std::holds_alternative<U>(column.values)
|
||||
? (native ? H5T_NATIVE_UINT64 : H5T_STD_U64LE)
|
||||
: std::holds_alternative<B>(column.values)
|
||||
? (native ? H5T_NATIVE_UINT8 : H5T_STD_U8LE)
|
||||
: (native ? H5T_NATIVE_DOUBLE : H5T_IEEE_F64LE);
|
||||
result = Hdf5Handle{
|
||||
RequireHdf5Id(H5Tcopy(type), "Solid column type failed."), H5Tclose};
|
||||
}
|
||||
if (column.width > 1) {
|
||||
const hsize_t width = column.width;
|
||||
result = Hdf5Handle{RequireHdf5Id(H5Tarray_create2(result.Get(), 1, &width),
|
||||
"Solid array type failed."),
|
||||
H5Tclose};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<SolidPlan> BuildSolidPlan(const Domain& domain, SolidStateView view,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
try {
|
||||
if (std::holds_alternative<
|
||||
std::reference_wrapper<const SolidDynamicAnalysisState>>(view))
|
||||
Check(math_internal::ArithmeticEnvironmentSupported(),
|
||||
"Solid RN64 serialization requires supported arithmetic "
|
||||
"environment.");
|
||||
Check(domain.IsSolid() && domain.Steps().Size() == 1,
|
||||
"Solid output requires exactly one Solid step.");
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
Check(model.HasValue(), "Solid output model failed.");
|
||||
auto dofs = DofManager::Create(model.Value());
|
||||
Check(dofs.HasValue(), "Solid output DOF partition failed.");
|
||||
SolidPlan plan;
|
||||
const bool dynamic = std::holds_alternative<
|
||||
std::reference_wrapper<const SolidDynamicAnalysisState>>(view);
|
||||
const bool frequency = std::holds_alternative<
|
||||
std::reference_wrapper<const SolidFrequencyState>>(view);
|
||||
const bool nonlinear = domain.Steps()[0].UsesNonlinearGeometry();
|
||||
Check(dynamic == (domain.Dynamic() != nullptr) &&
|
||||
frequency == domain.Steps()[0].IsFrequency(),
|
||||
"Solid output procedure/state mismatch.");
|
||||
plan.metadata = {
|
||||
{"feature_id", "hex8-structural-analysis"},
|
||||
{"analysis_procedure", dynamic ? (nonlinear ? "hht-nonlinear-dynamics"
|
||||
: "hht-linear-dynamics")
|
||||
: frequency ? "linear-frequency"
|
||||
: nonlinear ? "nonlinear-static-full-newton"
|
||||
: "linear-static"},
|
||||
{"element_formulation", "FESA-Hex8"},
|
||||
{"integration_rule", "gauss-2x2x2"},
|
||||
{"mass_formulation", "consistent-reference"},
|
||||
{"source_input_identity", SourceInputIdentity(domain)},
|
||||
{"source_step_name", domain.Steps()[0].Name()},
|
||||
{"unit_system_label", "user-consistent-unspecified"},
|
||||
{"coordinate_convention",
|
||||
"global-cartesian; reference-strain; current-cauchy-stress"},
|
||||
{"component_order", "node-major; UX,UY,UZ"},
|
||||
{"solver_version", std::string(SolverVersion())}};
|
||||
Model(plan, domain, dofs.Value());
|
||||
Diagnostics(plan, diagnostics);
|
||||
if (const auto* ref =
|
||||
std::get_if<std::reference_wrapper<const SolidStaticState>>(
|
||||
&view)) {
|
||||
const auto& state = ref->get();
|
||||
auto validator = SolidStaticState::Create(domain, dofs.Value());
|
||||
Check(validator.HasValue(), "Static output validator creation failed.");
|
||||
Check(!state.AcceptedFrames().empty(), "Static output history is empty.");
|
||||
SolidTable accepted{"/steps/Step-1/accepted_frames",
|
||||
state.AcceptedFrames().size(),
|
||||
{{"frame_index", 1, U{}},
|
||||
{"increment_index", 1, U{}},
|
||||
{"load_factor", 1, D{}},
|
||||
{"iteration_count", 1, U{}},
|
||||
{"is_factor_one", 1, B{}}}};
|
||||
for (const auto& frame : state.AcceptedFrames()) {
|
||||
Check(validator.Value().CommitAcceptedFrame(frame).IsOk(),
|
||||
"Static accepted frame is invalid.");
|
||||
FrameData(plan, domain, frame.frame_index, frame.increment_index,
|
||||
frame.load_factor, false, frame.displacement, frame.reaction,
|
||||
frame.residual, frame.solid_results,
|
||||
frame.physical_strain_energy);
|
||||
std::get<U>(accepted.columns[0].values).push_back(frame.frame_index);
|
||||
std::get<U>(accepted.columns[1].values)
|
||||
.push_back(frame.increment_index);
|
||||
std::get<D>(accepted.columns[2].values).push_back(frame.load_factor);
|
||||
std::get<U>(accepted.columns[3].values)
|
||||
.push_back(frame.iteration_count);
|
||||
std::get<B>(accepted.columns[4].values)
|
||||
.push_back(frame.is_factor_one ? 1 : 0);
|
||||
}
|
||||
Check(state.AcceptedFrames().back().is_factor_one,
|
||||
"Static output is incomplete.");
|
||||
if (nonlinear) plan.tables.push_back(std::move(accepted));
|
||||
} else if (const auto* dynamic_ref = std::get_if<
|
||||
std::reference_wrapper<const SolidDynamicAnalysisState>>(
|
||||
&view)) {
|
||||
Check(math_internal::ArithmeticEnvironmentSupported(),
|
||||
"Solid RN64 serialization requires supported arithmetic "
|
||||
"environment.");
|
||||
const auto& state = dynamic_ref->get();
|
||||
auto validator = SolidDynamicAnalysisState::Create(domain, dofs.Value());
|
||||
Check(validator.HasValue(), "Dynamic output validator creation failed.");
|
||||
Check(state.AcceptedFrames().size() ==
|
||||
domain.Dynamic()->increment_count + 1,
|
||||
"Dynamic output history is incomplete.");
|
||||
SolidTable accepted{"/steps/Step-1/accepted_frames",
|
||||
state.AcceptedFrames().size(),
|
||||
{{"frame_index", 1, U{}},
|
||||
{"increment_index", 1, U{}},
|
||||
{"time", 1, D{}},
|
||||
{"iteration_count", 1, U{}},
|
||||
{"is_initial", 1, B{}}}};
|
||||
for (const auto& frame : state.AcceptedFrames()) {
|
||||
Check(validator.Value().CommitAcceptedFrame(frame).IsOk(),
|
||||
"Full-pair dynamic accepted frame is invalid.");
|
||||
FrameData(plan, domain, frame.frame_index, frame.increment_index,
|
||||
frame.time, true, frame.displacement, frame.reaction,
|
||||
frame.residual, frame.solid_results, frame.strain_energy);
|
||||
const std::string root =
|
||||
"/steps/Step-1/frames/" + std::to_string(frame.frame_index);
|
||||
const auto add = [&](SolidData data) {
|
||||
data.integers = {{"frame_index", frame.frame_index},
|
||||
{"increment_index", frame.increment_index}};
|
||||
data.doubles = {{"time", frame.time}};
|
||||
plan.datasets.push_back(std::move(data));
|
||||
};
|
||||
add(Data(root + "/nodal/velocity", {domain.Nodes().size(), 3},
|
||||
Values(frame.velocity), "V1,V2,V3", "length/time", "nodal"));
|
||||
auto a = Data(root + "/nodal/acceleration", {domain.Nodes().size(), 3},
|
||||
Values(frame.acceleration), "A1,A2,A3", "length/time^2",
|
||||
"nodal");
|
||||
a.strings["acceleration_kind"] = "physical";
|
||||
add(std::move(a));
|
||||
a = Data(root + "/nodal/algorithmic_acceleration",
|
||||
{domain.Nodes().size(), 3},
|
||||
Values(frame.algorithmic_acceleration), "A1,A2,A3",
|
||||
"length/time^2", "nodal");
|
||||
a.strings["acceleration_kind"] = "algorithmic";
|
||||
add(std::move(a));
|
||||
for (const auto& quantity :
|
||||
std::vector<std::tuple<const char*, double, const char*>>{
|
||||
{"kinetic_energy", frame.kinetic_energy, "0.5*v^T*M*v"},
|
||||
{"external_work", frame.external_work,
|
||||
"sum(0.5*(F_old+F_new)*delta_u)"},
|
||||
{"energy_balance_error", frame.energy_balance_error,
|
||||
"T+U-T0-U0-Wext"}}) {
|
||||
auto data = Data(root + "/global/" + std::get<0>(quantity), {},
|
||||
D{std::get<1>(quantity)}, "scalar", "force*length",
|
||||
"global");
|
||||
data.strings["definition"] = std::get<2>(quantity);
|
||||
add(std::move(data));
|
||||
}
|
||||
add(Data(root + "/global/linear_momentum", {3},
|
||||
D(frame.linear_momentum.begin(), frame.linear_momentum.end()),
|
||||
"P1,P2,P3", "mass*length/time", "global"));
|
||||
auto angular = Data(
|
||||
root + "/global/angular_momentum", {3},
|
||||
D(frame.angular_momentum.begin(), frame.angular_momentum.end()),
|
||||
"L1,L2,L3", "mass*length^2/time", "global");
|
||||
angular.strings["origin"] = "global0";
|
||||
add(std::move(angular));
|
||||
std::get<U>(accepted.columns[0].values).push_back(frame.frame_index);
|
||||
std::get<U>(accepted.columns[1].values)
|
||||
.push_back(frame.increment_index);
|
||||
std::get<D>(accepted.columns[2].values).push_back(frame.time);
|
||||
std::get<U>(accepted.columns[3].values)
|
||||
.push_back(frame.iteration_count);
|
||||
std::get<B>(accepted.columns[4].values)
|
||||
.push_back(frame.is_initial ? 1 : 0);
|
||||
}
|
||||
plan.tables.push_back(std::move(accepted));
|
||||
} else {
|
||||
const auto& state =
|
||||
std::get<std::reference_wrapper<const SolidFrequencyState>>(view)
|
||||
.get();
|
||||
Check(domain.Frequency()->requested_mode_count > 0,
|
||||
"Modal request is invalid.");
|
||||
const std::size_t count =
|
||||
static_cast<std::size_t>(domain.Frequency()->requested_mode_count);
|
||||
Check(state.full_dof_count == dofs.Value().FullDofCount() &&
|
||||
state.free_dof_count == dofs.Value().FreeDofs().size() &&
|
||||
state.constraint_mask == plan.mask,
|
||||
"Modal DOF inventory is invalid.");
|
||||
Check(state.prescribed_displacement.size() == plan.mask.size(),
|
||||
"Modal prescribed shape is invalid.");
|
||||
for (const auto value : state.prescribed_displacement)
|
||||
Check(value == 0, "Modal prescribed value is invalid.");
|
||||
Check(state.modes.size() == count && state.vectors.Rows() == count &&
|
||||
state.vectors.Columns() == plan.mask.size(),
|
||||
"Modal output shape is invalid.");
|
||||
Check(std::isfinite(state.classification_scale) &&
|
||||
state.classification_scale > 0 &&
|
||||
std::isfinite(state.mass_lower_bound) &&
|
||||
state.mass_lower_bound > 0,
|
||||
"Modal certification scale is invalid.");
|
||||
Check(std::isfinite(state.eta_gram) && state.eta_gram >= 0 &&
|
||||
state.eta_gram <= 1e-10 && std::isfinite(state.max_eta_orth) &&
|
||||
state.max_eta_orth >= 0 && state.max_eta_orth <= 1e-10,
|
||||
"Modal Gram evidence is invalid.");
|
||||
Check(state.eigenpair_checks.size() == state.backend.eigenvalues.size() &&
|
||||
state.backend.column_ids.size() ==
|
||||
state.eigenpair_checks.size() &&
|
||||
state.eigenpair_checks.size() >= count &&
|
||||
state.eigenpair_checks.size() <= state.free_dof_count,
|
||||
"Modal backend verification inventory is invalid.");
|
||||
std::size_t expected_cluster = 1;
|
||||
SolidTable modes{"/steps/Step-1/eigen/modes",
|
||||
count,
|
||||
{{"mode_id", 1, U{}},
|
||||
{"eigenvalue", 1, D{}},
|
||||
{"classification", 1, B{}},
|
||||
{"cluster_id", 1, U{}}}};
|
||||
SolidTable frequencies{"/steps/Step-1/eigen/frequencies",
|
||||
0,
|
||||
{{"mode_id", 1, U{}}, {"frequency", 1, D{}}}};
|
||||
D vectors;
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
const auto& mode = state.modes[i];
|
||||
Check(mode.mode_id == i + 1 && mode.cluster_id > 0 &&
|
||||
std::isfinite(mode.eigenvalue) &&
|
||||
mode.eigenvalue / state.classification_scale >= -1e-9 &&
|
||||
(i == 0 || mode.eigenvalue >= state.modes[i - 1].eigenvalue),
|
||||
"Modal identity/order is invalid.");
|
||||
if (i > 0 && (mode.eigenvalue - state.modes[i - 1].eigenvalue) /
|
||||
state.classification_scale >
|
||||
1e-8)
|
||||
++expected_cluster;
|
||||
Check(mode.cluster_id == expected_cluster &&
|
||||
mode.eigenvalue == state.eigenpair_checks[i].eigenvalue,
|
||||
"Modal cluster or selected-pair identity is invalid.");
|
||||
const bool near =
|
||||
std::abs(mode.eigenvalue) / state.classification_scale <= 1e-9;
|
||||
Check(mode.classification == (near ? 0 : 1),
|
||||
"Modal classification is invalid.");
|
||||
Check(mode.frequency.has_value() == (mode.eigenvalue >= 0),
|
||||
"Modal frequency presence is invalid.");
|
||||
if (mode.frequency) {
|
||||
Check(*mode.frequency ==
|
||||
std::sqrt(mode.eigenvalue) / (2 * std::acos(-1.0)),
|
||||
"Modal frequency is invalid.");
|
||||
++frequencies.rows;
|
||||
std::get<U>(frequencies.columns[0].values).push_back(mode.mode_id);
|
||||
std::get<D>(frequencies.columns[1].values).push_back(*mode.frequency);
|
||||
}
|
||||
std::get<U>(modes.columns[0].values).push_back(mode.mode_id);
|
||||
std::get<D>(modes.columns[1].values).push_back(mode.eigenvalue);
|
||||
std::get<B>(modes.columns[2].values).push_back(mode.classification);
|
||||
std::get<U>(modes.columns[3].values).push_back(mode.cluster_id);
|
||||
for (std::size_t j = 0; j < plan.mask.size(); ++j) {
|
||||
Check(std::isfinite(state.vectors(i, j)) &&
|
||||
(!plan.mask[j] || state.vectors(i, j) == 0),
|
||||
"Modal vector is invalid.");
|
||||
vectors.push_back(state.vectors(i, j));
|
||||
}
|
||||
}
|
||||
modes.strings = {{"component_names", "eigenvalue"},
|
||||
{"unit_dimensions", "1/time^2"},
|
||||
{"location", "global"},
|
||||
{"coordinate_system", "global-cartesian"}};
|
||||
frequencies.strings = {{"component_names", "frequency"},
|
||||
{"unit_dimensions", "1/time"},
|
||||
{"location", "global"},
|
||||
{"coordinate_system", "global-cartesian"}};
|
||||
plan.tables.push_back(std::move(modes));
|
||||
plan.tables.push_back(std::move(frequencies));
|
||||
auto data =
|
||||
Data("/steps/Step-1/eigen/vectors", {count, domain.Nodes().size(), 3},
|
||||
std::move(vectors), "UX,UY,UZ", "mass-normalized", "nodal");
|
||||
data.strings["normalization"] = "phi^T*M*phi=1";
|
||||
plan.datasets.push_back(std::move(data));
|
||||
SolidTable pairs{"/steps/Step-1/eigen/verification/pairs",
|
||||
state.eigenpair_checks.size(),
|
||||
{{"sorted_rank", 1, U{}},
|
||||
{"solver_column", 1, U{}},
|
||||
{"eigenvalue", 1, D{}},
|
||||
{"modal_mass", 1, D{}},
|
||||
{"rayleigh_quotient", 1, D{}},
|
||||
{"eta_r", 1, D{}},
|
||||
{"eta_norm", 1, D{}},
|
||||
{"eta_energy", 1, D{}}}};
|
||||
|
||||
std::set<std::size_t> solver_columns;
|
||||
for (std::size_t i = 0; i < state.eigenpair_checks.size(); ++i) {
|
||||
Check(state.backend.column_ids[i] < state.eigenpair_checks.size() &&
|
||||
solver_columns.insert(state.backend.column_ids[i]).second,
|
||||
"Modal backend column inventory is invalid.");
|
||||
const auto& pair = state.eigenpair_checks[i];
|
||||
const double normalized = pair.eigenvalue / state.classification_scale;
|
||||
Check(std::isfinite(pair.eigenvalue) && std::isfinite(normalized),
|
||||
"Backend eigenvalue or normalized eigenvalue is nonfinite.");
|
||||
Check(normalized >= -1e-9,
|
||||
"Significant negative backend eigenvalue cannot be published.");
|
||||
Check(i == 0 ||
|
||||
pair.eigenvalue >= state.eigenpair_checks[i - 1].eigenvalue,
|
||||
"Backend eigenvalues are not sorted.");
|
||||
|
||||
Check(pair.sorted_rank == i + 1 &&
|
||||
pair.solver_column == state.backend.column_ids[i] &&
|
||||
pair.eigenvalue == state.backend.eigenvalues[i],
|
||||
"Modal pair identity is invalid.");
|
||||
Check(std::isfinite(pair.modal_mass) && pair.modal_mass > 0 &&
|
||||
std::abs(pair.modal_mass - 1) <= 1e-10,
|
||||
"Modal mass evidence is invalid.");
|
||||
for (const double eta : {pair.eta_r, pair.eta_norm, pair.eta_energy})
|
||||
Check(std::isfinite(eta) && eta >= 0 && eta <= 1e-10,
|
||||
"Modal pair gate is invalid.");
|
||||
std::get<U>(pairs.columns[0].values).push_back(pair.sorted_rank);
|
||||
std::get<U>(pairs.columns[1].values).push_back(pair.solver_column);
|
||||
const std::array<double, 6> values{{pair.eigenvalue, pair.modal_mass,
|
||||
pair.rayleigh_quotient, pair.eta_r,
|
||||
pair.eta_norm, pair.eta_energy}};
|
||||
for (std::size_t j = 0; j < 6; ++j)
|
||||
std::get<D>(pairs.columns[j + 2].values).push_back(values[j]);
|
||||
}
|
||||
plan.tables.push_back(std::move(pairs));
|
||||
plan.datasets.push_back(Data("/steps/Step-1/eigen/verification/eta_gram",
|
||||
{}, D{state.eta_gram}, "scalar",
|
||||
"dimensionless", "global"));
|
||||
plan.datasets.push_back(
|
||||
Data("/steps/Step-1/eigen/verification/max_eta_orth", {},
|
||||
D{state.max_eta_orth}, "scalar", "dimensionless", "global"));
|
||||
}
|
||||
for (const auto& table : plan.tables)
|
||||
for (const auto& column : table.columns)
|
||||
std::visit(
|
||||
[&](const auto& values) {
|
||||
Check(values.size() == table.rows * column.width,
|
||||
"Solid column shape is invalid.");
|
||||
using T = typename std::decay_t<decltype(values)>::value_type;
|
||||
for (const auto& value : values) {
|
||||
if constexpr (std::is_same_v<T, double>)
|
||||
Check(std::isfinite(value),
|
||||
"Solid numeric column is nonfinite.");
|
||||
if constexpr (std::is_same_v<T, std::string>)
|
||||
Check(IsValidUtf8(value) &&
|
||||
value.find('\0') == std::string::npos,
|
||||
"Solid string column is invalid UTF8.");
|
||||
}
|
||||
},
|
||||
column.values);
|
||||
for (const auto& data : plan.datasets) {
|
||||
std::size_t size = 1;
|
||||
for (const auto width : data.dimensions) {
|
||||
Check(SizeProductFits(size, width, size),
|
||||
"Solid dataset shape overflow.");
|
||||
}
|
||||
Check(size == data.values.size(), "Solid dataset shape is invalid.");
|
||||
for (const auto value : data.values)
|
||||
Check(std::isfinite(value), "Solid dataset value is nonfinite.");
|
||||
}
|
||||
for (const auto& item : plan.metadata)
|
||||
Check(IsValidUtf8(item.second) &&
|
||||
item.second.find('\0') == std::string::npos,
|
||||
"Solid metadata UTF8 is invalid.");
|
||||
return Result<SolidPlan>::Success(std::move(plan));
|
||||
} catch (const std::exception& error) {
|
||||
return Result<SolidPlan>::Failure(
|
||||
OutputFailure("invalid-solid-output", error.what()));
|
||||
}
|
||||
}
|
||||
|
||||
Status WriteSolidFile(const std::filesystem::path& output, const Domain& domain,
|
||||
SolidStateView view,
|
||||
const std::vector<Diagnostic>& diagnostics,
|
||||
const SolidFileHooks& hooks) {
|
||||
const auto plan = BuildSolidPlan(domain, view, diagnostics);
|
||||
if (!plan.HasValue()) return plan.GetStatus();
|
||||
try {
|
||||
Hdf5ErrorSilencer silence;
|
||||
const auto candidate = MakeCandidatePath(output);
|
||||
CandidateFileGuard guard(candidate);
|
||||
Hdf5Handle file{
|
||||
RequireHdf5Id(H5Fcreate(candidate.string().c_str(), H5F_ACC_EXCL,
|
||||
H5P_DEFAULT, H5P_DEFAULT),
|
||||
"Solid candidate create failed."),
|
||||
hooks.candidate_close};
|
||||
if (hooks.before_write) hooks.before_write(file.Get());
|
||||
if (std::holds_alternative<
|
||||
std::reference_wrapper<const SolidDynamicAnalysisState>>(view))
|
||||
Check(math_internal::ArithmeticEnvironmentSupported(),
|
||||
"Solid RN64 serialization arithmetic environment changed.");
|
||||
{
|
||||
auto group = CreateGroup(file.Get(), "/metadata");
|
||||
WriteUint64Attribute(group.Get(), "schema_version", 1);
|
||||
for (const auto& item : plan.Value().metadata)
|
||||
WriteStringAttribute(group.Get(), item.first.c_str(), item.second);
|
||||
}
|
||||
(void)CreateGroup(file.Get(), "/model");
|
||||
WriteNodes(file.Get(), domain);
|
||||
for (const auto& table : plan.Value().tables)
|
||||
if (table.path != "/model/nodes" && table.path != "/diagnostics")
|
||||
Table(file.Get(), table);
|
||||
WriteDiagnostics(file.Get(), diagnostics);
|
||||
{
|
||||
const std::vector<hsize_t> dimensions{domain.Nodes().size(), 3};
|
||||
auto space = CreateDatasetSpace(dimensions);
|
||||
Hdf5Handle dataset{
|
||||
RequireHdf5Id(H5Dcreate2(file.Get(), "/model/nodal_constraint_mask",
|
||||
H5T_STD_U8LE, space.Get(), H5P_DEFAULT,
|
||||
H5P_DEFAULT, H5P_DEFAULT),
|
||||
"Solid mask create failed."),
|
||||
H5Dclose};
|
||||
RequireHdf5(H5Dwrite(dataset.Get(), H5T_NATIVE_UINT8, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, plan.Value().mask.data()),
|
||||
"Solid mask write failed.");
|
||||
WriteStringAttribute(dataset.Get(), "component_names", "UX,UY,UZ");
|
||||
}
|
||||
for (const auto& data : plan.Value().datasets) {
|
||||
Parent(file.Get(), data.path);
|
||||
auto dataset = WriteDoubleValues(file.Get(), data.path, data.dimensions,
|
||||
data.values.data(), data.values.size());
|
||||
for (const auto& item : data.strings)
|
||||
WriteStringAttribute(dataset.Get(), item.first.c_str(), item.second);
|
||||
for (const auto& item : data.integers)
|
||||
WriteUint64Attribute(dataset.Get(), item.first.c_str(), item.second);
|
||||
for (const auto& item : data.doubles)
|
||||
WriteDoubleAttribute(dataset.Get(), item.first.c_str(), item.second);
|
||||
}
|
||||
RequireHdf5(H5Fflush(file.Get(), H5F_SCOPE_GLOBAL),
|
||||
"Solid candidate flush failed.");
|
||||
RequireHdf5(file.CloseChecked(), "Solid candidate close failed.");
|
||||
if (hooks.before_self_check) hooks.before_self_check(candidate);
|
||||
const Status check =
|
||||
SelfCheckSolidFile(candidate, plan.Value(), hooks.reader_close);
|
||||
if (!check.IsOk()) return check;
|
||||
if (hooks.before_finalize) hooks.before_finalize(candidate);
|
||||
if (std::holds_alternative<
|
||||
std::reference_wrapper<const SolidDynamicAnalysisState>>(view))
|
||||
Check(math_internal::ArithmeticEnvironmentSupported(),
|
||||
"Solid RN64 publication arithmetic environment changed.");
|
||||
const Status finalized =
|
||||
AtomicFinalizeValidatedCandidate(candidate, output, check);
|
||||
if (finalized.IsOk()) guard.Release();
|
||||
return finalized;
|
||||
} catch (const std::exception& error) {
|
||||
return OutputFailure("solid-output-failure", error.what());
|
||||
}
|
||||
}
|
||||
} // namespace fesa::hdf5_internal
|
||||
|
||||
namespace fesa {
|
||||
Status SolidHdf5ResultsWriter::Write(
|
||||
const std::filesystem::path& path, const Domain& domain,
|
||||
const SolidStaticState& state, const std::vector<Diagnostic>& diagnostics) {
|
||||
return hdf5_internal::WriteSolidFile(path, domain, std::cref(state),
|
||||
diagnostics);
|
||||
}
|
||||
Status SolidHdf5ResultsWriter::Write(
|
||||
const std::filesystem::path& path, const Domain& domain,
|
||||
const SolidFrequencyState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
return hdf5_internal::WriteSolidFile(path, domain, std::cref(state),
|
||||
diagnostics);
|
||||
}
|
||||
Status SolidHdf5ResultsWriter::Write(
|
||||
const std::filesystem::path& path, const Domain& domain,
|
||||
const SolidDynamicAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
return hdf5_internal::WriteSolidFile(path, domain, std::cref(state),
|
||||
diagnostics);
|
||||
}
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,217 @@
|
||||
#include "io/hdf5/solid_hdf5_self_check.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
namespace {
|
||||
void Check(bool condition, const char* message) {
|
||||
if (!condition) throw Hdf5Failure(message);
|
||||
}
|
||||
Hdf5Handle Dataset(hid_t file, const std::string& path,
|
||||
const std::vector<hsize_t>& dimensions) {
|
||||
Hdf5Handle dataset{RequireHdf5Id(H5Dopen2(file, path.c_str(), H5P_DEFAULT),
|
||||
"Solid required dataset is missing."),
|
||||
H5Dclose};
|
||||
Hdf5Handle space{
|
||||
RequireHdf5Id(H5Dget_space(dataset.Get()), "Solid dataset space failed."),
|
||||
H5Sclose};
|
||||
Check(H5Sget_simple_extent_ndims(space.Get()) ==
|
||||
static_cast<int>(dimensions.size()),
|
||||
"Solid dataset rank mismatch.");
|
||||
std::vector<hsize_t> actual(dimensions.size());
|
||||
RequireHdf5(H5Sget_simple_extent_dims(space.Get(), actual.data(), nullptr),
|
||||
"Solid dimensions read failed.");
|
||||
Check(actual == dimensions, "Solid dataset shape mismatch.");
|
||||
Hdf5Handle properties{RequireHdf5Id(H5Dget_create_plist(dataset.Get()),
|
||||
"Solid dataset properties failed."),
|
||||
H5Pclose};
|
||||
Check(H5Pget_layout(properties.Get()) == H5D_CONTIGUOUS &&
|
||||
H5Pget_nfilters(properties.Get()) == 0,
|
||||
"Solid dataset storage mismatch.");
|
||||
return dataset;
|
||||
}
|
||||
void String(hid_t object, const std::string& name,
|
||||
const std::string& expected) {
|
||||
Hdf5Handle attr{RequireHdf5Id(H5Aopen(object, name.c_str(), H5P_DEFAULT),
|
||||
"Solid string attribute missing."),
|
||||
H5Aclose};
|
||||
Hdf5Handle type{
|
||||
RequireHdf5Id(H5Aget_type(attr.Get()), "Solid attribute type failed."),
|
||||
H5Tclose};
|
||||
Hdf5Handle space{
|
||||
RequireHdf5Id(H5Aget_space(attr.Get()), "Solid attribute space failed."),
|
||||
H5Sclose};
|
||||
Check(H5Tget_class(type.Get()) == H5T_STRING &&
|
||||
H5Tis_variable_str(type.Get()) > 0 &&
|
||||
H5Tget_cset(type.Get()) == H5T_CSET_UTF8 &&
|
||||
H5Sget_simple_extent_type(space.Get()) == H5S_SCALAR,
|
||||
"Solid string attribute type mismatch.");
|
||||
char* value = nullptr;
|
||||
RequireHdf5(H5Aread(attr.Get(), type.Get(), &value),
|
||||
"Solid string attribute read failed.");
|
||||
const std::string actual = value ? value : "";
|
||||
RequireHdf5(H5free_memory(value), "Solid string release failed.");
|
||||
Check(IsValidUtf8(actual) && actual == expected,
|
||||
"Solid string attribute mismatch.");
|
||||
}
|
||||
template <class T>
|
||||
void Number(hid_t object, const std::string& name, T expected, hid_t ft,
|
||||
hid_t mt) {
|
||||
Hdf5Handle attr{RequireHdf5Id(H5Aopen(object, name.c_str(), H5P_DEFAULT),
|
||||
"Solid numeric attribute missing."),
|
||||
H5Aclose};
|
||||
Hdf5Handle type{
|
||||
RequireHdf5Id(H5Aget_type(attr.Get()), "Solid attribute type failed."),
|
||||
H5Tclose};
|
||||
Hdf5Handle space{
|
||||
RequireHdf5Id(H5Aget_space(attr.Get()), "Solid attribute space failed."),
|
||||
H5Sclose};
|
||||
Check(H5Tequal(type.Get(), ft) > 0 &&
|
||||
H5Sget_simple_extent_type(space.Get()) == H5S_SCALAR,
|
||||
"Solid numeric attribute type mismatch.");
|
||||
T actual{};
|
||||
RequireHdf5(H5Aread(attr.Get(), mt, &actual),
|
||||
"Solid numeric attribute read failed.");
|
||||
Check(actual == expected, "Solid numeric attribute mismatch.");
|
||||
}
|
||||
void Column(hid_t dataset, hid_t compound, std::size_t index,
|
||||
const SolidColumn& column, std::size_t rows) {
|
||||
char* name = H5Tget_member_name(compound, static_cast<unsigned>(index));
|
||||
Check(name != nullptr, "Solid member name missing.");
|
||||
const std::string actual_name = name;
|
||||
RequireHdf5(H5free_memory(name), "Solid member name release failed.");
|
||||
Check(actual_name == column.name,
|
||||
"Solid compound member order/name mismatch.");
|
||||
Hdf5Handle stored{
|
||||
RequireHdf5Id(H5Tget_member_type(compound, static_cast<unsigned>(index)),
|
||||
"Solid member type missing."),
|
||||
H5Tclose};
|
||||
const auto expected = SolidColumnType(column, false);
|
||||
Check(H5Tequal(stored.Get(), expected.Get()) > 0,
|
||||
"Solid compound member type mismatch.");
|
||||
const auto native = SolidColumnType(column, true);
|
||||
Hdf5Handle memory{
|
||||
RequireHdf5Id(H5Tcreate(H5T_COMPOUND, H5Tget_size(native.Get())),
|
||||
"Solid column memory type failed."),
|
||||
H5Tclose};
|
||||
RequireHdf5(H5Tinsert(memory.Get(), column.name.c_str(), 0, native.Get()),
|
||||
"Solid column memory member failed.");
|
||||
std::visit(
|
||||
[&](const auto& values) {
|
||||
using T = typename std::decay_t<decltype(values)>::value_type;
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
std::vector<char*> actual(rows, nullptr);
|
||||
if (rows)
|
||||
RequireHdf5(H5Dread(dataset, memory.Get(), H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, actual.data()),
|
||||
"Solid string column read failed.");
|
||||
bool same = true;
|
||||
for (std::size_t i = 0; i < rows; ++i) {
|
||||
const std::string value = actual[i] ? actual[i] : "";
|
||||
same = same && IsValidUtf8(value) && value == values[i];
|
||||
}
|
||||
Hdf5Handle space{RequireHdf5Id(H5Dget_space(dataset),
|
||||
"Solid reclaim space failed."),
|
||||
H5Sclose};
|
||||
if (rows)
|
||||
RequireHdf5(H5Dvlen_reclaim(memory.Get(), space.Get(), H5P_DEFAULT,
|
||||
actual.data()),
|
||||
"Solid string column release failed.");
|
||||
Check(same, "Solid string column identity mismatch.");
|
||||
} else {
|
||||
std::vector<T> actual(values.size());
|
||||
if (rows)
|
||||
RequireHdf5(H5Dread(dataset, memory.Get(), H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, actual.data()),
|
||||
"Solid numeric column read failed.");
|
||||
Check(actual == values, "Solid numeric column identity mismatch.");
|
||||
if constexpr (std::is_same_v<T, double>)
|
||||
for (const auto value : actual)
|
||||
Check(std::isfinite(value), "Solid numeric column is nonfinite.");
|
||||
}
|
||||
},
|
||||
column.values);
|
||||
}
|
||||
} // namespace
|
||||
Status SelfCheckSolidFile(const std::filesystem::path& path,
|
||||
const SolidPlan& plan,
|
||||
herr_t (*reader_close)(hid_t)) {
|
||||
try {
|
||||
Hdf5ErrorSilencer silence;
|
||||
Hdf5Handle file{RequireHdf5Id(H5Fopen(path.string().c_str(), H5F_ACC_RDONLY,
|
||||
H5P_DEFAULT),
|
||||
"Solid candidate readback open failed."),
|
||||
reader_close};
|
||||
{
|
||||
Hdf5Handle group{
|
||||
RequireHdf5Id(H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
||||
"Solid metadata missing."),
|
||||
H5Gclose};
|
||||
Number<std::uint64_t>(group.Get(), "schema_version", 1, H5T_STD_U64LE,
|
||||
H5T_NATIVE_UINT64);
|
||||
for (const auto& item : plan.metadata)
|
||||
String(group.Get(), item.first, item.second);
|
||||
}
|
||||
for (const auto& table : plan.tables) {
|
||||
auto dataset = Dataset(file.Get(), table.path, {table.rows});
|
||||
Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()),
|
||||
"Solid compound type failed."),
|
||||
H5Tclose};
|
||||
Check(H5Tget_class(type.Get()) == H5T_COMPOUND &&
|
||||
H5Tget_nmembers(type.Get()) ==
|
||||
static_cast<int>(table.columns.size()),
|
||||
"Solid compound member inventory mismatch.");
|
||||
for (std::size_t i = 0; i < table.columns.size(); ++i)
|
||||
Column(dataset.Get(), type.Get(), i, table.columns[i], table.rows);
|
||||
for (const auto& item : table.strings)
|
||||
String(dataset.Get(), item.first, item.second);
|
||||
}
|
||||
{
|
||||
auto dataset = Dataset(file.Get(), "/model/nodal_constraint_mask",
|
||||
{plan.mask.size() / 3, 3});
|
||||
Hdf5Handle type{
|
||||
RequireHdf5Id(H5Dget_type(dataset.Get()), "Solid mask type failed."),
|
||||
H5Tclose};
|
||||
Check(H5Tequal(type.Get(), H5T_STD_U8LE) > 0,
|
||||
"Solid mask dtype mismatch.");
|
||||
std::vector<std::uint8_t> mask(plan.mask.size());
|
||||
RequireHdf5(H5Dread(dataset.Get(), H5T_NATIVE_UINT8, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, mask.data()),
|
||||
"Solid mask read failed.");
|
||||
Check(mask == plan.mask, "Solid mask identity mismatch.");
|
||||
String(dataset.Get(), "component_names", "UX,UY,UZ");
|
||||
}
|
||||
for (const auto& data : plan.datasets) {
|
||||
auto dataset = Dataset(file.Get(), data.path, data.dimensions);
|
||||
Hdf5Handle type{RequireHdf5Id(H5Dget_type(dataset.Get()),
|
||||
"Solid result dtype failed."),
|
||||
H5Tclose};
|
||||
Check(H5Tequal(type.Get(), H5T_IEEE_F64LE) > 0,
|
||||
"Solid result dtype mismatch.");
|
||||
std::vector<double> values(data.values.size());
|
||||
if (!values.empty())
|
||||
RequireHdf5(H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, values.data()),
|
||||
"Solid result read failed.");
|
||||
Check(values == data.values, "Solid result exact readback mismatch.");
|
||||
for (const auto value : values)
|
||||
Check(std::isfinite(value), "Solid result is nonfinite.");
|
||||
for (const auto& item : data.strings)
|
||||
String(dataset.Get(), item.first, item.second);
|
||||
for (const auto& item : data.integers)
|
||||
Number(dataset.Get(), item.first, item.second, H5T_STD_U64LE,
|
||||
H5T_NATIVE_UINT64);
|
||||
for (const auto& item : data.doubles)
|
||||
Number(dataset.Get(), item.first, item.second, H5T_IEEE_F64LE,
|
||||
H5T_NATIVE_DOUBLE);
|
||||
}
|
||||
RequireHdf5(file.CloseChecked(), "Solid readback close failed.");
|
||||
return Status::Ok();
|
||||
} catch (const std::exception& error) {
|
||||
return OutputFailure("solid-self-check-failure", error.what());
|
||||
}
|
||||
}
|
||||
} // namespace fesa::hdf5_internal
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef FESA_IO_HDF5_SOLID_HDF5_SELF_CHECK_H_
|
||||
#define FESA_IO_HDF5_SOLID_HDF5_SELF_CHECK_H_
|
||||
#include <hdf5.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <variant>
|
||||
|
||||
#include "fesa/results/solid_results_writer.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
namespace fesa::hdf5_internal {
|
||||
/// @brief Exact prescribed Solid compound columns; width>1 denotes a fixed
|
||||
/// array.
|
||||
struct SolidColumn {
|
||||
std::string name;
|
||||
std::size_t width{1};
|
||||
std::variant<std::vector<std::uint64_t>, std::vector<std::uint8_t>,
|
||||
std::vector<double>, std::vector<std::string>>
|
||||
values;
|
||||
};
|
||||
struct SolidTable {
|
||||
std::string path;
|
||||
std::size_t rows{0};
|
||||
std::vector<SolidColumn> columns;
|
||||
std::map<std::string, std::string> strings;
|
||||
};
|
||||
struct SolidData {
|
||||
std::string path;
|
||||
std::vector<hsize_t> dimensions;
|
||||
std::vector<double> values;
|
||||
std::map<std::string, std::string> strings;
|
||||
std::map<std::string, std::uint64_t> integers;
|
||||
std::map<std::string, double> doubles;
|
||||
};
|
||||
/// @brief Expected RN64 serialization inventory, never a rounded-only physics
|
||||
/// check.
|
||||
struct SolidPlan {
|
||||
std::map<std::string, std::string> metadata;
|
||||
std::vector<std::uint8_t> mask;
|
||||
std::vector<SolidTable> tables;
|
||||
std::vector<SolidData> datasets;
|
||||
};
|
||||
/// @brief Private fault seams around real writes/readback/close/replace.
|
||||
struct SolidFileHooks {
|
||||
herr_t (*candidate_close)(hid_t) = H5Fclose;
|
||||
herr_t (*reader_close)(hid_t) = H5Fclose;
|
||||
std::function<void(hid_t)> before_write;
|
||||
std::function<void(const std::filesystem::path&)> before_self_check;
|
||||
std::function<void(const std::filesystem::path&)> before_finalize;
|
||||
};
|
||||
using SolidStateView =
|
||||
std::variant<std::reference_wrapper<const SolidStaticState>,
|
||||
std::reference_wrapper<const SolidFrequencyState>,
|
||||
std::reference_wrapper<const SolidDynamicAnalysisState>>;
|
||||
Result<SolidPlan> BuildSolidPlan(const Domain&, SolidStateView,
|
||||
const std::vector<Diagnostic>&);
|
||||
Status WriteSolidFile(const std::filesystem::path&, const Domain&,
|
||||
SolidStateView, const std::vector<Diagnostic>&,
|
||||
const SolidFileHooks& = {});
|
||||
Status SelfCheckSolidFile(const std::filesystem::path&, const SolidPlan&,
|
||||
herr_t (*reader_close)(hid_t) = H5Fclose);
|
||||
Hdf5Handle SolidColumnType(const SolidColumn&, bool native);
|
||||
} // namespace fesa::hdf5_internal
|
||||
#endif
|
||||
Reference in New Issue
Block a user