339 lines
10 KiB
C++
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
|