273 lines
8.8 KiB
C++
273 lines
8.8 KiB
C++
#include "fesa/analysis/linear_static_analysis.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <hdf5.h>
|
|
|
|
#include "fesa/analysis/analysis.h"
|
|
#include "fesa/app/fesa_application.h"
|
|
#include "fesa/assembly/parallel_for.h"
|
|
#include "fesa/results/results_writer.h"
|
|
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
|
|
|
namespace {
|
|
|
|
class TempDirectory {
|
|
public:
|
|
explicit TempDirectory(const std::string& label) {
|
|
static std::atomic<unsigned long long> sequence{0U};
|
|
const auto tick =
|
|
std::chrono::steady_clock::now().time_since_epoch().count();
|
|
path_ = std::filesystem::temp_directory_path() /
|
|
("fesa-surface-pressure-linear-" + label + "-" +
|
|
std::to_string(tick) + "-" +
|
|
std::to_string(sequence.fetch_add(1U)));
|
|
std::error_code error;
|
|
if (!std::filesystem::create_directory(path_, error) || error) {
|
|
throw std::runtime_error{"Unable to create surface pressure fixture."};
|
|
}
|
|
}
|
|
|
|
TempDirectory(const TempDirectory&) = delete;
|
|
TempDirectory& operator=(const TempDirectory&) = delete;
|
|
|
|
~TempDirectory() {
|
|
std::error_code ignored;
|
|
std::filesystem::remove_all(path_, ignored);
|
|
}
|
|
|
|
const std::filesystem::path& Path() const noexcept { return path_; }
|
|
|
|
private:
|
|
std::filesystem::path path_;
|
|
};
|
|
|
|
void WriteText(const std::filesystem::path& path, const std::string& text) {
|
|
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
|
|
stream.write(text.data(), static_cast<std::streamsize>(text.size()));
|
|
if (!stream) {
|
|
throw std::runtime_error{"Unable to write surface pressure input."};
|
|
}
|
|
}
|
|
|
|
std::string ShellDeck(const std::string& source_type,
|
|
const std::string& connectivity,
|
|
const bool prescribed_displacement,
|
|
const bool with_pressure) {
|
|
std::string boundary;
|
|
if (prescribed_displacement) {
|
|
boundary = R"inp(*Boundary
|
|
N1, 1, 6
|
|
N2, 1, 2
|
|
N2, 3, 3, 5.0
|
|
N2, 4, 6
|
|
N3, 1, 6
|
|
N4, 1, 6
|
|
)inp";
|
|
} else {
|
|
boundary = "*Boundary\nAll, 1, 6\n";
|
|
}
|
|
const std::string surface = with_pressure
|
|
? "*Surface, TYPE=ELEMENT, NAME=Pressure\n"
|
|
"Shell-1.1, SPOS\n"
|
|
: "";
|
|
const std::string dsload = with_pressure
|
|
? "*Dsload\nPressure, P, 4.0\n"
|
|
: "";
|
|
return std::string{R"inp(*Part, name=ShellPart
|
|
*Node
|
|
1, 0., 0., 0.
|
|
2, 1., 0., 0.
|
|
3, 1., 1., 0.
|
|
4, 0., 1., 0.
|
|
*Element, type=)inp"} +
|
|
source_type + "\n1, " + connectivity + R"inp(
|
|
*Elset, elset=ShellSet
|
|
1
|
|
*Shell Section, elset=ShellSet, material=Steel
|
|
0.1
|
|
*End Part
|
|
*Assembly, name=Assembly
|
|
*Instance, name=Shell-1, part=ShellPart
|
|
*End Instance
|
|
)inp" +
|
|
surface + R"inp(*Nset, nset=N1, instance=Shell-1
|
|
1
|
|
*Nset, nset=N2, instance=Shell-1
|
|
2
|
|
*Nset, nset=N3, instance=Shell-1
|
|
3
|
|
*Nset, nset=N4, instance=Shell-1
|
|
4
|
|
*Nset, nset=All, instance=Shell-1
|
|
1, 2, 3, 4
|
|
*End Assembly
|
|
*Material, name=Steel
|
|
*Elastic
|
|
1000., 0.25
|
|
)inp" +
|
|
boundary + R"inp(*Step, name=Load, nlgeom=NO
|
|
*Static
|
|
0.1, 1., 0.01, 1.
|
|
)inp" +
|
|
dsload + "*End Step\n";
|
|
}
|
|
|
|
class CapturingResultsWriter final : public fesa::ResultsWriter {
|
|
public:
|
|
fesa::Status Write(const std::filesystem::path&, const fesa::Domain&,
|
|
const fesa::AnalysisState& state,
|
|
const std::vector<fesa::Diagnostic>&) override {
|
|
state_ = std::make_unique<fesa::AnalysisState>(state);
|
|
return fesa::Status::Ok();
|
|
}
|
|
|
|
const fesa::AnalysisState& State() const {
|
|
if (!state_) {
|
|
throw std::logic_error{"No linear state was captured."};
|
|
}
|
|
return *state_;
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<fesa::AnalysisState> state_;
|
|
};
|
|
|
|
fesa::AnalysisState RunLinear(const std::filesystem::path& input,
|
|
const std::filesystem::path& output) {
|
|
fesa::SerialParallelFor serial;
|
|
fesa::MklPardisoSolver solver;
|
|
CapturingResultsWriter writer;
|
|
std::unique_ptr<fesa::Analysis> analysis =
|
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, solver, writer);
|
|
const fesa::Status status = analysis->Run({input, output});
|
|
if (!status.IsOk()) {
|
|
throw std::runtime_error{status.Diagnostics().empty()
|
|
? "Linear pressure analysis failed."
|
|
: status.Diagnostics().front().code};
|
|
}
|
|
return writer.State();
|
|
}
|
|
|
|
std::array<double, 3> SumTranslation(const fesa::Vector& values) {
|
|
std::array<double, 3> sum{};
|
|
for (std::size_t node = 0U; node < values.Size() / 6U; ++node) {
|
|
for (std::size_t component = 0U; component < 3U; ++component) {
|
|
sum[component] += values[node * 6U + component];
|
|
}
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(LinearShellSurfacePressure,
|
|
UsesReferenceNormalAndAreaAfterPrescribedDisplacement) {
|
|
TempDirectory directory{"reference-configuration"};
|
|
const auto input = directory.Path() / "prescribed.inp";
|
|
WriteText(input, ShellDeck("S4", "1, 2, 3, 4", true, true));
|
|
|
|
const fesa::AnalysisState state =
|
|
RunLinear(input, directory.Path() / "captured.h5");
|
|
|
|
EXPECT_DOUBLE_EQ(state.Displacement()[8U], 5.0);
|
|
EXPECT_EQ(SumTranslation(state.ExternalForce()),
|
|
(std::array<double, 3>{0.0, 0.0, -4.0}));
|
|
}
|
|
|
|
TEST(LinearShellSurfacePressure,
|
|
AssemblesBeforeProjectionAndIncludesConstrainedReaction) {
|
|
TempDirectory directory{"reaction"};
|
|
const auto input = directory.Path() / "reaction.inp";
|
|
WriteText(input, ShellDeck("S4", "1, 2, 3, 4", false, true));
|
|
|
|
const fesa::AnalysisState state =
|
|
RunLinear(input, directory.Path() / "captured.h5");
|
|
|
|
EXPECT_EQ(SumTranslation(state.ExternalForce()),
|
|
(std::array<double, 3>{0.0, 0.0, -4.0}));
|
|
EXPECT_EQ(SumTranslation(state.Reaction()),
|
|
(std::array<double, 3>{0.0, 0.0, 4.0}));
|
|
}
|
|
|
|
TEST(LinearShellSurfacePressure,
|
|
SatisfiesFreeResidualGlobalResultantAndMoment) {
|
|
TempDirectory directory{"equilibrium"};
|
|
const auto input = directory.Path() / "equilibrium.inp";
|
|
WriteText(input, ShellDeck("S4", "1, 2, 3, 4", false, true));
|
|
|
|
const fesa::AnalysisState state =
|
|
RunLinear(input, directory.Path() / "captured.h5");
|
|
|
|
for (const double value : state.Equilibrium()) {
|
|
EXPECT_NEAR(value, 0.0, 1.0e-8);
|
|
}
|
|
EXPECT_NEAR(state.VerificationMetrics()[0U], 0.0, 1.0e-10);
|
|
}
|
|
|
|
TEST(LinearShellSurfacePressure, PreservesS4S4rMeshAndCyclicOrderInvariance) {
|
|
TempDirectory directory{"source-invariance"};
|
|
const auto s4_input = directory.Path() / "s4.inp";
|
|
const auto s4r_input = directory.Path() / "s4r.inp";
|
|
const auto cyclic_input = directory.Path() / "cyclic.inp";
|
|
WriteText(s4_input, ShellDeck("S4", "1, 2, 3, 4", false, true));
|
|
WriteText(s4r_input, ShellDeck("S4R", "1, 2, 3, 4", false, true));
|
|
WriteText(cyclic_input, ShellDeck("S4", "2, 3, 4, 1", false, true));
|
|
|
|
const fesa::AnalysisState s4 =
|
|
RunLinear(s4_input, directory.Path() / "s4.h5");
|
|
const fesa::AnalysisState s4r =
|
|
RunLinear(s4r_input, directory.Path() / "s4r.h5");
|
|
const fesa::AnalysisState cyclic =
|
|
RunLinear(cyclic_input, directory.Path() / "cyclic.h5");
|
|
|
|
ASSERT_EQ(s4.ExternalForce().Size(), s4r.ExternalForce().Size());
|
|
ASSERT_EQ(s4.ExternalForce().Size(), cyclic.ExternalForce().Size());
|
|
for (std::size_t index = 0U; index < s4.ExternalForce().Size(); ++index) {
|
|
EXPECT_DOUBLE_EQ(s4.ExternalForce()[index], s4r.ExternalForce()[index]);
|
|
EXPECT_DOUBLE_EQ(s4.ExternalForce()[index], cyclic.ExternalForce()[index]);
|
|
EXPECT_DOUBLE_EQ(s4.Reaction()[index], s4r.Reaction()[index]);
|
|
EXPECT_DOUBLE_EQ(s4.Reaction()[index], cyclic.Reaction()[index]);
|
|
}
|
|
}
|
|
|
|
TEST(LinearShellSurfacePressure, WritesSchemaZeroWithoutPressureDataset) {
|
|
TempDirectory directory{"schema-zero"};
|
|
const auto input = directory.Path() / "pressure.inp";
|
|
const auto output = directory.Path() / "pressure_results.h5";
|
|
WriteText(input, ShellDeck("S4", "1, 2, 3, 4", false, true));
|
|
|
|
EXPECT_EQ(fesa::FesaApplication{}.Run({input.string()}), 0);
|
|
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
|
const hid_t file =
|
|
H5Fopen(output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
|
|
ASSERT_GE(file, 0);
|
|
const hid_t metadata = H5Gopen2(file, "/metadata", H5P_DEFAULT);
|
|
ASSERT_GE(metadata, 0);
|
|
const hid_t schema = H5Aopen(metadata, "schema_version", H5P_DEFAULT);
|
|
ASSERT_GE(schema, 0);
|
|
std::uint64_t schema_version = 0U;
|
|
ASSERT_GE(H5Aread(schema, H5T_NATIVE_UINT64, &schema_version), 0);
|
|
EXPECT_EQ(schema_version, 0U);
|
|
EXPECT_EQ(H5Lexists(file, "/steps/Step-1/frames/0/surface_pressure",
|
|
H5P_DEFAULT),
|
|
0);
|
|
EXPECT_EQ(H5Lexists(file, "/pressure", H5P_DEFAULT), 0);
|
|
EXPECT_GE(H5Aclose(schema), 0);
|
|
EXPECT_GE(H5Gclose(metadata), 0);
|
|
EXPECT_GE(H5Fclose(file), 0);
|
|
}
|