feat(nonlinear-static-newton-raphson): step 1 - nonlinear-static-domain-contract

This commit is contained in:
KOKO\Mimi
2026-08-24 13:17:17 +09:00
parent e7c6932d88
commit c00fb79b8f
28 changed files with 814 additions and 275 deletions
+143 -3
View File
@@ -174,6 +174,12 @@ TipSecond, 6, 1.
)inp";
}
std::string NonlinearShellDeck() {
return ReplaceOnce(
ReplaceOnce(ShellDeck(), "nlgeom=NO", "nlgeom=YES"),
"0.1, 1., 0.01, 1.", "0.1, 1., 0.001, 0.1");
}
std::string SupportedInventoryDeck(bool include_no_ops) {
const std::string preprint =
include_no_ops ? "*Preprint, echo=NO, model=NO, history=NO, contact=NO\n"
@@ -570,6 +576,142 @@ TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
plain.Value().Steps()[0].Loads().size());
}
// NSNR-IO-001, NSNR-IO-005, NSNR-IO-007
TEST(InpDomainMapping, MapsNonlinearStaticShellProcedureAndRawControls) {
auto result = MapText("nonlinear-static-shell", NonlinearShellDeck());
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
ASSERT_EQ(domain.Steps().Size(), 1U);
const auto& step = domain.Steps()[0U];
EXPECT_EQ(step.ProcedureKind(),
fesa::StaticProcedureKind::kNonlinearFullNewton);
EXPECT_TRUE(step.IsNonlinearStatic());
EXPECT_DOUBLE_EQ(step.InitialIncrement(), 0.1);
EXPECT_DOUBLE_EQ(step.TimePeriod(), 1.0);
EXPECT_DOUBLE_EQ(step.MinimumIncrement(), 0.001);
EXPECT_DOUBLE_EQ(step.MaximumIncrement(), 0.1);
EXPECT_TRUE(domain.BeamElements().Empty());
ASSERT_EQ(domain.ShellElements().Size(), 4U);
EXPECT_EQ(domain.ShellElements()[0U].source_type,
fesa::ShellSourceElementType::kS4);
EXPECT_EQ(domain.ShellElements()[1U].source_type,
fesa::ShellSourceElementType::kS4r);
}
// NSNR-IO-002, NSNR-IO-003
TEST(InpDomainMapping, KeepsExplicitAndImplicitLinearStaticProcedures) {
const std::vector<std::string> decks{
ShellDeck(), ReplaceOnce(ShellDeck(), ", nlgeom=NO", "")};
for (std::size_t index = 0U; index < decks.size(); ++index) {
SCOPED_TRACE(index);
auto result = MapText("linear-static-procedure-" + std::to_string(index),
decks[index]);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().Steps().Size(), 1U);
EXPECT_EQ(result.Value().Steps()[0U].ProcedureKind(),
fesa::StaticProcedureKind::kLinearSmallDisplacement);
EXPECT_FALSE(result.Value().Steps()[0U].IsNonlinearStatic());
}
}
// NSNR-IO-004
TEST(InpDomainMapping, RejectsInvalidNonlinearStaticControlValues) {
struct InvalidCase {
std::string name;
std::string controls;
};
const std::vector<InvalidCase> cases{
{"zero", "0., 1., 0.001, 0.1"},
{"nonfinite", "nan, 1., 0.001, 0.1"},
{"nonunit-period", "0.1, 0.5, 0.001, 0.1"},
{"reversed-bounds", "0.1, 1., 0.2, 0.1"}};
for (const auto& test_case : cases) {
SCOPED_TRACE(test_case.name);
auto result = MapText(
"invalid-nonlinear-static-" + test_case.name,
ReplaceOnce(NonlinearShellDeck(), "0.1, 1., 0.001, 0.1",
test_case.controls));
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kInput);
ASSERT_NE(FindDiagnostic(result.GetStatus(), "invalid-nonlinear-static-data"),
nullptr);
}
}
// NSNR-IO-006, NSNR-IO-008
TEST(InpDomainMapping, RejectsUnsupportedNonlinearProceduresAndMixedModels) {
const std::string nonlinear = NonlinearShellDeck();
const std::vector<std::pair<std::string, std::string>> procedure_cases{
{"dynamic",
ReplaceOnce(nonlinear, "*Static\n0.1, 1., 0.001, 0.1",
"*Dynamic\n0.1, 1., 0.001, 0.1")},
{"mixed-steps",
nonlinear + "*Step, name=Linear, nlgeom=NO\n*Static\n0.1, 1., 0.01, "
"1.\n*End Step\n"},
{"beam-nlgeom",
ReplaceOnce(MinimalDeck(), "nlgeom=NO", "nlgeom=YES")}};
for (const auto& test_case : procedure_cases) {
SCOPED_TRACE(test_case.first);
auto result = MapText("unsupported-nonlinear-" + test_case.first,
test_case.second);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kInput);
ASSERT_NE(FindDiagnostic(result.GetStatus(), "unsupported-analysis-procedure"),
nullptr);
}
const std::string mixed_model = ReplaceOnce(
nonlinear, "0020, 2, 5, 6, 3\n*Elset",
"0020, 2, 5, 6, 3\n*Element, type=B33\n30, 1, 2\n*Elset");
auto mixed_result = MapText("mixed-nonlinear-model", mixed_model);
ASSERT_FALSE(mixed_result.HasValue());
EXPECT_EQ(mixed_result.GetStatus().Category(), fesa::FailureCategory::kInput);
ASSERT_NE(FindDiagnostic(mixed_result.GetStatus(),
"unsupported-mixed-element-model"),
nullptr);
}
// NSNR-IO-009, NSNR-IO-010, NSNR-IO-011
TEST(InpDomainMapping, AcceptsOnlyNonlinearPreselectOutputNoOps) {
auto plain = MapText("nonlinear-output-plain", NonlinearShellDeck());
ASSERT_TRUE(plain.HasValue());
for (const std::string_view request : {"field", "history"}) {
const std::string deck = ReplaceOnce(
NonlinearShellDeck(), "*End Step\n",
"*Output, " + std::string{request} + ", variable=PRESELECT\n*End Step\n");
auto result = MapText("nonlinear-output-" + std::string{request}, deck);
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(result.Value().Nodes().size(), plain.Value().Nodes().size());
EXPECT_EQ(result.Value().Steps().Size(), plain.Value().Steps().Size());
EXPECT_EQ(result.Value().Steps()[0U].BoundaryConditions().size(),
plain.Value().Steps()[0U].BoundaryConditions().size());
EXPECT_EQ(result.Value().Steps()[0U].Loads().size(),
plain.Value().Steps()[0U].Loads().size());
ASSERT_EQ(result.Value().Warnings().size(), 1U);
EXPECT_EQ(result.Value().Warnings()[0U].keyword, "OUTPUT");
}
const std::vector<std::string> malformed_requests{
"*Output, field\n", "*Output, field, history, variable=PRESELECT\n",
"*Output, field, variable=ALL\n"};
for (std::size_t index = 0U; index < malformed_requests.size(); ++index) {
SCOPED_TRACE(index);
auto result = MapText(
"malformed-nonlinear-output-" + std::to_string(index),
ReplaceOnce(NonlinearShellDeck(), "*End Step\n",
malformed_requests[index] + "*End Step\n"));
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kInput);
ASSERT_NE(FindDiagnostic(result.GetStatus(), "invalid-keyword-parameter"),
nullptr);
}
}
// MITC4-MAP-001
// C-MODEL-002
TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
@@ -738,8 +880,6 @@ TEST(InpDomainMapping,
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."),
@@ -898,7 +1038,7 @@ TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
{"malformed-node-arity", ReplaceOnce(base, "1, 0., 0., 0.", "1, 0., 0."),
"invalid-data-arity", fesa::FailureCategory::kInput},
{"nlgeom", ReplaceOnce(base, "nlgeom=NO", "nlgeom=YES"),
"unsupported-nonlinear-geometry", fesa::FailureCategory::kModel},
"unsupported-analysis-procedure", fesa::FailureCategory::kInput},
{"unknown-keyword",
ReplaceOnce(base, "*Assembly, name=Assembly",
"*Density\n1.\n*Assembly, name=Assembly"),
@@ -150,6 +150,9 @@ TEST(DomainMappingComponents,
EXPECT_EQ(domain.Materials().Size(), 1U);
EXPECT_EQ(domain.Properties().Size(), 1U);
ASSERT_EQ(domain.Steps().Size(), 1U);
EXPECT_EQ(domain.Steps()[0].ProcedureKind(),
StaticProcedureKind::kLinearSmallDisplacement);
EXPECT_FALSE(domain.Steps()[0].IsNonlinearStatic());
EXPECT_EQ(domain.Steps()[0].BoundaryConditions().size(), 7U);
EXPECT_EQ(domain.Steps()[0].Loads().size(), 1U);
ASSERT_EQ(domain.Warnings().size(), 3U);
+5
View File
@@ -49,6 +49,8 @@ fesa::ModelDefinition MakeOwnedDefinition() {
definition.steps = {{"Step-1",
{{"Root", 1, 6, 0.0, {"models/owned.inp", 60U}}},
{{"Tip", 3, -1000.0, {"models/owned.inp", 61U}}},
fesa::StaticProcedureKind::kLinearSmallDisplacement,
false,
0.1,
1.0,
1.0e-5,
@@ -197,6 +199,9 @@ TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
EXPECT_DOUBLE_EQ(domain.Steps()[0].TimePeriod(), 1.0);
EXPECT_DOUBLE_EQ(domain.Steps()[0].MinimumIncrement(), 1.0e-5);
EXPECT_DOUBLE_EQ(domain.Steps()[0].MaximumIncrement(), 1.0);
EXPECT_EQ(domain.Steps()[0].ProcedureKind(),
fesa::StaticProcedureKind::kLinearSmallDisplacement);
EXPECT_FALSE(domain.Steps()[0].IsNonlinearStatic());
ASSERT_EQ(domain.Steps()[0].BoundaryConditions().size(), 6U);
ASSERT_EQ(domain.Steps()[0].Loads().size(), 1U);
ASSERT_EQ(domain.Steps()[0].ConcentratedLoads().Size(), 1U);
+6 -2
View File
@@ -70,8 +70,9 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
"Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
const fesa::NodalLoad load{"Tip", 3, -1000.0, {"models/beam.inp", 70U}};
const fesa::StaticStepDefinition step{
"Step-1", {boundary}, {load}, 0.1,
1.0, 1.0e-5, 1.0, {"models/beam.inp", 50U}};
"Step-1", {boundary}, {load},
fesa::StaticProcedureKind::kLinearSmallDisplacement, false, 0.1,
1.0, 1.0e-5, 1.0, {"models/beam.inp", 50U}};
const fesa::NodeSet node_set{
"Root", std::string{"Instance-A"}, {5U}, {"models/beam.inp", 25U}};
const fesa::ElementSet element_set{
@@ -118,6 +119,9 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
EXPECT_EQ(definition.steps[0].time_period, 1.0);
EXPECT_EQ(definition.steps[0].minimum_increment, 1.0e-5);
EXPECT_EQ(definition.steps[0].maximum_increment, 1.0);
EXPECT_EQ(definition.steps[0].procedure_kind,
fesa::StaticProcedureKind::kLinearSmallDisplacement);
EXPECT_FALSE(definition.steps[0].nlgeom);
ASSERT_EQ(definition.warnings.size(), 1U);
EXPECT_EQ(definition.warnings[0].location.line, 75U);