|
|
|
@@ -0,0 +1,491 @@
|
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
#include <hdf5.h>
|
|
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <filesystem>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <random>
|
|
|
|
|
#include <stdexcept>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#include "fesa/app/fesa_application.h"
|
|
|
|
|
|
|
|
|
|
#define NOMINMAX
|
|
|
|
|
#include <Windows.h>
|
|
|
|
|
|
|
|
|
|
#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/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"
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
class HdfHandle {
|
|
|
|
|
public:
|
|
|
|
|
HdfHandle(hid_t value, herr_t (*close)(hid_t))
|
|
|
|
|
: value_(value), close_(close) {
|
|
|
|
|
if (value < 0) throw std::runtime_error{"Cannot open HDF test object."};
|
|
|
|
|
}
|
|
|
|
|
~HdfHandle() { close_(value_); }
|
|
|
|
|
HdfHandle(const HdfHandle&) = delete;
|
|
|
|
|
HdfHandle& operator=(const HdfHandle&) = delete;
|
|
|
|
|
hid_t Get() const { return value_; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
hid_t value_;
|
|
|
|
|
herr_t (*close_)(hid_t);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::vector<hsize_t> Shape(hid_t file, const std::string& path) {
|
|
|
|
|
HdfHandle data{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
|
|
|
|
|
HdfHandle space{H5Dget_space(data.Get()), H5Sclose};
|
|
|
|
|
const int rank = H5Sget_simple_extent_ndims(space.Get());
|
|
|
|
|
if (rank < 0) throw std::runtime_error{"Invalid HDF test shape."};
|
|
|
|
|
std::vector<hsize_t> shape(static_cast<std::size_t>(rank));
|
|
|
|
|
EXPECT_GE(H5Sget_simple_extent_dims(space.Get(), shape.data(), nullptr), 0);
|
|
|
|
|
return shape;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<double> Doubles(hid_t file, const std::string& path) {
|
|
|
|
|
std::size_t count = 1;
|
|
|
|
|
for (auto extent : Shape(file, path))
|
|
|
|
|
count *= static_cast<std::size_t>(extent);
|
|
|
|
|
std::vector<double> result(count);
|
|
|
|
|
HdfHandle data{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
|
|
|
|
|
EXPECT_GE(H5Dread(data.Get(), H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
|
|
|
|
H5P_DEFAULT, result.data()),
|
|
|
|
|
0);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string StringAttribute(hid_t object, const char* name) {
|
|
|
|
|
HdfHandle attribute{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
|
|
|
|
HdfHandle type{H5Aget_type(attribute.Get()), H5Tclose};
|
|
|
|
|
char* value = nullptr;
|
|
|
|
|
EXPECT_GE(H5Aread(attribute.Get(), type.Get(), &value), 0);
|
|
|
|
|
const std::string result = value == nullptr ? "" : value;
|
|
|
|
|
if (value != nullptr) H5free_memory(value);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct FrameRow {
|
|
|
|
|
std::uint64_t frame_index;
|
|
|
|
|
std::uint64_t increment_index;
|
|
|
|
|
double time;
|
|
|
|
|
double time_increment;
|
|
|
|
|
std::uint8_t is_initial;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::vector<FrameRow> Frames(hid_t file) {
|
|
|
|
|
const std::string path = "/steps/Step-1/accepted_frames";
|
|
|
|
|
const auto shape = Shape(file, path);
|
|
|
|
|
if (shape.size() != 1) throw std::runtime_error{"Invalid frame shape."};
|
|
|
|
|
std::vector<FrameRow> frames(static_cast<std::size_t>(shape[0]));
|
|
|
|
|
HdfHandle type{H5Tcreate(H5T_COMPOUND, sizeof(FrameRow)), H5Tclose};
|
|
|
|
|
EXPECT_GE(H5Tinsert(type.Get(), "frame_index", HOFFSET(FrameRow, frame_index),
|
|
|
|
|
H5T_NATIVE_UINT64),
|
|
|
|
|
0);
|
|
|
|
|
EXPECT_GE(H5Tinsert(type.Get(), "increment_index",
|
|
|
|
|
HOFFSET(FrameRow, increment_index), H5T_NATIVE_UINT64),
|
|
|
|
|
0);
|
|
|
|
|
EXPECT_GE(
|
|
|
|
|
H5Tinsert(type.Get(), "time", HOFFSET(FrameRow, time), H5T_NATIVE_DOUBLE),
|
|
|
|
|
0);
|
|
|
|
|
EXPECT_GE(H5Tinsert(type.Get(), "time_increment",
|
|
|
|
|
HOFFSET(FrameRow, time_increment), H5T_NATIVE_DOUBLE),
|
|
|
|
|
0);
|
|
|
|
|
EXPECT_GE(H5Tinsert(type.Get(), "is_initial", HOFFSET(FrameRow, is_initial),
|
|
|
|
|
H5T_NATIVE_UINT8),
|
|
|
|
|
0);
|
|
|
|
|
HdfHandle data{H5Dopen2(file, path.c_str(), H5P_DEFAULT), H5Dclose};
|
|
|
|
|
EXPECT_GE(H5Dread(data.Get(), type.Get(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
|
|
|
|
|
frames.data()),
|
|
|
|
|
0);
|
|
|
|
|
return frames;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string ReadBytes(const std::filesystem::path& path) {
|
|
|
|
|
std::ifstream input{path, std::ios::binary};
|
|
|
|
|
return {std::istreambuf_iterator<char>{input},
|
|
|
|
|
std::istreambuf_iterator<char>{}};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string Replace(std::string text, const std::string& from,
|
|
|
|
|
const std::string& to) {
|
|
|
|
|
const auto position = text.find(from);
|
|
|
|
|
if (position == std::string::npos)
|
|
|
|
|
throw std::runtime_error{"Missing fixture text."};
|
|
|
|
|
text.replace(position, from.size(), to);
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AppRun {
|
|
|
|
|
int exit_code;
|
|
|
|
|
std::string error;
|
|
|
|
|
};
|
|
|
|
|
AppRun RunApplication(const std::filesystem::path& input) {
|
|
|
|
|
testing::internal::CaptureStderr();
|
|
|
|
|
const int result = fesa::FesaApplication{}.Run({input.string()});
|
|
|
|
|
return {result, testing::internal::GetCapturedStderr()};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ThermalApplicationTest : public testing::Test {
|
|
|
|
|
protected:
|
|
|
|
|
void SetUp() override {
|
|
|
|
|
static std::atomic<unsigned long> counter{0};
|
|
|
|
|
do {
|
|
|
|
|
directory_ =
|
|
|
|
|
std::filesystem::temp_directory_path() /
|
|
|
|
|
("fesa-thermal-app-" + std::to_string(std::random_device{}()) + "-" +
|
|
|
|
|
std::to_string(counter.fetch_add(1)));
|
|
|
|
|
} while (!std::filesystem::create_directory(directory_));
|
|
|
|
|
}
|
|
|
|
|
void TearDown() override {
|
|
|
|
|
std::error_code error;
|
|
|
|
|
std::filesystem::remove_all(directory_, error);
|
|
|
|
|
}
|
|
|
|
|
std::filesystem::path Path(const std::string& name) const {
|
|
|
|
|
return directory_ / name;
|
|
|
|
|
}
|
|
|
|
|
void Write(const std::string& name, const std::string& text) {
|
|
|
|
|
std::ofstream output{Path(name), std::ios::binary};
|
|
|
|
|
output << text;
|
|
|
|
|
ASSERT_TRUE(output.good());
|
|
|
|
|
}
|
|
|
|
|
std::filesystem::path directory_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::string Deck(const std::string& controls = ".3,.7",
|
|
|
|
|
const std::string& initial = "-2") {
|
|
|
|
|
return R"inp(*Part,name=P
|
|
|
|
|
*Node
|
|
|
|
|
1,0,0,0
|
|
|
|
|
2,1,0,0
|
|
|
|
|
3,1,1,0
|
|
|
|
|
4,0,1,0
|
|
|
|
|
*Element,type=DS4
|
|
|
|
|
1,1,2,3,4
|
|
|
|
|
*Elset,elset=E
|
|
|
|
|
1
|
|
|
|
|
*Shell Section,elset=E,material=M
|
|
|
|
|
1,5
|
|
|
|
|
*End Part
|
|
|
|
|
*Assembly,name=A
|
|
|
|
|
*Instance,name=I,part=P
|
|
|
|
|
*End Instance
|
|
|
|
|
*Nset,nset=All,instance=I
|
|
|
|
|
1,2,3,4
|
|
|
|
|
*End Assembly
|
|
|
|
|
*Material,name=M
|
|
|
|
|
*Density
|
|
|
|
|
1
|
|
|
|
|
*Conductivity
|
|
|
|
|
1
|
|
|
|
|
*Specific Heat
|
|
|
|
|
1
|
|
|
|
|
*Initial Conditions,type=TEMPERATURE
|
|
|
|
|
All,)inp" +
|
|
|
|
|
initial + R"inp(
|
|
|
|
|
*Step,name=Raw Heat,inc=10
|
|
|
|
|
*Heat Transfer
|
|
|
|
|
)inp" + controls +
|
|
|
|
|
R"inp(
|
|
|
|
|
*Output,field,variable=PRESELECT
|
|
|
|
|
*End Step
|
|
|
|
|
)inp";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest, ExecutesThermalInputThroughAtomicOutput) {
|
|
|
|
|
Write("Heat.Model.INP", Deck());
|
|
|
|
|
const auto run = RunApplication(Path("Heat.Model.INP"));
|
|
|
|
|
ASSERT_EQ(run.exit_code, 0) << run.error;
|
|
|
|
|
EXPECT_GT(H5Fis_hdf5(Path("Heat.Model_results.h5").string().c_str()), 0);
|
|
|
|
|
EXPECT_EQ(std::filesystem::file_size(Path("Heat.Model_log.txt")), 0U);
|
|
|
|
|
HdfHandle file{H5Fopen(Path("Heat.Model_results.h5").string().c_str(),
|
|
|
|
|
H5F_ACC_RDONLY, H5P_DEFAULT),
|
|
|
|
|
H5Fclose};
|
|
|
|
|
HdfHandle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT), H5Gclose};
|
|
|
|
|
EXPECT_EQ(StringAttribute(metadata.Get(), "source_step_name"), "Raw Heat");
|
|
|
|
|
EXPECT_EQ(StringAttribute(metadata.Get(), "feature_id"),
|
|
|
|
|
"shell-transient-heat-transfer");
|
|
|
|
|
EXPECT_EQ(StringAttribute(metadata.Get(), "analysis_procedure"),
|
|
|
|
|
"uncoupled-transient-heat-transfer");
|
|
|
|
|
EXPECT_EQ(StringAttribute(metadata.Get(), "source_input_identity")
|
|
|
|
|
.find("path=" + Path("Heat.Model.INP").generic_u8string() +
|
|
|
|
|
";content_identity="),
|
|
|
|
|
0U);
|
|
|
|
|
const auto frames = Frames(file.Get());
|
|
|
|
|
ASSERT_EQ(frames.size(), 4U);
|
|
|
|
|
for (std::size_t index = 0; index < frames.size(); ++index) {
|
|
|
|
|
const auto& frame = frames[index];
|
|
|
|
|
EXPECT_EQ(frame.frame_index, index);
|
|
|
|
|
EXPECT_EQ(frame.increment_index, index);
|
|
|
|
|
EXPECT_EQ(frame.is_initial, index == 0 ? 1 : 0);
|
|
|
|
|
EXPECT_DOUBLE_EQ(frame.time,
|
|
|
|
|
index == 3 ? .7 : static_cast<double>(index) * .3);
|
|
|
|
|
EXPECT_DOUBLE_EQ(frame.time_increment, index == 0 ? 0
|
|
|
|
|
: index == 3 ? std::fma(-2., .3, .7)
|
|
|
|
|
: .3);
|
|
|
|
|
const auto root = "/steps/Step-1/frames/" + std::to_string(index);
|
|
|
|
|
EXPECT_EQ(Shape(file.Get(), root + "/nodal/temperature"),
|
|
|
|
|
(std::vector<hsize_t>{4, 5}));
|
|
|
|
|
for (double value : Doubles(file.Get(), root + "/nodal/temperature")) {
|
|
|
|
|
EXPECT_TRUE(std::isfinite(value));
|
|
|
|
|
EXPECT_LT(value, 0.);
|
|
|
|
|
if (index == 0) EXPECT_DOUBLE_EQ(value, -2.);
|
|
|
|
|
}
|
|
|
|
|
if (index == 0) {
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
H5Lexists(file.Get(), (root + "/nodal/reaction_heat_rate").c_str(),
|
|
|
|
|
H5P_DEFAULT),
|
|
|
|
|
0);
|
|
|
|
|
EXPECT_EQ(H5Lexists(file.Get(), (root + "/element").c_str(), H5P_DEFAULT),
|
|
|
|
|
0);
|
|
|
|
|
} else {
|
|
|
|
|
EXPECT_EQ(Shape(file.Get(), root + "/nodal/reaction_heat_rate"),
|
|
|
|
|
(std::vector<hsize_t>{0}));
|
|
|
|
|
EXPECT_EQ(Shape(file.Get(), root + "/element/heat_flux"),
|
|
|
|
|
(std::vector<hsize_t>{1, 4, 6, 3}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
EXPECT_EQ(Shape(file.Get(), "/diagnostics"), (std::vector<hsize_t>{1}));
|
|
|
|
|
HdfHandle diagnostics{H5Dopen2(file.Get(), "/diagnostics", H5P_DEFAULT),
|
|
|
|
|
H5Dclose};
|
|
|
|
|
HdfHandle string_type{H5Tcopy(H5T_C_S1), H5Tclose};
|
|
|
|
|
ASSERT_GE(H5Tset_size(string_type.Get(), H5T_VARIABLE), 0);
|
|
|
|
|
ASSERT_GE(H5Tset_cset(string_type.Get(), H5T_CSET_UTF8), 0);
|
|
|
|
|
HdfHandle row_type{H5Tcreate(H5T_COMPOUND, sizeof(char*)), H5Tclose};
|
|
|
|
|
ASSERT_GE(H5Tinsert(row_type.Get(), "code", 0, string_type.Get()), 0);
|
|
|
|
|
char* code = nullptr;
|
|
|
|
|
ASSERT_GE(H5Dread(diagnostics.Get(), row_type.Get(), H5S_ALL, H5S_ALL,
|
|
|
|
|
H5P_DEFAULT, &code),
|
|
|
|
|
0);
|
|
|
|
|
EXPECT_STREQ(code, "ignored-input-keyword");
|
|
|
|
|
H5free_memory(code);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest,
|
|
|
|
|
EndpointCloseUsesActualDeltaAndLargeIncrementUsesPeriod) {
|
|
|
|
|
for (const auto& controls : {".3,.9", "2,.7"}) {
|
|
|
|
|
SCOPED_TRACE(controls);
|
|
|
|
|
Write("model.inp", Deck(controls, "0"));
|
|
|
|
|
const auto run = RunApplication(Path("model.inp"));
|
|
|
|
|
ASSERT_EQ(run.exit_code, 0) << run.error;
|
|
|
|
|
HdfHandle file{H5Fopen(Path("model_results.h5").string().c_str(),
|
|
|
|
|
H5F_ACC_RDONLY, H5P_DEFAULT),
|
|
|
|
|
H5Fclose};
|
|
|
|
|
const auto frames = Frames(file.Get());
|
|
|
|
|
if (std::string{controls} == ".3,.9") {
|
|
|
|
|
ASSERT_EQ(frames.size(), 4U);
|
|
|
|
|
EXPECT_DOUBLE_EQ(frames.back().time, .9);
|
|
|
|
|
EXPECT_DOUBLE_EQ(frames.back().time_increment, .3);
|
|
|
|
|
EXPECT_NE(frames.back().time_increment, frames[3].time - frames[2].time);
|
|
|
|
|
} else {
|
|
|
|
|
ASSERT_EQ(frames.size(), 2U);
|
|
|
|
|
EXPECT_DOUBLE_EQ(frames.back().time, .7);
|
|
|
|
|
EXPECT_DOUBLE_EQ(frames.back().time_increment, .7);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest, RawInitialPrecedesPrescribedBoundaryProjection) {
|
|
|
|
|
auto deck = Replace(Deck(".1,.2"), "*Output,field",
|
|
|
|
|
"*Boundary\nAll,11,11,1\nAll,15,15,1\n*Output,field");
|
|
|
|
|
Write("model.inp", deck);
|
|
|
|
|
const auto run = RunApplication(Path("model.inp"));
|
|
|
|
|
ASSERT_EQ(run.exit_code, 0) << run.error;
|
|
|
|
|
HdfHandle file{H5Fopen(Path("model_results.h5").string().c_str(),
|
|
|
|
|
H5F_ACC_RDONLY, H5P_DEFAULT),
|
|
|
|
|
H5Fclose};
|
|
|
|
|
for (double value :
|
|
|
|
|
Doubles(file.Get(), "/steps/Step-1/frames/0/nodal/temperature"))
|
|
|
|
|
EXPECT_DOUBLE_EQ(value, -2);
|
|
|
|
|
for (int frame = 1; frame <= 2; ++frame) {
|
|
|
|
|
const auto root = "/steps/Step-1/frames/" + std::to_string(frame);
|
|
|
|
|
const auto temperature = Doubles(file.Get(), root + "/nodal/temperature");
|
|
|
|
|
for (std::size_t node = 0; node < 4; ++node) {
|
|
|
|
|
EXPECT_DOUBLE_EQ(temperature[5 * node], 1);
|
|
|
|
|
EXPECT_DOUBLE_EQ(temperature[5 * node + 4], 1);
|
|
|
|
|
}
|
|
|
|
|
EXPECT_EQ(Shape(file.Get(), root + "/nodal/reaction_heat_rate"),
|
|
|
|
|
(std::vector<hsize_t>{8}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest, InputAndSolverFailuresPreservePreviousFinal) {
|
|
|
|
|
const std::string previous{"previous final\0bytes", 20};
|
|
|
|
|
for (const auto& test : std::vector<std::pair<std::string, int>>{
|
|
|
|
|
{Replace(Deck(), "*Output,field",
|
|
|
|
|
"*Boundary\nAll,12,12,1\n*Output,field"),
|
|
|
|
|
3},
|
|
|
|
|
{Deck("1.e-300,1.e-300", "1.e200"), 5}}) {
|
|
|
|
|
Write("model.inp", test.first);
|
|
|
|
|
Write("model_results.h5", previous);
|
|
|
|
|
const auto run = RunApplication(Path("model.inp"));
|
|
|
|
|
EXPECT_EQ(run.exit_code, test.second) << run.error;
|
|
|
|
|
EXPECT_FALSE(run.error.empty());
|
|
|
|
|
EXPECT_EQ(ReadBytes(Path("model_results.h5")), previous);
|
|
|
|
|
EXPECT_EQ(std::filesystem::file_size(Path("model_log.txt")), 0U);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest,
|
|
|
|
|
BusyLeasePrecedesParsingAndPreservesBothOutputs) {
|
|
|
|
|
Write("model.inp", "deliberately invalid input");
|
|
|
|
|
Write("model_results.h5", "previous final");
|
|
|
|
|
Write("model_log.txt", "previous log");
|
|
|
|
|
fesa::DerivedOutputPaths paths{Path("model.inp"), Path("model_results.h5"),
|
|
|
|
|
Path("model_log.txt")};
|
|
|
|
|
auto lease = fesa::AcquireOutputWriteLease(paths);
|
|
|
|
|
ASSERT_TRUE(lease.HasValue());
|
|
|
|
|
const auto run = RunApplication(paths.input_path);
|
|
|
|
|
EXPECT_EQ(run.exit_code, 6);
|
|
|
|
|
EXPECT_NE(run.error.find("code=output-busy"), std::string::npos);
|
|
|
|
|
EXPECT_EQ(ReadBytes(paths.results_path), "previous final");
|
|
|
|
|
EXPECT_EQ(ReadBytes(paths.log_path), "previous log");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest,
|
|
|
|
|
RealFinalizeFailurePreservesFinalAndRemovesCandidate) {
|
|
|
|
|
Write("model.inp", Deck(".1,.1", "0"));
|
|
|
|
|
Write("model_results.h5", "previous final");
|
|
|
|
|
// Deny replacement of this final while permitting reads. The output lease is
|
|
|
|
|
// separate; the candidate must reach the actual atomic finalize operation.
|
|
|
|
|
const HANDLE locked =
|
|
|
|
|
CreateFileW(Path("model_results.h5").c_str(), GENERIC_READ,
|
|
|
|
|
FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
|
|
|
|
ASSERT_NE(locked, INVALID_HANDLE_VALUE);
|
|
|
|
|
const auto run = RunApplication(Path("model.inp"));
|
|
|
|
|
EXPECT_TRUE(CloseHandle(locked));
|
|
|
|
|
EXPECT_EQ(run.exit_code, 6) << run.error;
|
|
|
|
|
EXPECT_EQ(ReadBytes(Path("model_results.h5")), "previous final");
|
|
|
|
|
EXPECT_EQ(std::filesystem::file_size(Path("model_log.txt")), 0U);
|
|
|
|
|
for (const auto& entry : std::filesystem::directory_iterator{directory_}) {
|
|
|
|
|
EXPECT_EQ(entry.path().filename().string().find(".tmp."),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest, MissingAdaptersHaveSpecificFailureCategories) {
|
|
|
|
|
Write("model.inp", Deck(".1,.1", "0"));
|
|
|
|
|
auto prepared = fesa::PrepareAnalysisInput(Path("model.inp"));
|
|
|
|
|
ASSERT_TRUE(prepared.HasValue());
|
|
|
|
|
ASSERT_EQ(prepared.Value().domain.Steps().Size(), 0U);
|
|
|
|
|
fesa::SerialParallelFor parallel;
|
|
|
|
|
fesa::MklPardisoSolver solver;
|
|
|
|
|
fesa::MklPardisoTangentSolver tangent;
|
|
|
|
|
fesa::LinearStaticProcedureExecutor linear{parallel, solver};
|
|
|
|
|
fesa::NonlinearStaticProcedureExecutor nonlinear{parallel, tangent};
|
|
|
|
|
fesa::LinearFrequencyProcedureExecutor frequency;
|
|
|
|
|
fesa::TransientHeatProcedureExecutor thermal{parallel, solver};
|
|
|
|
|
fesa::NewtonIterationLogWriter log;
|
|
|
|
|
ASSERT_TRUE(log.Open(Path("model_log.txt")).IsOk());
|
|
|
|
|
auto missing = fesa::ExecutePreparedAnalysis(prepared.Value().domain, linear,
|
|
|
|
|
nonlinear, frequency, log);
|
|
|
|
|
ASSERT_FALSE(missing.HasValue());
|
|
|
|
|
EXPECT_EQ(missing.GetStatus().Category(), fesa::FailureCategory::kSolver);
|
|
|
|
|
ASSERT_EQ(missing.GetStatus().Diagnostics().size(), 1U);
|
|
|
|
|
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].code,
|
|
|
|
|
"missing-thermal-executor");
|
|
|
|
|
auto outcome =
|
|
|
|
|
fesa::ExecutePreparedAnalysis(prepared.Value().domain, linear, nonlinear,
|
|
|
|
|
frequency, log, nullptr, &thermal);
|
|
|
|
|
ASSERT_TRUE(outcome.HasValue());
|
|
|
|
|
ASSERT_TRUE(
|
|
|
|
|
std::holds_alternative<fesa::ThermalExecutionResult>(outcome.Value()));
|
|
|
|
|
ASSERT_TRUE(log.Close().IsOk());
|
|
|
|
|
fesa::Hdf5ResultsWriter linear_writer;
|
|
|
|
|
fesa::NonlinearHdf5ResultsWriter nonlinear_writer;
|
|
|
|
|
fesa::EigenHdf5ResultsWriter eigen_writer;
|
|
|
|
|
const fesa::DerivedOutputPaths paths{
|
|
|
|
|
Path("model.inp"), Path("model_results.h5"), Path("model_log.txt")};
|
|
|
|
|
Write("model_results.h5", "previous final");
|
|
|
|
|
const auto status = fesa::FinalizePreparedAnalysisResult(
|
|
|
|
|
paths, prepared.Value().domain, outcome.Value(),
|
|
|
|
|
prepared.Value().diagnostics, linear_writer, nonlinear_writer,
|
|
|
|
|
eigen_writer);
|
|
|
|
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
|
|
|
|
|
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
|
|
|
|
EXPECT_EQ(status.Diagnostics()[0].code, "missing-thermal-writer");
|
|
|
|
|
EXPECT_EQ(ReadBytes(paths.results_path), "previous final");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest,
|
|
|
|
|
ExistingStructuralStaticNonlinearAndHhtRoutesRemainAvailable) {
|
|
|
|
|
auto base = Replace(Deck(), "type=DS4", "type=S4");
|
|
|
|
|
base = Replace(base, "*Conductivity\n1\n*Specific Heat\n1",
|
|
|
|
|
"*Elastic\n1000,.25");
|
|
|
|
|
base.erase(base.find("*Initial Conditions"));
|
|
|
|
|
base += "*Boundary\nAll,1,6\n";
|
|
|
|
|
for (const auto& step :
|
|
|
|
|
{"*Step,name=Structural,nlgeom=NO\n*Static\n.1,.1,.01,.1\n*End Step\n",
|
|
|
|
|
"*Step,name=Structural,nlgeom=YES\n*Static\n.1,1.,.01,.1\n*End Step\n",
|
|
|
|
|
"*Step,name=Structural,nlgeom=YES,inc=10\n*Dynamic,direct\n.1,.2\n*End "
|
|
|
|
|
"Step\n"}) {
|
|
|
|
|
SCOPED_TRACE(step);
|
|
|
|
|
const bool dynamic =
|
|
|
|
|
std::string{step}.find("*Dynamic") != std::string::npos;
|
|
|
|
|
Write("model.inp",
|
|
|
|
|
(dynamic ? base : Replace(base, "*Density\n1\n", "")) + step);
|
|
|
|
|
const auto run = RunApplication(Path("model.inp"));
|
|
|
|
|
ASSERT_EQ(run.exit_code, 0) << run.error;
|
|
|
|
|
EXPECT_GT(H5Fis_hdf5(Path("model_results.h5").string().c_str()), 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ThermalApplicationTest,
|
|
|
|
|
NativeExecutableAcceptsThermalInputAndRejectsInteriorBoundary) {
|
|
|
|
|
std::vector<wchar_t> module(32768);
|
|
|
|
|
const DWORD length = GetModuleFileNameW(nullptr, module.data(),
|
|
|
|
|
static_cast<DWORD>(module.size()));
|
|
|
|
|
ASSERT_GT(length, 0U);
|
|
|
|
|
ASSERT_LT(length, module.size());
|
|
|
|
|
const auto executable =
|
|
|
|
|
std::filesystem::path{std::wstring{module.data(), length}}
|
|
|
|
|
.parent_path()
|
|
|
|
|
.parent_path() /
|
|
|
|
|
"src/fesa/fesa.exe";
|
|
|
|
|
ASSERT_TRUE(std::filesystem::exists(executable));
|
|
|
|
|
std::string previous;
|
|
|
|
|
for (const bool valid : {true, false}) {
|
|
|
|
|
Write("native.inp", valid
|
|
|
|
|
? Deck(".1,.1", "0")
|
|
|
|
|
: Replace(Deck(), "*Output,field",
|
|
|
|
|
"*Boundary\nAll,12,12,1\n*Output,field"));
|
|
|
|
|
std::wstring command = L"\"" + executable.wstring() + L"\" \"" +
|
|
|
|
|
Path("native.inp").wstring() + L"\"";
|
|
|
|
|
STARTUPINFOW startup{};
|
|
|
|
|
startup.cb = sizeof(startup);
|
|
|
|
|
PROCESS_INFORMATION process{};
|
|
|
|
|
ASSERT_TRUE(CreateProcessW(executable.c_str(), command.data(), nullptr,
|
|
|
|
|
nullptr, FALSE, CREATE_NO_WINDOW, nullptr,
|
|
|
|
|
nullptr, &startup, &process));
|
|
|
|
|
const DWORD wait = WaitForSingleObject(process.hProcess, 60000);
|
|
|
|
|
if (wait != WAIT_OBJECT_0) TerminateProcess(process.hProcess, 99);
|
|
|
|
|
DWORD exit = 99;
|
|
|
|
|
EXPECT_TRUE(GetExitCodeProcess(process.hProcess, &exit));
|
|
|
|
|
CloseHandle(process.hThread);
|
|
|
|
|
CloseHandle(process.hProcess);
|
|
|
|
|
ASSERT_EQ(wait, WAIT_OBJECT_0);
|
|
|
|
|
EXPECT_EQ(exit, valid ? 0U : 3U);
|
|
|
|
|
EXPECT_GT(H5Fis_hdf5(Path("native_results.h5").string().c_str()), 0);
|
|
|
|
|
if (valid) {
|
|
|
|
|
previous = ReadBytes(Path("native_results.h5"));
|
|
|
|
|
} else {
|
|
|
|
|
EXPECT_EQ(ReadBytes(Path("native_results.h5")), previous);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|