Files
FESADev/tests/unit/io/abaqus/thermal_domain_test.cpp
T

1051 lines
45 KiB
C++

#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <limits>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/thermal_time_grid.h"
#include "fesa/fem/dof_manager.h"
#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) {
// Atomically claim a directory: separate CTest processes never share files.
do {
directory_ =
std::filesystem::temp_directory_path() /
("fesa-thermal-domain-" + std::to_string(std::random_device{}()) +
"-" + std::to_string(next_id_.fetch_add(1U)));
} while (!std::filesystem::create_directory(directory_));
path_ = directory_ / "model.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);
std::filesystem::remove(directory_, error);
}
const std::filesystem::path& Path() const noexcept { return path_; }
private:
static std::atomic<unsigned long> next_id_;
std::filesystem::path directory_;
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";
}
std::string Replace(std::string deck, const std::string& from,
const std::string& to) {
const auto position = deck.find(from);
if (position == std::string::npos)
throw std::runtime_error{"Missing fixture token."};
deck.replace(position, from.size(), to);
return deck;
}
std::size_t LineOf(const std::string& deck, const std::string& token) {
const auto position = deck.find(token);
if (position == std::string::npos)
throw std::runtime_error{"Missing source token."};
return 1U + static_cast<std::size_t>(
std::count(deck.begin(), deck.begin() + position, '\n'));
}
void ExpectRejected(const std::string& deck, const std::string& keyword = "") {
const TemporaryInput input{deck};
const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
ASSERT_TRUE(parsed.HasValue());
const auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
ASSERT_FALSE(result.HasValue());
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
const auto& diagnostic = result.GetStatus().Diagnostics().front();
EXPECT_EQ(diagnostic.severity, fesa::Severity::kError);
EXPECT_EQ(diagnostic.location.file, input.Path());
EXPECT_GT(diagnostic.location.line, 0U);
if (!keyword.empty()) EXPECT_EQ(diagnostic.keyword, keyword);
}
std::string Number(double value) {
std::ostringstream stream;
stream << std::setprecision(std::numeric_limits<double>::max_digits10)
<< value;
return stream.str();
}
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", ""},
{"*Specific Heat\n460.,\n", "*Specific Heat\n460.,\n*Elastic\n1., .3\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, RejectsThermalNoOpAndProcedureViolations) {
const std::vector<std::pair<std::string, std::string>> cases{
{"*Part, name=P",
"*Preprint, echo=NO, model=NO, history=NO\n*Part, name=P"},
{"1., 100.\n", "1., 100., , , ,\n"},
{"*Cflux", "*Static\n.1, 1., .1, 1.\n*Cflux"},
{"*End Step", "*Output, field\n*Node Output\nNT\n*End Step"}};
for (const auto& test_case : cases) {
auto deck = ThermalDeck();
const auto position = deck.find(test_case.first);
ASSERT_NE(position, std::string::npos);
deck.replace(position, test_case.first.size(), test_case.second);
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);
const auto& domain = result.Value();
ASSERT_EQ(domain.Nodes().size(), 729U);
ASSERT_EQ(domain.ThermalElements().Size(), 676U);
ASSERT_EQ(domain.ThermalMaterials().Size(), 1U);
ASSERT_EQ(domain.ThermalSections().size(), 1U);
ASSERT_NE(domain.ThermalStep(), nullptr);
const auto& step = *domain.ThermalStep();
EXPECT_EQ(step.name, "Step-1");
EXPECT_EQ(step.maximum_increments, 100000U);
EXPECT_EQ(step.nominal_increment, 1.);
EXPECT_EQ(step.time_period, 100.);
const auto grid = fesa::ThermalTimeGrid::Create(
step.time_period, step.nominal_increment, step.maximum_increments);
ASSERT_TRUE(grid.HasValue());
EXPECT_EQ(grid.Value().Count(), 100U);
EXPECT_EQ(grid.Value().Time(37U), 37.);
EXPECT_EQ(domain.ThermalMaterials()[0].Name(), "Material-1");
EXPECT_EQ(domain.ThermalMaterials()[0].Conductivity(), 54.);
EXPECT_EQ(domain.ThermalMaterials()[0].Density(), 7850.);
EXPECT_EQ(domain.ThermalMaterials()[0].SpecificHeat(), 460.);
EXPECT_EQ(domain.ThermalSections()[0].name, "Set-1");
EXPECT_EQ(domain.ThermalSections()[0].thickness, .5);
EXPECT_EQ(domain.ThermalSections()[0].location.file, path);
EXPECT_EQ(domain.ThermalSections()[0].location.line, 1421U);
for (std::size_t i = 0; i < domain.Nodes().size(); ++i) {
EXPECT_EQ(domain.Nodes()[i].source_id.instance_name, "Part-1-1");
EXPECT_EQ(domain.Nodes()[i].source_id.source_label,
static_cast<std::int64_t>(i + 1U));
}
for (std::size_t i = 0; i < domain.ThermalElements().Size(); ++i) {
EXPECT_EQ(domain.ThermalElements()[i].SourceId().instance_name, "Part-1-1");
EXPECT_EQ(domain.ThermalElements()[i].SourceId().source_label,
static_cast<std::int64_t>(i + 1U));
}
std::vector<std::int64_t> expected_boundary_labels{2, 5, 6, 7};
for (std::int64_t label = 58; label <= 105; ++label)
expected_boundary_labels.push_back(label);
for (std::int64_t label = 298; label <= 441; ++label)
expected_boundary_labels.push_back(label);
ASSERT_EQ(domain.ThermalModelBoundaries().size(),
expected_boundary_labels.size());
ASSERT_EQ(domain.ThermalModelBoundaries().size(), step.Boundaries().size());
for (std::size_t i = 0; i < step.Boundaries().size(); ++i) {
const auto& model_bc = domain.ThermalModelBoundaries()[i];
const auto& step_bc = step.Boundaries()[i];
EXPECT_EQ(model_bc.node_index, step_bc.node_index);
EXPECT_EQ(domain.Nodes()[model_bc.node_index].source_id.source_label,
expected_boundary_labels[i]);
EXPECT_EQ(model_bc.dof, 11);
EXPECT_EQ(step_bc.dof, 11);
EXPECT_EQ(model_bc.value, 0.);
EXPECT_EQ(step_bc.value, 1000.);
EXPECT_EQ(model_bc.location.file, path);
EXPECT_LT(model_bc.location.line, step_bc.location.line);
}
ASSERT_EQ(domain.ElementSurfaces().size(), 1U);
ASSERT_EQ(domain.ElementSurfaces()[0].facets.size(), 169U);
EXPECT_EQ(domain.ElementSurfaces()[0].facets.front().source_element_label,
170);
EXPECT_EQ(domain.ElementSurfaces()[0].facets.back().source_element_label,
338);
EXPECT_EQ(domain.ElementSurfaces()[0].facets[0].source_element_type, "DS4");
ASSERT_EQ(domain.Warnings().size(), 6U);
for (const auto& block : parsed.Value().blocks) {
if (block.canonical_name != "PREPRINT" &&
block.canonical_name != "PHYSICAL CONSTANTS" &&
block.canonical_name != "SURFACE" &&
block.canonical_name != "RESTART" && block.canonical_name != "OUTPUT")
continue;
EXPECT_EQ(std::count_if(domain.Warnings().begin(), domain.Warnings().end(),
[&](const auto& warning) {
return warning.location.file == path &&
warning.location.line ==
block.location.line &&
warning.keyword == block.canonical_name;
}),
1);
}
}
TEST(ThermalDomainMapping, StrictOutputAndRestartNoOps) {
for (const std::string block :
{"*Output, field\n", "*Output, field, variable=ALL\n",
"*Output, field, variable=PRESELECT, frequency=0\n",
"*Output, history\n", "*Output, history, frequency=7\n",
"*Output, history, frequency=0, variable=PRESELECT\n",
"*Output, field, history, variable=PRESELECT\n",
"*Output, field=YES, variable=PRESELECT\n",
"*Output, field, variable=PRESELECT\nNT\n",
"*Output, history, frequency=0\nNT\n", "*Restart\n",
"*Restart, write\n", "*Restart, frequency=0\n",
"*Restart, write=YES, frequency=0\n", "*Restart, write, frequency=1\n",
"*Restart, write, frequency=0, overlay\n",
"*Restart, write, frequency=0\n0\n"}) {
SCOPED_TRACE(block);
ExpectRejected(Replace(ThermalDeck(), "*End Step", block + "*End Step"));
}
}
TEST(ThermalDomainMapping, EveryAcceptedNoOpHasExactlyOneSourceWarning) {
const std::vector<std::pair<std::string, std::string>> blocks{
{"*Preprint, echo=NO, model=NO, history=NO, contact=NO\n", "PREPRINT"},
{"*Physical Constants, absolute zero=-273.15\n", "PHYSICAL CONSTANTS"},
{"*Surface, type=ELEMENT, name=S\nI.1, SPOS\n", "SURFACE"},
{"*Restart, write, frequency=0\n", "RESTART"},
{"*Output, field, variable=PRESELECT\n", "OUTPUT"},
{"*Output, history, frequency=0\n", "OUTPUT"}};
auto deck = ThermalDeck();
deck = blocks[0].first + deck;
deck = Replace(deck, "*Material", blocks[1].first + "*Material");
deck = Replace(deck, "*End Assembly", blocks[2].first + "*End Assembly");
for (std::size_t i = 3U; i < blocks.size(); ++i)
deck = Replace(deck, "*End Step", blocks[i].first + "*End Step");
const TemporaryInput input{deck};
const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
ASSERT_TRUE(parsed.HasValue());
const auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
ASSERT_TRUE(result.HasValue());
const auto& warnings = result.Value().Warnings();
ASSERT_EQ(warnings.size(), blocks.size());
for (const auto& block : blocks) {
const auto line = LineOf(deck, block.first);
EXPECT_EQ(std::count_if(warnings.begin(), warnings.end(),
[&](const auto& w) {
return w.severity == fesa::Severity::kWarning &&
w.keyword == block.second &&
w.location.file == input.Path() &&
w.location.line == line;
}),
1);
}
EXPECT_DOUBLE_EQ(
result.Value().ThermalInitialConditions()[0].station_values[0], 20.);
}
TEST(ThermalDomainMapping, SurfaceFacetsAreResolvedAndPreserved) {
for (const std::string side : {"SPOS", "SNEG"}) {
auto deck = Replace(
ThermalDeck(), "*End Assembly",
"*Elset, elset=Faces, instance=I\n1\n"
"*Surface, type=ELEMENT, name=Direct\nI.1, " +
side + "\n*Surface, type=ELEMENT, name=SetSurface\nFaces, " + side +
"\n*End Assembly");
const auto result = MapText(deck);
ASSERT_TRUE(result.HasValue());
const auto& surfaces = result.Value().ElementSurfaces();
ASSERT_EQ(surfaces.size(), 2U);
for (std::size_t i = 0; i < surfaces.size(); ++i) {
ASSERT_EQ(surfaces[i].facets.size(), 1U);
const auto& facet = surfaces[i].facets[0];
EXPECT_EQ(facet.element_index, 0U);
EXPECT_EQ(facet.source_element_label, 1);
EXPECT_EQ(facet.instance_name_raw, "I");
EXPECT_EQ(facet.part_name_raw, "P");
EXPECT_EQ(facet.source_element_type, "DS4");
EXPECT_EQ(facet.side, side == "SPOS" ? fesa::ShellSurfaceSide::kSpos
: fesa::ShellSurfaceSide::kSneg);
EXPECT_EQ(facet.member_kind, i == 0U ? fesa::SurfaceMemberKind::kDirect
: fesa::SurfaceMemberKind::kElset);
EXPECT_EQ(facet.first_member_location.line,
LineOf(deck, (i == 0U ? "I.1, " : "Faces, ") + side));
}
}
}
TEST(ThermalDomainMapping, RejectsUnresolvedOrInvalidSurfaces) {
for (const std::string row :
{"Missing.1, SPOS", "I.99, SPOS", "Missing, SPOS", "E, SPOS",
"P.1, SPOS", "I.E, SPOS", "I.1, S1", "I.1", "I.1, SPOS, 1"}) {
SCOPED_TRACE(row);
ExpectRejected(
Replace(ThermalDeck(), "*End Assembly",
"*Surface, type=ELEMENT, name=S\n" + row + "\n*End Assembly"),
"SURFACE");
}
for (const std::string header :
{"*Surface, name=S", "*Surface, type=ELEMENT",
"*Surface, type=NODE, name=S",
"*Surface, type=ELEMENT, name=S, internal"}) {
ExpectRejected(Replace(ThermalDeck(), "*End Assembly",
header + "\nI.1, SPOS\n*End Assembly"),
"SURFACE");
}
ExpectRejected(Replace(ThermalDeck(), "*End Assembly",
"*Surface, type=ELEMENT, name=S\n*End Assembly"),
"SURFACE");
}
TEST(ThermalDomainMapping,
PreprintAndPhysicalConstantsAreStrictAndSourceAware) {
for (const std::string block :
{"*Preprint\n", "*Preprint, echo=NO, model=NO, history=NO\n",
"*Preprint, echo=NO, model=NO, history=NO, contact=YES\n",
"*Preprint, echo=NO, model=NO, history=NO, contact\n",
"*Preprint, echo=NO, model=NO, history=NO, contact=NO, unknown=NO\n",
"*Preprint, echo=NO, model=NO, history=NO, contact=NO\ndata\n",
"*Physical Constants\n", "*Physical Constants, absolute zero=nan\n",
"*Physical Constants, absolute zero=inf\n",
"*Physical Constants, absolute zero=\n",
"*Physical Constants, absolute zero=-273.15, stefan boltzmann=1\n",
"*Physical Constants, absolute zero=-273.15\ndata\n"}) {
SCOPED_TRACE(block);
ExpectRejected(block + ThermalDeck());
}
auto structural = Replace(ThermalDeck(), "type=DS4", "type=S4");
structural =
Replace(structural,
"*Density\n7850.,\n*Conductivity\n54.,\n*Specific Heat\n460.,\n",
"*Elastic\n210000., .3\n");
structural = Replace(
structural, "*Initial Conditions, type=TEMPERATURE\nBottom, 20.\n", "");
structural = Replace(structural, "Bottom, 11, 11", "Bottom, 1, 1");
structural = Replace(structural, "*Heat Transfer\n1., 100.",
"*Static\n1., 1., 1., 1.");
structural = Replace(structural,
"*Boundary\nBottom, 15, 15, 30.\n*Cflux\nBottom, 11, "
"-2.\n*Cflux\nBottom, 11, -3.\n",
"");
const std::string block = "*Physical Constants, absolute zero=-273.15\n";
structural = block + structural;
const auto result = MapText(structural);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Diagnostics()[0].keyword, "PHYSICAL CONSTANTS");
EXPECT_EQ(result.GetStatus().Diagnostics()[0].location.line, 1U);
}
TEST(ThermalDomainMapping, RejectsChildOutputUnknownAndWrongNoOpScopes) {
const std::vector<std::string> blocks{
"*Preprint, echo=NO, model=NO, history=NO, contact=NO\n",
"*Physical Constants, absolute zero=-273.15\n",
"*Restart, write, frequency=0\n", "*Output, field, variable=PRESELECT\n",
"*Surface, type=ELEMENT, name=S\nI.1, SPOS\n"};
for (std::size_t i = 0; i < blocks.size(); ++i) {
ExpectRejected(
Replace(ThermalDeck(), "*End Part", blocks[i] + "*End Part"));
const auto target = i < 2U || i == 4U ? "*End Step" : "*Material";
ExpectRejected(Replace(ThermalDeck(), target, blocks[i] + target));
}
for (const std::string child :
{"NODE OUTPUT", "ELEMENT OUTPUT", "CONTACT OUTPUT", "ENERGY OUTPUT"}) {
ExpectRejected(Replace(ThermalDeck(), "*End Step",
"*Output, field, variable=PRESELECT\n*" + child +
"\n*End Step"),
child);
}
ExpectRejected(Replace(ThermalDeck(), "*End Step",
"*Unknown Thermal Keyword\n*End Step"));
}
TEST(ThermalDomainMapping, MaterialConstantsAndSectionLinksAreStrict) {
for (const auto& property : std::vector<std::pair<std::string, std::string>>{
{"Density", "7850."},
{"Conductivity", "54."},
{"Specific Heat", "460."}}) {
const std::string original =
"*" + property.first + "\n" + property.second + ",\n";
for (const std::string tail : {"\n", ",\n"}) {
const auto result = MapText(
Replace(ThermalDeck(), original,
"*" + property.first + "\n" + property.second + tail));
ASSERT_TRUE(result.HasValue()) << property.first << tail;
}
for (const std::string value :
{"0", "-1", "nan", "inf", "1e400", "", "1, 20", "1,,", "1\n2"}) {
SCOPED_TRACE(property.first + ":" + value);
ExpectRejected(Replace(ThermalDeck(), original,
"*" + property.first + "\n" + value + "\n"));
}
for (const std::string option : {"dependencies=1", "type=ANISOTROPIC",
"type=ORTHOTROPIC", "unknown=1"})
ExpectRejected(Replace(ThermalDeck(), "*" + property.first + "\n",
"*" + property.first + ", " + option + "\n"));
ExpectRejected(Replace(ThermalDeck(), original, ""), "MATERIAL");
ExpectRejected(Replace(ThermalDeck(), original, original + original));
}
for (const std::string row :
{"0", "-1", "nan", "inf", ".5,", ".5, 5.0", ".5, 3", ".5,5,", ".5\n.5"})
ExpectRejected(Replace(ThermalDeck(), ".5, 5\n", row + "\n"),
"SHELL SECTION");
for (const std::string option :
{"offset=0", "offset=SPOS", "composite", "nodal thickness"})
ExpectRejected(Replace(ThermalDeck(), "*Shell Section, elset=E, material=M",
"*Shell Section, elset=E, material=M, " + option),
"SHELL SECTION");
ExpectRejected(Replace(ThermalDeck(), "material=M", "material=Missing"),
"SHELL SECTION");
ExpectRejected(Replace(ThermalDeck(), "*Shell Section, elset=E",
"*Shell Section, elset=Missing"),
"SHELL SECTION");
ExpectRejected(Replace(ThermalDeck(),
"*Shell Section, elset=E, material=M\n.5, 5\n", ""),
"ELEMENT");
ExpectRejected(Replace(ThermalDeck(), "*End Part",
"*Shell Section, elset=E, material=M\n.5\n*End Part"),
"SHELL SECTION");
}
TEST(ThermalDomainMapping, InitialTemperatureDefaultsAndOverlaps) {
const auto unspecified = MapText(
Replace(ThermalDeck(),
"*Initial Conditions, type=TEMPERATURE\nBottom, 20.\n", ""));
ASSERT_TRUE(unspecified.HasValue());
EXPECT_TRUE(unspecified.Value().ThermalInitialConditions().empty());
for (const std::string row : {"1, 20.", "Bottom, 20."}) {
const auto result = MapText(
Replace(ThermalDeck(), "Bottom, 20.\n", "Bottom, 20.\n" + row + "\n"));
ASSERT_TRUE(result.HasValue());
for (const auto& initial : result.Value().ThermalInitialConditions())
for (const auto temperature : initial.station_values)
EXPECT_DOUBLE_EQ(temperature, 20.);
}
ExpectRejected(
Replace(ThermalDeck(), "Bottom, 20.\n", "Bottom, 20.\n1, -20.\n"),
"INITIAL CONDITIONS");
for (const std::string row :
{"Missing, 20.", "99, 20.", "1", "1, 20., 30.", "1, nan", ", 20."})
ExpectRejected(Replace(ThermalDeck(), "Bottom, 20.\n", row + "\n"),
"INITIAL CONDITIONS");
for (const std::string header :
{"*Initial Conditions", "*Initial Conditions, type=STRESS",
"*Initial Conditions, type=TEMPERATURE, amplitude=A"})
ExpectRejected(
Replace(ThermalDeck(), "*Initial Conditions, type=TEMPERATURE", header),
"INITIAL CONDITIONS");
ExpectRejected(
Replace(ThermalDeck(), "*End Step",
"*Initial Conditions, type=TEMPERATURE\n1, 1\n*End Step"),
"INITIAL CONDITIONS");
}
TEST(ThermalDomainMapping, InactiveTargetsFailWithOriginalKeywordAndIdentity) {
const auto inactive = Replace(ThermalDeck(), "*Element, type=DS4",
"99, 2., 2., 0.\n*Element, type=DS4");
const auto accepted = MapText(inactive);
ASSERT_TRUE(accepted.HasValue());
EXPECT_EQ(accepted.Value().Nodes().size(), 5U);
const auto view = fesa::AnalysisModel::Create(accepted.Value());
ASSERT_TRUE(view.HasValue());
EXPECT_EQ(view.Value().ActiveThermalNodes().size(), 4U);
for (const auto& replacement :
std::vector<std::pair<std::string, std::string>>{
{"Bottom, 20.", "99, 20."},
{"Bottom, 11, 11", "99, 11, 11"},
{"Bottom, 15, 15, 30.", "99, 15, 15, 30."},
{"Bottom, 11, -2.", "99, 11, -2."}}) {
const auto deck = Replace(inactive, replacement.first, replacement.second);
const auto result = MapText(deck);
ASSERT_FALSE(result.HasValue());
const auto& diagnostic = result.GetStatus().Diagnostics()[0];
EXPECT_EQ(diagnostic.entity_identity, "99");
EXPECT_EQ(diagnostic.location.line, LineOf(deck, replacement.second));
EXPECT_NE(diagnostic.keyword, "THERMAL");
}
}
TEST(ThermalDomainMapping,
ModelBoundaryInheritanceAndStepOverrideReachDofManager) {
auto deck = Replace(ThermalDeck(), "Bottom, 11, 11\n",
"Bottom, 11, 11, 7.\n4, 15, 15\n1, 11, 11, 7.\n");
deck = Replace(deck, "Bottom, 15, 15, 30.",
"1, 11, 11, -30.\n1, 11, 11, -30.\n2, 15, 15");
const auto result = MapText(deck);
ASSERT_TRUE(result.HasValue());
const auto& domain = result.Value();
ASSERT_EQ(domain.ThermalModelBoundaries().size(), 4U);
ASSERT_EQ(domain.ThermalStep()->Boundaries().size(), 3U);
EXPECT_DOUBLE_EQ(domain.ThermalModelBoundaries()[0].value, 7.);
EXPECT_DOUBLE_EQ(domain.ThermalStep()->Boundaries()[0].value, -30.);
const auto model = fesa::AnalysisModel::Create(domain);
ASSERT_TRUE(model.HasValue());
const auto dofs = fesa::DofManager::CreateThermal(model.Value());
ASSERT_TRUE(dofs.HasValue());
const std::vector<std::size_t> expected_dofs{0U, 5U, 9U, 19U};
EXPECT_EQ(dofs.Value().ConstrainedDofs(), expected_dofs);
const std::array<double, 4> expected_values{-30., 7., 0., 0.};
for (std::size_t i = 0; i < expected_values.size(); ++i)
EXPECT_DOUBLE_EQ(dofs.Value().PrescribedValues()[i], expected_values[i]);
}
TEST(ThermalDomainMapping,
BoundariesRejectConflictsAndUnsupportedRowsAtBothLevels) {
for (const std::string original :
{"Bottom, 11, 11\n", "Bottom, 15, 15, 30.\n"}) {
const std::string equal = original.find("15") == std::string::npos
? "1, 11, 11, 0.\n"
: "1, 15, 15, 30.\n";
EXPECT_TRUE(
MapText(Replace(ThermalDeck(), original, original + equal)).HasValue());
const std::string conflict = original.find("15") == std::string::npos
? "1, 11, 11, 2.\n"
: "1, 15, 15, 2.\n";
ExpectRejected(Replace(ThermalDeck(), original, original + conflict),
"BOUNDARY");
for (const std::string row :
{"Bottom, 12, 12", "Bottom, 13, 13", "Bottom, 14, 14",
"Bottom, 11, 15", "Bottom, 11", "Bottom, 11, 11,",
"Bottom, 11, 11, 1, 2", "Bottom, 15, 15, inf", "Missing, 11, 11"})
ExpectRejected(Replace(ThermalDeck(), original, row + "\n"), "BOUNDARY");
for (const std::string option : {"OP=NEW", "AMPLITUDE=A"})
ExpectRejected(Replace(ThermalDeck(), "*Boundary\n" + original,
"*Boundary, " + option + "\n" + original),
"BOUNDARY");
}
ExpectRejected(
Replace(ThermalDeck(), "*End Part", "*Boundary\n1, 11, 11\n*End Part"),
"BOUNDARY");
ExpectRejected(Replace(ThermalDeck(), "*Heat Transfer",
"*Boundary\n1, 11, 11\n*Heat Transfer"),
"BOUNDARY");
}
TEST(ThermalDomainMapping,
SignedFluxRetainsRowOrderDuplicatesAndConstrainedTargets) {
auto deck =
Replace(ThermalDeck(), "1\n2\n*End Assembly", "2, 1, 2\n*End Assembly");
deck = Replace(
deck, "*Cflux\nBottom, 11, -2.\n*Cflux\nBottom, 11, -3.",
"*Cflux\n2, 15, 9.\nBottom, 11, -2.\n1, 11, 3.\n*Cflux\n1, 11, 3.");
const auto result = MapText(deck);
ASSERT_TRUE(result.HasValue());
const auto& loads = result.Value().ThermalStep()->Loads();
ASSERT_EQ(loads.size(), 5U);
const std::array<unsigned, 5> nodes{1, 1, 0, 0, 0};
const std::array<int, 5> dofs{15, 11, 11, 11, 11};
const std::array<double, 5> powers{9., -2., -2., 3., 3.};
for (std::size_t i = 0; i < loads.size(); ++i) {
EXPECT_EQ(loads[i].node_index, nodes[i]);
EXPECT_EQ(loads[i].dof, dofs[i]);
EXPECT_DOUBLE_EQ(loads[i].power, powers[i]);
EXPECT_EQ(loads[i].source_order, i);
}
EXPECT_EQ(loads[1].location.line, loads[2].location.line);
EXPECT_LT(loads[3].location.line, loads[4].location.line);
}
TEST(ThermalDomainMapping, FluxRejectsOptionsRowsAndWrongScopes) {
for (const std::string row :
{"Bottom, 12, 1", "Bottom, 13, 1", "Bottom, 14, 1", "Bottom, 11",
"Bottom, 11, 1,", "Bottom, 11, inf", "Bottom, 11, nan",
"Missing, 11, 1", ", 11, 1"})
ExpectRejected(Replace(ThermalDeck(), "Bottom, 11, -2.", row), "CFLUX");
for (const std::string option : {"OP=NEW", "AMPLITUDE=A", "unknown=1"})
ExpectRejected(Replace(ThermalDeck(), "*Cflux", "*Cflux, " + option),
"CFLUX");
for (const std::string target :
{"*Part", "*End Part", "*End Assembly", "*Step", "*Heat Transfer"})
ExpectRejected(
Replace(ThermalDeck(), target, "*Cflux\n1, 11, 1\n" + target), "CFLUX");
}
TEST(ThermalDomainMapping,
TimeControlsUseSharedGridIncludingInputCellBoundaries) {
struct Case {
double increment;
double period;
unsigned count;
double last;
};
const double tiny = std::numeric_limits<double>::denorm_min();
const std::vector<Case> cases{
{1., 100., 100, 1.},
{3., 10., 4, 1.},
{3., 2., 1, 2.},
{2., 2., 1, 2.},
{.3, .9, 3, .3},
{.3, std::nextafter(.9, 1.), 4, 1.6653345369377348e-16},
{6 * tiny, 20 * tiny, 3, 6 * tiny},
{5 * tiny, 17 * tiny, 4, 2 * tiny}};
for (const auto& item : cases) {
SCOPED_TRACE(Number(item.increment) + ", " + Number(item.period));
auto deck = Replace(ThermalDeck(), "1., 100.",
Number(item.increment) + ", " + Number(item.period));
deck = Replace(deck, "inc=100", "inc=" + std::to_string(item.count));
const auto result = MapText(deck);
ASSERT_TRUE(result.HasValue());
const auto& step = *result.Value().ThermalStep();
EXPECT_EQ(step.nominal_increment, item.increment);
EXPECT_EQ(step.time_period, item.period);
const auto grid = fesa::ThermalTimeGrid::Create(
step.time_period, step.nominal_increment, step.maximum_increments);
ASSERT_TRUE(grid.HasValue());
EXPECT_EQ(grid.Value().Count(), item.count);
EXPECT_DOUBLE_EQ(grid.Value().Time(item.count), item.period);
EXPECT_EQ(grid.Value().Delta(item.count), item.last);
if (item.count > 1U)
ExpectRejected(Replace(deck, "inc=" + std::to_string(item.count),
"inc=" + std::to_string(item.count - 1U)),
"HEAT TRANSFER");
}
auto largest_grid = Replace(ThermalDeck(), "1., 100.", "1., 4294967295.");
largest_grid = Replace(largest_grid, "inc=100", "inc=4294967295");
const auto upper = MapText(largest_grid);
ASSERT_TRUE(upper.HasValue());
EXPECT_EQ(upper.Value().ThermalStep()->maximum_increments, 4294967295U);
ExpectRejected(Replace(largest_grid, "1., 4294967295.", "1., 4294967296."),
"HEAT TRANSFER");
const auto defaults =
MapText(Replace(ThermalDeck(), ", inc=100", ", nlgeom=NO"));
ASSERT_TRUE(defaults.HasValue());
EXPECT_EQ(defaults.Value().ThermalStep()->maximum_increments, 100U);
for (const std::string option : {"", ", end=PERIOD"}) {
for (int empty = 0; empty <= 3; ++empty) {
auto deck = Replace(
ThermalDeck(), "*Heat Transfer\n1., 100.",
"*Heat Transfer" + option + "\n1., 100." + std::string(empty, ','));
EXPECT_TRUE(MapText(deck).HasValue()) << deck;
}
}
}
TEST(ThermalDomainMapping, RejectsInvalidTimeAndMixedProceduresInEitherOrder) {
for (const std::string row :
{"1, 100,,,,", "1, 100, 0", "1, 100,, 2", "1", "1,", "0, 1", "-1, 1",
"1, 0", "1, -1", "nan, 1", "1, inf", "1e-300, 1e300", "1, 100\n1, 100"})
ExpectRejected(Replace(ThermalDeck(), "1., 100.", row), "HEAT TRANSFER");
for (const std::string option :
{"steady state", "deltmx=1", "end=SS", "unknown=1"})
ExpectRejected(
Replace(ThermalDeck(), "*Heat Transfer", "*Heat Transfer, " + option),
"HEAT TRANSFER");
for (const std::string option :
{"inc=0", "inc=-1", "inc=4294967296", "inc=1.5", "inc="})
ExpectRejected(Replace(ThermalDeck(), "inc=100", option), "STEP");
for (const std::string procedure :
{"*Static\n1, 1, 1, 1\n", "*Frequency\n1\n",
"*Dynamic, direct\n1, 100\n", "*Heat Transfer\n1, 100\n"}) {
ExpectRejected(Replace(ThermalDeck(), "*Heat Transfer\n",
procedure + "*Heat Transfer\n"));
ExpectRejected(
Replace(ThermalDeck(), "1., 100.\n", "1., 100.\n" + procedure));
}
ExpectRejected(ThermalDeck() + "*Step\n*Heat Transfer\n1,1\n*End Step\n",
"STEP");
}
TEST(ThermalDomainMapping, ThermalMaterialAndSourceProvenanceArePreserved) {
const auto deck = Replace(
ThermalDeck(), "*Material, name=M",
"*Material, name=Unused\n*Density\n2\n*Conductivity\n3\n*Specific "
"Heat\n4\n*Material, name=M");
const TemporaryInput input{deck};
const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
ASSERT_TRUE(parsed.HasValue());
const auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
ASSERT_TRUE(result.HasValue());
const auto& domain = result.Value();
ASSERT_EQ(domain.ThermalMaterials().Size(), 2U);
ASSERT_EQ(domain.ThermalSections().size(), 1U);
const auto& section = domain.ThermalSections()[0];
EXPECT_EQ(section.material_index, 1U);
EXPECT_EQ(section.name, "E");
EXPECT_EQ(section.source_id.source_label_text, "E");
EXPECT_EQ(section.location.file, input.Path());
EXPECT_EQ(section.location.line, LineOf(deck, "*Shell Section"));
EXPECT_DOUBLE_EQ(section.thickness, .5);
const auto& material = domain.ThermalMaterials()[section.material_index];
EXPECT_EQ(material.Name(), "M");
EXPECT_DOUBLE_EQ(material.Conductivity(), 54.);
EXPECT_DOUBLE_EQ(material.Density(), 7850.);
EXPECT_DOUBLE_EQ(material.SpecificHeat(), 460.);
EXPECT_EQ(material.Location().line, LineOf(deck, "*Material, name=M"));
EXPECT_EQ(domain.ThermalElements()[0].SourceId().instance_name, "I");
EXPECT_EQ(domain.ThermalElements()[0].SourceId().source_label, 1);
EXPECT_EQ(domain.ThermalStep()->name, "Heat");
EXPECT_EQ(domain.ThermalStep()->location.file, input.Path());
EXPECT_EQ(domain.ThermalStep()->location.line, LineOf(deck, "*Step"));
}
TEST(ThermalDomainMapping,
FiniteThermalScalarsPreserveSubnormalAndSignedValues) {
const double tiny = std::numeric_limits<double>::denorm_min();
const std::string text = Number(tiny);
for (const auto& item : std::vector<std::pair<std::string, std::string>>{
{"54.,", text},
{"7850.,", text},
{"460.,", text},
{".5, 5", text + ",5"},
{"Bottom, 20.", "Bottom, -" + text},
{"Bottom, 15, 15, 30.", "Bottom, 15, 15, -" + text},
{"Bottom, 11, -2.", "Bottom, 11, -" + text}}) {
SCOPED_TRACE(item.first);
const auto result =
MapText(Replace(ThermalDeck(), item.first, item.second));
ASSERT_TRUE(result.HasValue());
const auto& domain = result.Value();
if (item.first == "54.,")
EXPECT_EQ(domain.ThermalMaterials()[0].Conductivity(), tiny);
if (item.first == "7850.,")
EXPECT_EQ(domain.ThermalMaterials()[0].Density(), tiny);
if (item.first == "460.,")
EXPECT_EQ(domain.ThermalMaterials()[0].SpecificHeat(), tiny);
if (item.first == ".5, 5")
EXPECT_EQ(domain.ThermalSections()[0].thickness, tiny);
if (item.first == "Bottom, 20.")
EXPECT_EQ(domain.ThermalInitialConditions()[0].station_values[4], -tiny);
if (item.first == "Bottom, 15, 15, 30.")
EXPECT_EQ(domain.ThermalStep()->Boundaries()[0].value, -tiny);
if (item.first == "Bottom, 11, -2.")
EXPECT_EQ(domain.ThermalStep()->Loads()[0].power, -tiny);
}
for (const std::string value :
{"+2.5", "-0", "0", "-20.25", "1.7976931348623157e308"}) {
const auto result =
MapText(Replace(ThermalDeck(), "Bottom, 20.", "Bottom, " + value));
ASSERT_TRUE(result.HasValue());
}
for (const std::string value : {"1e-999", "1e999", "0x1p0", "+-1", "1D2"}) {
for (const std::string original :
{"54.,", "460.,", ".5, 5", "Bottom, 20.", "Bottom, 11, -2."}) {
const auto replacement =
original == "Bottom, 20."
? "Bottom, " + value
: (original == "Bottom, 11, -2." ? "Bottom, 11, " + value
: value);
ExpectRejected(Replace(ThermalDeck(), original, replacement));
}
}
}
TEST(ThermalDomainMapping, ThermalSetAndSurfaceDuplicatesExpandOnce) {
auto deck =
Replace(ThermalDeck(), "*Elset, elset=E\n1\n", "*Elset, elset=E\n1,1\n");
deck = Replace(
deck, "*End Assembly",
"*Surface, type=ELEMENT, name=S\nI.1, SPOS\nI.1, SPOS\n*End Assembly");
const auto result = MapText(deck);
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(result.Value().ThermalElements().Size(), 1U);
ASSERT_EQ(result.Value().ElementSurfaces().size(), 1U);
EXPECT_EQ(result.Value().ElementSurfaces()[0].facets.size(), 1U);
EXPECT_EQ(result.Value().Warnings().size(), 1U);
ExpectRejected(Replace(deck, "I.1, SPOS\nI.1, SPOS", "I.1, SPOS\nI.1, SNEG"),
"SURFACE");
}
TEST(ThermalDomainMapping,
DeferredFailuresRetainExactInputLocationAndIdentity) {
auto deck = Replace(ThermalDeck(), "inc=100", "inc=99");
const TemporaryInput input{deck};
const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
ASSERT_TRUE(parsed.HasValue());
const auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
ASSERT_FALSE(result.HasValue());
const auto& diagnostic = result.GetStatus().Diagnostics()[0];
EXPECT_EQ(diagnostic.location.file, input.Path());
EXPECT_EQ(diagnostic.location.line, LineOf(deck, "1., 100."));
EXPECT_EQ(diagnostic.keyword, "HEAT TRANSFER");
EXPECT_EQ(diagnostic.entity_identity, "Heat");
auto preprint =
std::string{"*Preprint, echo=NO, model=NO, history=NO\n"} + ThermalDeck();
const auto missing_contact = MapText(preprint);
ASSERT_FALSE(missing_contact.HasValue());
EXPECT_EQ(missing_contact.GetStatus().Diagnostics()[0].location.line, 1U);
EXPECT_EQ(missing_contact.GetStatus().Diagnostics()[0].keyword, "PREPRINT");
}
TEST(ThermalDomainMapping, RejectsMixedElementsAndWrongThermalMaterialScopes) {
ExpectRejected(Replace(ThermalDeck(), "*Elset, elset=E",
"*Element, type=S4\n2,1,2,3,4\n*Elset, elset=E"),
"ELEMENT");
for (const std::string block :
{"*Conductivity\n54\n", "*Specific Heat\n460\n",
"*Initial Conditions, type=TEMPERATURE\n1, 1\n"}) {
ExpectRejected(Replace(ThermalDeck(), "*End Part", block + "*End Part"));
ExpectRejected(
Replace(ThermalDeck(), "*End Assembly", block + "*End Assembly"));
ExpectRejected(Replace(ThermalDeck(), "*End Step", block + "*End Step"));
}
for (const std::string property :
{"*Conductivity\n54.,\n", "*Specific Heat\n460.,\n"}) {
auto deck = Replace(ThermalDeck(), property, "");
deck = Replace(deck, "*Material", property + "*Material");
ExpectRejected(deck);
}
}
TEST(ThermalDomainMapping, InlineElementSetResolvesSectionCaseInsensitively) {
auto deck = Replace(ThermalDeck(), "*Element, type=DS4",
"*Element, type=ds4, elset=inlineShells");
deck = Replace(deck, "*Elset, elset=E\n1\n", "");
deck = Replace(deck, "*Shell Section, elset=E",
"*Shell Section, elset=INLINESHELLS");
const TemporaryInput input{deck};
const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
ASSERT_TRUE(parsed.HasValue());
const auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
ASSERT_TRUE(result.HasValue())
<< result.GetStatus().Diagnostics().front().message;
const auto& domain = result.Value();
ASSERT_EQ(domain.ElementSets().size(), 1U);
const auto& set = domain.ElementSets()[0];
EXPECT_EQ(set.name, "inlineShells");
EXPECT_EQ(set.instance_name, "I");
EXPECT_EQ(set.element_indices, std::vector<fesa::EntityIndex>{0U});
EXPECT_EQ(set.location.file, input.Path());
EXPECT_EQ(set.location.line, LineOf(deck, "*Element"));
ASSERT_EQ(domain.ThermalElements().Size(), 1U);
EXPECT_EQ(domain.ThermalElements()[0].ThermalSectionIndex(), 0U);
EXPECT_EQ(domain.ThermalSections()[0].name, "INLINESHELLS");
EXPECT_EQ(domain.ThermalSections()[0].location.line,
LineOf(deck, "*Shell Section"));
}
TEST(ThermalDomainMapping, InlineElementSetPreservesElementRowOrder) {
auto deck = Replace(ThermalDeck(), "*Element, type=DS4\n1, 1, 2, 3, 4",
"5,2,0,0\n6,3,0,0\n7,3,1,0\n8,2,1,0\n"
"*Element, type=DS4, elset=E\n7,1,2,3,4\n3,5,6,7,8");
deck = Replace(deck, "*Elset, elset=E\n1\n", "");
const auto result = MapText(deck);
ASSERT_TRUE(result.HasValue())
<< result.GetStatus().Diagnostics().front().message;
const auto& domain = result.Value();
ASSERT_EQ(domain.ElementSets().size(), 1U);
ASSERT_EQ(domain.ElementSets()[0].element_indices.size(), 2U);
EXPECT_EQ(domain.ElementSets()[0].element_indices,
(std::vector<fesa::EntityIndex>{0U, 1U}));
EXPECT_EQ(domain.ThermalElements()[0].SourceId().source_label, 7);
EXPECT_EQ(domain.ThermalElements()[1].SourceId().source_label, 3);
EXPECT_EQ(domain.ThermalElements()[0].ThermalSectionIndex(), 0U);
EXPECT_EQ(domain.ThermalElements()[1].ThermalSectionIndex(), 0U);
}
TEST(ThermalDomainMapping,
InlineElementSetRejectsInvalidAndDuplicateDefinitions) {
auto deck = Replace(ThermalDeck(), "*Elset, elset=E\n1\n", "");
for (const std::string option :
{"elset", "elset=", "elset=E, unknown=1", "elset=E, elset=e"}) {
SCOPED_TRACE(option);
const auto result = MapText(
Replace(deck, "*Element, type=DS4", "*Element, type=DS4, " + option));
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Diagnostics()[0].keyword, "ELEMENT");
EXPECT_EQ(result.GetStatus().Diagnostics()[0].location.line,
LineOf(deck, "*Element"));
}
ExpectRejected(Replace(ThermalDeck(), "*Element, type=DS4",
"*Element, type=DS4, elset=e"),
"ELSET");
deck = Replace(deck, "*Element, type=DS4", "*Element, type=DS4, elset=E");
ExpectRejected(
Replace(deck, "*Shell Section",
"*Element, type=DS4, elset=e\n2,1,2,3,4\n*Shell Section"),
"ELEMENT");
auto conflicting =
Replace(deck, "*Shell Section", "*Elset, elset=Other\n1\n*Shell Section");
conflicting =
Replace(conflicting, "*End Part",
"*Shell Section, elset=Other, material=M\n.5\n*End Part");
ExpectRejected(conflicting, "SHELL SECTION");
}
TEST(ThermalDomainMapping, RejectsBeamSectionAndBeamOnlyChildrenAtTheirSource) {
const std::string beam =
"*Beam General Section, elset=E, material=M, "
"section=GENERAL\n1,1,0,1,1\n1,0,0\n";
for (const std::string suffix :
{"", "*Section Points\n0,0\n", "*Transverse Shear Stiffness\n1,1,1\n"}) {
const auto deck =
Replace(ThermalDeck(), "*End Part", beam + suffix + "*End Part");
const auto result = MapText(deck);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Diagnostics()[0].keyword,
"BEAM GENERAL SECTION");
EXPECT_EQ(result.GetStatus().Diagnostics()[0].location.line,
LineOf(deck, "*Beam General Section"));
}
ExpectRejected(
Replace(ThermalDeck(), "*End Part", "*Section Points\n0,0\n*End Part"),
"SECTION POINTS");
ExpectRejected(Replace(ThermalDeck(), "*End Part",
"*Transverse Shear Stiffness\n1,1,1\n*End Part"),
"TRANSVERSE SHEAR STIFFNESS");
}
TEST(ThermalDomainMapping, RejectsMalformedDs4ConnectivityAndElementOptions) {
for (const std::string row :
{"1,1,2,3", "1,1,2,3,4,1", "1,1,2,3,3", "1,0,2,3,4", "1,-1,2,3,4",
"1,1.5,2,3,4", "1,1,2,3,99", "0,1,2,3,4", "1.5,1,2,3,4",
"1,1,2,3,4\n1,1,2,3,4"}) {
SCOPED_TRACE(row);
ExpectRejected(Replace(ThermalDeck(), "1, 1, 2, 3, 4", row), "ELEMENT");
}
ExpectRejected(Replace(ThermalDeck(), "*Element, type=DS4",
"*Element, type=DS4, unknown=1"),
"ELEMENT");
}
} // namespace