feat(abaqus-subset-completion): step 4 — step-bc-load-and-noop-directives

This commit is contained in:
KOKO\Mimi
2026-08-01 03:23:25 +09:00
parent af076c39a3
commit 94c83a39c5
4 changed files with 785 additions and 68 deletions
+24 -2
View File
@@ -156,8 +156,6 @@ add_test(
COMMAND "$<TARGET_FILE:fesa_abaqus_parser_tests>"
--gtest_filter=AbaqusInputContract/*
)
# Steps 1-4 make this normative matrix pass, then remove WILL_FAIL.
set_tests_properties(AbaqusInputContract PROPERTIES WILL_FAIL TRUE)
add_test(
NAME SetResolution
@@ -237,6 +235,30 @@ add_test(
--gtest_filter=ActiveInstance.*
)
add_test(
NAME StepMapping
COMMAND "$<TARGET_FILE:fesa_deck_to_domain_tests>"
--gtest_filter=StepMapping.*
)
add_test(
NAME Boundary
COMMAND "$<TARGET_FILE:fesa_deck_to_domain_tests>"
--gtest_filter=Boundary.*
)
add_test(
NAME Cload
COMMAND "$<TARGET_FILE:fesa_deck_to_domain_tests>"
--gtest_filter=Cload.*
)
add_test(
NAME SuppliedCantilever
COMMAND "$<TARGET_FILE:fesa_deck_to_domain_tests>"
--gtest_filter=SuppliedCantilever.*
)
add_executable(fesa_fem_primitives_tests
unit/fem/beam_frame_test.cpp
unit/fem/dof_manager_test.cpp
@@ -6,6 +6,7 @@
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
@@ -64,6 +65,14 @@ bool has_diagnostic(
});
}
const fesa::Diagnostic* find_diagnostic(
const fesa::DomainBuildResult& result,
const std::string_view code) {
const auto found = std::ranges::find(
result.diagnostics, code, &fesa::Diagnostic::code);
return found == result.diagnostics.end() ? nullptr : &*found;
}
void expect_equivalent_analysis_data(
const fesa::Domain& flat,
const fesa::Domain& hierarchical) {
@@ -265,4 +274,212 @@ TEST(ActiveInstance, RejectsMultipleInstances) {
EXPECT_EQ(diagnostic->source->line, 6U);
}
TEST(StepMapping, MapsOneStaticStepAndRejectsUnsupportedConfigurations) {
const auto valid =
parse_and_map(fixture_path("valid/noop_directives.inp"));
ASSERT_TRUE(valid.domain.has_value());
EXPECT_TRUE(valid.diagnostics.empty());
EXPECT_EQ(valid.domain->step().name, "Step-1");
struct InvalidCase final {
std::string_view name;
std::string_view contents;
std::string_view code;
std::size_t line;
};
const InvalidCase invalid_cases[]{
{
"fesa-step-multiple.inp",
"*STEP, NAME=First\n"
"*STATIC\n"
"*END STEP\n"
"*STEP, NAME=Second\n"
"*STATIC\n"
"*END STEP\n",
"abaqus.semantic.step_count",
4U,
},
{
"fesa-step-nlgeom.inp",
"*STEP, NLGEOM=YES\n"
"*STATIC\n"
"*END STEP\n",
"abaqus.semantic.unsupported_step_option",
1U,
},
{
"fesa-static-invalid.inp",
"*STEP\n"
"*STATIC\n"
"1.0, -1.0\n"
"*END STEP\n",
"abaqus.semantic.invalid_static_data",
3U,
},
{
"fesa-static-extra-row.inp",
"*STEP\n"
"*STATIC\n"
"1.0\n"
"2.0\n"
"*END STEP\n",
"abaqus.semantic.invalid_static_data",
4U,
},
};
for (const InvalidCase& test_case : invalid_cases) {
const TemporaryDeck input{test_case.name, test_case.contents};
const auto result = parse_and_map(input.path());
SCOPED_TRACE(test_case.name);
EXPECT_FALSE(result.domain.has_value());
const fesa::Diagnostic* diagnostic =
find_diagnostic(result, test_case.code);
ASSERT_NE(diagnostic, nullptr);
ASSERT_TRUE(diagnostic->source.has_value());
EXPECT_EQ(diagnostic->source->line, test_case.line);
}
}
TEST(StepMapping, RejectsModelDataInsideStep) {
const TemporaryDeck input{
"fesa-node-inside-step.inp",
"*STEP\n"
"*NODE\n"
"1, 0.0, 0.0, 0.0\n"
"*END STEP\n"};
const auto result = parse_and_map(input.path());
EXPECT_FALSE(result.domain.has_value());
const fesa::Diagnostic* diagnostic =
find_diagnostic(result, "abaqus.syntax.invalid_node_scope");
ASSERT_NE(diagnostic, nullptr);
ASSERT_TRUE(diagnostic->source.has_value());
EXPECT_EQ(diagnostic->source->line, 2U);
}
TEST(StepMapping, RejectsBoundaryAfterCompletedStep) {
const TemporaryDeck input{
"fesa-boundary-after-step.inp",
"*STEP\n"
"*STATIC\n"
"*END STEP\n"
"*BOUNDARY\n"
"1, 1\n"};
const auto result = parse_and_map(input.path());
EXPECT_FALSE(result.domain.has_value());
const fesa::Diagnostic* diagnostic =
find_diagnostic(result, "abaqus.syntax.invalid_boundary_scope");
ASSERT_NE(diagnostic, nullptr);
ASSERT_TRUE(diagnostic->source.has_value());
EXPECT_EQ(diagnostic->source->line, 4U);
}
TEST(Boundary, CanonicalizesIdenticalGlobalAndStepPrescriptions) {
const TemporaryDeck input{
"fesa-boundary-canonical.inp",
"*NODE\n"
"1, 0.0, 0.0, 0.0\n"
"*BOUNDARY\n"
"1, 1, 3, 2.5\n"
"*STEP\n"
"*STATIC\n"
"*BOUNDARY\n"
"1, 1, 3, 2.5\n"
"*END STEP\n"};
const auto result = parse_and_map(input.path());
ASSERT_TRUE(result.domain.has_value());
EXPECT_TRUE(result.diagnostics.empty());
const auto& prescribed = result.domain->step().prescribed_dofs;
ASSERT_EQ(prescribed.size(), 3U);
for (std::size_t index = 0; index < prescribed.size(); ++index) {
EXPECT_EQ(prescribed[index].node, fesa::NodeId{0});
EXPECT_EQ(prescribed[index].dof, index + 1U);
EXPECT_DOUBLE_EQ(prescribed[index].value, 2.5);
}
}
TEST(Boundary, ReportsConflictAtTheConflictingDataRow) {
const auto result =
parse_and_map(fixture_path("invalid/boundary_conflict.inp"));
EXPECT_FALSE(result.domain.has_value());
const fesa::Diagnostic* diagnostic =
find_diagnostic(result, "abaqus.semantic.conflicting_boundary");
ASSERT_NE(diagnostic, nullptr);
ASSERT_TRUE(diagnostic->source.has_value());
EXPECT_EQ(diagnostic->source->line, 10U);
}
TEST(Cload, SumsForceAndMomentComponentsInInputOrder) {
const TemporaryDeck input{
"fesa-cload-sum.inp",
"*NODE\n"
"1, 0.0, 0.0, 0.0\n"
"*STEP, NAME=Load\n"
"*STATIC\n"
"*CLOAD\n"
"1, 1, 2.0\n"
"1, 1, -0.5\n"
"1, 6, 4.0\n"
"*END STEP\n"};
const auto result = parse_and_map(input.path());
ASSERT_TRUE(result.domain.has_value());
EXPECT_TRUE(result.diagnostics.empty());
ASSERT_EQ(result.domain->step().nodal_loads.size(), 1U);
const auto& load = result.domain->step().nodal_loads.front();
EXPECT_EQ(load.node, fesa::NodeId{0});
EXPECT_EQ(
load.values,
(std::array<double, 6>{1.5, 0.0, 0.0, 0.0, 0.0, 4.0}));
}
TEST(Cload, ReportsInvalidDofAtTheDataRow) {
const auto result =
parse_and_map(fixture_path("invalid/cload_invalid_dof.inp"));
EXPECT_FALSE(result.domain.has_value());
const fesa::Diagnostic* diagnostic =
find_diagnostic(result, "abaqus.semantic.invalid_dof");
ASSERT_NE(diagnostic, nullptr);
ASSERT_TRUE(diagnostic->source.has_value());
EXPECT_EQ(diagnostic->source->line, 6U);
}
TEST(SuppliedCantilever, NormalizesReferenceModelThroughPublicParserAndMapper) {
const std::filesystem::path path =
std::filesystem::path{FESA_TEST_SOURCE_DIR}.parent_path() /
"reference" / "cantilever beam" / "cantilever beam.inp";
const auto result = parse_and_map(path);
ASSERT_TRUE(result.domain.has_value());
EXPECT_TRUE(result.diagnostics.empty());
const fesa::Domain& domain = *result.domain;
EXPECT_EQ(domain.nodes().size(), 11U);
EXPECT_EQ(domain.beam_elements().size(), 10U);
EXPECT_EQ(domain.step().prescribed_dofs.size(), 6U);
ASSERT_EQ(domain.step().nodal_loads.size(), 1U);
const auto node = std::ranges::find_if(
domain.nodes(),
[](const fesa::Node& candidate) {
return candidate.origin.local_label == 11;
});
ASSERT_NE(node, domain.nodes().end());
EXPECT_EQ(domain.step().nodal_loads.front().node, node->id);
EXPECT_EQ(
domain.step().nodal_loads.front().values,
(std::array<double, 6>{0.0, 0.0, -10000.0, 0.0, 0.0, 0.0}));
}
} // namespace