1051 lines
45 KiB
C++
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
|