Add MITC4 drilling diagnostics output and comparison contract
This commit is contained in:
@@ -18,6 +18,10 @@
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
#include "io/hdf5/mitc4_file_hooks.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
@@ -849,13 +853,13 @@ void CheckTables(const hid_t file, const DynamicAnalysisState& state,
|
||||
Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
const DynamicAnalysisState& state,
|
||||
const hdf5_internal::WriterModelData& model,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
const std::vector<Diagnostic>& diagnostics, herr_t (*close_reader)(hid_t)) {
|
||||
try {
|
||||
Hdf5Handle file{
|
||||
hdf5_internal::RequireHdf5Id(
|
||||
H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||
"Unable to reopen dynamic candidate."),
|
||||
H5Fclose};
|
||||
close_reader};
|
||||
Hdf5Handle metadata{hdf5_internal::RequireHdf5Id(
|
||||
H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
||||
"The dynamic metadata group is missing."),
|
||||
@@ -902,9 +906,16 @@ Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
"Unable to inspect accepted frame group.");
|
||||
Check(information.nlinks == state.AcceptedFrames().size(),
|
||||
"Unexpected frame group inventory.");
|
||||
const auto drilling = hdf5_internal::RequireDrillingDiagnostics(
|
||||
file.Get(), state.AcceptedFrames().front().mitc4_drilling->mode);
|
||||
hdf5_internal::RequireDrilling(drilling.frames.size() == state.AcceptedFrames().size());
|
||||
std::size_t drilling_index = 0U;
|
||||
for (const auto& frame : state.AcceptedFrames()) {
|
||||
hdf5_internal::CheckDrillingFrame(drilling.frames[drilling_index++], frame.frame_index,
|
||||
frame.global_equilibrium, *frame.mitc4_drilling);
|
||||
WriteFrame(file.Get(), domain, frame, true);
|
||||
}
|
||||
hdf5_internal::RequireHdf5(file.CloseChecked(), "Unable to close dynamic readback.");
|
||||
return Status::Ok();
|
||||
} catch (const std::exception& error) {
|
||||
return Failure("dynamic-output-schema", error.what());
|
||||
@@ -913,10 +924,10 @@ Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DynamicHdf5ResultsWriter::Write(
|
||||
Status hdf5_internal::WriteDynamicFile(
|
||||
const std::filesystem::path& output_path, const Domain& domain,
|
||||
const DynamicAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
const std::vector<Diagnostic>& diagnostics, const Mitc4FileHooks& hooks) {
|
||||
if (output_path.empty() || output_path.filename().empty() ||
|
||||
!state.IsInitialized() || state.AcceptedFrames().empty() ||
|
||||
domain.Dynamic() == nullptr)
|
||||
@@ -924,6 +935,11 @@ Status DynamicHdf5ResultsWriter::Write(
|
||||
"Dynamic output requires a named path, initialized state, "
|
||||
"and accepted frames.");
|
||||
try {
|
||||
for (const auto& frame : state.AcceptedFrames()) {
|
||||
hdf5_internal::ValidateDrillingEvidence(frame.global_equilibrium, frame.mitc4_drilling);
|
||||
hdf5_internal::RequireDrilling(frame.mitc4_drilling->mode ==
|
||||
state.AcceptedFrames().front().mitc4_drilling->mode);
|
||||
}
|
||||
hdf5_internal::Hdf5ErrorSilencer silence;
|
||||
const auto& step = *domain.Dynamic();
|
||||
Check(state.AcceptedFrames().size() ==
|
||||
@@ -965,24 +981,28 @@ Status DynamicHdf5ResultsWriter::Write(
|
||||
H5Fcreate(candidate_path.string().c_str(), H5F_ACC_EXCL,
|
||||
H5P_DEFAULT, H5P_DEFAULT),
|
||||
"Unable to create dynamic candidate."),
|
||||
H5Fclose};
|
||||
hooks.candidate_close};
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/model/shell");
|
||||
hdf5_internal::WriteModel(file.Get(), domain, model);
|
||||
WriteMetadata(file.Get(), domain, state.AcceptedFrames().front());
|
||||
hdf5_internal::WriteDrillingMetadata(file.Get(), state.AcceptedFrames().front().mitc4_drilling->mode);
|
||||
WriteDensity(file.Get(), domain);
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/steps");
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/steps/Step-1");
|
||||
WriteAcceptedFrames(file.Get(), state.AcceptedFrames());
|
||||
for (const auto& frame : state.AcceptedFrames()) {
|
||||
WriteFrame(file.Get(), domain, frame);
|
||||
hdf5_internal::WriteDrillingFrame(file.Get(),
|
||||
"/steps/Step-1/frames/" + std::to_string(frame.frame_index), *frame.mitc4_drilling);
|
||||
}
|
||||
WriteDiagnostics(file.Get(), diagnostics);
|
||||
hdf5_internal::RequireHdf5(H5Fflush(file.Get(), H5F_SCOPE_GLOBAL),
|
||||
"Unable to flush dynamic candidate.");
|
||||
hdf5_internal::RequireHdf5(file.CloseChecked(),
|
||||
"Unable to close dynamic candidate.");
|
||||
if (hooks.before_self_check) hooks.before_self_check(candidate_path);
|
||||
const Status check =
|
||||
SelfCheck(candidate_path, domain, state, model, diagnostics);
|
||||
SelfCheck(candidate_path, domain, state, model, diagnostics, hooks.reader_close);
|
||||
const Status finalization = hdf5_internal::AtomicFinalizeValidatedCandidate(
|
||||
candidate_path, output_path, check);
|
||||
if (finalization.IsOk()) cleanup.Release();
|
||||
@@ -994,4 +1014,10 @@ Status DynamicHdf5ResultsWriter::Write(
|
||||
}
|
||||
}
|
||||
|
||||
Status DynamicHdf5ResultsWriter::Write(
|
||||
const std::filesystem::path& output_path, const Domain& domain,
|
||||
const DynamicAnalysisState& state, const std::vector<Diagnostic>& diagnostics) {
|
||||
return hdf5_internal::WriteDynamicFile(output_path, domain, state, diagnostics);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
#include "math/finite_values.h"
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
#include "io/hdf5/mitc4_file_hooks.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
@@ -875,13 +879,14 @@ void WriteDiagnostics(hid_t file, const std::vector<Diagnostic>& diagnostics) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Status EigenHdf5ResultsWriter::Write(
|
||||
Status hdf5_internal::WriteEigenFile(
|
||||
const std::filesystem::path& output_path, const Domain& domain,
|
||||
const EigenAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
const std::vector<Diagnostic>& diagnostics, const Mitc4FileHooks& hooks) {
|
||||
const Status valid = Validate(domain, state, diagnostics);
|
||||
if (!valid.IsOk()) return valid;
|
||||
try {
|
||||
hdf5_internal::RequireDrilling(state.drilling_mode.has_value());
|
||||
hdf5_internal::Hdf5ErrorSilencer silence;
|
||||
const auto candidate_path = hdf5_internal::MakeCandidatePath(output_path);
|
||||
hdf5_internal::CandidateFileGuard cleanup{candidate_path};
|
||||
@@ -890,7 +895,7 @@ Status EigenHdf5ResultsWriter::Write(
|
||||
H5Fcreate(candidate_path.string().c_str(), H5F_ACC_EXCL,
|
||||
H5P_DEFAULT, H5P_DEFAULT),
|
||||
"Unable to create eigen candidate."),
|
||||
H5Fclose};
|
||||
hooks.candidate_close};
|
||||
hdf5_internal::WriterModelData model{
|
||||
{}, state.constraint_mask, state.prescribed_displacement};
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/model");
|
||||
@@ -899,6 +904,7 @@ Status EigenHdf5ResultsWriter::Write(
|
||||
WriteDensities(file.Get(), domain);
|
||||
WriteDiagnostics(file.Get(), diagnostics);
|
||||
WriteEigenMetadata(file.Get());
|
||||
hdf5_internal::WriteDrillingMetadata(file.Get(), *state.drilling_mode);
|
||||
WriteStep(file.Get(), domain, state);
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), kEigenRoot);
|
||||
WriteModes(file.Get(), state, domain.Frequency()->source_step_name_raw);
|
||||
@@ -940,8 +946,9 @@ Status EigenHdf5ResultsWriter::Write(
|
||||
"Unable to flush eigen candidate.");
|
||||
hdf5_internal::RequireHdf5(file.CloseChecked(),
|
||||
"Unable to close eigen candidate.");
|
||||
if (hooks.before_self_check) hooks.before_self_check(candidate_path);
|
||||
const Status checked = hdf5_internal::SelfCheckEigenFile(
|
||||
candidate_path, domain, state, diagnostics);
|
||||
candidate_path, domain, state, diagnostics, hooks.reader_close);
|
||||
const Status result = hdf5_internal::AtomicFinalizeValidatedCandidate(
|
||||
candidate_path, output_path, checked);
|
||||
if (result.IsOk()) cleanup.Release();
|
||||
@@ -953,4 +960,10 @@ Status EigenHdf5ResultsWriter::Write(
|
||||
}
|
||||
}
|
||||
|
||||
Status EigenHdf5ResultsWriter::Write(
|
||||
const std::filesystem::path& output_path, const Domain& domain,
|
||||
const EigenAnalysisState& state, const std::vector<Diagnostic>& diagnostics) {
|
||||
return hdf5_internal::WriteEigenFile(output_path, domain, state, diagnostics);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
namespace {
|
||||
|
||||
@@ -726,13 +728,21 @@ bool CheckModalRows(hid_t file, const Domain& domain,
|
||||
|
||||
Status SelfCheckEigenFile(const std::filesystem::path& path,
|
||||
const Domain& domain, const EigenAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
const std::vector<Diagnostic>& diagnostics,
|
||||
herr_t (*close_reader)(hid_t)) {
|
||||
try {
|
||||
Hdf5ErrorSilencer silence;
|
||||
Hdf5Handle file{RequireHdf5Id(H5Fopen(path.string().c_str(), H5F_ACC_RDONLY,
|
||||
H5P_DEFAULT),
|
||||
"Unable to reopen eigen candidate."),
|
||||
H5Fclose};
|
||||
close_reader};
|
||||
const auto drilling = ReadDrillingDiagnostics(file.Get());
|
||||
if (state.drilling_mode) {
|
||||
RequireDrilling(drilling.has_value() && drilling->formulation ==
|
||||
Mitc4DrillingModeName(*state.drilling_mode) && drilling->frames.empty());
|
||||
} else {
|
||||
RequireDrilling(!drilling.has_value());
|
||||
}
|
||||
for (const char* required :
|
||||
{"/metadata", "/model/nodes", "/model/elements",
|
||||
"/model/shell/densities", "/diagnostics", "/steps/Step-1/eigen/modes",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef FESA_IO_HDF5_EIGEN_HDF5_SELF_CHECK_H_
|
||||
#define FESA_IO_HDF5_EIGEN_HDF5_SELF_CHECK_H_
|
||||
|
||||
#include <hdf5.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
@@ -17,7 +19,8 @@ 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);
|
||||
const std::vector<Diagnostic>& diagnostics,
|
||||
herr_t (*close_reader)(hid_t) = H5Fclose);
|
||||
|
||||
} // namespace fesa::hdf5_internal
|
||||
|
||||
|
||||
@@ -12,12 +12,16 @@
|
||||
#include "io/hdf5/hdf5_result_writer.h"
|
||||
#include "io/hdf5/hdf5_self_check.h"
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
#include "io/hdf5/mitc4_file_hooks.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
Status Hdf5ResultsWriter::Write(const std::filesystem::path& output_path,
|
||||
Status hdf5_internal::WriteStaticFile(const std::filesystem::path& output_path,
|
||||
const Domain& domain,
|
||||
const AnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
const std::vector<Diagnostic>& diagnostics, const Mitc4FileHooks& hooks) {
|
||||
auto plan_result =
|
||||
hdf5_internal::BuildWriterPlan(output_path, domain, state, diagnostics);
|
||||
if (!plan_result.HasValue()) {
|
||||
@@ -26,6 +30,8 @@ Status Hdf5ResultsWriter::Write(const std::filesystem::path& output_path,
|
||||
hdf5_internal::WriterPlan plan = std::move(plan_result.Value());
|
||||
|
||||
try {
|
||||
if (hdf5_internal::IsShellDomain(domain))
|
||||
hdf5_internal::ValidateDrillingEvidence(state.Equilibrium(), state.Mitc4Drilling());
|
||||
hdf5_internal::Hdf5ErrorSilencer silence_backend_errors;
|
||||
const std::filesystem::path candidate_path =
|
||||
hdf5_internal::MakeCandidatePath(output_path);
|
||||
@@ -35,7 +41,7 @@ Status Hdf5ResultsWriter::Write(const std::filesystem::path& output_path,
|
||||
H5Fcreate(candidate_path.string().c_str(), H5F_ACC_EXCL,
|
||||
H5P_DEFAULT, H5P_DEFAULT),
|
||||
"Unable to create the temporary HDF5 file."),
|
||||
H5Fclose};
|
||||
hooks.candidate_close};
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/model");
|
||||
(void)hdf5_internal::CreateGroup(file.Get(),
|
||||
"/steps/Step-1/frames/0/nodal");
|
||||
@@ -50,14 +56,19 @@ Status Hdf5ResultsWriter::Write(const std::filesystem::path& output_path,
|
||||
}
|
||||
hdf5_internal::WriteModel(file.Get(), domain, plan.model_data);
|
||||
hdf5_internal::WriteResults(file.Get(), domain, state, diagnostics, plan);
|
||||
if (hdf5_internal::IsShellDomain(domain)) {
|
||||
hdf5_internal::WriteDrillingMetadata(file.Get(), state.Mitc4Drilling()->mode);
|
||||
hdf5_internal::WriteDrillingFrame(file.Get(), hdf5_internal::kStepRoot, *state.Mitc4Drilling());
|
||||
}
|
||||
hdf5_internal::RequireHdf5(H5Fflush(file.Get(), H5F_SCOPE_GLOBAL),
|
||||
"Unable to flush the temporary HDF5 file.");
|
||||
hdf5_internal::RequireHdf5(
|
||||
file.CloseChecked(),
|
||||
"Unable to close the temporary HDF5 file after writing.");
|
||||
|
||||
if (hooks.before_self_check) hooks.before_self_check(candidate_path);
|
||||
const Status self_check = hdf5_internal::SelfCheck(
|
||||
candidate_path, domain, state, diagnostics.size(), plan.model_data);
|
||||
candidate_path, domain, state, diagnostics.size(), plan.model_data, hooks.reader_close);
|
||||
const Status atomic = hdf5_internal::AtomicFinalizeValidatedCandidate(
|
||||
candidate_path, output_path, self_check);
|
||||
if (atomic.IsOk()) {
|
||||
@@ -71,4 +82,10 @@ Status Hdf5ResultsWriter::Write(const std::filesystem::path& output_path,
|
||||
}
|
||||
}
|
||||
|
||||
Status Hdf5ResultsWriter::Write(
|
||||
const std::filesystem::path& output_path, const Domain& domain,
|
||||
const AnalysisState& state, const std::vector<Diagnostic>& diagnostics) {
|
||||
return hdf5_internal::WriteStaticFile(output_path, domain, state, diagnostics);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
|
||||
Hdf5Handle OpenDatasetForCheck(const hid_t file, const char* path) {
|
||||
@@ -516,18 +518,30 @@ void RequireSchemaV1ShellIdentity(const hid_t file, const char* path,
|
||||
void SelfCheckFile(const std::filesystem::path& path, const Domain& domain,
|
||||
const AnalysisState& state,
|
||||
const std::size_t diagnostic_count,
|
||||
const WriterModelData& model_data) {
|
||||
const WriterModelData& model_data,
|
||||
herr_t (*close_reader)(hid_t)) {
|
||||
if (H5Fis_hdf5(path.string().c_str()) <= 0) {
|
||||
throw Hdf5Failure{"The temporary output is not an HDF5 file."};
|
||||
}
|
||||
Hdf5Handle file{
|
||||
RequireHdf5Id(H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||
"Unable to reopen the temporary HDF5 file read-only."),
|
||||
H5Fclose};
|
||||
close_reader};
|
||||
Hdf5Handle metadata{
|
||||
RequireHdf5Id(H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
||||
"The HDF5 metadata group is missing."),
|
||||
H5Gclose};
|
||||
if (IsShellDomain(domain)) {
|
||||
const auto drilling = ReadDrillingDiagnostics(file.Get());
|
||||
if (state.Mitc4Drilling()) {
|
||||
ValidateDrillingEvidence(state.Equilibrium(), state.Mitc4Drilling());
|
||||
RequireDrilling(drilling.has_value() && drilling->formulation ==
|
||||
Mitc4DrillingModeName(state.Mitc4Drilling()->mode) && drilling->frames.size() == 1U);
|
||||
CheckDrillingFrame(drilling->frames.front(), 0U, state.Equilibrium(), *state.Mitc4Drilling());
|
||||
} else {
|
||||
RequireDrilling(!drilling.has_value());
|
||||
}
|
||||
}
|
||||
RequireUint64Attribute(metadata.Get(), "schema_version", 0U);
|
||||
RequireStringAttribute(metadata.Get(), "solver_version",
|
||||
std::string{SolverVersion()});
|
||||
@@ -751,18 +765,36 @@ void SelfCheckFile(const std::filesystem::path& path, const Domain& domain,
|
||||
void NonlinearSelfCheckFile(const std::filesystem::path& path,
|
||||
const Domain& domain,
|
||||
const NonlinearAnalysisState& state,
|
||||
const std::size_t diagnostic_count) {
|
||||
const std::size_t diagnostic_count,
|
||||
herr_t (*close_reader)(hid_t)) {
|
||||
if (H5Fis_hdf5(path.string().c_str()) <= 0) {
|
||||
throw Hdf5Failure{"The nonlinear candidate is not an HDF5 file."};
|
||||
}
|
||||
Hdf5Handle file{
|
||||
RequireHdf5Id(H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||
"Unable to reopen nonlinear candidate read-only."),
|
||||
H5Fclose};
|
||||
close_reader};
|
||||
Hdf5Handle metadata{
|
||||
RequireHdf5Id(H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
||||
"The nonlinear metadata group is missing."),
|
||||
H5Gclose};
|
||||
RequireDrilling(!state.AcceptedFrames().empty());
|
||||
const auto drilling = ReadDrillingDiagnostics(file.Get());
|
||||
const auto& expected_drilling = state.AcceptedFrames().front().mitc4_drilling;
|
||||
if (expected_drilling) {
|
||||
RequireDrilling(drilling.has_value() && drilling->formulation ==
|
||||
Mitc4DrillingModeName(expected_drilling->mode) &&
|
||||
drilling->frames.size() == state.AcceptedFrames().size());
|
||||
for (std::size_t i = 0; i < drilling->frames.size(); ++i) {
|
||||
const auto& frame = state.AcceptedFrames()[i];
|
||||
ValidateDrillingEvidence(frame.global_equilibrium, frame.mitc4_drilling);
|
||||
RequireDrilling(frame.mitc4_drilling->mode == expected_drilling->mode);
|
||||
CheckDrillingFrame(drilling->frames[i], frame.frame_index, frame.global_equilibrium, *frame.mitc4_drilling);
|
||||
}
|
||||
} else {
|
||||
RequireDrilling(!drilling.has_value());
|
||||
for (const auto& frame : state.AcceptedFrames()) RequireDrilling(!frame.mitc4_drilling);
|
||||
}
|
||||
RequireUint64Attribute(metadata.Get(), "schema_version", 1U);
|
||||
RequireStringAttribute(metadata.Get(), "feature_id",
|
||||
"nonlinear-static-newton-raphson");
|
||||
@@ -919,9 +951,10 @@ void NonlinearSelfCheckFile(const std::filesystem::path& path,
|
||||
|
||||
Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
const AnalysisState& state, const std::size_t diagnostic_count,
|
||||
const WriterModelData& model_data) {
|
||||
const WriterModelData& model_data,
|
||||
herr_t (*close_reader)(hid_t)) {
|
||||
try {
|
||||
SelfCheckFile(path, domain, state, diagnostic_count, model_data);
|
||||
SelfCheckFile(path, domain, state, diagnostic_count, model_data, close_reader);
|
||||
return Status::Ok();
|
||||
} catch (const Hdf5Failure& failure) {
|
||||
return OutputFailure("hdf5-write-failure", failure.what());
|
||||
@@ -933,9 +966,10 @@ Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
Status NonlinearSelfCheck(const std::filesystem::path& path,
|
||||
const Domain& domain,
|
||||
const NonlinearAnalysisState& state,
|
||||
const std::size_t diagnostic_count) {
|
||||
const std::size_t diagnostic_count,
|
||||
herr_t (*close_reader)(hid_t)) {
|
||||
try {
|
||||
NonlinearSelfCheckFile(path, domain, state, diagnostic_count);
|
||||
NonlinearSelfCheckFile(path, domain, state, diagnostic_count, close_reader);
|
||||
return Status::Ok();
|
||||
} catch (const Hdf5Failure& failure) {
|
||||
return OutputFailure("hdf5-write-failure", failure.what());
|
||||
|
||||
@@ -15,13 +15,15 @@ namespace fesa::hdf5_internal {
|
||||
/// @brief Reopens and validates the complete closed schema-v0 candidate.
|
||||
Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
const AnalysisState& state, std::size_t diagnostic_count,
|
||||
const WriterModelData& model_data);
|
||||
const WriterModelData& model_data,
|
||||
herr_t (*close_reader)(hid_t) = H5Fclose);
|
||||
|
||||
/// @brief Reopens and validates the complete closed schema-v1 candidate.
|
||||
Status NonlinearSelfCheck(const std::filesystem::path& path,
|
||||
const Domain& domain,
|
||||
const NonlinearAnalysisState& state,
|
||||
std::size_t diagnostic_count);
|
||||
std::size_t diagnostic_count,
|
||||
herr_t (*close_reader)(hid_t) = H5Fclose);
|
||||
|
||||
} // namespace fesa::hdf5_internal
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
#ifndef FESA_IO_HDF5_MITC4_DRILLING_DIAGNOSTICS_H_
|
||||
#define FESA_IO_HDF5_MITC4_DRILLING_DIAGNOSTICS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/results/result_records.h"
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
|
||||
struct StoredDrillingFrame {
|
||||
std::string storage_step;
|
||||
std::uint64_t frame_index;
|
||||
std::array<double, 6> raw_equilibrium;
|
||||
std::array<double, 3> drilling_moment;
|
||||
std::array<double, 6> equilibrium_with_drilling;
|
||||
};
|
||||
|
||||
struct StoredDrillingDiagnostics {
|
||||
std::string formulation;
|
||||
std::uint64_t version;
|
||||
std::vector<StoredDrillingFrame> frames;
|
||||
};
|
||||
|
||||
inline void RequireDrilling(bool valid) {
|
||||
if (!valid) throw Hdf5Failure{"Invalid MITC4 drilling diagnostics contract."};
|
||||
}
|
||||
|
||||
inline void ValidateDrillingEvidence(
|
||||
const std::array<double, 6>& raw,
|
||||
const std::optional<Mitc4DrillingEvidence>& evidence) {
|
||||
RequireDrilling(evidence.has_value());
|
||||
RequireDrilling(Mitc4DrillingModeName(evidence->mode)[0] != '\0' &&
|
||||
IsFinite(raw) && IsFinite(evidence->drilling_moment) &&
|
||||
IsFinite(evidence->equilibrium_with_drilling));
|
||||
for (std::size_t i = 0; i < 6U; ++i) {
|
||||
RequireDrilling(evidence->equilibrium_with_drilling[i] ==
|
||||
(i < 3U ? raw[i] : raw[i] - evidence->drilling_moment[i - 3U]));
|
||||
}
|
||||
}
|
||||
|
||||
inline void WriteDrillingMetadata(hid_t file, Mitc4DrillingMode mode) {
|
||||
RequireDrilling(Mitc4DrillingModeName(mode)[0] != '\0');
|
||||
Hdf5Handle metadata{RequireHdf5Id(H5Gopen2(file, "/metadata", H5P_DEFAULT),
|
||||
"Missing drilling metadata group."), H5Gclose};
|
||||
WriteStringAttribute(metadata.Get(), "mitc4_drilling_formulation",
|
||||
Mitc4DrillingModeName(mode));
|
||||
WriteUint64Attribute(metadata.Get(), "drilling_diagnostics_version", 1U);
|
||||
}
|
||||
|
||||
inline void WriteDrillingFrame(hid_t file, const std::string& root,
|
||||
const Mitc4DrillingEvidence& evidence) {
|
||||
WriteModelDoubleDataset(file, root + "/global/drilling_moment", {3U},
|
||||
evidence.drilling_moment.data(), 3U, "MDRILL_X,MDRILL_Y,MDRILL_Z",
|
||||
"force*length,force*length,force*length", "global-cartesian", "global-origin");
|
||||
WriteModelDoubleDataset(file, root + "/global/equilibrium_with_drilling", {6U},
|
||||
evidence.equilibrium_with_drilling.data(), 6U,
|
||||
"FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3",
|
||||
"force,force,force,force*length,force*length,force*length",
|
||||
"global-cartesian", "global-origin");
|
||||
}
|
||||
|
||||
inline std::string ReadDrillingString(hid_t object, const char* name) {
|
||||
Hdf5Handle attribute{RequireHdf5Id(H5Aopen(object, name, H5P_DEFAULT),
|
||||
"Missing drilling string."), H5Aclose};
|
||||
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
||||
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
|
||||
RequireDrilling(H5Sget_simple_extent_type(space.Get()) == H5S_SCALAR &&
|
||||
H5Tget_class(type.Get()) == H5T_STRING &&
|
||||
H5Tis_variable_str(type.Get()) > 0 &&
|
||||
H5Tget_cset(type.Get()) == H5T_CSET_UTF8);
|
||||
char* value = nullptr;
|
||||
RequireHdf5(H5Aread(attribute.Get(), type.Get(), &value), "Cannot read drilling string.");
|
||||
const std::string result = value == nullptr ? "" : value;
|
||||
if (value != nullptr) H5free_memory(value);
|
||||
RequireDrilling(IsValidUtf8(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
inline std::array<double, N> ReadDrillingValues(hid_t file, const std::string& path) {
|
||||
Hdf5Handle dataset{RequireHdf5Id(H5Dopen2(file, path.c_str(), H5P_DEFAULT),
|
||||
"Missing drilling dataset."), H5Dclose};
|
||||
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
|
||||
Hdf5Handle space{H5Dget_space(dataset.Get()), H5Sclose};
|
||||
hsize_t dimension = 0U;
|
||||
RequireDrilling(H5Tequal(type.Get(), H5T_IEEE_F64LE) > 0 &&
|
||||
H5Sget_simple_extent_ndims(space.Get()) == 1 &&
|
||||
H5Sget_simple_extent_dims(space.Get(), &dimension, nullptr) >= 0 &&
|
||||
dimension == N);
|
||||
std::array<double, N> values{};
|
||||
RequireHdf5(H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, values.data()), "Cannot read drilling dataset.");
|
||||
RequireDrilling(IsFinite(values));
|
||||
return values;
|
||||
}
|
||||
|
||||
// Markerless historical files retain their old contract; no mode is inferred.
|
||||
inline std::optional<StoredDrillingDiagnostics> ReadDrillingDiagnostics(hid_t file) {
|
||||
Hdf5Handle metadata{RequireHdf5Id(H5Gopen2(file, "/metadata", H5P_DEFAULT),
|
||||
"Missing drilling metadata group."), H5Gclose};
|
||||
const htri_t mode = H5Aexists(metadata.Get(), "mitc4_drilling_formulation");
|
||||
const htri_t version = H5Aexists(metadata.Get(), "drilling_diagnostics_version");
|
||||
RequireDrilling(mode >= 0 && version >= 0 && (mode > 0) == (version > 0));
|
||||
if (mode == 0) return std::nullopt;
|
||||
StoredDrillingDiagnostics result{};
|
||||
result.formulation = ReadDrillingString(metadata.Get(), "mitc4_drilling_formulation");
|
||||
RequireDrilling(result.formulation == Mitc4DrillingModeName(Mitc4DrillingMode::kDirectSpring) ||
|
||||
result.formulation == Mitc4DrillingModeName(Mitc4DrillingMode::kLegacyCorotatedTwist));
|
||||
Hdf5Handle attribute{H5Aopen(metadata.Get(), "drilling_diagnostics_version", H5P_DEFAULT), H5Aclose};
|
||||
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
|
||||
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
|
||||
RequireDrilling(H5Tequal(type.Get(), H5T_STD_U64LE) > 0 &&
|
||||
H5Sget_simple_extent_type(space.Get()) == H5S_SCALAR);
|
||||
RequireHdf5(H5Aread(attribute.Get(), H5T_NATIVE_UINT64, &result.version), "Cannot read drilling version.");
|
||||
RequireDrilling(result.version == 1U);
|
||||
if (H5Lexists(file, "/steps/Step-1/frames", H5P_DEFAULT) > 0) {
|
||||
Hdf5Handle frames{H5Gopen2(file, "/steps/Step-1/frames", H5P_DEFAULT), H5Gclose};
|
||||
H5G_info_t info{};
|
||||
RequireHdf5(H5Gget_info(frames.Get(), &info), "Cannot inspect drilling frames.");
|
||||
for (hsize_t i = 0; i < info.nlinks; ++i) {
|
||||
const auto size = H5Lget_name_by_idx(frames.Get(), ".", H5_INDEX_NAME, H5_ITER_INC,
|
||||
i, nullptr, 0U, H5P_DEFAULT);
|
||||
RequireDrilling(size > 0);
|
||||
std::vector<char> name(static_cast<std::size_t>(size) + 1U);
|
||||
RequireDrilling(H5Lget_name_by_idx(frames.Get(), ".", H5_INDEX_NAME, H5_ITER_INC,
|
||||
i, name.data(), name.size(), H5P_DEFAULT) == size);
|
||||
const std::string token{name.data()};
|
||||
const std::string root = "/steps/Step-1/frames/" + token + "/global/";
|
||||
// Each new MITC4 static/dynamic frame has an existing raw equilibrium.
|
||||
StoredDrillingFrame frame{};
|
||||
frame.storage_step = "Step-1";
|
||||
RequireDrilling(!token.empty() && token.find_first_not_of("0123456789") == std::string::npos);
|
||||
frame.frame_index = std::stoull(token);
|
||||
RequireDrilling(std::to_string(frame.frame_index) == token);
|
||||
frame.raw_equilibrium = ReadDrillingValues<6>(file, root + "equilibrium");
|
||||
frame.drilling_moment = ReadDrillingValues<3>(file, root + "drilling_moment");
|
||||
frame.equilibrium_with_drilling = ReadDrillingValues<6>(file, root + "equilibrium_with_drilling");
|
||||
for (std::size_t j = 0; j < 6U; ++j)
|
||||
RequireDrilling(frame.equilibrium_with_drilling[j] == (j < 3U ? frame.raw_equilibrium[j] :
|
||||
frame.raw_equilibrium[j] - frame.drilling_moment[j - 3U]));
|
||||
result.frames.push_back(frame);
|
||||
}
|
||||
std::sort(result.frames.begin(), result.frames.end(),
|
||||
[](const auto& left, const auto& right) { return left.frame_index < right.frame_index; });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline StoredDrillingDiagnostics RequireDrillingDiagnostics(hid_t file, Mitc4DrillingMode mode) {
|
||||
const auto result = ReadDrillingDiagnostics(file);
|
||||
RequireDrilling(result.has_value() && result->formulation == Mitc4DrillingModeName(mode));
|
||||
return *result;
|
||||
}
|
||||
|
||||
inline void CheckDrillingFrame(const StoredDrillingFrame& stored,
|
||||
std::uint64_t index,
|
||||
const std::array<double, 6>& raw,
|
||||
const Mitc4DrillingEvidence& evidence) {
|
||||
RequireDrilling(stored.frame_index == index && stored.raw_equilibrium == raw &&
|
||||
stored.drilling_moment == evidence.drilling_moment &&
|
||||
stored.equilibrium_with_drilling == evidence.equilibrium_with_drilling);
|
||||
}
|
||||
|
||||
} // namespace fesa::hdf5_internal
|
||||
|
||||
#endif // FESA_IO_HDF5_MITC4_DRILLING_DIAGNOSTICS_H_
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef FESA_IO_HDF5_MITC4_FILE_HOOKS_H_
|
||||
#define FESA_IO_HDF5_MITC4_FILE_HOOKS_H_
|
||||
|
||||
#include <hdf5.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa {
|
||||
class Domain;
|
||||
class AnalysisState;
|
||||
class NonlinearAnalysisState;
|
||||
class DynamicAnalysisState;
|
||||
struct EigenAnalysisState;
|
||||
struct Diagnostic;
|
||||
|
||||
namespace hdf5_internal {
|
||||
|
||||
// Private lifecycle fault seams. Production uses real closers and no callback.
|
||||
// Injected closers must release the handle even when reporting failure.
|
||||
struct Mitc4FileHooks {
|
||||
herr_t (*candidate_close)(hid_t) = H5Fclose;
|
||||
herr_t (*reader_close)(hid_t) = H5Fclose;
|
||||
std::function<void(const std::filesystem::path&)> before_self_check;
|
||||
};
|
||||
|
||||
Status WriteStaticFile(const std::filesystem::path&, const Domain&,
|
||||
const AnalysisState&, const std::vector<Diagnostic>&, const Mitc4FileHooks& = {});
|
||||
Status WriteNonlinearFile(const std::filesystem::path&, const Domain&,
|
||||
const NonlinearAnalysisState&, const std::vector<Diagnostic>&, const Mitc4FileHooks& = {});
|
||||
Status WriteDynamicFile(const std::filesystem::path&, const Domain&,
|
||||
const DynamicAnalysisState&, const std::vector<Diagnostic>&, const Mitc4FileHooks& = {});
|
||||
Status WriteEigenFile(const std::filesystem::path&, const Domain&,
|
||||
const EigenAnalysisState&, const std::vector<Diagnostic>&, const Mitc4FileHooks& = {});
|
||||
|
||||
} // namespace hdf5_internal
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_IO_HDF5_MITC4_FILE_HOOKS_H_
|
||||
@@ -22,6 +22,10 @@
|
||||
#include "math/finite_values.h"
|
||||
#include "results/shell_result_recovery.h"
|
||||
|
||||
#include "io/hdf5/mitc4_drilling_diagnostics.h"
|
||||
|
||||
#include "io/hdf5/mitc4_file_hooks.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
@@ -955,10 +959,10 @@ Status ValidateDiagnostics(const std::vector<Diagnostic>& diagnostics) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Status NonlinearHdf5ResultsWriter::Write(
|
||||
Status hdf5_internal::WriteNonlinearFile(
|
||||
const std::filesystem::path& output_path, const Domain& domain,
|
||||
const NonlinearAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
const std::vector<Diagnostic>& diagnostics, const Mitc4FileHooks& hooks) {
|
||||
if (output_path.empty() || output_path.filename().empty()) {
|
||||
return Failure("invalid-output-path", "The HDF5 output path must name a file.");
|
||||
}
|
||||
@@ -981,6 +985,11 @@ Status NonlinearHdf5ResultsWriter::Write(
|
||||
}
|
||||
|
||||
try {
|
||||
for (const auto& frame : state.AcceptedFrames()) {
|
||||
hdf5_internal::ValidateDrillingEvidence(frame.global_equilibrium, frame.mitc4_drilling);
|
||||
hdf5_internal::RequireDrilling(frame.mitc4_drilling->mode ==
|
||||
state.AcceptedFrames().front().mitc4_drilling->mode);
|
||||
}
|
||||
hdf5_internal::Hdf5ErrorSilencer silence_backend_errors;
|
||||
const std::filesystem::path candidate_path =
|
||||
hdf5_internal::MakeCandidatePath(output_path);
|
||||
@@ -989,25 +998,29 @@ Status NonlinearHdf5ResultsWriter::Write(
|
||||
H5Fcreate(candidate_path.string().c_str(), H5F_ACC_EXCL,
|
||||
H5P_DEFAULT, H5P_DEFAULT),
|
||||
"Unable to create nonlinear temporary HDF5 file."),
|
||||
H5Fclose};
|
||||
hooks.candidate_close};
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/model");
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/model/shell");
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/steps/Step-1");
|
||||
WriteMetadata(file.Get(), domain);
|
||||
hdf5_internal::WriteDrillingMetadata(file.Get(), state.AcceptedFrames().front().mitc4_drilling->mode);
|
||||
WriteNodes(file.Get(), domain);
|
||||
WriteElements(file.Get(), domain);
|
||||
WriteModelData(file.Get(), domain, model_data);
|
||||
WriteAcceptedFrameTable(file.Get(), state.AcceptedFrames());
|
||||
for (const auto& frame : state.AcceptedFrames()) {
|
||||
WriteFrame(file.Get(), domain, frame);
|
||||
hdf5_internal::WriteDrillingFrame(file.Get(),
|
||||
"/steps/Step-1/frames/" + std::to_string(frame.frame_index), *frame.mitc4_drilling);
|
||||
}
|
||||
WriteDiagnostics(file.Get(), diagnostics);
|
||||
hdf5_internal::RequireHdf5(H5Fflush(file.Get(), H5F_SCOPE_GLOBAL),
|
||||
"Unable to flush nonlinear temporary HDF5 file.");
|
||||
hdf5_internal::RequireHdf5(
|
||||
file.CloseChecked(), "Unable to close nonlinear temporary HDF5 file.");
|
||||
if (hooks.before_self_check) hooks.before_self_check(candidate_path);
|
||||
const Status self_check = hdf5_internal::NonlinearSelfCheck(
|
||||
candidate_path, domain, state, diagnostics.size());
|
||||
candidate_path, domain, state, diagnostics.size(), hooks.reader_close);
|
||||
const Status atomic = hdf5_internal::AtomicFinalizeValidatedCandidate(
|
||||
candidate_path, output_path, self_check);
|
||||
if (atomic.IsOk()) {
|
||||
@@ -1021,4 +1034,10 @@ Status NonlinearHdf5ResultsWriter::Write(
|
||||
}
|
||||
}
|
||||
|
||||
Status NonlinearHdf5ResultsWriter::Write(
|
||||
const std::filesystem::path& output_path, const Domain& domain,
|
||||
const NonlinearAnalysisState& state, const std::vector<Diagnostic>& diagnostics) {
|
||||
return hdf5_internal::WriteNonlinearFile(output_path, domain, state, diagnostics);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
Reference in New Issue
Block a user