Reject duplicate Solid procedures and unsupported static restart

This commit is contained in:
KOKO\Mimi
2026-10-09 01:25:10 +09:00
parent 9acf18db4b
commit 3ddd77ce6c
2 changed files with 126 additions and 6 deletions
+20 -6
View File
@@ -2387,6 +2387,16 @@ class DomainMappingContext::Impl {
}
void ParseStepBlock(const KeywordBlock& block) {
if (model_element_family_ == ElementFamily::kSolid &&
(block.canonical_name == "STATIC" ||
block.canonical_name == "FREQUENCY" ||
block.canonical_name == "DYNAMIC") &&
(step_.has_static || step_.frequency || step_.dynamic)) {
InputFailure("unsupported-analysis-procedure", block.location,
block.canonical_name, step_.name_raw,
"Solid steps accept exactly one procedure.");
return;
}
if (model_element_family_ == ElementFamily::kThermalShell &&
(block.canonical_name == "STATIC" ||
block.canonical_name == "FREQUENCY" ||
@@ -2525,17 +2535,16 @@ class DomainMappingContext::Impl {
}
step_no_op_seen_ = true;
if (step_.frequency || step_.has_heat_transfer ||
(step_.dynamic && model_element_family_ == ElementFamily::kSolid)) {
model_element_family_ == ElementFamily::kSolid) {
const auto* write = Parameter(block, "WRITE");
const auto* frequency = Parameter(block, "FREQUENCY");
if (!ValidateParameters(block, {"WRITE", "FREQUENCY"}) ||
write == nullptr || write->value || frequency == nullptr ||
!frequency->value || *frequency->value != "0") {
if (!failure_) {
InputFailure("unsupported-keyword", block.location,
block.canonical_name, "",
"Dynamic and frequency RESTART accept only WRITE, "
"FREQUENCY=0.");
InputFailure(
"unsupported-keyword", block.location, block.canonical_name, "",
"Supported RESTART no-op requires WRITE, FREQUENCY=0.");
}
return;
}
@@ -2900,8 +2909,13 @@ class DomainMappingContext::Impl {
if (model_element_family_ == ElementFamily::kSolid &&
Parameter(block, "DIRECT")) {
if (step_.perturbation_explicit || step_.has_static || step_.frequency ||
!ValidateParameters(block, {"DIRECT"}))
step_.dynamic) {
InputFailure("unsupported-analysis-procedure", block.location,
block.canonical_name, step_.name_raw,
"STATIC DIRECT requires one non-perturbation procedure.");
return;
}
if (!ValidateParameters(block, {"DIRECT"})) return;
const auto* direct = Parameter(block, "DIRECT");
if (direct->value || block.data.size() != 1U ||
(block.data[0].fields.size() < 2U ||
@@ -446,4 +446,110 @@ TEST(Hex8Semantics, RejectsInvalidProgrammaticSolidCandidates) {
unused.nodes.push_back({{"I", 90, "90"}, {2, 2, 2}, {"solid.inp", 25}});
EXPECT_FALSE(fesa::Domain::Create(std::move(unused)).HasValue());
}
TEST(Hex8Semantics, RejectsSecondProcedureBeforeReadingItsOptionsOrData) {
const std::array<std::string, 4> procedures{
"*Static\n1,1,1,1\n", "*Static,direct\n.1,1\n", "*Frequency\n2\n",
"*Dynamic,direct\n.1,1\n"};
for (const auto& first : procedures) {
ASSERT_TRUE(Map(Deck(first, "", "*Density\n2\n")).HasValue()) << first;
for (const auto& second : procedures) {
SCOPED_TRACE(first + second);
const auto result = Map(Deck(first + second, "", "*Density\n2\n"));
ASSERT_FALSE(result.HasValue());
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code,
"unsupported-analysis-procedure");
}
const auto malformed =
Map(Deck(first + "*Static,direct,unapproved=YES\nnot-a-number\n", "",
"*Density\n2\n"));
ASSERT_FALSE(malformed.HasValue());
ASSERT_FALSE(malformed.GetStatus().Diagnostics().empty());
EXPECT_EQ(malformed.GetStatus().Diagnostics()[0].code,
"unsupported-analysis-procedure");
EXPECT_EQ(malformed.GetStatus().Diagnostics()[0].keyword, "STATIC");
}
}
TEST(Hex8Semantics, AllowsOnlyApprovedStaticRestartNoOp) {
auto approved = Map(
Replace(Deck(), "*End Step", "*Restart,write,frequency=0\n*End Step"));
ASSERT_TRUE(approved.HasValue());
ASSERT_EQ(approved.Value().Warnings().size(), 1U);
EXPECT_EQ(approved.Value().Warnings()[0].keyword, "RESTART");
for (const std::string options :
{"read", "read,step=1,inc=1", "write", "write,frequency=1",
"write,frequency=0,endstep", "write,frequency=0,numberinterval=1",
"write=YES,frequency=0"}) {
SCOPED_TRACE(options);
auto result = Map(
Replace(Deck(), "*End Step", "*Restart," + options + "\n*End Step"));
ASSERT_FALSE(result.HasValue());
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
EXPECT_EQ(result.GetStatus().Diagnostics()[0].keyword, "RESTART");
}
}
TEST(Hex8Semantics,
PreservesShuffledSourceLabelsAndRejectsDuplicateIdentities) {
const auto deck = Replace(Deck(),
"10,0,0,0\n20,1,0,0\n30,1,1,0\n40,0,1,0\n"
"50,0,0,1\n60,1,0,1\n70,1,1,1\n80,0,1,1\n",
"80,0,1,1\n20,1,0,0\n60,1,0,1\n40,0,1,0\n"
"10,0,0,0\n70,1,1,1\n30,1,1,0\n50,0,0,1\n");
auto domain = Map(deck);
ASSERT_TRUE(domain.HasValue());
EXPECT_EQ(domain.Value().Nodes()[0].source_id.source_label, 80);
EXPECT_EQ(domain.Value().Nodes()[4].source_id.source_label, 10);
EXPECT_EQ(domain.Value().Elements()[0].NodeIndices(),
(std::vector<fesa::EntityIndex>{4, 1, 6, 3, 7, 2, 5, 0}));
auto model = fesa::AnalysisModel::Create(domain.Value());
ASSERT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::Create(model.Value());
ASSERT_TRUE(dofs.HasValue());
EXPECT_EQ(dofs.Value().ConstrainedDofs(),
(std::vector<std::size_t>{12, 13, 14}));
EXPECT_FALSE(Map(Replace(deck, "20,1,0,0", "80,1,0,0")).HasValue());
EXPECT_FALSE(
Map(Replace(deck, "99,10,20,30,40,50,60,70,80",
"99,10,20,30,40,50,60,70,80\n99,10,20,30,40,50,60,70,80"))
.HasValue());
auto shuffled = SolidDefinition();
std::swap(shuffled.nodes[0], shuffled.nodes[7]);
shuffled.solid_elements[0].node_indices = {7, 1, 2, 3, 4, 5, 6, 0};
auto direct = fesa::Domain::Create(std::move(shuffled));
ASSERT_TRUE(direct.HasValue());
EXPECT_EQ(direct.Value().Nodes()[0].source_id.source_label, 80);
EXPECT_EQ(direct.Value().Nodes()[7].source_id.source_label, 10);
EXPECT_EQ(direct.Value().SolidElements()[0].node_indices,
(std::array<fesa::EntityIndex, 8>{7, 1, 2, 3, 4, 5, 6, 0}));
auto nodes = SolidDefinition();
nodes.nodes[1].source_id = nodes.nodes[0].source_id;
EXPECT_FALSE(fesa::Domain::Create(std::move(nodes)).HasValue());
auto elements = SolidDefinition();
elements.solid_elements.push_back(elements.solid_elements[0]);
EXPECT_FALSE(fesa::Domain::Create(std::move(elements)).HasValue());
}
TEST(Hex8Semantics, RejectsUnknownSolidAssignmentTargets) {
for (const std::string assignment :
{"elset=Missing,material=M", "elset=E,material=Missing"}) {
SCOPED_TRACE(assignment);
const auto result = Map(Replace(Deck(), "elset=E,material=M", assignment));
ASSERT_FALSE(result.HasValue());
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code,
"unresolved-solid-section");
}
}
TEST(Hex8Semantics, RejectsInvalidSolidDynamicSchedulesAndIncrementLimits) {
const auto valid = Deck("*Dynamic,direct\n.1,1\n", "", "*Density\n2\n");
ASSERT_TRUE(Map(valid).HasValue());
for (const std::string schedule : {".3,1", "0,1", "-1,1", ".1,0", "nan,1"}) {
SCOPED_TRACE(schedule);
EXPECT_FALSE(Map(Replace(valid, ".1,1\n", schedule + "\n")).HasValue());
}
for (const std::string limit : {"0", "9", "4294967296"}) {
SCOPED_TRACE(limit);
EXPECT_FALSE(Map(Replace(valid, "inc=100", "inc=" + limit)).HasValue());
}
}
} // namespace