Files
FESA/tests/reference/cantilever_reference_test.cpp
T

242 lines
8.1 KiB
C++

#include <fesa/analysis/run_solver.hpp>
#include <fesa/io/hdf5/writer.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
namespace {
constexpr double kEquilibriumRelativeTolerance = 2.0e-13;
constexpr double kEquilibriumAbsoluteTolerance = 1.0e-12;
constexpr std::string_view kInstanceName = "PART-1_1-1";
class TemporaryPath final {
public:
explicit TemporaryPath(std::filesystem::path path)
: path_{std::move(path)} {
std::error_code error;
std::filesystem::create_directories(path_.parent_path(), error);
if (error) {
throw std::runtime_error{
"Failed to create reference test directory."};
}
std::filesystem::remove(path_, error);
}
~TemporaryPath() {
std::error_code error;
std::filesystem::remove(path_, error);
}
TemporaryPath(const TemporaryPath&) = delete;
TemporaryPath& operator=(const TemporaryPath&) = delete;
[[nodiscard]] const std::filesystem::path& path() const noexcept {
return path_;
}
private:
std::filesystem::path path_;
};
std::filesystem::path reference_path(const std::string_view name) {
return std::filesystem::path{FESA_REPOSITORY_ROOT} / "reference" /
"cantilever beam" / name;
}
std::filesystem::path test_output_path(const std::string_view name) {
return std::filesystem::path{FESA_TEST_BINARY_DIR} / "testing" / name;
}
std::string quote(const std::filesystem::path& path) {
return '"' + path.string() + '"';
}
std::string read_text(const std::filesystem::path& path) {
std::ifstream input{path, std::ios::binary};
return {
std::istreambuf_iterator<char>{input},
std::istreambuf_iterator<char>{},
};
}
const fesa::Vec3& coordinates_for(
const fesa::Hdf5ModelSnapshot& model,
const fesa::NodeId node_id) {
const auto found = std::ranges::find_if(
model.nodes,
[node_id](const fesa::Hdf5NodeSnapshot& node) {
return node.id == node_id;
});
if (found == model.nodes.end()) {
throw std::runtime_error{
"Result or load references an unknown node ID."};
}
return found->coordinates;
}
void add_nodal_resultant(
std::array<double, 3>& force,
std::array<double, 3>& moment,
const fesa::Vec3& position,
const std::array<double, 6>& values) {
force[0] += values[0];
force[1] += values[1];
force[2] += values[2];
moment[0] += values[3] + position.y * values[2] -
position.z * values[1];
moment[1] += values[4] + position.z * values[0] -
position.x * values[2];
moment[2] += values[5] + position.x * values[1] -
position.y * values[0];
}
void expect_finite(const fesa::ResultFrame& frame) {
for (const auto& displacement : frame.nodal.displacement) {
for (const double value : displacement) {
EXPECT_TRUE(std::isfinite(value));
}
}
for (const auto& reaction : frame.nodal.reaction) {
for (const double value : reaction) {
EXPECT_TRUE(std::isfinite(value));
}
}
for (const fesa::BeamElementFrame& beam : frame.element.beams) {
EXPECT_TRUE(fesa::is_finite(beam.local_frame.ex));
EXPECT_TRUE(fesa::is_finite(beam.local_frame.ey));
EXPECT_TRUE(fesa::is_finite(beam.local_frame.ez));
for (const fesa::BeamSectionResult& end : beam.end_results) {
EXPECT_TRUE(std::isfinite(end.xi));
EXPECT_TRUE(std::isfinite(end.centroid_sigma_xx));
for (const double value : end.section_strain) {
EXPECT_TRUE(std::isfinite(value));
}
for (const double value : end.section_force) {
EXPECT_TRUE(std::isfinite(value));
}
for (const double value : end.sigma_xx) {
EXPECT_TRUE(std::isfinite(value));
}
}
}
}
TEST(CantileverReference, CorrelatesAllAvailableAbaqusResults) {
const TemporaryPath results{
test_output_path("cantilever-reference.h5")};
const TemporaryPath standard_output{
test_output_path("cantilever-reference-compare.stdout.txt")};
const TemporaryPath error_output{
test_output_path("cantilever-reference-compare.stderr.txt")};
const fesa::AnalysisRunResult run = fesa::run_solver({
reference_path("cantilever beam fesa.inp"),
results.path(),
});
ASSERT_TRUE(run.succeeded);
ASSERT_TRUE(run.diagnostics.empty());
const fesa::Hdf5ReadResult read =
fesa::read_hdf5_results(results.path());
ASSERT_TRUE(read.diagnostics.empty());
ASSERT_TRUE(read.database.has_value());
ASSERT_TRUE(read.model.has_value());
ASSERT_TRUE(read.analysis.has_value());
ASSERT_EQ(read.database->steps.size(), 1U);
ASSERT_EQ(read.database->steps[0].frames.size(), 1U);
const fesa::ResultFrame& frame =
read.database->steps[0].frames[0];
expect_finite(frame);
std::array<double, 3> total_force{};
std::array<double, 3> total_moment{};
std::array<double, 3> applied_force{};
std::array<double, 3> applied_moment{};
ASSERT_EQ(frame.nodal.node_ids.size(), frame.nodal.reaction.size());
for (std::size_t index = 0; index < frame.nodal.node_ids.size(); ++index) {
add_nodal_resultant(
total_force,
total_moment,
coordinates_for(*read.model, frame.nodal.node_ids[index]),
frame.nodal.reaction[index]);
}
for (const fesa::NodalLoad& load : read.analysis->step.nodal_loads) {
const fesa::Vec3& position =
coordinates_for(*read.model, load.node);
add_nodal_resultant(
total_force,
total_moment,
position,
load.values);
add_nodal_resultant(
applied_force,
applied_moment,
position,
load.values);
}
for (std::size_t component = 0; component < total_force.size(); ++component) {
const double tolerance = std::max(
kEquilibriumAbsoluteTolerance,
kEquilibriumRelativeTolerance *
std::abs(applied_force[component]));
EXPECT_NEAR(total_force[component], 0.0, tolerance);
}
for (std::size_t component = 0; component < total_moment.size(); ++component) {
const double tolerance = std::max(
kEquilibriumAbsoluteTolerance,
kEquilibriumRelativeTolerance *
std::abs(applied_moment[component]));
EXPECT_NEAR(total_moment[component], 0.0, tolerance);
}
const std::string command =
'"' + quote(std::filesystem::path{FESA_REFERENCE_COMPARE_PATH}) +
" --results " + quote(results.path()) +
" --instance " + std::string{kInstanceName} +
" --displacements " +
quote(reference_path("cantilever beam displacements.csv")) +
" --reactions " +
quote(reference_path("cantilever beam reactions.csv")) +
" --internal-forces " +
quote(reference_path("cantilever beam elemental forces.csv")) +
std::string{" --displacement-absolute-scale 1e-10"} +
" --reaction-absolute-scale 1e-8" +
" --internal-force-absolute-scale 1e-8" +
" 1>" + quote(standard_output.path()) +
" 2>" + quote(error_output.path()) + '"';
const int exit_code = std::system(command.c_str());
const std::string standard_text = read_text(standard_output.path());
const std::string error_text = read_text(error_output.path());
EXPECT_EQ(exit_code, 0)
<< standard_text << error_text;
EXPECT_NE(
standard_text.find("quantity=displacement"),
std::string::npos);
EXPECT_NE(
standard_text.find("quantity=reaction"),
std::string::npos);
EXPECT_NE(
standard_text.find("quantity=internal_force"),
std::string::npos);
EXPECT_NE(standard_text.find("rmse="), std::string::npos);
EXPECT_NE(standard_text.find("relative_l2="), std::string::npos);
}
} // namespace