Files
FESA/tests/unit/io/abaqus/material_section_mapping_test.cpp
T
2026-08-01 04:07:18 +09:00

339 lines
10 KiB
C++

#include <fesa/io/abaqus/parser.hpp>
#include <fesa/io/abaqus/semantic_mapper.hpp>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <gtest/gtest.h>
namespace {
class TemporaryDeck final {
public:
TemporaryDeck(std::string_view name, std::string_view contents)
: path_{std::filesystem::path{testing::TempDir()} / name} {
std::ofstream output{path_, std::ios::binary};
output.write(
contents.data(), static_cast<std::streamsize>(contents.size()));
if (!output) {
throw std::runtime_error{"Failed to write temporary Abaqus deck."};
}
}
~TemporaryDeck() {
std::error_code error;
std::filesystem::remove(path_, error);
}
TemporaryDeck(const TemporaryDeck&) = delete;
TemporaryDeck& operator=(const TemporaryDeck&) = delete;
[[nodiscard]] const std::filesystem::path& path() const noexcept {
return path_;
}
private:
std::filesystem::path path_;
};
fesa::DomainBuildResult parse_and_map(const TemporaryDeck& input) {
auto parsed = fesa::parse_deck(input.path());
if (!parsed.deck.has_value()) {
return {std::nullopt, std::move(parsed.diagnostics)};
}
return fesa::map_deck_to_domain(*parsed.deck);
}
void expect_diagnostic(
const fesa::DomainBuildResult& result,
const std::string_view code,
const std::size_t line) {
EXPECT_FALSE(result.domain.has_value());
const auto diagnostic = std::ranges::find_if(
result.diagnostics,
[code, line](const fesa::Diagnostic& candidate) {
return candidate.stage == fesa::DiagnosticStage::semantic &&
candidate.code == code && candidate.source.has_value() &&
candidate.source->line == line;
});
EXPECT_NE(diagnostic, result.diagnostics.end());
}
std::string_view valid_flat_prefix() {
return
"*NODE\n"
"1, 0.0, 0.0, 0.0\n"
"2, 1.0, 0.0, 0.0\n"
"*ELEMENT, TYPE=B31, ELSET=Beam\n"
"1, 1, 2\n"
"*ELSET, ELSET=Beam\n"
"1\n"
"*MATERIAL, NAME=Steel\n"
"*ELASTIC\n"
"200.0, 0.25\n";
}
std::string complete_flat_deck(std::string_view section_and_shear) {
std::string contents{valid_flat_prefix()};
contents.append(section_and_shear);
contents.append(
"*STEP\n"
"*STATIC\n"
"*END STEP\n");
return contents;
}
TEST(MaterialMapping, ResolvesForwardMaterialAndExplicitElsetAssignment) {
const TemporaryDeck input{
"fesa-forward-material.inp",
"*PART, NAME=BeamPart\n"
"*NODE\n"
"1, 0.0, 0.0, 0.0\n"
"2, 1.0, 0.0, 0.0\n"
"*ELEMENT, TYPE=B31\n"
"1, 1, 2\n"
"*ELSET, ELSET=Beam\n"
"1\n"
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n"
"*END PART\n"
"*ASSEMBLY, NAME=Assembly\n"
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
"*END INSTANCE\n"
"*END ASSEMBLY\n"
"*MATERIAL, NAME=Steel\n"
"*ELASTIC\n"
"200.0, 0.25\n"
"*STEP\n"
"*STATIC\n"
"*END STEP\n"};
const auto result = parse_and_map(input);
ASSERT_TRUE(result.domain.has_value());
ASSERT_TRUE(result.diagnostics.empty());
ASSERT_EQ(result.domain->materials().size(), 1U);
EXPECT_DOUBLE_EQ(result.domain->materials()[0].young, 200.0);
EXPECT_DOUBLE_EQ(result.domain->materials()[0].poisson, 0.25);
ASSERT_EQ(result.domain->sections().size(), 1U);
EXPECT_EQ(
result.domain->beam_elements()[0].material,
result.domain->materials()[0].id);
EXPECT_EQ(
result.domain->beam_elements()[0].section,
result.domain->sections()[0].id);
}
TEST(ShearDefault, PreservesPhase1DefaultEffectiveAreasAndSource) {
const TemporaryDeck input{
"fesa-default-shear.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n")};
const auto result = parse_and_map(input);
ASSERT_TRUE(result.domain.has_value());
ASSERT_EQ(result.domain->sections().size(), 1U);
const fesa::BeamSection& section = result.domain->sections()[0];
EXPECT_DOUBLE_EQ(section.shear_area_y, 1.0);
EXPECT_DOUBLE_EQ(section.shear_area_z, 1.0);
EXPECT_EQ(
section.shear_source,
fesa::ShearPropertySource::phase1_default);
}
TEST(ShearDefault, ConvertsExplicitK23AndK13UsingIsotropicShearModulus) {
const TemporaryDeck input{
"fesa-explicit-shear.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n"
"*TRANSVERSE SHEAR STIFFNESS\n"
"64.0, 40.0, 0.0\n")};
const auto result = parse_and_map(input);
ASSERT_TRUE(result.domain.has_value());
ASSERT_EQ(result.domain->sections().size(), 1U);
const fesa::BeamSection& section = result.domain->sections()[0];
EXPECT_DOUBLE_EQ(section.shear_area_y, 0.8);
EXPECT_DOUBLE_EQ(section.shear_area_z, 0.5);
EXPECT_EQ(section.shear_source, fesa::ShearPropertySource::input);
}
TEST(BeamSection, RejectsInvalidElasticPropertiesAtTheDataRow) {
const TemporaryDeck input{
"fesa-invalid-elastic.inp",
"*MATERIAL, NAME=Steel\n"
"*ELASTIC\n"
"-1.0, 0.25\n"
"*STEP\n"
"*STATIC\n"
"*END STEP\n"};
const auto result = parse_and_map(input);
expect_diagnostic(
result, "abaqus.semantic.invalid_elastic_data", 3U);
}
TEST(BeamSection, RejectsMaterialWithoutElasticData) {
const TemporaryDeck input{
"fesa-missing-elastic.inp",
"*MATERIAL, NAME=Steel\n"
"*STEP\n"
"*STATIC\n"
"*END STEP\n"};
const auto result = parse_and_map(input);
expect_diagnostic(result, "abaqus.semantic.missing_elastic", 1U);
}
TEST(MaterialMapping, RejectsDuplicateMaterialDefinition) {
const TemporaryDeck input{
"fesa-duplicate-material.inp",
"*MATERIAL, NAME=Steel\n"
"*ELASTIC\n"
"200.0, 0.25\n"
"*MATERIAL, NAME=Steel\n"
"*ELASTIC\n"
"210.0, 0.3\n"
"*STEP\n"
"*STATIC\n"
"*END STEP\n"};
const auto result = parse_and_map(input);
expect_diagnostic(
result, "abaqus.semantic.duplicate_material", 4U);
}
TEST(MaterialMapping, RejectsSectionWithMissingMaterialReference) {
const TemporaryDeck input{
"fesa-missing-material.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Missing\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n")};
const auto result = parse_and_map(input);
expect_diagnostic(result, "abaqus.semantic.missing_material", 11U);
}
TEST(BeamSection, RejectsNonzeroProductMomentAtTheDataRow) {
const TemporaryDeck input{
"fesa-nonzero-iyz.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.1, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n")};
const auto result = parse_and_map(input);
expect_diagnostic(
result, "abaqus.semantic.invalid_section_data", 12U);
}
TEST(BeamSection, RejectsNonpositiveGeneralPropertyAtTheDataRow) {
const TemporaryDeck input{
"fesa-invalid-area.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"0.0, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n")};
const auto result = parse_and_map(input);
expect_diagnostic(
result, "abaqus.semantic.invalid_section_data", 12U);
}
TEST(BeamSection, RejectsNonfiniteOrientationAtTheOrientationRow) {
const TemporaryDeck input{
"fesa-nonfinite-orientation.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, nan, 0.0\n")};
const auto result = parse_and_map(input);
expect_diagnostic(
result, "abaqus.semantic.invalid_section_data", 13U);
}
TEST(BeamSection, RejectsSectionWhoseElementSetDoesNotExist) {
const TemporaryDeck input{
"fesa-missing-section-elset.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Missing, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n")};
const auto result = parse_and_map(input);
expect_diagnostic(
result, "abaqus.semantic.missing_element_set", 11U);
}
TEST(BeamSection, RejectsNonzeroScfAtTheDataRow) {
const TemporaryDeck input{
"fesa-nonzero-scf.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n"
"*TRANSVERSE SHEAR STIFFNESS\n"
"64.0, 40.0, 0.25\n")};
const auto result = parse_and_map(input);
expect_diagnostic(result, "abaqus.semantic.nonzero_scf", 15U);
}
TEST(BeamSection, RejectsElementWithoutSectionAssignment) {
const TemporaryDeck input{
"fesa-missing-section.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Other, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n"
"*ELSET, ELSET=Other\n")};
const auto result = parse_and_map(input);
expect_diagnostic(result, "abaqus.semantic.missing_section", 5U);
}
TEST(BeamSection, RejectsDuplicateSectionAssignment) {
const TemporaryDeck input{
"fesa-duplicate-section.inp",
complete_flat_deck(
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n"
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
"1.2, 2.0, 0.0, 3.0, 4.0\n"
"0.0, 1.0, 0.0\n")};
const auto result = parse_and_map(input);
expect_diagnostic(
result, "abaqus.semantic.duplicate_section_assignment", 14U);
}
} // namespace