446 lines
20 KiB
C++
446 lines
20 KiB
C++
#include "fesa/io/hdf5/thermal_hdf5_results_writer.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fesa/build_info.h"
|
|
#include "io/hdf5/hdf5_atomic_file.h"
|
|
#include "io/hdf5/hdf5_model_writer.h"
|
|
#include "io/hdf5/hdf5_primitives.h"
|
|
#include "io/hdf5/hdf5_raii.h"
|
|
#include "io/hdf5/hdf5_result_writer.h"
|
|
#include "io/hdf5/thermal_hdf5_self_check.h"
|
|
|
|
namespace fesa {
|
|
namespace {
|
|
using hdf5_internal::Hdf5Handle;
|
|
|
|
/// @brief Carries node/source-DOF reaction identities in stable row order.
|
|
struct ReactionRow {
|
|
std::uint64_t internal_node_id;
|
|
std::uint64_t source_dof;
|
|
};
|
|
/// @brief Carries accepted schedule values independently of stored-time
|
|
/// differences.
|
|
struct FrameRow {
|
|
std::uint64_t frame_index;
|
|
std::uint64_t increment_index;
|
|
double time;
|
|
double time_increment;
|
|
std::uint8_t is_initial;
|
|
};
|
|
/// @brief Carries thermal properties with global Domain material identities.
|
|
struct MaterialRow {
|
|
std::uint64_t internal_material_id;
|
|
const char* name;
|
|
double conductivity;
|
|
double density;
|
|
double specific_heat;
|
|
};
|
|
/// @brief Carries section provenance and global material linkage.
|
|
struct SectionRow {
|
|
std::uint64_t internal_section_id;
|
|
const char* source_file;
|
|
std::uint64_t source_line;
|
|
const char* source_elset;
|
|
std::uint64_t material_internal_id;
|
|
double thickness;
|
|
};
|
|
/// @brief Carries DS4 source identities and fixed four-node connectivity.
|
|
struct ElementRow {
|
|
std::uint64_t internal_element_id;
|
|
const char* instance_name;
|
|
const char* source_label;
|
|
const char* source_element_type;
|
|
const char* internal_formulation;
|
|
std::uint64_t node_internal_ids[4];
|
|
std::uint64_t shell_section_internal_id;
|
|
std::uint64_t material_internal_id;
|
|
};
|
|
|
|
/// @brief Writes fixed named fields, preserving zero-length reaction tables.
|
|
template <class Row>
|
|
Hdf5Handle Compound(hid_t file, const char* path, const std::vector<Row>& rows,
|
|
hid_t file_type, hid_t memory_type) {
|
|
if (rows.empty()) {
|
|
const hsize_t zero[] = {0U};
|
|
const hsize_t unlimited[] = {H5S_UNLIMITED};
|
|
Hdf5Handle space{
|
|
hdf5_internal::RequireHdf5Id(H5Screate_simple(1, zero, unlimited),
|
|
"Unable to create empty compound space."),
|
|
H5Sclose};
|
|
Hdf5Handle properties{hdf5_internal::RequireHdf5Id(
|
|
H5Pcreate(H5P_DATASET_CREATE),
|
|
"Unable to create empty compound properties."),
|
|
H5Pclose};
|
|
const hsize_t chunk[] = {1U};
|
|
hdf5_internal::RequireHdf5(H5Pset_chunk(properties.Get(), 1, chunk),
|
|
"Unable to configure empty compound chunk.");
|
|
return Hdf5Handle{
|
|
hdf5_internal::RequireHdf5Id(
|
|
H5Dcreate2(file, path, file_type, space.Get(), H5P_DEFAULT,
|
|
properties.Get(), H5P_DEFAULT),
|
|
"Unable to create empty compound HDF5 dataset."),
|
|
H5Dclose};
|
|
}
|
|
return hdf5_internal::WriteCompoundDataset(file, path, rows.size(), file_type,
|
|
memory_type, rows.data());
|
|
}
|
|
/// @brief Defines an explicit named field without making padding a read
|
|
/// contract.
|
|
void Member(hid_t t, const char* n, size_t off, hid_t f) {
|
|
hdf5_internal::RequireHdf5(H5Tinsert(t, n, off, f),
|
|
"Unable to define compound field.");
|
|
}
|
|
/// @brief Builds fixed-width connectivity array datatypes.
|
|
Hdf5Handle Array(hid_t type, int rank, const hsize_t* dims) {
|
|
return Hdf5Handle{
|
|
hdf5_internal::RequireHdf5Id(H5Tarray_create2(type, rank, dims),
|
|
"Unable to create array type."),
|
|
H5Tclose};
|
|
}
|
|
/// @brief Separates fixed little-endian file fields from native memory fields.
|
|
template <class Row, class Init>
|
|
void Types(Hdf5Handle& ft, Hdf5Handle& mt, Init init) {
|
|
ft = Hdf5Handle{
|
|
hdf5_internal::RequireHdf5Id(H5Tcreate(H5T_COMPOUND, sizeof(Row)),
|
|
"Unable to create file compound."),
|
|
H5Tclose};
|
|
mt = Hdf5Handle{
|
|
hdf5_internal::RequireHdf5Id(H5Tcreate(H5T_COMPOUND, sizeof(Row)),
|
|
"Unable to create memory compound."),
|
|
H5Tclose};
|
|
init(ft.Get(), mt.Get());
|
|
}
|
|
/// @brief Writes all twelve thermal discriminator, source and schedule
|
|
/// attributes.
|
|
void Metadata(hid_t file, const Domain& domain,
|
|
const ThermalAnalysisState& state) {
|
|
auto g = hdf5_internal::CreateGroup(file, "/metadata");
|
|
auto a = [&](const char* n, const std::string& v) {
|
|
hdf5_internal::WriteStringAttribute(g.Get(), n, v);
|
|
};
|
|
hdf5_internal::WriteUint64Attribute(g.Get(), "schema_version", 1);
|
|
a("feature_id", "shell-transient-heat-transfer");
|
|
a("analysis_procedure", "uncoupled-transient-heat-transfer");
|
|
a("source_step_name",
|
|
domain.ThermalStep()->name.empty() ? "Step-1" : domain.ThermalStep()->name);
|
|
a("solver_version", std::string{SolverVersion()});
|
|
a("source_input_identity", hdf5_internal::SourceInputIdentity(domain));
|
|
a("unit_system_label", "user-consistent-unspecified");
|
|
a("internal_formulation", "FESA-Q4-P2x2-thermal-shell");
|
|
a("integration_rule", "surface-3x3-gauss; thickness-exact-P2x2");
|
|
a("time_integration", "backward-euler");
|
|
hdf5_internal::WriteDoubleAttribute(g.Get(), "nominal_time_increment",
|
|
state.NominalTimeIncrement());
|
|
hdf5_internal::WriteDoubleAttribute(g.Get(), "time_period",
|
|
state.TimePeriod());
|
|
}
|
|
/// @brief Writes all geometry with separate active thermal row maps and
|
|
/// recovery axes.
|
|
void WriteThermalModel(hid_t file, const Domain& d,
|
|
const ThermalAnalysisState& s) {
|
|
auto model = hdf5_internal::CreateGroup(file, "/model");
|
|
(void)model;
|
|
// Nodes retain all Domain rows; active result mappings remain separate.
|
|
Hdf5Handle ft, mt;
|
|
auto st = hdf5_internal::MakeUtf8StringType();
|
|
hdf5_internal::WriteNodes(file, d);
|
|
std::vector<ElementRow> elements;
|
|
for (size_t i = 0; i < d.ThermalElements().Size(); ++i) {
|
|
const auto& e = d.ThermalElements()[i];
|
|
elements.push_back({i,
|
|
e.SourceId().instance_name.c_str(),
|
|
e.SourceId().source_label_text.c_str(),
|
|
"DS4",
|
|
"FESA-Q4-P2x2-thermal-shell",
|
|
{e.NodeIndices()[0], e.NodeIndices()[1],
|
|
e.NodeIndices()[2], e.NodeIndices()[3]},
|
|
e.ThermalSectionIndex(),
|
|
e.MaterialIndex()});
|
|
}
|
|
hsize_t four[] = {4};
|
|
auto fu = Array(H5T_STD_U64LE, 1, four),
|
|
mu = Array(H5T_NATIVE_UINT64, 1, four);
|
|
Types<ElementRow>(ft, mt, [&](hid_t f, hid_t m) {
|
|
auto add = [&](hid_t t, hid_t u, hid_t ar) {
|
|
Member(t, "internal_element_id", HOFFSET(ElementRow, internal_element_id),
|
|
u);
|
|
Member(t, "instance_name", HOFFSET(ElementRow, instance_name), st.Get());
|
|
Member(t, "source_label", HOFFSET(ElementRow, source_label), st.Get());
|
|
Member(t, "source_element_type", HOFFSET(ElementRow, source_element_type),
|
|
st.Get());
|
|
Member(t, "internal_formulation",
|
|
HOFFSET(ElementRow, internal_formulation), st.Get());
|
|
Member(t, "node_internal_ids", HOFFSET(ElementRow, node_internal_ids),
|
|
ar);
|
|
Member(t, "shell_section_internal_id",
|
|
HOFFSET(ElementRow, shell_section_internal_id), u);
|
|
Member(t, "material_internal_id",
|
|
HOFFSET(ElementRow, material_internal_id), u);
|
|
};
|
|
add(f, H5T_STD_U64LE, fu.Get());
|
|
add(m, H5T_NATIVE_UINT64, mu.Get());
|
|
});
|
|
(void)Compound(file, "/model/elements", elements, ft.Get(), mt.Get());
|
|
std::vector<MaterialRow> mats;
|
|
for (size_t i = 0; i < d.ThermalMaterials().Size(); ++i) {
|
|
const auto& m = d.ThermalMaterials()[i];
|
|
size_t id = 0;
|
|
for (; id < d.Materials().Size(); ++id)
|
|
if (&d.Materials()[id] == &m) break;
|
|
mats.push_back({id, m.Name().c_str(), m.Conductivity(), m.Density(),
|
|
m.SpecificHeat()});
|
|
}
|
|
Types<MaterialRow>(ft, mt, [&](hid_t f, hid_t m) {
|
|
auto add = [&](hid_t t, hid_t u, hid_t q) {
|
|
Member(t, "internal_material_id",
|
|
HOFFSET(MaterialRow, internal_material_id), u);
|
|
Member(t, "name", HOFFSET(MaterialRow, name), st.Get());
|
|
Member(t, "conductivity", HOFFSET(MaterialRow, conductivity), q);
|
|
Member(t, "density", HOFFSET(MaterialRow, density), q);
|
|
Member(t, "specific_heat", HOFFSET(MaterialRow, specific_heat), q);
|
|
};
|
|
add(f, H5T_STD_U64LE, H5T_IEEE_F64LE);
|
|
add(m, H5T_NATIVE_UINT64, H5T_NATIVE_DOUBLE);
|
|
});
|
|
auto md =
|
|
Compound(file, "/model/thermal_materials", mats, ft.Get(), mt.Get());
|
|
hdf5_internal::WriteStringAttribute(md.Get(), "conductivity_units_label",
|
|
"power/(length*temperature)");
|
|
hdf5_internal::WriteStringAttribute(md.Get(), "density_units_label",
|
|
"mass/volume");
|
|
hdf5_internal::WriteStringAttribute(md.Get(), "specific_heat_units_label",
|
|
"energy/(mass*temperature)");
|
|
std::vector<SectionRow> secs;
|
|
std::vector<std::string> files;
|
|
files.reserve(d.ThermalSections().size());
|
|
|
|
for (size_t i = 0; i < d.ThermalSections().size(); ++i) {
|
|
const auto& q = d.ThermalSections()[i];
|
|
files.push_back(hdf5_internal::NormalizedPathString(q.location.file));
|
|
secs.push_back({i, files.back().c_str(), q.location.line, q.name.c_str(),
|
|
hdf5_internal::GlobalThermalMaterialId(d, q.material_index),
|
|
q.thickness});
|
|
}
|
|
Types<SectionRow>(ft, mt, [&](hid_t f, hid_t m) {
|
|
auto add = [&](hid_t t, hid_t u, hid_t q) {
|
|
Member(t, "internal_section_id", HOFFSET(SectionRow, internal_section_id),
|
|
u);
|
|
Member(t, "source_file", HOFFSET(SectionRow, source_file), st.Get());
|
|
Member(t, "source_line", HOFFSET(SectionRow, source_line), u);
|
|
Member(t, "source_elset", HOFFSET(SectionRow, source_elset), st.Get());
|
|
Member(t, "material_internal_id",
|
|
HOFFSET(SectionRow, material_internal_id), u);
|
|
Member(t, "thickness", HOFFSET(SectionRow, thickness), q);
|
|
};
|
|
add(f, H5T_STD_U64LE, H5T_IEEE_F64LE);
|
|
add(m, H5T_NATIVE_UINT64, H5T_NATIVE_DOUBLE);
|
|
});
|
|
auto sd = Compound(file, "/model/shell_sections", secs, ft.Get(), mt.Get());
|
|
hdf5_internal::WriteStringAttribute(sd.Get(), "thickness_units_label",
|
|
"length");
|
|
std::vector<std::uint64_t> nids(s.NodeOrder().begin(), s.NodeOrder().end()),
|
|
eids(s.ElementOrder().begin(), s.ElementOrder().end());
|
|
auto U = [&](const char* p, const std::vector<std::uint64_t>& v) {
|
|
auto sp = hdf5_internal::CreateDatasetSpace({v.size()});
|
|
Hdf5Handle ds{hdf5_internal::RequireHdf5Id(
|
|
H5Dcreate2(file, p, H5T_STD_U64LE, sp.Get(), H5P_DEFAULT,
|
|
H5P_DEFAULT, H5P_DEFAULT),
|
|
"Unable to create IDs."),
|
|
H5Dclose};
|
|
if (!v.empty())
|
|
hdf5_internal::RequireHdf5(H5Dwrite(ds.Get(), H5T_NATIVE_UINT64, H5S_ALL,
|
|
H5S_ALL, H5P_DEFAULT, v.data()),
|
|
"Unable to write IDs.");
|
|
};
|
|
U("/model/thermal_node_ids", nids);
|
|
U("/model/thermal_element_ids", eids);
|
|
std::vector<std::uint64_t> dofs = {11, 12, 13, 14, 15};
|
|
U("/model/temperature_dofs", dofs);
|
|
const std::vector<double> zeta = {-1, -.5, 0, .5, 1},
|
|
coords = {-1 / std::sqrt(3.), -1 / std::sqrt(3.),
|
|
1 / std::sqrt(3.), -1 / std::sqrt(3.),
|
|
1 / std::sqrt(3.), 1 / std::sqrt(3.),
|
|
-1 / std::sqrt(3.), 1 / std::sqrt(3.)},
|
|
fz = {-1, -.5, 0, 0, .5, 1};
|
|
auto D = [&](const char* p, std::vector<hsize_t> sh,
|
|
const std::vector<double>& v) {
|
|
(void)hdf5_internal::WriteDoubleValues(file, p, sh, v.data(), v.size());
|
|
};
|
|
D("/model/temperature_zeta", {5}, zeta);
|
|
D("/model/thermal_flux_inplane_coordinates", {4, 2}, coords);
|
|
D("/model/thermal_flux_zeta", {6}, fz);
|
|
std::vector<std::int8_t> side = {1, 0, -1, 1, 0, -1};
|
|
auto sp = hdf5_internal::CreateDatasetSpace({6});
|
|
Hdf5Handle sid{
|
|
hdf5_internal::RequireHdf5Id(
|
|
H5Dcreate2(file, "/model/thermal_flux_trace_side", H5T_STD_I8LE,
|
|
sp.Get(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT),
|
|
"Unable to create trace side."),
|
|
H5Dclose};
|
|
hdf5_internal::RequireHdf5(H5Dwrite(sid.Get(), H5T_NATIVE_INT8, H5S_ALL,
|
|
H5S_ALL, H5P_DEFAULT, side.data()),
|
|
"Unable to write trace side.");
|
|
std::vector<std::uint8_t> mask;
|
|
for (const auto& r : s.ConstraintMask())
|
|
mask.insert(mask.end(), r.begin(), r.end());
|
|
hdf5_internal::WriteUint8Dataset(file, "/model/thermal_constraint_mask",
|
|
{s.NodeOrder().size(), 5}, mask.data(),
|
|
mask.size());
|
|
std::vector<ReactionRow> reactions;
|
|
for (const auto& r : s.ReactionDofs())
|
|
reactions.push_back({r.node, (std::uint64_t)r.source_dof});
|
|
Types<ReactionRow>(ft, mt, [&](hid_t f, hid_t m) {
|
|
Member(f, "internal_node_id", HOFFSET(ReactionRow, internal_node_id),
|
|
H5T_STD_U64LE);
|
|
Member(f, "source_dof", HOFFSET(ReactionRow, source_dof), H5T_STD_U64LE);
|
|
Member(m, "internal_node_id", HOFFSET(ReactionRow, internal_node_id),
|
|
H5T_NATIVE_UINT64);
|
|
Member(m, "source_dof", HOFFSET(ReactionRow, source_dof),
|
|
H5T_NATIVE_UINT64);
|
|
});
|
|
(void)Compound(file, "/model/thermal_reaction_dofs", reactions, ft.Get(),
|
|
mt.Get());
|
|
std::vector<double> frames;
|
|
for (const auto& e : s.FluxLocalFrames())
|
|
for (const auto& p : e)
|
|
for (size_t g = 0; g < 3; ++g)
|
|
for (size_t a = 0; a < 3; ++a) frames.push_back(p[a][g]);
|
|
D("/model/thermal_flux_local_frames", {s.ElementOrder().size(), 4, 3, 3},
|
|
frames);
|
|
}
|
|
/// @brief Writes raw initial temperature and completed-only reactions and
|
|
/// fluxes.
|
|
void WriteFrames(hid_t file, const ThermalAnalysisState& s) {
|
|
(void)hdf5_internal::CreateGroup(file, "/steps/Step-1");
|
|
std::vector<FrameRow> rows;
|
|
for (const auto& f : s.AcceptedFrames())
|
|
rows.push_back({f.frame_index, f.increment_index, f.time, f.time_increment,
|
|
(std::uint8_t)f.is_initial});
|
|
Hdf5Handle ft, mt;
|
|
Types<FrameRow>(ft, mt, [&](hid_t x, hid_t y) {
|
|
auto a = [&](hid_t t, hid_t u, hid_t q) {
|
|
Member(t, "frame_index", HOFFSET(FrameRow, frame_index), u);
|
|
Member(t, "increment_index", HOFFSET(FrameRow, increment_index), u);
|
|
Member(t, "time", HOFFSET(FrameRow, time), q);
|
|
Member(t, "time_increment", HOFFSET(FrameRow, time_increment), q);
|
|
Member(t, "is_initial", HOFFSET(FrameRow, is_initial), H5T_STD_U8LE);
|
|
};
|
|
a(x, H5T_STD_U64LE, H5T_IEEE_F64LE);
|
|
a(y, H5T_NATIVE_UINT64, H5T_NATIVE_DOUBLE);
|
|
});
|
|
auto af =
|
|
Compound(file, "/steps/Step-1/accepted_frames", rows, ft.Get(), mt.Get());
|
|
hdf5_internal::WriteStringAttribute(af.Get(), "time_units_label", "time");
|
|
for (const auto& f : s.AcceptedFrames()) {
|
|
std::string root = "/steps/Step-1/frames/" + std::to_string(f.frame_index);
|
|
auto g = hdf5_internal::CreateGroup(file, root.c_str());
|
|
(void)hdf5_internal::CreateGroup(file, (root + "/nodal").c_str());
|
|
hdf5_internal::WriteUint64Attribute(g.Get(), "frame_index", f.frame_index);
|
|
hdf5_internal::WriteUint64Attribute(g.Get(), "increment_index",
|
|
f.increment_index);
|
|
hdf5_internal::WriteDoubleAttribute(g.Get(), "time", f.time);
|
|
hdf5_internal::WriteDoubleAttribute(g.Get(), "time_increment",
|
|
f.time_increment);
|
|
hdf5_internal::WriteUint8Attribute(g.Get(), "is_initial", f.is_initial);
|
|
auto td = hdf5_internal::WriteDoubleValues(
|
|
file, root + "/nodal/temperature", {s.NodeOrder().size(), 5},
|
|
f.temperature.Data(), f.temperature.Size());
|
|
hdf5_internal::WriteStringAttribute(td.Get(), "component_order",
|
|
"NT11,NT12,NT13,NT14,NT15");
|
|
hdf5_internal::WriteStringAttribute(td.Get(), "units_label", "temperature");
|
|
hdf5_internal::WriteStringAttribute(td.Get(), "coordinate_system",
|
|
"scalar");
|
|
hdf5_internal::WriteStringAttribute(td.Get(), "location",
|
|
"nodal-thickness-stations");
|
|
if (f.completed) {
|
|
(void)hdf5_internal::CreateGroup(file, (root + "/element").c_str());
|
|
auto rd = hdf5_internal::WriteDoubleValues(
|
|
file, root + "/nodal/reaction_heat_rate", {s.ReactionDofs().size()},
|
|
f.completed->reaction_heat_rate.Data(),
|
|
f.completed->reaction_heat_rate.Size());
|
|
hdf5_internal::WriteStringAttribute(rd.Get(), "component_order", "RFL");
|
|
hdf5_internal::WriteStringAttribute(rd.Get(), "units_label", "power");
|
|
hdf5_internal::WriteStringAttribute(rd.Get(), "coordinate_system",
|
|
"scalar");
|
|
hdf5_internal::WriteStringAttribute(rd.Get(), "location",
|
|
"prescribed-temperature-dofs");
|
|
hdf5_internal::WriteStringAttribute(rd.Get(), "positive_direction",
|
|
"into-model");
|
|
std::vector<double> q;
|
|
for (const auto& e : f.completed->global_heat_flux)
|
|
for (size_t p = 0; p < 4; ++p)
|
|
for (size_t z = 0; z < 6; ++z)
|
|
for (size_t c = 0; c < 3; ++c) q.push_back(e[6 * p + z][c]);
|
|
auto fd = hdf5_internal::WriteDoubleValues(
|
|
file, root + "/element/heat_flux", {s.ElementOrder().size(), 4, 6, 3},
|
|
q.data(), q.size());
|
|
hdf5_internal::WriteStringAttribute(fd.Get(), "component_order",
|
|
"QX,QY,QZ");
|
|
hdf5_internal::WriteStringAttribute(fd.Get(), "units_label",
|
|
"power/area");
|
|
hdf5_internal::WriteStringAttribute(fd.Get(), "coordinate_system",
|
|
"global-cartesian");
|
|
hdf5_internal::WriteStringAttribute(fd.Get(), "location",
|
|
"element-inplane-thickness-traces");
|
|
}
|
|
}
|
|
}
|
|
/// @brief Converts adapter failures into the existing output-failure category.
|
|
Status Fail(const char* m) {
|
|
return hdf5_internal::OutputFailure("thermal-output-schema", m);
|
|
}
|
|
} // namespace
|
|
|
|
Status hdf5_internal::WriteThermalFile(
|
|
const std::filesystem::path& output_path, const Domain& domain,
|
|
const ThermalAnalysisState& state,
|
|
const std::vector<Diagnostic>& diagnostics, const ThermalFileHooks& hooks) {
|
|
const auto inventory = ValidateThermalOutput(domain, state);
|
|
if (!inventory.IsOk()) return inventory;
|
|
if (!hooks.candidate_close || !hooks.reader_close)
|
|
return Fail("Missing thermal file closer.");
|
|
try {
|
|
hdf5_internal::Hdf5ErrorSilencer silence;
|
|
auto candidate = hdf5_internal::MakeCandidatePath(output_path);
|
|
hdf5_internal::CandidateFileGuard guard{candidate};
|
|
Hdf5Handle file{hdf5_internal::RequireHdf5Id(
|
|
H5Fcreate(candidate.string().c_str(), H5F_ACC_EXCL,
|
|
H5P_DEFAULT, H5P_DEFAULT),
|
|
"Unable to create thermal candidate."),
|
|
hooks.candidate_close};
|
|
if (hooks.before_write) hooks.before_write(file.Get());
|
|
Metadata(file.Get(), domain, state);
|
|
WriteThermalModel(file.Get(), domain, state);
|
|
WriteFrames(file.Get(), state);
|
|
hdf5_internal::WriteDiagnostics(file.Get(), diagnostics);
|
|
hdf5_internal::RequireHdf5(H5Fflush(file.Get(), H5F_SCOPE_GLOBAL),
|
|
"Unable to flush thermal candidate.");
|
|
hdf5_internal::RequireHdf5(file.CloseChecked(),
|
|
"Unable to close thermal candidate.");
|
|
if (hooks.before_self_check) hooks.before_self_check(candidate);
|
|
const Status ok = hdf5_internal::SelfCheckThermalFile(
|
|
candidate, domain, state, diagnostics, hooks.reader_close);
|
|
if (ok.IsOk() && hooks.before_finalize) hooks.before_finalize(candidate);
|
|
const Status out = hdf5_internal::AtomicFinalizeValidatedCandidate(
|
|
candidate, output_path, ok);
|
|
if (out.IsOk()) guard.Release();
|
|
return out;
|
|
} catch (const std::exception& e) {
|
|
return Fail(e.what());
|
|
}
|
|
}
|
|
Status ThermalHdf5ResultsWriter::Write(
|
|
const std::filesystem::path& output_path, const Domain& domain,
|
|
const ThermalAnalysisState& state,
|
|
const std::vector<Diagnostic>& diagnostics) {
|
|
return hdf5_internal::WriteThermalFile(output_path, domain, state,
|
|
diagnostics);
|
|
}
|
|
} // namespace fesa
|