feat(nonlinear-static-newton-raphson): step 1 - nonlinear-static-domain-contract
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
|
||||
from msvc_harness.adapters.cmake import CMakeAdapter
|
||||
from msvc_harness.config import load_config
|
||||
from msvc_harness.models import ProjectKind, ProjectSelection, Toolchain
|
||||
|
||||
|
||||
PRESET_NAME = "windows-msvc-ninja-debug"
|
||||
BINARY_DIR = ".harness/build-ninja-msvc-debug"
|
||||
|
||||
|
||||
def test_harness_uses_tracked_ninja_msvc_presets(tmp_path):
|
||||
presets = json.loads((ROOT / "CMakePresets.json").read_text(encoding="utf-8"))
|
||||
configure = presets["configurePresets"][0]
|
||||
assert configure == {
|
||||
"name": PRESET_NAME,
|
||||
"displayName": "Windows MSVC x64 Debug (Ninja)",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": f"${{sourceDir}}/{BINARY_DIR}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"FESA_GTEST_SOURCE_DIR": "$env{FESA_GTEST_SOURCE_DIR}",
|
||||
"MKL_DIR": "$env{MKL_DIR}",
|
||||
"TBB_DIR": "$env{TBB_DIR}",
|
||||
"HDF5_DIR": "$env{HDF5_DIR}",
|
||||
},
|
||||
}
|
||||
assert presets["buildPresets"] == [
|
||||
{"name": PRESET_NAME, "configurePreset": PRESET_NAME}
|
||||
]
|
||||
assert presets["testPresets"] == [
|
||||
{"name": PRESET_NAME, "configurePreset": PRESET_NAME}
|
||||
]
|
||||
|
||||
example = json.loads(
|
||||
(ROOT / ".harness" / "config.example.json").read_text(encoding="utf-8")
|
||||
)
|
||||
assert example == {
|
||||
"version": 1,
|
||||
"projectType": "cmake",
|
||||
"cmake": {
|
||||
"sourceDir": ".",
|
||||
"binaryDir": BINARY_DIR,
|
||||
"configurePreset": PRESET_NAME,
|
||||
"buildPreset": PRESET_NAME,
|
||||
"testPreset": PRESET_NAME,
|
||||
},
|
||||
}
|
||||
assert ".harness/build-ninja-msvc-debug/" in (
|
||||
ROOT / ".gitignore"
|
||||
).read_text(encoding="utf-8").splitlines()
|
||||
|
||||
harness_dir = tmp_path / ".harness"
|
||||
harness_dir.mkdir()
|
||||
(harness_dir / "config.json").write_text(
|
||||
json.dumps(example), encoding="utf-8"
|
||||
)
|
||||
config = load_config(tmp_path)
|
||||
tools = Toolchain(
|
||||
installation=tmp_path / "vs",
|
||||
msbuild=tmp_path / "msbuild.exe",
|
||||
cmake=tmp_path / "cmake.exe",
|
||||
ctest=tmp_path / "ctest.exe",
|
||||
)
|
||||
selection = ProjectSelection(ProjectKind.CMAKE, ROOT / "CMakePresets.json")
|
||||
plan = CMakeAdapter().create_plan(tmp_path, selection, config, tools)
|
||||
|
||||
assert [step.command.argv[1:] for step in plan.steps] == [
|
||||
("--preset", PRESET_NAME),
|
||||
("--build", "--preset", PRESET_NAME),
|
||||
("--preset", PRESET_NAME, "--show-only=json-v1"),
|
||||
("--preset", PRESET_NAME, "--output-on-failure"),
|
||||
]
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user