#include #include #include #include #include #include #include "fesa/io/abaqus/domain_mapper.h" #include "fesa/io/abaqus/input_reader.h" namespace { class TemporaryInputFile { public: TemporaryInputFile(const std::string& stem, const std::string& content) : path_{std::filesystem::temp_directory_path() / ("fesa-eigen-domain-" + stem + ".inp")} { std::ofstream stream{path_, std::ios::binary | std::ios::trunc}; stream.write(content.data(), static_cast(content.size())); if (!stream) { throw std::runtime_error{"Unable to create eigen domain fixture."}; } } ~TemporaryInputFile() { std::error_code error; std::filesystem::remove(path_, error); } const std::filesystem::path& Path() const noexcept { return path_; } private: std::filesystem::path path_; }; fesa::Result MapText(const std::string& stem, const std::string& content) { const TemporaryInputFile input{stem, content}; auto parsed = fesa::AbaqusInputReader{}.Read(input.Path()); if (!parsed.HasValue()) { return fesa::Result::Failure(parsed.GetStatus()); } return fesa::AbaqusDomainMapper{}.Map(parsed.Value()); } std::string EigenDeck(const std::string& density = "2700.,", const std::string& frequency = "10, , , , ,") { return R"inp(*Part, name=ShellPart *Node 1, 0., 0., 0. 2, 1., 0., 0. 3, 1., 1., 0. 4, 0., 1., 0. *Element, type=S4R 1, 1, 2, 3, 4 *Elset, elset=ShellSet 1 *Shell Section, elset=ShellSet, material=Steel 0.5, 5 *End Part *Assembly, name=Assembly *Instance, name=Shell-1, part=ShellPart *End Instance *Nset, nset=Root, instance=Shell-1 1 *End Assembly *Material, name=Steel *Density )inp" + density + R"inp( *Elastic 2.1e11, 0.3 *Boundary Root, 1, 6 *Step, name=Step-2, nlgeom=NO, perturbation *Frequency, eigensolver=Lanczos, sim, acoustic coupling=on, normalization=mass )inp" + frequency + R"inp( *Restart, write, frequency=0 *Output, field *Node Output, variable=PRESELECT *Output, history *Energy Output, variable=ALL *End Step )inp"; } const fesa::Diagnostic* FindDiagnostic(const fesa::Status& status, const std::string& code) { for (const auto& diagnostic : status.Diagnostics()) { if (diagnostic.code == code) { return &diagnostic; } } return nullptr; } TEST(EigenDomainMapping, PreservesFrequencyDensityAndSourceStepIdentity) { auto result = MapText("accepted", EigenDeck()); ASSERT_TRUE(result.HasValue()); const fesa::Domain& domain = result.Value(); ASSERT_EQ(domain.Densities().size(), 1U); EXPECT_EQ(domain.Densities()[0].material_index, fesa::EntityIndex{0U}); EXPECT_DOUBLE_EQ(domain.Densities()[0].rho, 2700.0); ASSERT_NE(domain.Frequency(), nullptr); EXPECT_EQ(domain.Frequency()->source_step_name_raw, "Step-2"); EXPECT_EQ(domain.Frequency()->source_step_name_canonical, "STEP-2"); EXPECT_EQ(domain.Frequency()->requested_mode_count, 10); EXPECT_TRUE(domain.Frequency()->perturbation_explicit); EXPECT_TRUE(domain.Frequency()->sim_explicit); ASSERT_EQ(domain.Steps().Size(), 1U); EXPECT_TRUE(domain.Steps()[0U].IsFrequency()); EXPECT_EQ(domain.Steps()[0U].Frequency(), domain.Frequency()); EXPECT_EQ(domain.Steps()[0U].Name(), "Step-2"); ASSERT_EQ(domain.Steps()[0U].BoundaryConditions().size(), 6U); EXPECT_TRUE(domain.Steps()[0U].Loads().empty()); } TEST(EigenDomainMapping, RejectsInvalidDensityAndFrequencySlots) { auto density = MapText("bad-density", EigenDeck("2700., 1.")); ASSERT_FALSE(density.HasValue()); EXPECT_NE(FindDiagnostic(density.GetStatus(), "invalid-density-data"), nullptr); auto slots = MapText("bad-slots", EigenDeck("2700.,", "10, 0")); ASSERT_FALSE(slots.HasValue()); EXPECT_NE(FindDiagnostic(slots.GetStatus(), "invalid-frequency-data"), nullptr); } TEST(EigenDomainMapping, RejectsMissingActiveDensityAndNonzeroBoundary) { std::string missing_deck = EigenDeck(); const std::string density_block = "*Density\n2700.,\n"; const std::size_t density_location = missing_deck.find(density_block); ASSERT_NE(density_location, std::string::npos); missing_deck.erase(density_location, density_block.size()); auto missing = MapText("missing-density", missing_deck); ASSERT_FALSE(missing.HasValue()); EXPECT_NE(FindDiagnostic(missing.GetStatus(), "missing-material-density"), nullptr); std::string nonzero = EigenDeck(); const std::string source = "Root, 1, 6"; const std::size_t location = nonzero.rfind(source); ASSERT_NE(location, std::string::npos); nonzero.replace(location, source.size(), "Root, 1, 6, 1."); auto boundary = MapText("nonzero-boundary", nonzero); ASSERT_FALSE(boundary.HasValue()); EXPECT_NE(FindDiagnostic(boundary.GetStatus(), "unsupported-eigen-boundary"), nullptr); } TEST(EigenDomainMapping, RejectsMixedProceduresInvalidOutputAndSurface) { std::string mixed = EigenDeck(); mixed.insert(mixed.find("*Restart"), "*Static\n0.1, 1., 0.01, 1.\n"); auto mixed_result = MapText("mixed-procedures", mixed); ASSERT_FALSE(mixed_result.HasValue()); EXPECT_NE(FindDiagnostic(mixed_result.GetStatus(), "unsupported-analysis-procedure"), nullptr); auto wrong_output = MapText( "wrong-output", EigenDeck().replace(EigenDeck().find("*Output, field\n*Node Output"), std::string{"*Output, field\n*Node Output"}.size(), "*Output, history\n*Node Output")); ASSERT_FALSE(wrong_output.HasValue()); EXPECT_NE(FindDiagnostic(wrong_output.GetStatus(), "unsupported-keyword"), nullptr); std::string surface = EigenDeck(); surface.insert(surface.find("*Material"), "*Surface, name=Face, type=ELEMENT\n"); auto surface_result = MapText("surface", surface); ASSERT_FALSE(surface_result.HasValue()); } TEST(EigenDomainMapping, RejectsInvalidPreprintAndAcceptsStrictNoOp) { const std::string preprint = "*Preprint, echo=NO, model=NO, history=NO, contact=NO\n"; auto accepted = MapText("preprint", preprint + EigenDeck()); ASSERT_TRUE(accepted.HasValue()); const auto warning = std::find_if(accepted.Value().Warnings().begin(), accepted.Value().Warnings().end(), [](const fesa::Diagnostic& diagnostic) { return diagnostic.keyword == "PREPRINT"; }); ASSERT_NE(warning, accepted.Value().Warnings().end()); EXPECT_EQ(warning->message, "mandatory-eigen-output-unchanged"); auto rejected = MapText("bad-preprint", "*Preprint, echo=YES\n" + EigenDeck()); ASSERT_FALSE(rejected.HasValue()); EXPECT_NE(FindDiagnostic(rejected.GetStatus(), "unsupported-keyword"), nullptr); auto missing_parameters = MapText("bare-preprint", "*Preprint\n" + EigenDeck()); ASSERT_FALSE(missing_parameters.HasValue()); EXPECT_NE( FindDiagnostic(missing_parameters.GetStatus(), "unsupported-keyword"), nullptr); } } // namespace