feat(linear-static-mitc4-shell): step 1 - shell-domain-mapping
This commit is contained in:
@@ -128,6 +128,55 @@ Tip, 2, -1.
|
||||
)inp";
|
||||
}
|
||||
|
||||
std::string shellDeck() {
|
||||
return R"inp(*Part, name=ShellPart
|
||||
*Node
|
||||
1, 0., 0., 0.
|
||||
2, 1., 0., 0.
|
||||
3, 1., 1., 0.
|
||||
4, 0., 1., 0.
|
||||
5, 2., 0., 0.
|
||||
6, 2., 1., 0.
|
||||
*Element, type=S4
|
||||
0010, 1, 2, 3, 4
|
||||
*Element, type=S4R
|
||||
0020, 2, 5, 6, 3
|
||||
*Elset, elset=ShellS4
|
||||
10
|
||||
*Elset, elset=ShellS4R
|
||||
20
|
||||
*Shell Section, elset=ShellS4, material=Steel
|
||||
0.1, 5
|
||||
*Shell Section, elset=ShellS4R, material=Aluminum
|
||||
0.2
|
||||
*End Part
|
||||
*Assembly, name=Assembly
|
||||
*Instance, name=First, part=ShellPart
|
||||
*End Instance
|
||||
*Instance, name=Second, part=ShellPart
|
||||
*End Instance
|
||||
*Nset, nset=RootFirst, instance=First
|
||||
1
|
||||
*Nset, nset=TipSecond, instance=Second
|
||||
6
|
||||
*End Assembly
|
||||
*Material, name=Steel
|
||||
*Elastic
|
||||
210000., 0.3
|
||||
*Material, name=Aluminum
|
||||
*Elastic
|
||||
70000., 0.25
|
||||
*Boundary
|
||||
RootFirst, 1, 6
|
||||
*Step, name=Load, nlgeom=NO
|
||||
*Static
|
||||
0.1, 1., 0.01, 1.
|
||||
*Cload
|
||||
TipSecond, 6, 1.
|
||||
*End Step
|
||||
)inp";
|
||||
}
|
||||
|
||||
std::string supportedInventoryDeck(bool includeNoOps) {
|
||||
const std::string preprint = includeNoOps
|
||||
? "*Preprint, echo=NO, model=NO, history=NO, contact=NO\n"
|
||||
@@ -422,6 +471,188 @@ TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
|
||||
plain.value().steps()[0].loads.size());
|
||||
}
|
||||
|
||||
// MITC4-MAP-001
|
||||
TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
|
||||
auto result = mapText("mitc4-map-001", shellDeck());
|
||||
|
||||
ASSERT_TRUE(result.hasValue());
|
||||
const fesa::Domain& domain = result.value();
|
||||
EXPECT_TRUE(domain.elements().empty());
|
||||
ASSERT_EQ(domain.shellElements().size(), 4U);
|
||||
EXPECT_EQ(domain.shellElements()[0].sourceId.instanceName, "First");
|
||||
EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabelText, "0010");
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[0].sourceType,
|
||||
fesa::ShellSourceElementType::s4);
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[0].nodeIndices,
|
||||
(std::array<fesa::EntityIndex, 4>{0U, 1U, 2U, 3U}));
|
||||
EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U);
|
||||
EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U);
|
||||
|
||||
EXPECT_EQ(domain.shellElements()[1].sourceId.instanceName, "First");
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[1].sourceType,
|
||||
fesa::ShellSourceElementType::s4r);
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[1].nodeIndices,
|
||||
(std::array<fesa::EntityIndex, 4>{1U, 4U, 5U, 2U}));
|
||||
EXPECT_EQ(domain.shellElements()[1].sectionIndex, 1U);
|
||||
EXPECT_EQ(domain.shellElements()[1].materialIndex, 1U);
|
||||
|
||||
EXPECT_EQ(domain.shellElements()[2].sourceId.instanceName, "Second");
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[2].nodeIndices,
|
||||
(std::array<fesa::EntityIndex, 4>{6U, 7U, 8U, 9U}));
|
||||
EXPECT_EQ(domain.shellElements()[3].sourceId.instanceName, "Second");
|
||||
EXPECT_EQ(
|
||||
fesa::kMitc4InternalFormulation,
|
||||
std::string_view{"FESA-MITC4"});
|
||||
|
||||
ASSERT_EQ(domain.shellSections().size(), 2U);
|
||||
EXPECT_EQ(domain.shellSections()[0].name, "ShellS4");
|
||||
EXPECT_DOUBLE_EQ(domain.shellSections()[0].thickness, 0.1);
|
||||
EXPECT_EQ(domain.shellSections()[0].materialIndex, 0U);
|
||||
EXPECT_EQ(domain.shellSections()[1].name, "ShellS4R");
|
||||
EXPECT_DOUBLE_EQ(domain.shellSections()[1].thickness, 0.2);
|
||||
EXPECT_EQ(domain.shellSections()[1].materialIndex, 1U);
|
||||
|
||||
ASSERT_EQ(domain.steps().size(), 1U);
|
||||
ASSERT_EQ(domain.steps()[0].boundaries.size(), 1U);
|
||||
EXPECT_EQ(domain.steps()[0].boundaries[0].lastDof, 6);
|
||||
ASSERT_EQ(domain.steps()[0].loads.size(), 1U);
|
||||
EXPECT_EQ(domain.steps()[0].loads[0].dof, 6);
|
||||
}
|
||||
|
||||
// MITC4-MAP-002
|
||||
TEST(InpDomainMapping, RejectsInvalidShellAssignmentsAndProperties) {
|
||||
struct InvalidCase {
|
||||
std::string name;
|
||||
std::string deck;
|
||||
std::string expectedCode;
|
||||
fesa::FailureCategory category;
|
||||
};
|
||||
|
||||
const std::string base = shellDeck();
|
||||
const std::vector<InvalidCase> cases{
|
||||
{"unresolved-material",
|
||||
replaceOnce(base, "material=Steel", "material=Missing"),
|
||||
"unresolved-shell-section", fesa::FailureCategory::input},
|
||||
{"unresolved-elset",
|
||||
replaceOnce(base, "elset=ShellS4, material=Steel",
|
||||
"elset=Missing, material=Steel"),
|
||||
"unresolved-shell-section", fesa::FailureCategory::input},
|
||||
{"missing-assignment",
|
||||
replaceOnce(
|
||||
base,
|
||||
"*Shell Section, elset=ShellS4R, material=Aluminum\n0.2\n",
|
||||
""),
|
||||
"invalid-shell-section-assignment", fesa::FailureCategory::input},
|
||||
{"conflicting-assignment",
|
||||
replaceOnce(base, "elset=ShellS4R, material=Aluminum",
|
||||
"elset=ShellS4, material=Aluminum"),
|
||||
"invalid-shell-section-assignment", fesa::FailureCategory::input},
|
||||
{"invalid-thickness",
|
||||
replaceOnce(base, "0.2\n*End Part", "0.\n*End Part"),
|
||||
"invalid-shell-thickness", fesa::FailureCategory::model},
|
||||
{"invalid-material",
|
||||
replaceOnce(base, "70000., 0.25", "70000., 0.5"),
|
||||
"invalid-shell-material", fesa::FailureCategory::model}};
|
||||
|
||||
for (const auto& testCase : cases) {
|
||||
SCOPED_TRACE(testCase.name);
|
||||
auto result = mapText("mitc4-map-002-" + testCase.name, testCase.deck);
|
||||
ASSERT_FALSE(result.hasValue());
|
||||
EXPECT_EQ(result.status().failureCategory(), testCase.category);
|
||||
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// MITC4-MAP-003
|
||||
TEST(InpDomainMapping, RejectsInvalidShellConnectivityOptionsAndMixedModels) {
|
||||
struct InvalidCase {
|
||||
std::string name;
|
||||
std::string deck;
|
||||
std::string expectedCode;
|
||||
};
|
||||
|
||||
const std::string base = shellDeck();
|
||||
const std::vector<InvalidCase> cases{
|
||||
{"wrong-arity", replaceOnce(base, "0010, 1, 2, 3, 4", "0010, 1, 2, 3"),
|
||||
"invalid-shell-connectivity"},
|
||||
{"repeated-node", replaceOnce(base, "0010, 1, 2, 3, 4", "0010, 1, 2, 2, 4"),
|
||||
"invalid-shell-connectivity"},
|
||||
{"dangling-node", replaceOnce(base, "0010, 1, 2, 3, 4", "0010, 1, 2, 3, 99"),
|
||||
"invalid-shell-connectivity"},
|
||||
{"unsupported-section-option",
|
||||
replaceOnce(base, "material=Steel\n0.1", "material=Steel, offset=0.1\n0.1"),
|
||||
"unsupported-shell-section-option"},
|
||||
{"unsupported-element",
|
||||
replaceOnce(base, "type=S4R", "type=S8R"),
|
||||
"unsupported-element-formulation"},
|
||||
{"mixed-beam-shell",
|
||||
replaceOnce(base,
|
||||
"0020, 2, 5, 6, 3\n*Elset",
|
||||
"0020, 2, 5, 6, 3\n*Element, type=B33\n30, 1, 2\n*Elset"),
|
||||
"unsupported-mixed-element-model"}};
|
||||
|
||||
for (const auto& testCase : cases) {
|
||||
SCOPED_TRACE(testCase.name);
|
||||
auto result = mapText("mitc4-map-003-" + testCase.name, testCase.deck);
|
||||
ASSERT_FALSE(result.hasValue());
|
||||
EXPECT_EQ(
|
||||
result.status().failureCategory(),
|
||||
fesa::FailureCategory::input);
|
||||
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// MITC4-MAP-004
|
||||
TEST(InpDomainMapping, PreservesProcedureLoadAndOutputRequestBoundariesForShells) {
|
||||
const std::string withNoOps = replaceOnce(
|
||||
shellDeck(),
|
||||
"*End Step\n",
|
||||
"*Output, field\n*Node Output\nU, RF\n*Element Output\nS\n*End Step\n");
|
||||
auto valid = mapText("mitc4-map-004-no-ops", withNoOps);
|
||||
ASSERT_TRUE(valid.hasValue());
|
||||
ASSERT_EQ(valid.value().warnings().size(), 3U);
|
||||
EXPECT_EQ(valid.value().warnings()[0].keyword, "OUTPUT");
|
||||
EXPECT_EQ(valid.value().warnings()[1].keyword, "NODE OUTPUT");
|
||||
EXPECT_EQ(valid.value().warnings()[2].keyword, "ELEMENT OUTPUT");
|
||||
|
||||
struct InvalidCase {
|
||||
std::string name;
|
||||
std::string deck;
|
||||
std::string expectedCode;
|
||||
};
|
||||
const std::string base = shellDeck();
|
||||
const std::vector<InvalidCase> cases{
|
||||
{"second-step",
|
||||
base + "*Step\n*Static\n1., 1., 1., 1.\n*End Step\n",
|
||||
"unsupported-multiple-step"},
|
||||
{"nonlinear-step",
|
||||
replaceOnce(base, "nlgeom=NO", "nlgeom=YES"),
|
||||
"unsupported-nonlinear-geometry"},
|
||||
{"other-procedure",
|
||||
replaceOnce(base, "*Static\n0.1, 1., 0.01, 1.",
|
||||
"*Dynamic\n0.1, 1., 0.01, 1."),
|
||||
"unsupported-keyword"},
|
||||
{"distributed-load",
|
||||
replaceOnce(base, "*Cload\nTipSecond, 6, 1.",
|
||||
"*Dload\nShellS4, P, 1."),
|
||||
"unsupported-distributed-load"}};
|
||||
|
||||
for (const auto& testCase : cases) {
|
||||
SCOPED_TRACE(testCase.name);
|
||||
auto result = mapText("mitc4-map-004-" + testCase.name, testCase.deck);
|
||||
ASSERT_FALSE(result.hasValue());
|
||||
EXPECT_EQ(
|
||||
result.status().failureCategory(),
|
||||
fesa::FailureCategory::input);
|
||||
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
|
||||
struct InvalidCase {
|
||||
std::string name;
|
||||
|
||||
Reference in New Issue
Block a user