196 lines
6.4 KiB
C++
196 lines
6.4 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <atomic>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
#include "fesa/io/abaqus/domain_mapper.h"
|
|
#include "fesa/io/abaqus/input_reader.h"
|
|
|
|
namespace {
|
|
|
|
class TemporaryInput final {
|
|
public:
|
|
explicit TemporaryInput(const std::string& content)
|
|
: path_{std::filesystem::temp_directory_path() /
|
|
("fesa-thermal-domain-" + std::to_string(next_id_.fetch_add(1U)) +
|
|
".inp")} {
|
|
std::ofstream output{path_, std::ios::binary | std::ios::trunc};
|
|
output << content;
|
|
if (!output) throw std::runtime_error{"Unable to write thermal fixture."};
|
|
}
|
|
~TemporaryInput() {
|
|
std::error_code error;
|
|
std::filesystem::remove(path_, error);
|
|
}
|
|
const std::filesystem::path& Path() const noexcept { return path_; }
|
|
|
|
private:
|
|
static std::atomic<unsigned long> next_id_;
|
|
std::filesystem::path path_;
|
|
};
|
|
|
|
std::atomic<unsigned long> TemporaryInput::next_id_{0U};
|
|
|
|
fesa::Result<fesa::Domain> MapText(const std::string& text) {
|
|
const TemporaryInput input{text};
|
|
const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
|
|
if (!parsed.HasValue())
|
|
return fesa::Result<fesa::Domain>::Failure(parsed.GetStatus());
|
|
return fesa::AbaqusDomainMapper{}.Map(parsed.Value());
|
|
}
|
|
|
|
std::string ThermalDeck() {
|
|
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
|
|
.5, 5
|
|
*End Part
|
|
*Assembly, name=A
|
|
*Instance, name=I, part=P
|
|
*End Instance
|
|
*Nset, nset=Bottom, instance=I
|
|
1
|
|
2
|
|
*End Assembly
|
|
*Material, name=M
|
|
*Density
|
|
7850.,
|
|
*Conductivity
|
|
54.,
|
|
*Specific Heat
|
|
460.,
|
|
*Initial Conditions, type=TEMPERATURE
|
|
Bottom, 20.
|
|
*Boundary
|
|
Bottom, 11, 11
|
|
*Step, name=Heat, inc=100
|
|
*Heat Transfer
|
|
1., 100.
|
|
*Boundary
|
|
Bottom, 15, 15, 30.
|
|
*Cflux
|
|
Bottom, 11, -2.
|
|
*Cflux
|
|
Bottom, 11, -3.
|
|
*End Step
|
|
)inp";
|
|
}
|
|
|
|
TEST(ThermalDomainMapping, MapsDs4MaterialsTargetsAndAdditiveFluxes) {
|
|
const auto result = MapText(ThermalDeck());
|
|
ASSERT_TRUE(result.HasValue())
|
|
<< (result.HasValue() ? ""
|
|
: result.GetStatus().Diagnostics().front().message);
|
|
const auto& domain = result.Value();
|
|
ASSERT_EQ(domain.ThermalElements().Size(), 1U);
|
|
ASSERT_EQ(domain.ThermalMaterials().Size(), 1U);
|
|
EXPECT_DOUBLE_EQ(domain.ThermalMaterials()[0U].Conductivity(), 54.0);
|
|
ASSERT_NE(domain.ThermalStep(), nullptr);
|
|
EXPECT_EQ(domain.ThermalStep()->name, "Heat");
|
|
EXPECT_EQ(domain.ThermalStep()->maximum_increments, 100U);
|
|
EXPECT_EQ(domain.ThermalModelBoundaries().size(), 2U);
|
|
EXPECT_EQ(domain.ThermalStep()->Boundaries().size(), 2U);
|
|
ASSERT_EQ(domain.ThermalStep()->Loads().size(), 4U);
|
|
EXPECT_DOUBLE_EQ(domain.ThermalStep()->Loads()[0U].power, -2.0);
|
|
EXPECT_DOUBLE_EQ(domain.ThermalStep()->Loads()[3U].power, -3.0);
|
|
}
|
|
|
|
TEST(ThermalDomainMapping, RejectsInteriorAndConflictingThermalBoundaries) {
|
|
auto interior = ThermalDeck();
|
|
interior.replace(interior.find("Bottom, 11, 11\n"), 14U, "Bottom, 12, 12\n");
|
|
const auto invalid_dof = MapText(interior);
|
|
ASSERT_FALSE(invalid_dof.HasValue());
|
|
EXPECT_EQ(invalid_dof.GetStatus().Diagnostics()[0U].code, "invalid-dof");
|
|
|
|
auto conflicting = ThermalDeck();
|
|
conflicting.replace(conflicting.find("*Cflux"), 0U,
|
|
"*Boundary\nBottom, 15, 15, 99.\n");
|
|
const auto invalid_conflict = MapText(conflicting);
|
|
ASSERT_FALSE(invalid_conflict.HasValue());
|
|
EXPECT_EQ(invalid_conflict.GetStatus().Diagnostics()[0U].code,
|
|
"conflicting-boundary-condition");
|
|
}
|
|
|
|
TEST(ThermalDomainMapping,
|
|
AcceptsSectionPointSpellingsAndRejectsUnsupportedForms) {
|
|
const std::vector<std::pair<std::string, bool>> cases{
|
|
{".5\n", true}, {".5, 5\n", true}, {".5, 05\n", true},
|
|
{".5, 4\n", false}, {".5, 6\n", false}, {".5, 5, 0.\n", false}};
|
|
for (const auto& test_case : cases) {
|
|
SCOPED_TRACE(test_case.first);
|
|
auto deck = ThermalDeck();
|
|
const auto start = deck.find(".5, 5\n");
|
|
deck.replace(start, std::string{".5, 5\n"}.size(), test_case.first);
|
|
EXPECT_EQ(MapText(deck).HasValue(), test_case.second);
|
|
}
|
|
}
|
|
|
|
TEST(ThermalDomainMapping, RejectsThermalMaterialAndControlForms) {
|
|
struct Case {
|
|
std::string from;
|
|
std::string to;
|
|
};
|
|
const std::vector<Case> cases{
|
|
{"54.,\n", "54., 1.\n"},
|
|
{"460.,\n", "460.\n461.\n"},
|
|
{"7850.,\n", "-1.,\n"},
|
|
{"*Conductivity\n", "*Conductivity, dependencies=1\n"},
|
|
{"*Specific Heat\n460.,\n", ""},
|
|
{"*Heat Transfer\n1., 100.\n",
|
|
"*Heat Transfer, steady state\n1., 100.\n"},
|
|
{"*Heat Transfer\n1., 100.\n", "*Heat Transfer\n0., 100.\n"},
|
|
{"inc=100", "inc=99"},
|
|
{"*Step, name=Heat, inc=100", "*Step, name=Heat, nlgeom=YES, inc=100"},
|
|
{"*Step, name=Heat, inc=100", "*Step, name=Heat, perturbation, inc=100"}};
|
|
for (const auto& test_case : cases) {
|
|
SCOPED_TRACE(test_case.to);
|
|
auto deck = ThermalDeck();
|
|
const auto position = deck.find(test_case.from);
|
|
ASSERT_NE(position, std::string::npos);
|
|
deck.replace(position, test_case.from.size(), test_case.to);
|
|
EXPECT_FALSE(MapText(deck).HasValue());
|
|
}
|
|
}
|
|
|
|
TEST(ThermalDomainMapping, AppliesSameDofStepOverrideAndDefaultValues) {
|
|
auto deck = ThermalDeck();
|
|
const auto first = deck.find("Bottom, 15, 15, 30.");
|
|
deck.replace(first, std::string{"Bottom, 15, 15, 30."}.size(),
|
|
"Bottom, 11, 11, 30.");
|
|
const auto result = MapText(deck);
|
|
ASSERT_TRUE(result.HasValue());
|
|
ASSERT_EQ(result.Value().ThermalModelBoundaries().size(), 2U);
|
|
ASSERT_EQ(result.Value().ThermalStep()->Boundaries().size(), 2U);
|
|
EXPECT_DOUBLE_EQ(result.Value().ThermalModelBoundaries()[0U].value, 0.0);
|
|
EXPECT_DOUBLE_EQ(result.Value().ThermalStep()->Boundaries()[0U].value, 30.0);
|
|
}
|
|
|
|
TEST(ThermalDomainMapping, MapsApprovedShellThermalReference) {
|
|
auto path = std::filesystem::path{__FILE__}.parent_path();
|
|
for (int index = 0; index < 4; ++index) path = path.parent_path();
|
|
path /= "reference/shell-thermal/shell-thermal.inp";
|
|
const auto parsed = fesa::AbaqusInputReader{}.Read(path);
|
|
ASSERT_TRUE(parsed.HasValue());
|
|
const auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
|
|
ASSERT_TRUE(result.HasValue())
|
|
<< (result.HasValue() ? ""
|
|
: result.GetStatus().Diagnostics().front().message);
|
|
EXPECT_EQ(result.Value().Nodes().size(), 729U);
|
|
EXPECT_EQ(result.Value().ThermalElements().Size(), 676U);
|
|
ASSERT_NE(result.Value().ThermalStep(), nullptr);
|
|
EXPECT_EQ(result.Value().ThermalStep()->maximum_increments, 100000U);
|
|
}
|
|
|
|
} // namespace
|