Add MITC4 drilling diagnostics output and comparison contract

This commit is contained in:
KOKO\Mimi
2026-10-20 09:45:58 +09:00
parent 3d5c296651
commit a73d3fc9ca
31 changed files with 1175 additions and 45 deletions
@@ -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
+17 -4
View File
@@ -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
+12 -2
View File
@@ -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",
+4 -1
View File
@@ -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
+21 -4
View File
@@ -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
+42 -8
View File
@@ -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());
+4 -2
View File
@@ -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_
+42
View File
@@ -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