feat(hex8): connect Solid CLI and atomic HDF5 output
This commit is contained in:
@@ -0,0 +1,871 @@
|
||||
#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
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef FESA_TESTS_SOLID_OUTPUT_FIXTURE_H_
|
||||
#define FESA_TESTS_SOLID_OUTPUT_FIXTURE_H_
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
namespace solid_output_test {
|
||||
inline std::string Deck(unsigned procedure) {
|
||||
std::string s =
|
||||
"*Part,name=P\n*Node\n10,0,0,0\n20,1,0,0\n30,1,1,0\n40,0,1,0\n"
|
||||
"50,0,0,1\n60,1,0,1\n70,1,1,1\n80,0,1,1\n"
|
||||
"*Element,type=C3D8,elset=Solid\n99,10,20,30,40,50,60,70,80\n"
|
||||
"*Solid Section,elset=Solid,material=M\n,\n*End Part\n"
|
||||
"*Assembly,name=A\n*Instance,name=I,part=P\n*End Instance\n"
|
||||
"*Nset,nset=Fixed,instance=I\n10,20,30,40,50,60,70\n"
|
||||
"*Nset,nset=Tip,instance=I\n80\n*End Assembly\n"
|
||||
"*Material,name=M\n*Elastic\n100,.25\n*Density\n2\n"
|
||||
"*Boundary\nFixed,1,6\nTip,2,3\n";
|
||||
s += "*Step,name=RawSolid,inc=2,nlgeom=";
|
||||
s += (procedure == 1 || procedure == 4) ? "YES\n" : "NO\n";
|
||||
if (procedure == 0) s += "*Static\n1,1,1,1\n";
|
||||
if (procedure == 1) s += "*Static,Direct\n.5,1\n";
|
||||
if (procedure == 2)
|
||||
s += "*Frequency,eigensolver=Lanczos,normalization=mass\n1\n";
|
||||
if (procedure >= 3) s += "*Dynamic,Direct\n.1,.2\n";
|
||||
if (procedure != 2) s += "*Cload\nTip,1,.001\n";
|
||||
return s + "*End Step\n";
|
||||
}
|
||||
struct Files {
|
||||
std::filesystem::path folder, input, output, log;
|
||||
explicit Files(unsigned procedure) {
|
||||
static std::atomic<unsigned long long> sequence{0};
|
||||
folder = std::filesystem::temp_directory_path() /
|
||||
("fesa-solid-output-" +
|
||||
std::to_string(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count()) +
|
||||
"-" + std::to_string(sequence.fetch_add(1)));
|
||||
std::filesystem::create_directory(folder);
|
||||
input = folder / "Solid.inp";
|
||||
output = folder / "Solid_results.h5";
|
||||
log = folder / "Solid_log.txt";
|
||||
std::ofstream out(input);
|
||||
out << Deck(procedure);
|
||||
}
|
||||
~Files() {
|
||||
std::error_code ignored;
|
||||
std::filesystem::remove_all(folder, ignored);
|
||||
}
|
||||
};
|
||||
inline const char* Procedure(unsigned p) {
|
||||
static const char* names[] = {"linear-static", "nonlinear-static-full-newton",
|
||||
"linear-frequency", "hht-linear-dynamics",
|
||||
"hht-nonlinear-dynamics"};
|
||||
return names[p];
|
||||
}
|
||||
} // namespace solid_output_test
|
||||
#endif
|
||||
Reference in New Issue
Block a user