feat(cpp-object-oriented-modular-refactoring): step 11 - source-target-resolver
This commit is contained in:
@@ -14,26 +14,14 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/ascii.h"
|
||||
#include "fesa/math/vector3.h"
|
||||
#include "fesa/model/shell_geometry.h"
|
||||
#include "fesa/model/source_target_resolver.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
std::string UppercaseAscii(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](char character) {
|
||||
if (character >= 'a' && character <= 'z') {
|
||||
return static_cast<char>(character - 'a' + 'A');
|
||||
}
|
||||
return character;
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
bool EqualName(const std::string& left, const std::string& right) {
|
||||
return UppercaseAscii(left) == UppercaseAscii(right);
|
||||
}
|
||||
|
||||
std::vector<std::string> WithoutTrailingEmpty(std::vector<std::string> fields) {
|
||||
while (!fields.empty() && fields.back().empty()) {
|
||||
fields.pop_back();
|
||||
@@ -254,20 +242,6 @@ class MappingContext {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryPositiveInteger(const std::string& text, std::int64_t& value) const {
|
||||
if (text.empty()) {
|
||||
return false;
|
||||
}
|
||||
char* end = nullptr;
|
||||
errno = 0;
|
||||
const long long parsed = std::strtoll(text.c_str(), &end, 10);
|
||||
if (errno == ERANGE || end != text.c_str() + text.size() || parsed <= 0) {
|
||||
return false;
|
||||
}
|
||||
value = static_cast<std::int64_t>(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseDouble(const std::string& text, double& value,
|
||||
const SourceLocation& location, const std::string& keyword) {
|
||||
if (text.empty()) {
|
||||
@@ -311,16 +285,17 @@ class MappingContext {
|
||||
|
||||
bool ContainsName(const std::vector<RawPart>& values,
|
||||
const std::string& name) const {
|
||||
return std::any_of(
|
||||
values.begin(), values.end(),
|
||||
[&name](const RawPart& value) { return EqualName(value.name, name); });
|
||||
return std::any_of(values.begin(), values.end(),
|
||||
[&name](const RawPart& value) {
|
||||
return AsciiCaseInsensitiveEquals(value.name, name);
|
||||
});
|
||||
}
|
||||
|
||||
bool ContainsName(const std::vector<RawInstance>& values,
|
||||
const std::string& name) const {
|
||||
return std::any_of(values.begin(), values.end(),
|
||||
[&name](const RawInstance& value) {
|
||||
return EqualName(value.name, name);
|
||||
return AsciiCaseInsensitiveEquals(value.name, name);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -328,14 +303,14 @@ class MappingContext {
|
||||
const std::string& name) const {
|
||||
return std::any_of(values.begin(), values.end(),
|
||||
[&name](const RawMaterial& value) {
|
||||
return EqualName(value.name, name);
|
||||
return AsciiCaseInsensitiveEquals(value.name, name);
|
||||
});
|
||||
}
|
||||
|
||||
bool ContainsSetName(const std::vector<RawSet>& sets,
|
||||
const std::string& name) const {
|
||||
const auto matches = [&name](const RawSet& set) {
|
||||
return EqualName(set.name, name);
|
||||
return AsciiCaseInsensitiveEquals(set.name, name);
|
||||
};
|
||||
return std::any_of(sets.begin(), sets.end(), matches);
|
||||
}
|
||||
@@ -344,7 +319,7 @@ class MappingContext {
|
||||
return std::any_of(assembly_sets_.begin(), assembly_sets_.end(),
|
||||
[node_set, &name](const RawAssemblySet& set) {
|
||||
return set.is_node_set == node_set &&
|
||||
EqualName(set.name, name);
|
||||
AsciiCaseInsensitiveEquals(set.name, name);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -674,15 +649,15 @@ class MappingContext {
|
||||
RawElement::Type element_type{};
|
||||
ElementFamily element_family{};
|
||||
std::size_t expected_field_count = 0U;
|
||||
if (EqualName(*type, "B33")) {
|
||||
if (AsciiCaseInsensitiveEquals(*type, "B33")) {
|
||||
element_type = RawElement::Type::kB33;
|
||||
element_family = ElementFamily::kBeam;
|
||||
expected_field_count = 3U;
|
||||
} else if (EqualName(*type, "S4")) {
|
||||
} else if (AsciiCaseInsensitiveEquals(*type, "S4")) {
|
||||
element_type = RawElement::Type::kS4;
|
||||
element_family = ElementFamily::kShell;
|
||||
expected_field_count = 5U;
|
||||
} else if (EqualName(*type, "S4R")) {
|
||||
} else if (AsciiCaseInsensitiveEquals(*type, "S4R")) {
|
||||
element_type = RawElement::Type::kS4r;
|
||||
element_family = ElementFamily::kShell;
|
||||
expected_field_count = 5U;
|
||||
@@ -803,9 +778,7 @@ class MappingContext {
|
||||
return;
|
||||
}
|
||||
if (block.data[0].fields.size() == 2U) {
|
||||
std::int64_t ignored_integration_points = 0;
|
||||
if (!TryPositiveInteger(block.data[0].fields[1],
|
||||
ignored_integration_points)) {
|
||||
if (!ParsePositiveSourceLabel(block.data[0].fields[1]).HasValue()) {
|
||||
InputFailure("unsupported-shell-section-option", block.data[0].location,
|
||||
block.canonical_name, block.data[0].fields[1],
|
||||
"The optional shell integration-point field must be a "
|
||||
@@ -932,7 +905,7 @@ class MappingContext {
|
||||
if (element_set == nullptr || material == nullptr || section == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!EqualName(*section, "GENERAL")) {
|
||||
if (!AsciiCaseInsensitiveEquals(*section, "GENERAL")) {
|
||||
InputFailure("unsupported-section-formulation", block.location,
|
||||
block.canonical_name, *section,
|
||||
"Only SECTION=GENERAL belongs to the approved subset.");
|
||||
@@ -940,7 +913,8 @@ class MappingContext {
|
||||
}
|
||||
if (std::any_of(part.sections.begin(), part.sections.end(),
|
||||
[element_set](const RawSection& existing) {
|
||||
return EqualName(existing.element_set_name, *element_set);
|
||||
return AsciiCaseInsensitiveEquals(
|
||||
existing.element_set_name, *element_set);
|
||||
})) {
|
||||
InputFailure("duplicate-entity", block.location, block.canonical_name,
|
||||
*element_set,
|
||||
@@ -1332,7 +1306,7 @@ class MappingContext {
|
||||
"NLGEOM requires NO in the approved subset.");
|
||||
return;
|
||||
}
|
||||
if (!EqualName(*nlgeom->value, "NO")) {
|
||||
if (!AsciiCaseInsensitiveEquals(*nlgeom->value, "NO")) {
|
||||
if (model_element_family_ == ElementFamily::kShell) {
|
||||
InputFailure("unsupported-nonlinear-geometry", block.location,
|
||||
block.canonical_name, *nlgeom->value,
|
||||
@@ -1510,22 +1484,24 @@ class MappingContext {
|
||||
|
||||
const RawPart* FindPart(const std::string& name) const {
|
||||
const auto found = std::find_if(
|
||||
parts_.begin(), parts_.end(),
|
||||
[&name](const RawPart& part) { return EqualName(part.name, name); });
|
||||
parts_.begin(), parts_.end(), [&name](const RawPart& part) {
|
||||
return AsciiCaseInsensitiveEquals(part.name, name);
|
||||
});
|
||||
return found == parts_.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
const RawInstance* FindInstance(const std::string& name) const {
|
||||
const auto found = std::find_if(instances_.begin(), instances_.end(),
|
||||
[&name](const RawInstance& instance) {
|
||||
return EqualName(instance.name, name);
|
||||
});
|
||||
const auto found =
|
||||
std::find_if(instances_.begin(), instances_.end(),
|
||||
[&name](const RawInstance& instance) {
|
||||
return AsciiCaseInsensitiveEquals(instance.name, name);
|
||||
});
|
||||
return found == instances_.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
std::optional<EntityIndex> FindMaterialIndex(const std::string& name) const {
|
||||
for (std::size_t index = 0U; index < materials_.size(); ++index) {
|
||||
if (EqualName(materials_[index].name, name)) {
|
||||
if (AsciiCaseInsensitiveEquals(materials_[index].name, name)) {
|
||||
return static_cast<EntityIndex>(index);
|
||||
}
|
||||
}
|
||||
@@ -1534,9 +1510,10 @@ class MappingContext {
|
||||
|
||||
const RawSet* FindSet(const std::vector<RawSet>& sets,
|
||||
const std::string& name) const {
|
||||
const auto found = std::find_if(
|
||||
sets.begin(), sets.end(),
|
||||
[&name](const RawSet& set) { return EqualName(set.name, name); });
|
||||
const auto found =
|
||||
std::find_if(sets.begin(), sets.end(), [&name](const RawSet& set) {
|
||||
return AsciiCaseInsensitiveEquals(set.name, name);
|
||||
});
|
||||
return found == sets.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
@@ -2000,7 +1977,8 @@ class MappingContext {
|
||||
const auto definition_instance = std::find_if(
|
||||
definition_.instances.begin(), definition_.instances.end(),
|
||||
[&raw_instance](const InstanceDefinition& instance) {
|
||||
return EqualName(instance.name, raw_instance->name);
|
||||
return AsciiCaseInsensitiveEquals(instance.name,
|
||||
raw_instance->name);
|
||||
});
|
||||
if (definition_instance == definition_.instances.end()) {
|
||||
InputFailure("unresolved-reference", raw_set.location,
|
||||
@@ -2010,11 +1988,11 @@ class MappingContext {
|
||||
}
|
||||
if (raw_set.is_node_set) {
|
||||
definition_.node_sets.erase(
|
||||
std::remove_if(definition_.node_sets.begin(),
|
||||
definition_.node_sets.end(),
|
||||
[&raw_set](const NodeSet& set) {
|
||||
return EqualName(set.name, raw_set.name);
|
||||
}),
|
||||
std::remove_if(
|
||||
definition_.node_sets.begin(), definition_.node_sets.end(),
|
||||
[&raw_set](const NodeSet& set) {
|
||||
return AsciiCaseInsensitiveEquals(set.name, raw_set.name);
|
||||
}),
|
||||
definition_.node_sets.end());
|
||||
NodeSet set{
|
||||
raw_set.name, definition_instance->name, {}, raw_set.location};
|
||||
@@ -2040,7 +2018,8 @@ class MappingContext {
|
||||
std::remove_if(definition_.element_sets.begin(),
|
||||
definition_.element_sets.end(),
|
||||
[&raw_set](const ElementSet& set) {
|
||||
return EqualName(set.name, raw_set.name);
|
||||
return AsciiCaseInsensitiveEquals(set.name,
|
||||
raw_set.name);
|
||||
}),
|
||||
definition_.element_sets.end());
|
||||
ElementSet set{
|
||||
@@ -2066,68 +2045,57 @@ class MappingContext {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::vector<EntityIndex>> ResolveNodeTarget(
|
||||
const std::string& target, const SourceLocation& location,
|
||||
const std::string& keyword) {
|
||||
std::vector<const NodeSet*> matching_sets;
|
||||
/// @brief Builds one stable node-target index from the completed candidate.
|
||||
SourceTargetIndex BuildSourceTargetIndex() const {
|
||||
std::vector<SourceTargetIndexEntry> entries;
|
||||
std::size_t declaration_order = 0U;
|
||||
for (std::size_t node = 0U; node < definition_.nodes.size(); ++node) {
|
||||
const auto& source_id = definition_.nodes[node].source_id;
|
||||
entries.push_back({SourceEntityKind::kNode, source_id.instance_name, "",
|
||||
source_id, static_cast<EntityIndex>(node),
|
||||
declaration_order++});
|
||||
}
|
||||
for (const auto& set : definition_.node_sets) {
|
||||
if (EqualName(set.name, target)) {
|
||||
matching_sets.push_back(&set);
|
||||
for (const EntityIndex node : set.node_indices) {
|
||||
const auto& source_id = definition_.nodes[node].source_id;
|
||||
entries.push_back({SourceEntityKind::kNode,
|
||||
set.instance_name.value_or(source_id.instance_name),
|
||||
set.name, source_id, node, declaration_order++});
|
||||
}
|
||||
}
|
||||
return SourceTargetIndex{std::move(entries)};
|
||||
}
|
||||
|
||||
std::vector<EntityIndex> matching_nodes;
|
||||
std::int64_t label = 0;
|
||||
if (TryPositiveInteger(target, label)) {
|
||||
for (std::size_t index = 0U; index < definition_.nodes.size(); ++index) {
|
||||
if (definition_.nodes[index].source_id.source_label == label) {
|
||||
matching_nodes.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A token is resolved only after both approved interpretations have
|
||||
// been considered; declaration order never gives a node set priority
|
||||
// over an equally valid direct source-label target.
|
||||
if (matching_sets.size() > 1U) {
|
||||
std::optional<std::vector<EntityIndex>> ResolveNodeTarget(
|
||||
const SourceTargetResolver& resolver, const std::string& target,
|
||||
const SourceLocation& location, const std::string& keyword) {
|
||||
auto resolved = resolver.Resolve({SourceEntityKind::kNode, "", target});
|
||||
if (!resolved.HasValue()) {
|
||||
InputFailure(
|
||||
"unresolved-reference", location, keyword, target,
|
||||
"The node-set target is ambiguous across identity instances.");
|
||||
"The boundary or load target must resolve unambiguously to one "
|
||||
"node or one node set.");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (matching_nodes.size() > 1U) {
|
||||
InputFailure("unresolved-reference", location, keyword, target,
|
||||
"The direct node-label target is ambiguous across identity "
|
||||
"instances.");
|
||||
return std::nullopt;
|
||||
std::vector<EntityIndex> indices;
|
||||
indices.reserve(resolved.Value().size());
|
||||
for (const auto& entry : resolved.Value()) {
|
||||
indices.push_back(entry.entity_index);
|
||||
}
|
||||
if (matching_sets.size() == 1U && matching_nodes.size() == 1U) {
|
||||
InputFailure("unresolved-reference", location, keyword, target,
|
||||
"The target is ambiguous between a node-set name and a "
|
||||
"direct node label.");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (matching_sets.size() == 1U) {
|
||||
return matching_sets.front()->node_indices;
|
||||
}
|
||||
if (matching_nodes.size() == 1U) {
|
||||
return matching_nodes;
|
||||
}
|
||||
InputFailure(
|
||||
"unresolved-reference", location, keyword, target,
|
||||
"The boundary or load target must resolve to one node or node set.");
|
||||
return std::nullopt;
|
||||
return indices;
|
||||
}
|
||||
|
||||
void FinalizeStep() {
|
||||
std::vector<BoundaryCondition> boundaries = model_boundaries_;
|
||||
boundaries.insert(boundaries.end(), step_.boundaries.begin(),
|
||||
step_.boundaries.end());
|
||||
const SourceTargetIndex target_index = BuildSourceTargetIndex();
|
||||
const SourceTargetResolver target_resolver{target_index};
|
||||
|
||||
std::map<std::pair<EntityIndex, int>, double> prescribed_values;
|
||||
for (const auto& boundary : boundaries) {
|
||||
auto target =
|
||||
ResolveNodeTarget(boundary.target, boundary.location, "BOUNDARY");
|
||||
auto target = ResolveNodeTarget(target_resolver, boundary.target,
|
||||
boundary.location, "BOUNDARY");
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
@@ -2148,7 +2116,8 @@ class MappingContext {
|
||||
}
|
||||
}
|
||||
for (const auto& load : step_.loads) {
|
||||
if (!ResolveNodeTarget(load.target, load.location, "CLOAD")) {
|
||||
if (!ResolveNodeTarget(target_resolver, load.target, load.location,
|
||||
"CLOAD")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user