feat(cpp-object-oriented-modular-refactoring): step 11 - source-target-resolver

This commit is contained in:
KOKO\Mimi
2026-08-16 08:34:09 +09:00
parent 5430fffd62
commit a6324a9004
12 changed files with 714 additions and 273 deletions
+21 -62
View File
@@ -2,21 +2,19 @@
#include <algorithm>
#include <array>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/math/vector3.h"
#include "fesa/model/source_target_resolver.h"
namespace fesa {
namespace {
@@ -324,70 +322,29 @@ Status ValidateRecoveryInputs(const AnalysisModel& model,
return Status::Ok();
}
char AsciiLower(const char value) {
if (value >= 'A' && value <= 'Z') {
return static_cast<char>(value + ('a' - 'A'));
}
return value;
}
bool EqualName(const std::string& left, const std::string& right) {
return left.size() == right.size() &&
std::equal(left.begin(), left.end(), right.begin(),
[](const char left_value, const char right_value) {
return AsciiLower(left_value) == AsciiLower(right_value);
});
}
bool TryPositiveInteger(const std::string& text, std::int64_t& value) {
const char* const first = text.data();
const char* const last = first + text.size();
const auto parsed = std::from_chars(first, last, value);
return parsed.ec == std::errc{} && parsed.ptr == last && value > 0;
}
Result<std::vector<EntityIndex>> ResolveLoadTarget(const Domain& domain,
const NodalLoad& load) {
std::vector<const NodeSet*> sets;
for (const auto& set : domain.NodeSets()) {
if (EqualName(set.name, load.target)) {
sets.push_back(&set);
}
}
std::vector<EntityIndex> nodes;
std::int64_t source_label = 0;
if (TryPositiveInteger(load.target, source_label)) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
if (domain.Nodes()[node].source_id.source_label == source_label) {
nodes.push_back(static_cast<EntityIndex>(node));
}
}
}
if (sets.size() > 1U || nodes.size() > 1U ||
(!sets.empty() && !nodes.empty())) {
Result<std::vector<EntityIndex>> ResolveLoadTarget(
const SourceTargetResolver& resolver, const Domain& domain,
const NodalLoad& load) {
auto resolved = resolver.Resolve({SourceEntityKind::kNode, "", load.target});
if (!resolved.HasValue()) {
return RecoveryResultFailure<std::vector<EntityIndex>>(
"invalid-node-station-entity", load.location, load.target,
"A station-eligibility load target must resolve unambiguously.");
}
if (!sets.empty()) {
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
for (const EntityIndex node : sets.front()->node_indices) {
if (node >= domain.Nodes().size() || seen[node] != 0U) {
return RecoveryResultFailure<std::vector<EntityIndex>>(
"invalid-node-station-entity", load.location, load.target,
"A station-eligibility node set must contain unique valid nodes.");
}
seen[node] = 1U;
std::vector<EntityIndex> nodes;
nodes.reserve(resolved.Value().size());
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
for (const auto& target : resolved.Value()) {
const EntityIndex node = target.entity_index;
if (node >= domain.Nodes().size() || seen[node] != 0U) {
return RecoveryResultFailure<std::vector<EntityIndex>>(
"invalid-node-station-entity", load.location, load.target,
"A station-eligibility node set must contain unique valid nodes.");
}
return Result<std::vector<EntityIndex>>::Success(
sets.front()->node_indices);
seen[node] = 1U;
nodes.push_back(node);
}
if (!nodes.empty()) {
return Result<std::vector<EntityIndex>>::Success(std::move(nodes));
}
return RecoveryResultFailure<std::vector<EntityIndex>>(
"invalid-node-station-entity", load.location, load.target,
"A station-eligibility load target must resolve to a node or node set.");
return Result<std::vector<EntityIndex>>::Success(std::move(nodes));
}
bool AccumulateVectorAndScale(std::array<double, 3>& total, double& scale,
@@ -879,6 +836,8 @@ ResultRecovery::NormalizeSectionResultantsToNodeStations(
"invalid-node-station-entity", model.Step().location, model.Step().name,
"The active load view must preserve every sole-step load.");
}
const SourceTargetIndex target_index = SourceTargetIndex::FromDomain(domain);
const SourceTargetResolver target_resolver{target_index};
for (std::size_t order = 0U; order < model.ActiveLoads().size(); ++order) {
const EntityIndex load_index = model.ActiveLoads()[order];
if (load_index != order || load_index >= model.Step().loads.size()) {
@@ -893,7 +852,7 @@ ResultRecovery::NormalizeSectionResultantsToNodeStations(
"nonfinite-node-station-value", load.location, load.target,
"Station eligibility requires finite concentrated loads.");
}
auto targets = ResolveLoadTarget(domain, load);
auto targets = ResolveLoadTarget(target_resolver, domain, load);
if (!targets.HasValue()) {
return Result<std::vector<NodeStationResultRow>>::Failure(
targets.GetStatus());