Files
FESADev/tests/unit/io/hdf5/solid_hdf5_results_writer_test.cpp
T

872 lines
38 KiB
C++

#include "fesa/io/hdf5/solid_hdf5_results_writer.h"
#include <gtest/gtest.h>
#include <hdf5.h>
#include <immintrin.h>
#include <cfenv>
#include <fstream>
#include <iterator>
#include <limits>
#include <set>
#include "fesa/analysis/linear_frequency_procedure.h"
#include "fesa/analysis/linear_static_procedure.h"
#include "fesa/analysis/newton_iteration_log.h"
#include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/analysis/prepared_analysis.h"
#include "fesa/analysis/solid_frequency_procedure.h"
#include "fesa/app/output_paths.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/io/hdf5/eigen_hdf5_results_writer.h"
#include "fesa/io/hdf5/hdf5_results_writer.h"
#include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
#include "io/hdf5/hdf5_primitives.h"
#include "io/hdf5/solid_hdf5_self_check.h"
#include "solid_output_test_fixture.h"
namespace {
using namespace fesa;
using namespace fesa::hdf5_internal;
struct Actual {
PreparedAnalysisInput input;
PreparedAnalysisOutcome outcome;
};
Actual Execute(const solid_output_test::Files& files) {
auto input = PrepareAnalysisInput(files.input);
if (!input.HasValue()) throw std::runtime_error("actual input");
SerialParallelFor parallel;
MklPardisoSolver linear;
MklPardisoTangentSolver tangent;
LinearStaticProcedureExecutor oldlinear(parallel, linear);
NonlinearStaticProcedureExecutor oldnonlinear(parallel, tangent);
LinearFrequencyProcedureExecutor oldfrequency;
SolidStaticProcedureExecutor stat(parallel, linear, tangent);
SolidFrequencyProcedureExecutor frequency;
SolidHhtDynamicProcedureExecutor dynamic(parallel, linear, tangent);
NewtonIterationLogWriter log;
if (!log.Open(files.log).IsOk()) throw std::runtime_error("log");
auto result = ExecutePreparedAnalysis(
input.Value().domain, oldlinear, oldnonlinear, oldfrequency, log, nullptr,
nullptr, &stat, &frequency, &dynamic);
if (!log.Close().IsOk() || !result.HasValue())
throw std::runtime_error("actual execute");
return {std::move(input.Value()), std::move(result.Value())};
}
SolidStateView View(const Actual& actual) {
if (const auto* value =
std::get_if<SolidStaticExecutionResult>(&actual.outcome))
return std::cref(value->state);
if (const auto* value = std::get_if<SolidFrequencyState>(&actual.outcome))
return std::cref(*value);
return std::cref(std::get<SolidDynamicExecutionResult>(actual.outcome).state);
}
std::vector<char> Bytes(const std::filesystem::path& path) {
std::ifstream stream(path, std::ios::binary);
return {std::istreambuf_iterator<char>(stream), {}};
}
void Existing(const std::filesystem::path& path) {
std::ofstream out(path, std::ios::binary);
out << "previous final preserved";
}
std::string Attr(hid_t object, const char* name) {
Hdf5Handle a{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
Hdf5Handle type{H5Aget_type(a.Get()), H5Tclose};
EXPECT_EQ(H5Tget_class(type.Get()), H5T_STRING);
EXPECT_GT(H5Tis_variable_str(type.Get()), 0);
EXPECT_EQ(H5Tget_cset(type.Get()), H5T_CSET_UTF8);
char* pointer = nullptr;
EXPECT_GE(H5Aread(a.Get(), type.Get(), &pointer), 0);
std::string value = pointer ? pointer : "";
H5free_memory(pointer);
return value;
}
void Compound(hid_t file, const char* path,
const std::vector<std::string>& names,
const std::vector<char>& kinds,
const std::vector<hsize_t>& widths = {}) {
Hdf5Handle dataset{H5Dopen2(file, path, H5P_DEFAULT), H5Dclose};
ASSERT_GE(dataset.Get(), 0);
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
ASSERT_EQ(H5Tget_class(type.Get()), H5T_COMPOUND);
ASSERT_EQ(H5Tget_nmembers(type.Get()), static_cast<int>(names.size()));
for (std::size_t i = 0; i < names.size(); ++i) {
char* name = H5Tget_member_name(type.Get(), static_cast<unsigned>(i));
ASSERT_NE(name, nullptr);
EXPECT_EQ(name, names[i]);
H5free_memory(name);
Hdf5Handle field{H5Tget_member_type(type.Get(), static_cast<unsigned>(i)),
H5Tclose};
if (!widths.empty() && widths[i] > 1) {
ASSERT_EQ(H5Tget_class(field.Get()), H5T_ARRAY);
hsize_t width = 0;
ASSERT_EQ(H5Tget_array_dims2(field.Get(), &width), 1);
EXPECT_EQ(width, widths[i]);
field = Hdf5Handle{H5Tget_super(field.Get()), H5Tclose};
}
if (kinds[i] == 's') {
EXPECT_EQ(H5Tget_class(field.Get()), H5T_STRING);
EXPECT_GT(H5Tis_variable_str(field.Get()), 0);
EXPECT_EQ(H5Tget_cset(field.Get()), H5T_CSET_UTF8);
} else
EXPECT_GT(H5Tequal(field.Get(), kinds[i] == 'u' ? H5T_STD_U64LE
: kinds[i] == 'b' ? H5T_STD_U8LE
: H5T_IEEE_F64LE),
0);
}
}
template <class T>
T NumberAttr(hid_t object, const char* name, hid_t file_type,
hid_t native_type) {
Hdf5Handle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
Hdf5Handle type{H5Aget_type(attribute.Get()), H5Tclose};
Hdf5Handle space{H5Aget_space(attribute.Get()), H5Sclose};
EXPECT_GT(H5Tequal(type.Get(), file_type), 0);
EXPECT_EQ(H5Sget_simple_extent_type(space.Get()), H5S_SCALAR);
T value{};
EXPECT_GE(H5Aread(attribute.Get(), native_type, &value), 0);
return value;
}
void DatasetContract(hid_t file, const std::string& path,
const std::vector<hsize_t>& expected,
const char* components, const char* units,
const char* location) {
Hdf5Handle dataset{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
ASSERT_GE(dataset.Get(), 0);
Hdf5Handle type{H5Dget_type(dataset.Get()), H5Tclose};
EXPECT_GT(H5Tequal(type.Get(), H5T_IEEE_F64LE), 0);
Hdf5Handle space{H5Dget_space(dataset.Get()), H5Sclose};
ASSERT_EQ(H5Sget_simple_extent_ndims(space.Get()),
static_cast<int>(expected.size()));
std::vector<hsize_t> shape(expected.size());
H5Sget_simple_extent_dims(space.Get(), shape.data(), nullptr);
EXPECT_EQ(shape, expected);
EXPECT_EQ(Attr(dataset.Get(), "component_names"), components);
EXPECT_EQ(Attr(dataset.Get(), "unit_dimensions"), units);
EXPECT_EQ(Attr(dataset.Get(), "location"), location);
EXPECT_EQ(Attr(dataset.Get(), "coordinate_system"), "global-cartesian");
const auto marker = path.find("/frames/");
if (marker != std::string::npos) {
const auto index =
static_cast<std::uint64_t>(std::stoull(path.substr(marker + 8)));
Hdf5Handle metadata{H5Gopen2(file, "/metadata", H5P_DEFAULT), H5Gclose};
const auto procedure = Attr(metadata.Get(), "analysis_procedure");
const bool dynamic = procedure.find("hht-") == 0;
const bool nonlinear = procedure == "nonlinear-static-full-newton" ||
procedure == "hht-nonlinear-dynamics";
EXPECT_EQ(Attr(dataset.Get(), "step_name"), "Step-1");
EXPECT_EQ(NumberAttr<std::uint64_t>(dataset.Get(), "frame_index",
H5T_STD_U64LE, H5T_NATIVE_UINT64),
index);
EXPECT_EQ(NumberAttr<std::uint64_t>(dataset.Get(), "increment_index",
H5T_STD_U64LE, H5T_NATIVE_UINT64),
dynamic ? index
: procedure == "linear-static" ? 1
: index + 1);
EXPECT_EQ(
NumberAttr<double>(dataset.Get(), dynamic ? "time" : "load_factor",
H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE),
dynamic ? static_cast<double>(index) * .1
: procedure == "linear-static" ? 1
: static_cast<double>(index + 1) * .5);
if (path.find("/solid/stress") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "stress_measure"), "cauchy");
if (path.find("/solid/strain") != std::string::npos) {
EXPECT_EQ(Attr(dataset.Get(), "strain_measure"),
nonlinear ? "green-lagrange" : "infinitesimal");
EXPECT_EQ(Attr(dataset.Get(), "shear_convention"), "tensor");
}
if (path.find("/nodal/acceleration") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "acceleration_kind"), "physical");
if (path.find("/nodal/algorithmic_acceleration") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "acceleration_kind"), "algorithmic");
if (path.find("/global/strain_energy") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "definition"),
nonlinear ? "sum(W*detJ0)" : "0.5*u^T*K0*u");
if (path.find("/global/kinetic_energy") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "definition"), "0.5*v^T*M*v");
if (path.find("/global/external_work") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "definition"),
"sum(0.5*(F_old+F_new)*delta_u)");
if (path.find("/global/energy_balance_error") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "definition"), "T+U-T0-U0-Wext");
if (path.find("/global/angular_momentum") != std::string::npos)
EXPECT_EQ(Attr(dataset.Get(), "origin"), "global0");
}
}
TEST(SolidHdf5ResultsWriter,
AllFiveActualApiOutcomesHaveIndependentOfficialSchema) {
for (unsigned p = 0; p < 5; ++p) {
SCOPED_TRACE(p);
solid_output_test::Files files(p);
auto actual = Execute(files);
ASSERT_TRUE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics)
.IsOk());
Hdf5Handle file{
H5Fopen(files.output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
H5Fclose};
ASSERT_GE(file.Get(), 0);
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
H5Gclose};
EXPECT_EQ(Attr(metadata.Get(), "analysis_procedure"),
solid_output_test::Procedure(p));
EXPECT_EQ(Attr(metadata.Get(), "feature_id"), "hex8-structural-analysis");
EXPECT_EQ(Attr(metadata.Get(), "source_step_name"), "RawSolid");
EXPECT_EQ(Attr(metadata.Get(), "element_formulation"), "FESA-Hex8");
EXPECT_EQ(Attr(metadata.Get(), "integration_rule"), "gauss-2x2x2");
EXPECT_EQ(Attr(metadata.Get(), "mass_formulation"), "consistent-reference");
EXPECT_EQ(Attr(metadata.Get(), "component_order"), "node-major; UX,UY,UZ");
Compound(
file.Get(), "/model/nodes",
{"internal_node_id", "instance_name", "source_label", "coordinates"},
{'u', 's', 's', 'd'}, {1, 1, 1, 3});
Compound(
file.Get(), "/model/elements",
{"internal_element_id", "instance_name", "source_label",
"source_element_type", "internal_formulation", "node_internal_ids",
"solid_section_internal_id", "material_internal_id"},
{'u', 's', 's', 's', 's', 'u', 'u', 'u'}, {1, 1, 1, 1, 1, 8, 1, 1});
Compound(file.Get(), "/model/solid/materials",
{"internal_material_id", "name", "youngs_modulus", "poisson_ratio",
"density_present", "density"},
{'u', 's', 'd', 'd', 'b', 'd'});
Compound(file.Get(), "/model/solid/sections",
{"internal_section_id", "source_file", "source_line",
"source_elset", "material_internal_id"},
{'u', 's', 'u', 's', 'u'});
Compound(file.Get(), "/model/solid_integration_points",
{"internal_element_id", "point_id", "natural_coordinates",
"reference_coordinates"},
{'u', 'b', 'd', 'd'}, {1, 1, 3, 3});
Compound(file.Get(), "/diagnostics",
{"severity", "code", "file", "line", "keyword", "entity_identity",
"message"},
{'s', 's', 's', 'u', 's', 's', 's'});
Hdf5Handle mask{
H5Dopen2(file.Get(), "/model/nodal_constraint_mask", H5P_DEFAULT),
H5Dclose};
Hdf5Handle masktype{H5Dget_type(mask.Get()), H5Tclose};
EXPECT_GT(H5Tequal(masktype.Get(), H5T_STD_U8LE), 0);
EXPECT_EQ(Attr(mask.Get(), "component_names"), "UX,UY,UZ");
if (p == 2) {
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/frames", H5P_DEFAULT), 0);
Compound(file.Get(), "/steps/Step-1/eigen/modes",
{"mode_id", "eigenvalue", "classification", "cluster_id"},
{'u', 'd', 'b', 'u'});
Compound(file.Get(), "/steps/Step-1/eigen/frequencies",
{"mode_id", "frequency"}, {'u', 'd'});
Compound(file.Get(), "/steps/Step-1/eigen/verification/pairs",
{"sorted_rank", "solver_column", "eigenvalue", "modal_mass",
"rayleigh_quotient", "eta_r", "eta_norm", "eta_energy"},
{'u', 'u', 'd', 'd', 'd', 'd', 'd', 'd'});
DatasetContract(file.Get(), "/steps/Step-1/eigen/vectors", {1, 8, 3},
"UX,UY,UZ", "mass-normalized", "nodal");
for (const auto& quantity :
std::vector<std::tuple<const char*, const char*, const char*>>{
{"modes", "eigenvalue", "1/time^2"},
{"frequencies", "frequency", "1/time"}}) {
const auto path =
std::string("/steps/Step-1/eigen/") + std::get<0>(quantity);
Hdf5Handle dataset{H5Dopen2(file.Get(), path.c_str(), H5P_DEFAULT),
H5Dclose};
EXPECT_EQ(Attr(dataset.Get(), "component_names"),
std::get<1>(quantity));
EXPECT_EQ(Attr(dataset.Get(), "unit_dimensions"),
std::get<2>(quantity));
EXPECT_EQ(Attr(dataset.Get(), "location"), "global");
EXPECT_EQ(Attr(dataset.Get(), "coordinate_system"), "global-cartesian");
EXPECT_EQ(H5Aexists(dataset.Get(), "frame_index"), 0);
EXPECT_EQ(H5Aexists(dataset.Get(), "step_name"), 0);
}
const auto& state = std::get<SolidFrequencyState>(actual.outcome);
ASSERT_EQ(state.eigenpair_checks[0].solver_column, 0u);
} else {
const unsigned frames = p == 0 ? 1 : p == 1 ? 2 : 3;
if (p == 1)
Compound(file.Get(), "/steps/Step-1/accepted_frames",
{"frame_index", "increment_index", "load_factor",
"iteration_count", "is_factor_one"},
{'u', 'u', 'd', 'u', 'b'});
if (p >= 3)
Compound(file.Get(), "/steps/Step-1/accepted_frames",
{"frame_index", "increment_index", "time", "iteration_count",
"is_initial"},
{'u', 'u', 'd', 'u', 'b'});
if (p == 0)
EXPECT_EQ(
H5Lexists(file.Get(), "/steps/Step-1/accepted_frames", H5P_DEFAULT),
0);
for (unsigned i = 0; i < frames; ++i) {
const auto root = "/steps/Step-1/frames/" + std::to_string(i);
DatasetContract(file.Get(), root + "/nodal/displacement", {8, 3},
"U1,U2,U3", "length", "nodal");
DatasetContract(file.Get(), root + "/nodal/reaction", {8, 3},
"RF1,RF2,RF3", "force", "nodal");
DatasetContract(file.Get(), root + "/nodal/residual", {8, 3},
"R1,R2,R3", "force", "nodal");
DatasetContract(file.Get(), root + "/solid/stress", {1, 8, 6},
"S11,S22,S33,S12,S13,S23", "force/length^2",
"integration-point");
DatasetContract(file.Get(), root + "/solid/strain", {1, 8, 6},
"E11,E22,E33,E12,E13,E23", "dimensionless",
"integration-point");
DatasetContract(file.Get(), root + "/global/strain_energy", {},
"scalar", "force*length", "global");
if (p >= 3) {
DatasetContract(file.Get(), root + "/nodal/velocity", {8, 3},
"V1,V2,V3", "length/time", "nodal");
DatasetContract(file.Get(), root + "/nodal/acceleration", {8, 3},
"A1,A2,A3", "length/time^2", "nodal");
DatasetContract(file.Get(), root + "/nodal/algorithmic_acceleration",
{8, 3}, "A1,A2,A3", "length/time^2", "nodal");
for (const auto* energy :
{"kinetic_energy", "external_work", "energy_balance_error"})
DatasetContract(file.Get(), root + "/global/" + energy, {},
"scalar", "force*length", "global");
DatasetContract(file.Get(), root + "/global/linear_momentum", {3},
"P1,P2,P3", "mass*length/time", "global");
DatasetContract(file.Get(), root + "/global/angular_momentum", {3},
"L1,L2,L3", "mass*length^2/time", "global");
EXPECT_LE(
H5Lexists(file.Get(), (root + "/nodal/displacement_low").c_str(),
H5P_DEFAULT),
0);
}
}
}
}
}
TEST(SolidHdf5ResultsWriter,
EveryProcedurePreservesPreviousFinalAcrossLifecycleFailures) {
for (unsigned p = 0; p < 5; ++p) {
solid_output_test::Files files(p);
auto actual = Execute(files);
Existing(files.output);
const auto previous = Bytes(files.output);
for (unsigned failure = 0; failure < 5; ++failure) {
SCOPED_TRACE(p);
SCOPED_TRACE(failure);
SolidFileHooks hooks;
if (failure == 0)
hooks.before_write = [](hid_t) {
throw std::runtime_error("injected write");
};
if (failure == 1)
hooks.candidate_close = [](hid_t id) -> herr_t {
H5Fclose(id);
return -1;
};
if (failure == 2)
hooks.before_self_check = [](const auto& path) {
Hdf5Handle file{
H5Fopen(path.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT),
H5Fclose};
H5Ldelete(file.Get(), "/model/elements", H5P_DEFAULT);
};
if (failure == 3)
hooks.reader_close = [](hid_t id) -> herr_t {
H5Fclose(id);
return -1;
};
if (failure == 4)
hooks.before_finalize = [](const auto& path) {
std::filesystem::remove(path);
};
const auto status =
WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics, hooks);
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(Bytes(files.output), previous);
for (const auto& entry :
std::filesystem::directory_iterator(files.folder))
EXPECT_EQ(entry.path().filename().string().find("candidate"),
std::string::npos);
}
}
}
TEST(SolidHdf5ResultsWriter,
MalformedWholePairsAndIdentityFailBeforeCandidateCreation) {
solid_output_test::Files files(3);
auto actual = Execute(files);
Existing(files.output);
const auto previous = Bytes(files.output);
auto& state = std::get<SolidDynamicExecutionResult>(actual.outcome).state;
auto& frame = const_cast<SolidDynamicAcceptedFrameState&>(
state.AcceptedFrames().back());
const auto original = frame;
for (unsigned malformed = 0; malformed < 5; ++malformed) {
SCOPED_TRACE(malformed);
frame = original;
if (malformed == 0) frame.low.displacement = Vector{1};
if (malformed == 1)
frame.low.displacement[21] = std::numeric_limits<double>::quiet_NaN();
if (malformed == 2) frame.low.displacement[21] = frame.displacement[21];
if (malformed == 3) frame.nodal_source_ids[0].source_label_text = "wrong";
if (malformed == 4) frame.low.residual[21] = 1;
EXPECT_FALSE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics)
.IsOk());
EXPECT_EQ(Bytes(files.output), previous);
}
frame = original;
const int rounding = std::fegetround();
ASSERT_EQ(std::fesetround(FE_DOWNWARD), 0);
const auto status = WriteSolidFile(files.output, actual.input.domain,
View(actual), actual.input.diagnostics);
std::fesetround(rounding);
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(Bytes(files.output), previous);
}
TEST(SolidHdf5ResultsWriter,
ModalColumnsRemainZeroBasedAndRejectInvalidInventory) {
solid_output_test::Files files(2);
auto actual = Execute(files);
auto& state = std::get<SolidFrequencyState>(actual.outcome);
ASSERT_EQ(state.eigenpair_checks[0].solver_column, 0u);
Existing(files.output);
const auto previous = Bytes(files.output);
const auto original = state;
state.eigenpair_checks[0].solver_column = state.eigenpair_checks.size();
EXPECT_FALSE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics)
.IsOk());
EXPECT_EQ(Bytes(files.output), previous);
state = original;
state.backend.column_ids[0] = state.eigenpair_checks.size();
state.eigenpair_checks[0].solver_column = state.backend.column_ids[0];
EXPECT_FALSE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics)
.IsOk());
}
TEST(SolidHdf5ResultsWriter,
ReadbackRejectsMalformedTypesShapesValuesAndSourceIds) {
solid_output_test::Files files(3);
auto actual = Execute(files);
Existing(files.output);
const auto previous = Bytes(files.output);
for (unsigned malformed = 0; malformed < 9; ++malformed) {
SCOPED_TRACE(malformed);
SolidFileHooks hooks;
hooks.before_self_check = [&](const auto& path) {
Hdf5Handle file{H5Fopen(path.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT),
H5Fclose};
const char* result = "/steps/Step-1/frames/2/nodal/displacement";
if (malformed == 0) H5Ldelete(file.Get(), result, H5P_DEFAULT);
if (malformed == 1) {
Hdf5Handle dataset{H5Dopen2(file.Get(), result, H5P_DEFAULT), H5Dclose};
std::vector<double> values(24, 0);
values[0] = std::numeric_limits<double>::quiet_NaN();
H5Dwrite(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
H5P_DEFAULT, values.data());
}
if (malformed == 2 || malformed == 3) {
H5Ldelete(file.Get(), result, H5P_DEFAULT);
hsize_t shape[]{malformed == 2 ? 1u : 8u, 3};
Hdf5Handle space{H5Screate_simple(2, shape, nullptr), H5Sclose};
Hdf5Handle data{
H5Dcreate2(file.Get(), result,
malformed == 2 ? H5T_IEEE_F64LE : H5T_IEEE_F32LE,
space.Get(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT),
H5Dclose};
}
if (malformed == 4) {
Hdf5Handle dataset{
H5Dopen2(file.Get(), "/model/solid_integration_points",
H5P_DEFAULT),
H5Dclose};
Hdf5Handle memory{H5Tcreate(H5T_COMPOUND, 1), H5Tclose};
H5Tinsert(memory.Get(), "point_id", 0, H5T_NATIVE_UINT8);
std::vector<std::uint8_t> values(8, 99);
H5Dwrite(dataset.Get(), memory.Get(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
values.data());
}
if (malformed == 5) {
Hdf5Handle dataset{H5Dopen2(file.Get(), "/model/elements", H5P_DEFAULT),
H5Dclose};
Hdf5Handle memory{H5Tcreate(H5T_COMPOUND, sizeof(std::uint64_t)),
H5Tclose};
H5Tinsert(memory.Get(), "internal_element_id", 0, H5T_NATIVE_UINT64);
const std::uint64_t value = 99;
H5Dwrite(dataset.Get(), memory.Get(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
&value);
}
if (malformed == 6) {
Hdf5Handle dataset{H5Dopen2(file.Get(), result, H5P_DEFAULT), H5Dclose};
H5Adelete(dataset.Get(), "unit_dimensions");
WriteStringAttribute(dataset.Get(), "unit_dimensions", "wrong");
}
if (malformed == 7) {
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
H5Gclose};
H5Adelete(metadata.Get(), "analysis_procedure");
Hdf5Handle type{H5Tcopy(H5T_C_S1), H5Tclose};
H5Tset_size(type.Get(), 16);
Hdf5Handle space{H5Screate(H5S_SCALAR), H5Sclose};
Hdf5Handle attr{
H5Acreate2(metadata.Get(), "analysis_procedure", type.Get(),
space.Get(), H5P_DEFAULT, H5P_DEFAULT),
H5Aclose};
}
if (malformed == 8) {
H5Ldelete(file.Get(), "/model/elements", H5P_DEFAULT);
Hdf5Handle type{H5Tcreate(H5T_COMPOUND, 16), H5Tclose};
H5Tinsert(type.Get(), "material_internal_id", 0, H5T_STD_U64LE);
H5Tinsert(type.Get(), "internal_element_id", 8, H5T_STD_U64LE);
hsize_t one = 1;
Hdf5Handle space{H5Screate_simple(1, &one, nullptr), H5Sclose};
Hdf5Handle dataset{
H5Dcreate2(file.Get(), "/model/elements", type.Get(), space.Get(),
H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT),
H5Dclose};
}
};
EXPECT_FALSE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics, hooks)
.IsOk());
EXPECT_EQ(Bytes(files.output), previous);
}
}
TEST(SolidHdf5ResultsWriter,
ReadbackMatchesActualAcceptedValuesAndFullPairObservables) {
for (unsigned p = 0; p < 5; ++p) {
SCOPED_TRACE(p);
solid_output_test::Files files(p);
auto actual = Execute(files);
ASSERT_TRUE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics)
.IsOk());
Hdf5Handle file{
H5Fopen(files.output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
H5Fclose};
const auto compare = [&](const std::string& path,
const std::vector<double>& expected) {
Hdf5Handle dataset{H5Dopen2(file.Get(), path.c_str(), H5P_DEFAULT),
H5Dclose};
ASSERT_GE(dataset.Get(), 0);
std::vector<double> values(expected.size());
ASSERT_GE(H5Dread(dataset.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
H5P_DEFAULT, values.data()),
0);
EXPECT_EQ(values, expected);
};
const auto vector = [](const Vector& input) {
std::vector<double> values;
for (std::size_t i = 0; i < input.Size(); ++i) values.push_back(input[i]);
return values;
};
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
H5Gclose};
EXPECT_EQ(Attr(metadata.Get(), "source_input_identity"),
SourceInputIdentity(actual.input.domain));
if (p == 2) {
const auto& state = std::get<SolidFrequencyState>(actual.outcome);
std::vector<double> values;
for (std::size_t i = 0; i < state.vectors.Rows(); ++i)
for (std::size_t j = 0; j < state.vectors.Columns(); ++j)
values.push_back(state.vectors(i, j));
compare("/steps/Step-1/eigen/vectors", values);
} else {
const auto frame = [&](const auto& accepted) {
const auto root =
"/steps/Step-1/frames/" + std::to_string(accepted.frame_index);
compare(root + "/nodal/displacement", vector(accepted.displacement));
compare(root + "/nodal/reaction", vector(accepted.reaction));
compare(root + "/nodal/residual", vector(accepted.residual));
std::vector<double> stress, strain;
for (const auto& point : accepted.solid_results) {
stress.insert(stress.end(), point.cauchy_stress.begin(),
point.cauchy_stress.end());
strain.insert(strain.end(), point.strain.begin(), point.strain.end());
}
compare(root + "/solid/stress", stress);
compare(root + "/solid/strain", strain);
};
if (p < 2)
for (const auto& accepted :
std::get<SolidStaticExecutionResult>(actual.outcome)
.state.AcceptedFrames())
frame(accepted);
else {
bool nonzero_low = false;
for (const auto& accepted :
std::get<SolidDynamicExecutionResult>(actual.outcome)
.state.AcceptedFrames()) {
frame(accepted);
const auto root =
"/steps/Step-1/frames/" + std::to_string(accepted.frame_index);
compare(root + "/nodal/velocity", vector(accepted.velocity));
compare(root + "/nodal/acceleration", vector(accepted.acceleration));
compare(root + "/nodal/algorithmic_acceleration",
vector(accepted.algorithmic_acceleration));
compare(root + "/global/strain_energy", {accepted.strain_energy});
compare(root + "/global/kinetic_energy", {accepted.kinetic_energy});
compare(root + "/global/external_work", {accepted.external_work});
compare(root + "/global/energy_balance_error",
{accepted.energy_balance_error});
for (std::size_t i = 0; i < accepted.low.displacement.Size(); ++i)
nonzero_low = nonzero_low || accepted.low.displacement[i] != 0;
}
EXPECT_TRUE(nonzero_low);
}
}
}
}
class FailedTangent final : public TangentSolver {
public:
Status FactorizeFresh(const SparseMatrix&) override {
return Status::Failure(FailureCategory::kSolver,
{{Severity::kError,
"injected-tangent-failure",
{},
"",
{},
"test backend failed"}});
}
Status SolveCorrection(const Vector&, Vector&) const override {
return Status::Failure(FailureCategory::kSolver, {});
}
};
TEST(SolidPreparedAnalysis,
FailureRecordsAreLoggedAndLogFailureTakesPrecedence) {
for (unsigned p : {1u, 3u, 4u}) {
solid_output_test::Files files(p);
auto input = PrepareAnalysisInput(files.input);
ASSERT_TRUE(input.HasValue());
SerialParallelFor parallel;
MklPardisoSolver linear;
FailedTangent tangent;
LinearStaticProcedureExecutor oldlinear(parallel, linear);
NonlinearStaticProcedureExecutor oldnonlinear(parallel, tangent);
LinearFrequencyProcedureExecutor oldfrequency;
SolidStaticProcedureExecutor stat(parallel, linear, tangent);
SolidFrequencyProcedureExecutor frequency;
SolidHhtDynamicProcedureExecutor dynamic(parallel, linear, tangent);
for (bool closed : {false, true}) {
SCOPED_TRACE(p);
SCOPED_TRACE(closed);
NewtonIterationLogWriter log;
ASSERT_TRUE(log.Open(files.log).IsOk());
if (closed) ASSERT_TRUE(log.Close().IsOk());
const auto result = ExecutePreparedAnalysis(
input.Value().domain, oldlinear, oldnonlinear, oldfrequency, log,
nullptr, nullptr, &stat, &frequency, &dynamic);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(),
closed ? FailureCategory::kOutput : FailureCategory::kSolver);
if (!closed) {
ASSERT_TRUE(log.Close().IsOk());
const auto bytes = Bytes(files.log);
const std::string text(bytes.begin(), bytes.end());
EXPECT_NE(text.find("injected-tangent-failure"), std::string::npos);
EXPECT_EQ(text.find("rotation"), std::string::npos);
EXPECT_NE(text.find("stage="), std::string::npos);
}
}
}
}
TEST(SolidPreparedAnalysis, MissingSolidAdapterFailsWithoutLegacyDispatch) {
solid_output_test::Files files(0);
auto input = PrepareAnalysisInput(files.input);
ASSERT_TRUE(input.HasValue());
SerialParallelFor parallel;
MklPardisoSolver linear;
MklPardisoTangentSolver tangent;
LinearStaticProcedureExecutor oldlinear(parallel, linear);
NonlinearStaticProcedureExecutor oldnonlinear(parallel, tangent);
LinearFrequencyProcedureExecutor oldfrequency;
NewtonIterationLogWriter log;
ASSERT_TRUE(log.Open(files.log).IsOk());
const auto result = ExecutePreparedAnalysis(input.Value().domain, oldlinear,
oldnonlinear, oldfrequency, log);
ASSERT_FALSE(result.HasValue());
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code, "missing-solid-executor");
EXPECT_TRUE(log.Close().IsOk());
}
TEST(SolidHdf5ResultsWriter, UnsupportedEnvironmentAfterCallbackCannotPublish) {
solid_output_test::Files files(3);
auto actual = Execute(files);
Existing(files.output);
const auto previous = Bytes(files.output);
for (unsigned callback = 0; callback < 2; ++callback) {
for (unsigned control : {0x8000u, 0x40u}) {
SCOPED_TRACE(callback);
SCOPED_TRACE(control);
struct Restore {
unsigned original = _mm_getcsr();
~Restore() { _mm_setcsr(original); }
} restore;
SolidFileHooks hooks;
if (callback == 0)
hooks.before_write = [&](hid_t) {
_mm_setcsr(restore.original | control);
};
else
hooks.before_finalize = [&](const auto&) {
_mm_setcsr(restore.original | control);
};
EXPECT_FALSE(WriteSolidFile(files.output, actual.input.domain,
View(actual), actual.input.diagnostics, hooks)
.IsOk());
EXPECT_EQ(Bytes(files.output), previous);
}
}
}
TEST(SolidPreparedAnalysis, MissingWriterPreservesFinalForEveryTypedOutcome) {
for (unsigned p = 0; p < 5; ++p) {
solid_output_test::Files files(p);
auto actual = Execute(files);
Existing(files.output);
const auto previous = Bytes(files.output);
const auto paths =
DeriveSingleInputOutputPaths({files.input.string()}, files.folder);
ASSERT_TRUE(paths.HasValue());
Hdf5ResultsWriter linear;
NonlinearHdf5ResultsWriter nonlinear;
EigenHdf5ResultsWriter eigen;
const auto status = FinalizePreparedAnalysisResult(
paths.Value(), actual.input.domain, actual.outcome,
actual.input.diagnostics, linear, nonlinear, eigen);
ASSERT_FALSE(status.IsOk());
ASSERT_FALSE(status.Diagnostics().empty());
EXPECT_EQ(status.Diagnostics()[0].code, "missing-solid-writer");
EXPECT_EQ(Bytes(files.output), previous);
}
}
TEST(SolidNewtonIterationLog,
InvalidScalarEvidencePermanentlyFailsLogLifecycle) {
for (unsigned failure = 0; failure < 3; ++failure) {
solid_output_test::Files files(3);
NewtonIterationLogWriter log;
ASSERT_TRUE(log.Open(files.log).IsOk());
SolidDynamicIterationRecord record;
record.stage = "failed";
record.outcome = "failed";
if (failure == 0)
record.evidence.correction_metrics[2] =
std::numeric_limits<double>::infinity();
if (failure == 1) record.evidence.residual_scale = -1;
if (failure == 2)
record.evidence.worst_weighted_terms[0] =
std::numeric_limits<double>::quiet_NaN();
EXPECT_FALSE(log.Write(record).IsOk());
EXPECT_FALSE(log.Close().IsOk());
}
}
TEST(SolidHdf5ResultsWriter, AbsentStaticDensityIsAnExplicitPlaceholder) {
solid_output_test::Files files(0);
auto deck = solid_output_test::Deck(0);
const auto position = deck.find("*Density\n2\n");
ASSERT_NE(position, std::string::npos);
deck.erase(position, std::string("*Density\n2\n").size());
{
std::ofstream out(files.input);
out << deck;
}
auto actual = Execute(files);
ASSERT_TRUE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics)
.IsOk());
Hdf5Handle file{
H5Fopen(files.output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
H5Fclose};
Hdf5Handle dataset{
H5Dopen2(file.Get(), "/model/solid/materials", H5P_DEFAULT), H5Dclose};
struct Density {
std::uint8_t present;
double rho;
} row{};
Hdf5Handle type{H5Tcreate(H5T_COMPOUND, sizeof(row)), H5Tclose};
H5Tinsert(type.Get(), "density_present", HOFFSET(Density, present),
H5T_NATIVE_UINT8);
H5Tinsert(type.Get(), "density", HOFFSET(Density, rho), H5T_NATIVE_DOUBLE);
ASSERT_GE(
H5Dread(dataset.Get(), type.Get(), H5S_ALL, H5S_ALL, H5P_DEFAULT, &row),
0);
EXPECT_EQ(row.present, 0);
EXPECT_EQ(row.rho, 0);
}
TEST(SolidHdf5ResultsWriter,
ModalPhysicalTableAttributeCorruptionPreservesFinal) {
solid_output_test::Files files(2);
auto actual = Execute(files);
Existing(files.output);
const auto previous = Bytes(files.output);
for (const auto* quantity : {"modes", "frequencies"}) {
SolidFileHooks hooks;
hooks.before_self_check = [&](const auto& path) {
Hdf5Handle file{H5Fopen(path.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT),
H5Fclose};
const auto dataset_path = std::string("/steps/Step-1/eigen/") + quantity;
Hdf5Handle dataset{
H5Dopen2(file.Get(), dataset_path.c_str(), H5P_DEFAULT), H5Dclose};
H5Adelete(dataset.Get(), "unit_dimensions");
WriteStringAttribute(dataset.Get(), "unit_dimensions", "wrong");
};
EXPECT_FALSE(WriteSolidFile(files.output, actual.input.domain, View(actual),
actual.input.diagnostics, hooks)
.IsOk());
EXPECT_EQ(Bytes(files.output), previous);
}
}
TEST(SolidHdf5ResultsWriter,
UnselectedBackendPairsCannotPublishInvalidSpectrumOrDimension) {
solid_output_test::Files files(2);
auto deck = solid_output_test::Deck(2);
const auto position = deck.find("Tip,2,3\n");
ASSERT_NE(position, std::string::npos);
deck.erase(position, std::string("Tip,2,3\n").size());
{
std::ofstream out(files.input);
out << deck;
}
auto actual = Execute(files);
auto& state = std::get<SolidFrequencyState>(actual.outcome);
ASSERT_EQ(state.modes.size(), 1u);
ASSERT_EQ(state.free_dof_count, 3u);
ASSERT_GT(state.eigenpair_checks.size(), state.modes.size());
ASSERT_LE(state.eigenpair_checks.size(), state.free_dof_count);
const auto original = state;
Existing(files.output);
const auto previous = Bytes(files.output);
for (unsigned failure = 0; failure < 3; ++failure) {
SCOPED_TRACE(failure);
state = original;
if (failure == 0)
state.eigenpair_checks[1].eigenvalue = -1e-6 * state.classification_scale;
if (failure == 1)
state.eigenpair_checks[1].eigenvalue =
state.eigenpair_checks[0].eigenvalue * .5;
if (failure < 2)
state.backend.eigenvalues[1] = state.eigenpair_checks[1].eigenvalue;
if (failure == 2) {
while (state.eigenpair_checks.size() <= state.free_dof_count) {
auto pair = state.eigenpair_checks.back();
pair.sorted_rank = state.eigenpair_checks.size() + 1;
pair.solver_column = state.eigenpair_checks.size();
state.eigenpair_checks.push_back(pair);
state.backend.eigenvalues.push_back(pair.eigenvalue);
state.backend.column_ids.push_back(pair.solver_column);
}
}
const auto status = WriteSolidFile(files.output, actual.input.domain,
View(actual), actual.input.diagnostics);
ASSERT_FALSE(status.IsOk());
ASSERT_FALSE(status.Diagnostics().empty());
const auto& message = status.Diagnostics()[0].message;
EXPECT_NE(message.find(failure == 0 ? "Significant negative"
: failure == 1 ? "not sorted"
: "inventory"),
std::string::npos);
EXPECT_EQ(Bytes(files.output), previous);
}
}
} // namespace