fix(input): enforce strict Abaqus subset contract
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"step": 0,
|
||||
"name": "abaqus-input-contract",
|
||||
"status": "completed",
|
||||
"summary": "Defined the normative Phase 1 Abaqus subset and a 53-case public parser/mapper fixture matrix; 28 cases pass and 25 expected failures remain for Steps 1-4.",
|
||||
"summary": "Defined the normative Phase 1 Abaqus subset and expanded the public parser/mapper fixture matrix to 69 cases covering strict scopes, parameter forms, data ownership, exact mesh rows, duplicate Parts, Assembly-only hierarchical targets, and source-accurate diagnostics.",
|
||||
"started_at": "2026-08-01T02:16:39+0900",
|
||||
"completed_at": "2026-08-01T02:24:23+0900"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <fesa/io/abaqus/active_input.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -15,8 +16,11 @@ const std::string* parameter(
|
||||
return found == record.parameters.end() ? nullptr : &found->second;
|
||||
}
|
||||
|
||||
bool is_mesh_record(const DeckRecord& record) {
|
||||
return record.keyword == "NODE" || record.keyword == "ELEMENT";
|
||||
bool is_flat_model_record(const DeckRecord& record) {
|
||||
return record.keyword == "NODE" || record.keyword == "ELEMENT" ||
|
||||
record.keyword == "NSET" || record.keyword == "ELSET" ||
|
||||
record.keyword == "BEAM GENERAL SECTION" ||
|
||||
record.keyword == "TRANSVERSE SHEAR STIFFNESS";
|
||||
}
|
||||
|
||||
ActiveInputResult failure(
|
||||
@@ -53,13 +57,23 @@ ActiveInputResult select_active_input(const ParsedDeck& deck) {
|
||||
};
|
||||
}
|
||||
|
||||
const auto flat_mesh =
|
||||
std::ranges::find_if(deck.global_records, is_mesh_record);
|
||||
if (flat_mesh != deck.global_records.end()) {
|
||||
const auto flat_record =
|
||||
std::ranges::find_if(deck.global_records, is_flat_model_record);
|
||||
if (flat_record != deck.global_records.end()) {
|
||||
return failure(
|
||||
"abaqus.semantic.mixed_mesh_organization",
|
||||
"Flat mesh records cannot be mixed with Part/Assembly input.",
|
||||
flat_mesh->source);
|
||||
"Flat model records cannot be mixed with Part/Assembly input.",
|
||||
flat_record->source);
|
||||
}
|
||||
|
||||
std::set<std::string, std::less<>> part_names;
|
||||
for (const ParsedPart& part : deck.parts) {
|
||||
if (!part_names.insert(part.name).second) {
|
||||
return failure(
|
||||
"abaqus.semantic.duplicate_part",
|
||||
"Part name '" + part.name + "' is defined more than once.",
|
||||
part.source);
|
||||
}
|
||||
}
|
||||
|
||||
if (!deck.assembly.has_value()) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <fstream>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -21,6 +22,7 @@ enum class Scope { global, part, assembly, instance, step };
|
||||
struct KeywordLine final {
|
||||
std::string keyword;
|
||||
std::map<std::string, std::string, std::less<>> parameters;
|
||||
std::set<std::string, std::less<>> flag_parameters;
|
||||
SourceLocation source;
|
||||
};
|
||||
|
||||
@@ -188,6 +190,38 @@ std::optional<ParseDeckResult> validate_parameter_names(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ParseDeckResult> unsupported_parameter_value(
|
||||
const KeywordLine& keyword,
|
||||
std::string_view name);
|
||||
|
||||
std::optional<ParseDeckResult> validate_parameter_schema(
|
||||
const KeywordLine& keyword,
|
||||
const std::initializer_list<std::string_view> valued,
|
||||
const std::initializer_list<std::string_view> flags = {}) {
|
||||
for (const auto& [name, value] : keyword.parameters) {
|
||||
if (is_allowed_parameter(name, valued)) {
|
||||
if (keyword.flag_parameters.contains(name) || value.empty()) {
|
||||
return syntax_failure(
|
||||
"abaqus.syntax.invalid_parameter",
|
||||
"Parameter '" + name + "' on *" + keyword.keyword +
|
||||
" requires a nonempty value.",
|
||||
keyword.source);
|
||||
}
|
||||
} else if (is_allowed_parameter(name, flags)) {
|
||||
if (!keyword.flag_parameters.contains(name)) {
|
||||
return unsupported_parameter_value(keyword, name);
|
||||
}
|
||||
} else {
|
||||
return syntax_failure(
|
||||
"abaqus.syntax.unsupported_parameter",
|
||||
"Unsupported parameter '" + name + "' on *" +
|
||||
keyword.keyword + ".",
|
||||
keyword.source);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ParseDeckResult> unsupported_parameter_value(
|
||||
const KeywordLine& keyword,
|
||||
const std::string_view name) {
|
||||
@@ -201,10 +235,10 @@ std::optional<ParseDeckResult> unsupported_parameter_value(
|
||||
std::optional<ParseDeckResult> validate_noop_parameters(
|
||||
const KeywordLine& keyword) {
|
||||
if (keyword.keyword == "HEADING") {
|
||||
return validate_parameter_names(keyword, {});
|
||||
return validate_parameter_schema(keyword, {});
|
||||
}
|
||||
if (keyword.keyword == "PREPRINT") {
|
||||
if (auto error = validate_parameter_names(
|
||||
if (auto error = validate_parameter_schema(
|
||||
keyword, {"ECHO", "MODEL", "HISTORY", "CONTACT"})) {
|
||||
return error;
|
||||
}
|
||||
@@ -217,8 +251,8 @@ std::optional<ParseDeckResult> validate_noop_parameters(
|
||||
return std::nullopt;
|
||||
}
|
||||
if (keyword.keyword == "RESTART") {
|
||||
if (auto error =
|
||||
validate_parameter_names(keyword, {"WRITE", "FREQUENCY"})) {
|
||||
if (auto error = validate_parameter_schema(
|
||||
keyword, {"FREQUENCY"}, {"WRITE"})) {
|
||||
return error;
|
||||
}
|
||||
if (const std::string* write = parameter(keyword, "WRITE");
|
||||
@@ -239,8 +273,8 @@ std::optional<ParseDeckResult> validate_noop_parameters(
|
||||
return std::nullopt;
|
||||
}
|
||||
if (keyword.keyword == "OUTPUT") {
|
||||
if (auto error = validate_parameter_names(
|
||||
keyword, {"FIELD", "HISTORY", "VARIABLE"})) {
|
||||
if (auto error = validate_parameter_schema(
|
||||
keyword, {"VARIABLE"}, {"FIELD", "HISTORY"})) {
|
||||
return error;
|
||||
}
|
||||
const bool field = keyword.parameters.contains("FIELD");
|
||||
@@ -280,6 +314,7 @@ std::optional<KeywordLine> parse_keyword_line(
|
||||
KeywordLine parsed{
|
||||
uppercase_ascii(fields[0]),
|
||||
{},
|
||||
{},
|
||||
source,
|
||||
};
|
||||
for (std::size_t index = 1; index < fields.size(); ++index) {
|
||||
@@ -312,6 +347,9 @@ std::optional<KeywordLine> parse_keyword_line(
|
||||
source);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (equals == std::string_view::npos) {
|
||||
parsed.flag_parameters.insert(key);
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
@@ -418,8 +456,8 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
"*STEP is only valid in global input scope.",
|
||||
source);
|
||||
}
|
||||
if (auto error =
|
||||
validate_parameter_names(keyword, {"NAME", "NLGEOM"})) {
|
||||
if (auto error = validate_parameter_schema(
|
||||
keyword, {"NAME", "NLGEOM"})) {
|
||||
return std::move(*error);
|
||||
}
|
||||
if (const std::string* name = parameter(keyword, "NAME");
|
||||
@@ -481,6 +519,9 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
"*PART is only valid in global input scope.",
|
||||
source);
|
||||
}
|
||||
if (auto error = validate_parameter_schema(keyword, {"NAME"})) {
|
||||
return std::move(*error);
|
||||
}
|
||||
const std::string* name = parameter(keyword, "NAME");
|
||||
if (name == nullptr || name->empty()) {
|
||||
return missing_parameter(keyword, "NAME");
|
||||
@@ -497,6 +538,9 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
"*END PART does not match an open *PART.",
|
||||
source);
|
||||
}
|
||||
if (auto error = validate_parameter_schema(keyword, {})) {
|
||||
return std::move(*error);
|
||||
}
|
||||
deck.parts.push_back(std::move(*current_part));
|
||||
current_part.reset();
|
||||
scope = Scope::global;
|
||||
@@ -510,6 +554,9 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
"*ASSEMBLY is only valid in global input scope.",
|
||||
source);
|
||||
}
|
||||
if (auto error = validate_parameter_schema(keyword, {"NAME"})) {
|
||||
return std::move(*error);
|
||||
}
|
||||
if (deck.assembly.has_value() ||
|
||||
current_assembly.has_value()) {
|
||||
return syntax_failure(
|
||||
@@ -534,6 +581,9 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
"*END ASSEMBLY does not match an open *ASSEMBLY.",
|
||||
source);
|
||||
}
|
||||
if (auto error = validate_parameter_schema(keyword, {})) {
|
||||
return std::move(*error);
|
||||
}
|
||||
deck.assembly = std::move(*current_assembly);
|
||||
current_assembly.reset();
|
||||
scope = Scope::global;
|
||||
@@ -548,6 +598,10 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
"*INSTANCE is only valid in an open *ASSEMBLY.",
|
||||
source);
|
||||
}
|
||||
if (auto error =
|
||||
validate_parameter_schema(keyword, {"NAME", "PART"})) {
|
||||
return std::move(*error);
|
||||
}
|
||||
const std::string* name = parameter(keyword, "NAME");
|
||||
if (name == nullptr || name->empty()) {
|
||||
return missing_parameter(keyword, "NAME");
|
||||
@@ -571,6 +625,9 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
"*END INSTANCE does not match an open *INSTANCE.",
|
||||
source);
|
||||
}
|
||||
if (auto error = validate_parameter_schema(keyword, {})) {
|
||||
return std::move(*error);
|
||||
}
|
||||
current_assembly->instances.push_back(
|
||||
std::move(*current_instance));
|
||||
current_instance.reset();
|
||||
@@ -668,6 +725,59 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
keyword.keyword != "BOUNDARY" && keyword.keyword != "CLOAD") {
|
||||
return invalid_record_scope(keyword);
|
||||
}
|
||||
if (scope != Scope::instance) {
|
||||
const bool mesh_scope = scope == Scope::global || scope == Scope::part;
|
||||
const bool set_scope = mesh_scope || scope == Scope::assembly;
|
||||
if ((keyword.keyword == "NODE" || keyword.keyword == "ELEMENT" ||
|
||||
keyword.keyword == "BEAM GENERAL SECTION" ||
|
||||
keyword.keyword == "TRANSVERSE SHEAR STIFFNESS") &&
|
||||
!mesh_scope) {
|
||||
return invalid_record_scope(keyword);
|
||||
}
|
||||
if ((keyword.keyword == "NSET" || keyword.keyword == "ELSET") &&
|
||||
!set_scope) {
|
||||
return invalid_record_scope(keyword);
|
||||
}
|
||||
if ((keyword.keyword == "MATERIAL" ||
|
||||
keyword.keyword == "ELASTIC") &&
|
||||
scope != Scope::global) {
|
||||
return invalid_record_scope(keyword);
|
||||
}
|
||||
}
|
||||
if (scope != Scope::instance) {
|
||||
std::optional<ParseDeckResult> parameter_error;
|
||||
if (keyword.keyword == "NODE" || keyword.keyword == "ELASTIC" ||
|
||||
keyword.keyword == "TRANSVERSE SHEAR STIFFNESS") {
|
||||
parameter_error = validate_parameter_schema(keyword, {});
|
||||
} else if (keyword.keyword == "ELEMENT") {
|
||||
parameter_error = validate_parameter_schema(
|
||||
keyword, {"TYPE", "ELSET"});
|
||||
} else if (keyword.keyword == "NSET") {
|
||||
parameter_error = scope == Scope::assembly
|
||||
? validate_parameter_schema(
|
||||
keyword,
|
||||
{"NSET", "INSTANCE"},
|
||||
{"GENERATE"})
|
||||
: validate_parameter_schema(
|
||||
keyword, {"NSET"}, {"GENERATE"});
|
||||
} else if (keyword.keyword == "ELSET") {
|
||||
parameter_error = scope == Scope::assembly
|
||||
? validate_parameter_schema(
|
||||
keyword,
|
||||
{"ELSET", "INSTANCE"},
|
||||
{"GENERATE"})
|
||||
: validate_parameter_schema(
|
||||
keyword, {"ELSET"}, {"GENERATE"});
|
||||
} else if (keyword.keyword == "MATERIAL") {
|
||||
parameter_error = validate_parameter_schema(keyword, {"NAME"});
|
||||
} else if (keyword.keyword == "BEAM GENERAL SECTION") {
|
||||
parameter_error = validate_parameter_schema(
|
||||
keyword, {"SECTION", "ELSET", "MATERIAL"});
|
||||
}
|
||||
if (parameter_error.has_value()) {
|
||||
return std::move(*parameter_error);
|
||||
}
|
||||
}
|
||||
|
||||
DeckRecord next_record{
|
||||
std::move(keyword.keyword),
|
||||
@@ -678,7 +788,9 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
|
||||
switch (scope) {
|
||||
case Scope::global:
|
||||
deck.global_records.push_back(std::move(next_record));
|
||||
if (deck.global_records.back().keyword != "MATERIAL") {
|
||||
current_record = &deck.global_records.back();
|
||||
}
|
||||
break;
|
||||
case Scope::part:
|
||||
current_part->records.push_back(std::move(next_record));
|
||||
|
||||
@@ -195,7 +195,7 @@ private:
|
||||
|
||||
std::optional<double> parse_real(
|
||||
const std::string_view text,
|
||||
const DeckRecord& record,
|
||||
const SourceLocation& source,
|
||||
const std::string_view purpose) {
|
||||
double value = 0.0;
|
||||
const auto parsed = std::from_chars(
|
||||
@@ -209,7 +209,7 @@ private:
|
||||
"abaqus.semantic.invalid_number",
|
||||
"Invalid " + std::string{purpose} + " value '" +
|
||||
std::string{text} + "'.",
|
||||
record.source);
|
||||
source);
|
||||
return std::nullopt;
|
||||
}
|
||||
return value;
|
||||
@@ -323,11 +323,22 @@ private:
|
||||
if (record.keyword != "NODE") {
|
||||
continue;
|
||||
}
|
||||
if (record.data.empty()) {
|
||||
add_error(
|
||||
"abaqus.semantic.invalid_node_data",
|
||||
"*NODE requires at least one data row.",
|
||||
record.source);
|
||||
continue;
|
||||
}
|
||||
for (std::size_t row_index = 0; row_index < record.data.size();
|
||||
++row_index) {
|
||||
const auto& row = record.data[row_index];
|
||||
const SourceLocation source = data_source(record, row_index);
|
||||
if (row.size() < 4U) {
|
||||
if (row.size() != 4U || std::ranges::any_of(
|
||||
row,
|
||||
[](const std::string& field) {
|
||||
return field.empty();
|
||||
})) {
|
||||
add_error(
|
||||
"abaqus.semantic.invalid_node_data",
|
||||
"*NODE requires a label and three coordinates.",
|
||||
@@ -337,9 +348,9 @@ private:
|
||||
|
||||
const auto label =
|
||||
parse_label(row[0], "node", source);
|
||||
const auto x = parse_real(row[1], record, "node coordinate");
|
||||
const auto y = parse_real(row[2], record, "node coordinate");
|
||||
const auto z = parse_real(row[3], record, "node coordinate");
|
||||
const auto x = parse_real(row[1], source, "node coordinate");
|
||||
const auto y = parse_real(row[2], source, "node coordinate");
|
||||
const auto z = parse_real(row[3], source, "node coordinate");
|
||||
if (!label.has_value() || !x.has_value() || !y.has_value() ||
|
||||
!z.has_value()) {
|
||||
continue;
|
||||
@@ -385,6 +396,10 @@ private:
|
||||
std::vector<std::int64_t>& labels = target[set.set_name];
|
||||
if (assembly_scope) {
|
||||
labels = set.sorted_unique_labels;
|
||||
if (set.kind == ResolvedSetKind::node) {
|
||||
raw_assembly_node_sets_[set.set_name] =
|
||||
set.sorted_unique_labels;
|
||||
}
|
||||
} else {
|
||||
labels.insert(
|
||||
labels.end(),
|
||||
@@ -435,7 +450,7 @@ private:
|
||||
if (section_type == nullptr || element_set == nullptr ||
|
||||
material_name == nullptr) {
|
||||
valid = false;
|
||||
} else if (*section_type != "GENERAL") {
|
||||
} else if (uppercase_ascii(*section_type) != "GENERAL") {
|
||||
add_error(
|
||||
"abaqus.semantic.unsupported_section",
|
||||
"Phase 1 supports only SECTION=GENERAL.",
|
||||
@@ -646,19 +661,30 @@ private:
|
||||
if (type == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (*type != "B31") {
|
||||
if (uppercase_ascii(*type) != "B31") {
|
||||
add_error(
|
||||
"abaqus.semantic.unsupported_element",
|
||||
"Phase 1 supports only B31 elements.",
|
||||
record.source);
|
||||
continue;
|
||||
}
|
||||
if (record.data.empty()) {
|
||||
add_error(
|
||||
"abaqus.semantic.invalid_element_data",
|
||||
"*ELEMENT requires at least one data row.",
|
||||
record.source);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (std::size_t row_index = 0; row_index < record.data.size();
|
||||
++row_index) {
|
||||
const auto& row = record.data[row_index];
|
||||
const SourceLocation source = data_source(record, row_index);
|
||||
if (row.size() < 3U) {
|
||||
if (row.size() != 3U || std::ranges::any_of(
|
||||
row,
|
||||
[](const std::string& field) {
|
||||
return field.empty();
|
||||
})) {
|
||||
add_error(
|
||||
"abaqus.semantic.invalid_element_data",
|
||||
"B31 element data requires a label and two nodes.",
|
||||
@@ -701,7 +727,7 @@ private:
|
||||
add_error(
|
||||
"abaqus.semantic.missing_node",
|
||||
"B31 element references a missing node label.",
|
||||
record.source);
|
||||
source);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -751,6 +777,10 @@ private:
|
||||
node_sets_[name] = members;
|
||||
builder_.add_node_set({name, std::move(members)});
|
||||
}
|
||||
for (const auto& [name, labels] : raw_assembly_node_sets_) {
|
||||
assembly_node_sets_[name] =
|
||||
resolve_set<NodeId>(labels, node_ids_, name);
|
||||
}
|
||||
for (const auto& [name, labels] : raw_element_sets_) {
|
||||
std::vector<ElementId> members =
|
||||
resolve_set<ElementId>(labels, element_ids_, name);
|
||||
@@ -764,15 +794,17 @@ private:
|
||||
std::int64_t label = 0;
|
||||
const auto parsed =
|
||||
std::from_chars(target.data(), target.data() + target.size(), label);
|
||||
if (parsed.ec == std::errc{} &&
|
||||
if (flat_ && parsed.ec == std::errc{} &&
|
||||
parsed.ptr == target.data() + target.size()) {
|
||||
const auto found = node_ids_.find(label);
|
||||
if (found != node_ids_.end()) {
|
||||
return {found->second};
|
||||
}
|
||||
} else {
|
||||
const auto found = node_sets_.find(target);
|
||||
if (found != node_sets_.end()) {
|
||||
} else if (parsed.ec != std::errc{} ||
|
||||
parsed.ptr != target.data() + target.size()) {
|
||||
const auto& target_sets = flat_ ? node_sets_ : assembly_node_sets_;
|
||||
const auto found = target_sets.find(target);
|
||||
if (found != target_sets.end()) {
|
||||
return found->second;
|
||||
}
|
||||
}
|
||||
@@ -1011,8 +1043,11 @@ private:
|
||||
std::map<std::int64_t, SectionAssignment> element_assignments_;
|
||||
std::set<std::string, std::less<>> assigned_element_set_names_;
|
||||
RawSetMap raw_node_sets_;
|
||||
RawSetMap raw_assembly_node_sets_;
|
||||
RawSetMap raw_element_sets_;
|
||||
std::map<std::string, std::vector<NodeId>, std::less<>> node_sets_;
|
||||
std::map<std::string, std::vector<NodeId>, std::less<>>
|
||||
assembly_node_sets_;
|
||||
std::int64_t next_node_id_ = 0;
|
||||
std::int64_t next_element_id_ = 0;
|
||||
std::int64_t next_material_id_ = 0;
|
||||
|
||||
Vendored
+16
@@ -26,6 +26,7 @@ heading_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_defa
|
||||
preprint_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
|
||||
restart_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
|
||||
output_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
|
||||
enum_case_insensitive_valid valid valid/case_insensitive_enums.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
|
||||
node_duplicate_invalid invalid invalid/node_duplicate.inp semantic abaqus.semantic.duplicate_node_label 3 - - - - - - - - - - -
|
||||
element_type_invalid invalid invalid/element_wrong_type.inp semantic abaqus.semantic.unsupported_element 4 - - - - - - - - - - -
|
||||
part_unclosed_invalid invalid invalid/part_unclosed.inp syntax abaqus.syntax.unclosed_part 1 - - - - - - - - - - -
|
||||
@@ -52,3 +53,18 @@ preprint_parameter_invalid invalid invalid/preprint_invalid_parameter.inp syntax
|
||||
restart_scope_invalid invalid invalid/restart_outside_step.inp syntax abaqus.syntax.invalid_restart_scope 1 - - - - - - - - - - -
|
||||
output_parameter_invalid invalid invalid/output_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 3 - - - - - - - - - - -
|
||||
unknown_keyword_invalid invalid invalid/unknown_include.inp syntax abaqus.unsupported_keyword 1 - - - - - - - - - - -
|
||||
node_parameter_invalid invalid invalid/node_unsupported_parameter.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
|
||||
node_scope_invalid invalid invalid/node_wrong_scope.inp syntax abaqus.syntax.invalid_node_scope 2 - - - - - - - - - - -
|
||||
material_scope_invalid invalid invalid/material_wrong_scope.inp syntax abaqus.syntax.invalid_material_scope 2 - - - - - - - - - - -
|
||||
generate_form_invalid invalid invalid/generate_valued.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
|
||||
flat_set_instance_invalid invalid invalid/flat_set_instance.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
|
||||
global_set_hierarchy_invalid invalid invalid/global_set_hierarchical.inp semantic abaqus.semantic.mixed_mesh_organization 1 - - - - - - - - - - -
|
||||
duplicate_part_invalid invalid invalid/duplicate_part.inp semantic abaqus.semantic.duplicate_part 3 - - - - - - - - - - -
|
||||
node_surplus_field_invalid invalid invalid/node_surplus_field.inp semantic abaqus.semantic.invalid_node_data 2 - - - - - - - - - - -
|
||||
node_empty_invalid invalid invalid/node_empty.inp semantic abaqus.semantic.invalid_node_data 1 - - - - - - - - - - -
|
||||
element_surplus_field_invalid invalid invalid/element_surplus_field.inp semantic abaqus.semantic.invalid_element_data 2 - - - - - - - - - - -
|
||||
element_empty_invalid invalid invalid/element_empty.inp semantic abaqus.semantic.invalid_element_data 1 - - - - - - - - - - -
|
||||
hierarchical_part_target_invalid invalid invalid/hierarchical_part_set_target.inp semantic abaqus.semantic.missing_node_target 25 - - - - - - - - - - -
|
||||
material_data_invalid invalid invalid/material_data.inp syntax abaqus.syntax.data_without_keyword 2 - - - - - - - - - - -
|
||||
node_coordinate_invalid invalid invalid/node_invalid_coordinate.inp semantic abaqus.semantic.invalid_number 2 - - - - - - - - - - -
|
||||
element_missing_node_invalid invalid invalid/element_missing_node.inp semantic abaqus.semantic.missing_node 4 - - - - - - - - - - -
|
||||
|
||||
|
@@ -0,0 +1,8 @@
|
||||
*PART, NAME=BeamPart
|
||||
*END PART
|
||||
*PART, NAME=BeamPart
|
||||
*END PART
|
||||
*ASSEMBLY, NAME=RootAssembly
|
||||
*INSTANCE, NAME=Beam-1, PART=BeamPart
|
||||
*END INSTANCE
|
||||
*END ASSEMBLY
|
||||
@@ -0,0 +1,4 @@
|
||||
*ELEMENT, TYPE=B31
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,13 @@
|
||||
*NODE
|
||||
1, 0.0, 0.0, 0.0
|
||||
*ELEMENT, TYPE=B31, ELSET=Beam
|
||||
1, 1, 2
|
||||
*MATERIAL, NAME=Steel
|
||||
*ELASTIC
|
||||
210000.0, 0.3
|
||||
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
|
||||
1.0, 1.0, 0.0, 1.0, 1.0
|
||||
0.0, 1.0, 0.0
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,5 @@
|
||||
*ELEMENT, TYPE=B31
|
||||
1, 1, 2, 3
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,2 @@
|
||||
*NSET, NSET=Fixed, INSTANCE=Beam-1
|
||||
1
|
||||
@@ -0,0 +1,2 @@
|
||||
*NSET, NSET=Generated, GENERATE=YES
|
||||
1, 2, 1
|
||||
@@ -0,0 +1,7 @@
|
||||
*NSET, NSET=Ignored
|
||||
*PART, NAME=BeamPart
|
||||
*END PART
|
||||
*ASSEMBLY, NAME=RootAssembly
|
||||
*INSTANCE, NAME=Beam-1, PART=BeamPart
|
||||
*END INSTANCE
|
||||
*END ASSEMBLY
|
||||
@@ -0,0 +1,26 @@
|
||||
*PART, NAME=BeamPart
|
||||
*NODE
|
||||
1, 0.0, 0.0, 0.0
|
||||
2, 1.0, 0.0, 0.0
|
||||
*ELEMENT, TYPE=B31, ELSET=Beam
|
||||
1, 1, 2
|
||||
*NSET, NSET=Fixed
|
||||
1
|
||||
*ELSET, ELSET=Beam
|
||||
1
|
||||
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
|
||||
1.0, 1.0, 0.0, 1.0, 1.0
|
||||
0.0, 1.0, 0.0
|
||||
*END PART
|
||||
*ASSEMBLY, NAME=RootAssembly
|
||||
*INSTANCE, NAME=Beam-1, PART=BeamPart
|
||||
*END INSTANCE
|
||||
*END ASSEMBLY
|
||||
*MATERIAL, NAME=Steel
|
||||
*ELASTIC
|
||||
200.0, 0.25
|
||||
*STEP
|
||||
*STATIC
|
||||
*BOUNDARY
|
||||
Fixed, 1, 6
|
||||
*END STEP
|
||||
@@ -0,0 +1,2 @@
|
||||
*MATERIAL, NAME=Steel
|
||||
unexpected data
|
||||
@@ -0,0 +1,3 @@
|
||||
*PART, NAME=BeamPart
|
||||
*MATERIAL, NAME=Steel
|
||||
*END PART
|
||||
@@ -0,0 +1,4 @@
|
||||
*NODE
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,5 @@
|
||||
*NODE
|
||||
1, invalid, 0.0, 0.0
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,5 @@
|
||||
*NODE
|
||||
1, 0.0, 0.0, 0.0, 9.0
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,2 @@
|
||||
*NODE, EXTRA=1
|
||||
1, 0.0, 0.0, 0.0
|
||||
@@ -0,0 +1,4 @@
|
||||
*ASSEMBLY, NAME=RootAssembly
|
||||
*NODE
|
||||
1, 0.0, 0.0, 0.0
|
||||
*END ASSEMBLY
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
1, 0.0, 0.0, 0.0
|
||||
** Comments and blank lines do not terminate the current data record.
|
||||
|
||||
2, 1.0, 0.0, 0.0,
|
||||
2, 1.0, 0.0, 0.0
|
||||
*eLeMeNt, TYPE=B31, elset=Beam
|
||||
1, 1, 2
|
||||
*NSET, NSET=Fixed
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
*NODE
|
||||
1, 0.0, 0.0, 0.0
|
||||
2, 1.0, 0.0, 0.0
|
||||
*ELEMENT, TYPE=b31, ELSET=Beam
|
||||
1, 1, 2
|
||||
*NSET, NSET=Fixed
|
||||
1
|
||||
*ELSET, ELSET=Beam
|
||||
1
|
||||
*MATERIAL, NAME=Steel
|
||||
*ELASTIC
|
||||
200.0, 0.25
|
||||
*BEAM GENERAL SECTION, SECTION=general, ELSET=Beam, MATERIAL=Steel
|
||||
1.0, 1.0, 0.0, 1.0, 1.0
|
||||
0.0, 1.0, 0.0
|
||||
*BOUNDARY
|
||||
Fixed, 1, 6
|
||||
*STEP, NAME=Load
|
||||
*STATIC
|
||||
*CLOAD
|
||||
2, 2, -1.0
|
||||
*END STEP
|
||||
@@ -173,6 +173,128 @@ TEST(DeckToDomain, NormalizesFlatAndSingleInstanceDecksEquivalently) {
|
||||
fesa::ShearPropertySource::phase1_default);
|
||||
}
|
||||
|
||||
TEST(DeckToDomain, RequiresExactNodeAndB31DataWidthsAndNonemptyRecords) {
|
||||
struct Case final {
|
||||
std::string_view name;
|
||||
std::string_view mesh;
|
||||
std::string_view code;
|
||||
};
|
||||
const Case cases[]{
|
||||
{
|
||||
"node-surplus-field",
|
||||
"*NODE\n1, 0.0, 0.0, 0.0, 9.0\n"
|
||||
"2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n1, 1, 2\n",
|
||||
"abaqus.semantic.invalid_node_data",
|
||||
},
|
||||
{
|
||||
"node-empty",
|
||||
"*NODE\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n1, 1, 2\n",
|
||||
"abaqus.semantic.invalid_node_data",
|
||||
},
|
||||
{
|
||||
"element-surplus-field",
|
||||
"*NODE\n1, 0.0, 0.0, 0.0\n2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n1, 1, 2, 3\n",
|
||||
"abaqus.semantic.invalid_element_data",
|
||||
},
|
||||
{
|
||||
"element-empty",
|
||||
"*NODE\n1, 0.0, 0.0, 0.0\n2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n",
|
||||
"abaqus.semantic.invalid_element_data",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& test_case : cases) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-exact-mesh-data-" + std::string{test_case.name} + ".inp",
|
||||
std::string{test_case.mesh} +
|
||||
"*ELSET, ELSET=Beam\n1\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n210000.0, 0.3\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, "
|
||||
"MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n0.0, 1.0, 0.0\n"
|
||||
"*STEP\n*STATIC\n*END STEP\n",
|
||||
};
|
||||
|
||||
const auto result = parse_and_map(input.path());
|
||||
|
||||
SCOPED_TRACE(test_case.name);
|
||||
EXPECT_FALSE(result.domain.has_value());
|
||||
EXPECT_TRUE(has_diagnostic(result, test_case.code));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DeckToDomain, ReportsMeshDataErrorsAtTheOffendingRows) {
|
||||
const TemporaryDeck invalid_coordinate{
|
||||
"fesa-node-coordinate-source.inp",
|
||||
"*NODE\n"
|
||||
"1, invalid, 0.0, 0.0\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*END STEP\n"};
|
||||
const TemporaryDeck missing_node{
|
||||
"fesa-element-node-source.inp",
|
||||
"*NODE\n"
|
||||
"1, 0.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n"
|
||||
"1, 1, 2\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n"
|
||||
"210000.0, 0.3\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*END STEP\n"};
|
||||
|
||||
const auto coordinate_result = parse_and_map(invalid_coordinate.path());
|
||||
const auto missing_node_result = parse_and_map(missing_node.path());
|
||||
|
||||
const fesa::Diagnostic* coordinate =
|
||||
find_diagnostic(coordinate_result, "abaqus.semantic.invalid_number");
|
||||
ASSERT_NE(coordinate, nullptr);
|
||||
ASSERT_TRUE(coordinate->source.has_value());
|
||||
EXPECT_EQ(coordinate->source->line, 2U);
|
||||
|
||||
const fesa::Diagnostic* node =
|
||||
find_diagnostic(missing_node_result, "abaqus.semantic.missing_node");
|
||||
ASSERT_NE(node, nullptr);
|
||||
ASSERT_TRUE(node->source.has_value());
|
||||
EXPECT_EQ(node->source->line, 4U);
|
||||
}
|
||||
|
||||
TEST(DeckToDomain, AcceptsCaseInsensitiveElementAndSectionValues) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-case-insensitive-enums.inp",
|
||||
"*NODE\n"
|
||||
"1, 0.0, 0.0, 0.0\n"
|
||||
"2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=b31, ELSET=Beam\n"
|
||||
"1, 1, 2\n"
|
||||
"*ELSET, ELSET=Beam\n"
|
||||
"1\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n"
|
||||
"210000.0, 0.3\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=general, ELSET=Beam, MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*END STEP\n"};
|
||||
|
||||
const auto result = parse_and_map(input.path());
|
||||
|
||||
ASSERT_TRUE(result.domain.has_value());
|
||||
EXPECT_TRUE(result.diagnostics.empty());
|
||||
EXPECT_EQ(result.domain->beam_elements().size(), 1U);
|
||||
}
|
||||
|
||||
TEST(ActiveInstance, ExcludesPartsNotReferencedByTheInstance) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-active-instance.inp",
|
||||
@@ -406,6 +528,42 @@ TEST(Boundary, CanonicalizesIdenticalGlobalAndStepPrescriptions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Boundary, HierarchicalTargetsRequireAssemblySets) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-hierarchical-part-set-target.inp",
|
||||
"*PART, NAME=BeamPart\n"
|
||||
"*NODE\n"
|
||||
"1, 0.0, 0.0, 0.0\n"
|
||||
"2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n"
|
||||
"1, 1, 2\n"
|
||||
"*NSET, NSET=Fixed\n"
|
||||
"1\n"
|
||||
"*ELSET, ELSET=Beam\n"
|
||||
"1\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n"
|
||||
"*END PART\n"
|
||||
"*ASSEMBLY, NAME=RootAssembly\n"
|
||||
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
|
||||
"*END INSTANCE\n"
|
||||
"*END ASSEMBLY\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n"
|
||||
"210000.0, 0.3\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*BOUNDARY\n"
|
||||
"Fixed, 1, 6\n"
|
||||
"*END STEP\n"};
|
||||
|
||||
const auto result = parse_and_map(input.path());
|
||||
|
||||
EXPECT_FALSE(result.domain.has_value());
|
||||
EXPECT_TRUE(has_diagnostic(result, "abaqus.semantic.missing_node_target"));
|
||||
}
|
||||
|
||||
TEST(Boundary, ReportsConflictAtTheConflictingDataRow) {
|
||||
const auto result =
|
||||
parse_and_map(fixture_path("invalid/boundary_conflict.inp"));
|
||||
|
||||
@@ -130,6 +130,37 @@ TEST(ActiveInput, RejectsMixedFlatAndHierarchicalMeshAtTheFlatRecord) {
|
||||
EXPECT_EQ(diagnostic.source->file, fixture_path("invalid/mixed_mesh.inp"));
|
||||
}
|
||||
|
||||
TEST(ActiveInput, RejectsGlobalSetsInHierarchicalInput) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-global-set-with-hierarchy.inp",
|
||||
"*NSET, NSET=Ignored\n"
|
||||
"*PART, NAME=BeamPart\n"
|
||||
"*END PART\n"
|
||||
"*ASSEMBLY, NAME=RootAssembly\n"
|
||||
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
|
||||
"*END INSTANCE\n"
|
||||
"*END ASSEMBLY\n"};
|
||||
|
||||
expect_failure(
|
||||
parse(input.path()), "abaqus.semantic.mixed_mesh_organization", 1U);
|
||||
}
|
||||
|
||||
TEST(ActiveInput, RejectsDuplicatePartNamesAtTheSecondDefinition) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-duplicate-parts.inp",
|
||||
"*PART, NAME=BeamPart\n"
|
||||
"*END PART\n"
|
||||
"*PART, NAME=BeamPart\n"
|
||||
"*END PART\n"
|
||||
"*ASSEMBLY, NAME=RootAssembly\n"
|
||||
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
|
||||
"*END INSTANCE\n"
|
||||
"*END ASSEMBLY\n"};
|
||||
|
||||
expect_failure(
|
||||
parse(input.path()), "abaqus.semantic.duplicate_part", 3U);
|
||||
}
|
||||
|
||||
TEST(ActiveInput, RejectsHierarchicalInputWithoutAnAssembly) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-missing-assembly.inp",
|
||||
|
||||
@@ -74,7 +74,7 @@ TEST(AbaqusParser, ParsesCaseInsensitiveKeywordsCommentsAndCommaFields) {
|
||||
EXPECT_EQ(nodes.data[0], (std::vector<std::string>{
|
||||
"1", "0.0", "0.0", "0.0"}));
|
||||
EXPECT_EQ(nodes.data[1], (std::vector<std::string>{
|
||||
"2", "1.0", "0.0", "0.0", ""}));
|
||||
"2", "1.0", "0.0", "0.0"}));
|
||||
EXPECT_EQ(nodes.source.file, path);
|
||||
EXPECT_EQ(nodes.source.line, 3U);
|
||||
EXPECT_EQ(nodes.source.column, 1U);
|
||||
@@ -121,6 +121,214 @@ TEST(AbaqusParser, RejectsUnsupportedKeywordsInsteadOfIgnoringThem) {
|
||||
EXPECT_EQ(result.diagnostics[0].source->line, 2U);
|
||||
}
|
||||
|
||||
TEST(AbaqusParser, RejectsUnsupportedParametersOnSupportedKeywords) {
|
||||
struct Case final {
|
||||
std::string_view name;
|
||||
std::string_view contents;
|
||||
std::size_t line;
|
||||
};
|
||||
const Case cases[]{
|
||||
{"node", "*NODE, EXTRA=1\n1, 0.0, 0.0, 0.0\n", 1U},
|
||||
{"element", "*ELEMENT, TYPE=B31, EXTRA=1\n1, 1, 2\n", 1U},
|
||||
{"part", "*PART, NAME=P, EXTRA=1\n*END PART\n", 1U},
|
||||
{"end-part", "*PART, NAME=P\n*END PART, EXTRA=1\n", 2U},
|
||||
{
|
||||
"assembly",
|
||||
"*ASSEMBLY, NAME=A, EXTRA=1\n*END ASSEMBLY\n",
|
||||
1U,
|
||||
},
|
||||
{
|
||||
"end-assembly",
|
||||
"*ASSEMBLY, NAME=A\n*END ASSEMBLY, EXTRA=1\n",
|
||||
2U,
|
||||
},
|
||||
{
|
||||
"instance",
|
||||
"*ASSEMBLY, NAME=A\n"
|
||||
"*INSTANCE, NAME=I, PART=P, EXTRA=1\n"
|
||||
"*END INSTANCE\n"
|
||||
"*END ASSEMBLY\n",
|
||||
2U,
|
||||
},
|
||||
{
|
||||
"end-instance",
|
||||
"*ASSEMBLY, NAME=A\n"
|
||||
"*INSTANCE, NAME=I, PART=P\n"
|
||||
"*END INSTANCE, EXTRA=1\n"
|
||||
"*END ASSEMBLY\n",
|
||||
3U,
|
||||
},
|
||||
{"nset", "*NSET, NSET=S, EXTRA=1\n1\n", 1U},
|
||||
{"elset", "*ELSET, ELSET=S, EXTRA=1\n1\n", 1U},
|
||||
{"material", "*MATERIAL, NAME=M, EXTRA=1\n", 1U},
|
||||
{
|
||||
"elastic",
|
||||
"*MATERIAL, NAME=M\n*ELASTIC, EXTRA=1\n1.0, 0.3\n",
|
||||
2U,
|
||||
},
|
||||
{
|
||||
"beam-general-section",
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=S, "
|
||||
"MATERIAL=M, EXTRA=1\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n",
|
||||
1U,
|
||||
},
|
||||
{
|
||||
"transverse-shear-stiffness",
|
||||
"*TRANSVERSE SHEAR STIFFNESS, EXTRA=1\n1.0, 1.0, 0.0\n",
|
||||
1U,
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& test_case : cases) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-parser-unsupported-parameter-" +
|
||||
std::string{test_case.name} + ".inp",
|
||||
test_case.contents,
|
||||
};
|
||||
|
||||
const auto result = fesa::parse_deck(input.path());
|
||||
|
||||
SCOPED_TRACE(test_case.name);
|
||||
EXPECT_FALSE(result.deck.has_value());
|
||||
ASSERT_EQ(result.diagnostics.size(), 1U);
|
||||
EXPECT_EQ(
|
||||
result.diagnostics[0].code,
|
||||
"abaqus.syntax.unsupported_parameter");
|
||||
ASSERT_TRUE(result.diagnostics[0].source.has_value());
|
||||
EXPECT_EQ(result.diagnostics[0].source->line, test_case.line);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AbaqusParser, RejectsInvalidParameterFormsAndSetScopeOptions) {
|
||||
struct Case final {
|
||||
std::string_view name;
|
||||
std::string_view contents;
|
||||
std::string_view code;
|
||||
};
|
||||
const Case cases[]{
|
||||
{
|
||||
"generate-valued",
|
||||
"*NSET, NSET=S, GENERATE=YES\n1, 2, 1\n",
|
||||
"abaqus.syntax.unsupported_parameter",
|
||||
},
|
||||
{
|
||||
"generate-empty-valued",
|
||||
"*NSET, NSET=S, GENERATE=\n1, 2, 1\n",
|
||||
"abaqus.syntax.unsupported_parameter",
|
||||
},
|
||||
{
|
||||
"empty-element-set",
|
||||
"*ELEMENT, TYPE=B31, ELSET=\n1, 1, 2\n",
|
||||
"abaqus.syntax.invalid_parameter",
|
||||
},
|
||||
{
|
||||
"instance-on-flat-set",
|
||||
"*NSET, NSET=S, INSTANCE=I\n1\n",
|
||||
"abaqus.syntax.unsupported_parameter",
|
||||
},
|
||||
{
|
||||
"instance-on-part-set",
|
||||
"*PART, NAME=P\n"
|
||||
"*NSET, NSET=S, INSTANCE=I\n"
|
||||
"1\n"
|
||||
"*END PART\n",
|
||||
"abaqus.syntax.unsupported_parameter",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& test_case : cases) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-parser-parameter-form-" + std::string{test_case.name} +
|
||||
".inp",
|
||||
test_case.contents,
|
||||
};
|
||||
|
||||
const auto result = fesa::parse_deck(input.path());
|
||||
|
||||
SCOPED_TRACE(test_case.name);
|
||||
EXPECT_FALSE(result.deck.has_value());
|
||||
ASSERT_EQ(result.diagnostics.size(), 1U);
|
||||
EXPECT_EQ(result.diagnostics[0].code, test_case.code);
|
||||
ASSERT_TRUE(result.diagnostics[0].source.has_value());
|
||||
EXPECT_EQ(result.diagnostics[0].source->line, 1U +
|
||||
(test_case.name == "instance-on-part-set" ? 1U : 0U));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AbaqusParser, RejectsModelKeywordsOutsideTheirDocumentedScopes) {
|
||||
struct Case final {
|
||||
std::string_view name;
|
||||
std::string_view contents;
|
||||
std::string_view code;
|
||||
std::size_t line;
|
||||
};
|
||||
const Case cases[]{
|
||||
{
|
||||
"node-in-assembly",
|
||||
"*ASSEMBLY, NAME=A\n"
|
||||
"*NODE\n"
|
||||
"1, 0.0, 0.0, 0.0\n"
|
||||
"*END ASSEMBLY\n",
|
||||
"abaqus.syntax.invalid_node_scope",
|
||||
2U,
|
||||
},
|
||||
{
|
||||
"material-in-part",
|
||||
"*PART, NAME=P\n"
|
||||
"*MATERIAL, NAME=M\n"
|
||||
"*END PART\n",
|
||||
"abaqus.syntax.invalid_material_scope",
|
||||
2U,
|
||||
},
|
||||
{
|
||||
"section-in-assembly",
|
||||
"*ASSEMBLY, NAME=A\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=S, MATERIAL=M\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n"
|
||||
"*END ASSEMBLY\n",
|
||||
"abaqus.syntax.invalid_section_scope",
|
||||
2U,
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& test_case : cases) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-parser-invalid-scope-" + std::string{test_case.name} +
|
||||
".inp",
|
||||
test_case.contents,
|
||||
};
|
||||
|
||||
const auto result = fesa::parse_deck(input.path());
|
||||
|
||||
SCOPED_TRACE(test_case.name);
|
||||
EXPECT_FALSE(result.deck.has_value());
|
||||
ASSERT_EQ(result.diagnostics.size(), 1U);
|
||||
EXPECT_EQ(result.diagnostics[0].code, test_case.code);
|
||||
ASSERT_TRUE(result.diagnostics[0].source.has_value());
|
||||
EXPECT_EQ(result.diagnostics[0].source->line, test_case.line);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AbaqusParser, RejectsDataRowsOnMaterial) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-parser-material-data.inp",
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"unexpected data\n"};
|
||||
|
||||
const auto result = fesa::parse_deck(input.path());
|
||||
|
||||
EXPECT_FALSE(result.deck.has_value());
|
||||
ASSERT_EQ(result.diagnostics.size(), 1U);
|
||||
EXPECT_EQ(
|
||||
result.diagnostics[0].code,
|
||||
"abaqus.syntax.data_without_keyword");
|
||||
ASSERT_TRUE(result.diagnostics[0].source.has_value());
|
||||
EXPECT_EQ(result.diagnostics[0].source->line, 2U);
|
||||
}
|
||||
|
||||
TEST(ScopedDeck, PreservesPartAssemblyAndInstanceScopes) {
|
||||
const auto path =
|
||||
fixture_path("minimal_part_instance_cantilever.inp");
|
||||
|
||||
Reference in New Issue
Block a user