#include #include #include #include #include #include #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 next_id_; std::filesystem::path path_; }; std::atomic TemporaryInput::next_id_{0U}; fesa::Result MapText(const std::string& text) { const TemporaryInput input{text}; const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path()); if (!parsed.HasValue()) return fesa::Result::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> 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 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