Files
FESA/src/fesa/io/abaqus/set_resolver.cpp
T

412 lines
13 KiB
C++

#include <fesa/io/abaqus/set_resolver.hpp>
#include <algorithm>
#include <charconv>
#include <compare>
#include <cstddef>
#include <cstdint>
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace fesa {
namespace {
struct SetNamespace final {
ResolvedSetScope scope;
std::string scope_name;
ResolvedSetKind kind;
auto operator<=>(const SetNamespace&) const = default;
};
struct SetKey final {
SetNamespace name_space;
std::string set_name;
auto operator<=>(const SetKey&) const = default;
};
struct RawMember final {
std::string text;
SourceLocation source;
};
struct RawSet final {
std::vector<RawMember> members;
};
enum class VisitState { unvisited, visiting, resolved, failed };
const std::string* parameter(
const DeckRecord& record,
const std::string_view name) {
const auto found = record.parameters.find(name);
return found == record.parameters.end() ? nullptr : &found->second;
}
bool has_parameter(
const DeckRecord& record,
const std::string_view name) {
return record.parameters.contains(name);
}
SourceLocation data_source(
const DeckRecord& record,
const std::size_t row) {
return row < record.data_sources.size()
? record.data_sources[row]
: record.source;
}
bool parse_positive_label(
const std::string_view text,
std::int64_t& value) {
const auto parsed =
std::from_chars(text.data(), text.data() + text.size(), value);
return parsed.ec == std::errc{} &&
parsed.ptr == text.data() + text.size() && value > 0;
}
class SetResolver final {
public:
explicit SetResolver(const ParsedDeck& deck) : deck_{deck} {}
[[nodiscard]] SetResolutionResult resolve() {
collect_scope(
deck_.global_records,
{ResolvedSetScope::global, "global", ResolvedSetKind::node});
for (const ParsedPart& part : deck_.parts) {
collect_scope(
part.records,
{ResolvedSetScope::part,
part.name,
ResolvedSetKind::node});
}
collect_assembly();
std::vector<ResolvedSet> sets;
sets.reserve(raw_sets_.size());
for (const auto& [key, raw_set] : raw_sets_) {
static_cast<void>(raw_set);
if (!resolve_set(key)) {
continue;
}
sets.push_back({
key.name_space.scope_name,
key.set_name,
resolved_sets_.at(key),
key.name_space.scope,
key.name_space.kind,
});
}
if (!diagnostics_.empty()) {
sets.clear();
}
return {std::move(sets), std::move(diagnostics_)};
}
private:
void add_error(
std::string code,
std::string message,
const SourceLocation& source) {
diagnostics_.push_back({
DiagnosticStage::semantic,
Severity::error,
std::move(code),
std::move(message),
source,
});
}
void collect_scope(
const std::vector<DeckRecord>& records,
SetNamespace name_space) {
collect_entities(records, name_space);
collect_set_records(records, std::move(name_space), nullptr);
}
void collect_entities(
const std::vector<DeckRecord>& records,
const SetNamespace& base_namespace) {
for (const DeckRecord& record : records) {
ResolvedSetKind kind;
if (record.keyword == "NODE") {
kind = ResolvedSetKind::node;
} else if (record.keyword == "ELEMENT") {
kind = ResolvedSetKind::element;
} else {
continue;
}
SetNamespace entity_namespace = base_namespace;
entity_namespace.kind = kind;
std::set<std::int64_t>& labels = entities_[entity_namespace];
for (std::size_t row = 0; row < record.data.size(); ++row) {
if (record.data[row].empty()) {
continue;
}
std::int64_t label = 0;
if (!parse_positive_label(record.data[row][0], label)) {
continue;
}
labels.insert(label);
if (kind != ResolvedSetKind::element) {
continue;
}
const std::string* set_name = parameter(record, "ELSET");
if (set_name == nullptr || set_name->empty()) {
continue;
}
raw_sets_[{entity_namespace, *set_name}].members.push_back({
std::to_string(label),
data_source(record, row),
});
}
}
}
void collect_set_records(
const std::vector<DeckRecord>& records,
const SetNamespace& base_namespace,
const std::string* active_instance) {
for (const DeckRecord& record : records) {
SetNamespace set_namespace = base_namespace;
std::string_view name_parameter;
if (record.keyword == "NSET") {
set_namespace.kind = ResolvedSetKind::node;
name_parameter = "NSET";
} else if (record.keyword == "ELSET") {
set_namespace.kind = ResolvedSetKind::element;
name_parameter = "ELSET";
} else {
continue;
}
const std::string* set_name = parameter(record, name_parameter);
if (set_name == nullptr || set_name->empty()) {
add_error(
"abaqus.semantic.missing_parameter",
"*" + record.keyword + " requires parameter " +
std::string{name_parameter} + ".",
record.source);
continue;
}
if (set_namespace.scope == ResolvedSetScope::assembly) {
const std::string* instance = parameter(record, "INSTANCE");
if (active_instance == nullptr || instance == nullptr ||
*instance != *active_instance) {
add_error(
"abaqus.semantic.wrong_instance",
"Assembly set '" + *set_name +
"' must reference the active Instance.",
record.source);
continue;
}
}
RawSet& raw_set = raw_sets_[{set_namespace, *set_name}];
if (has_parameter(record, "GENERATE")) {
collect_generate(record, raw_set);
} else {
collect_explicit(record, raw_set);
}
}
}
void collect_explicit(
const DeckRecord& record,
RawSet& raw_set) {
for (std::size_t row = 0; row < record.data.size(); ++row) {
for (const std::string& field : record.data[row]) {
if (field.empty()) {
continue;
}
raw_set.members.push_back({field, data_source(record, row)});
}
}
}
void collect_generate(
const DeckRecord& record,
RawSet& raw_set) {
const SourceLocation source =
record.data.empty() ? record.source : data_source(record, 0U);
if (record.data.size() != 1U || record.data[0].size() != 3U ||
std::ranges::any_of(
record.data[0],
[](const std::string& field) { return field.empty(); })) {
add_error(
"abaqus.semantic.invalid_generate",
"*" + record.keyword +
", GENERATE requires exactly start, end, increment.",
source);
return;
}
std::int64_t start = 0;
std::int64_t end = 0;
std::int64_t increment = 0;
if (!parse_positive_label(record.data[0][0], start) ||
!parse_positive_label(record.data[0][1], end) ||
!parse_positive_label(record.data[0][2], increment) ||
start > end || (end - start) % increment != 0) {
add_error(
"abaqus.semantic.invalid_generate",
"Invalid *" + record.keyword + " generate range.",
source);
return;
}
for (std::int64_t label = start;; label += increment) {
raw_set.members.push_back({std::to_string(label), source});
if (label == end) {
break;
}
}
}
void collect_assembly() {
if (!deck_.assembly.has_value()) {
return;
}
const ParsedAssembly& assembly = *deck_.assembly;
const ParsedInstance* active_instance =
assembly.instances.size() == 1U
? &assembly.instances.front()
: nullptr;
SetNamespace assembly_namespace{
ResolvedSetScope::assembly,
assembly.name,
ResolvedSetKind::node,
};
if (active_instance != nullptr) {
const auto part = std::ranges::find(
deck_.parts, active_instance->part_name, &ParsedPart::name);
if (part != deck_.parts.end()) {
for (const ResolvedSetKind kind : {
ResolvedSetKind::node,
ResolvedSetKind::element}) {
const SetNamespace part_namespace{
ResolvedSetScope::part,
part->name,
kind,
};
SetNamespace lifted_namespace = assembly_namespace;
lifted_namespace.kind = kind;
const auto labels = entities_.find(part_namespace);
if (labels != entities_.end()) {
entities_[lifted_namespace] = labels->second;
}
}
}
}
const std::string* instance_name =
active_instance == nullptr ? nullptr : &active_instance->name;
collect_set_records(
assembly.records, assembly_namespace, instance_name);
}
bool resolve_set(const SetKey& key) {
VisitState& state = states_[key];
if (state == VisitState::resolved) {
return true;
}
if (state == VisitState::failed) {
return false;
}
state = VisitState::visiting;
bool succeeded = true;
std::vector<std::int64_t> labels;
for (const RawMember& member : raw_sets_.at(key).members) {
std::int64_t label = 0;
if (parse_positive_label(member.text, label)) {
const auto entity_namespace = entities_.find(key.name_space);
if (entity_namespace == entities_.end() ||
!entity_namespace->second.contains(label)) {
add_error(
"abaqus.semantic.missing_set_member",
"Set '" + key.set_name +
"' references missing entity label " +
std::to_string(label) + ".",
member.source);
succeeded = false;
continue;
}
labels.push_back(label);
continue;
}
const SetKey nested_key{key.name_space, member.text};
const auto nested = raw_sets_.find(nested_key);
if (nested == raw_sets_.end()) {
add_error(
"abaqus.semantic.missing_set_member",
"Set '" + key.set_name + "' references missing set '" +
member.text + "'.",
member.source);
succeeded = false;
continue;
}
if (states_[nested_key] == VisitState::visiting) {
add_error(
"abaqus.semantic.set_cycle",
"Set '" + key.set_name +
"' closes a nested set reference cycle through '" +
member.text + "'.",
member.source);
succeeded = false;
continue;
}
if (!resolve_set(nested_key)) {
succeeded = false;
continue;
}
const std::vector<std::int64_t>& nested_labels =
resolved_sets_.at(nested_key);
labels.insert(
labels.end(), nested_labels.begin(), nested_labels.end());
}
if (!succeeded) {
state = VisitState::failed;
return false;
}
std::ranges::sort(labels);
labels.erase(std::ranges::unique(labels).begin(), labels.end());
resolved_sets_[key] = std::move(labels);
state = VisitState::resolved;
return true;
}
const ParsedDeck& deck_;
std::map<SetNamespace, std::set<std::int64_t>> entities_;
std::map<SetKey, RawSet> raw_sets_;
std::map<SetKey, VisitState> states_;
std::map<SetKey, std::vector<std::int64_t>> resolved_sets_;
std::vector<Diagnostic> diagnostics_;
};
} // namespace
SetResolutionResult resolve_sets(const ParsedDeck& deck) {
return SetResolver{deck}.resolve();
}
} // namespace fesa