Files
FESADev/src/fesa/io/hdf5/thermal_hdf5_results_writer.cpp
T

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