feat(cpp-object-oriented-modular-refactoring): step 18 - load-hierarchy

This commit is contained in:
KOKO\Mimi
2026-08-16 10:56:57 +09:00
parent 08d352ae46
commit 9ad72e6d21
18 changed files with 705 additions and 137 deletions
+73 -67
View File
@@ -9,6 +9,7 @@
#include <vector>
#include "fesa/constraints/essential_constraints.h"
#include "fesa/loads/load.h"
#include "fesa/model/source_target_resolver.h"
namespace fesa {
@@ -25,33 +26,6 @@ Status LoadFailure(const std::string& code, const SourceLocation& location,
{{Severity::kError, code, location, keyword, identity, message}});
}
Result<std::vector<EntityIndex>> ResolveTarget(
const SourceTargetResolver& resolver, const Domain& domain,
const NodalLoad& load) {
auto resolved = resolver.Resolve({SourceEntityKind::kNode, "", load.target});
if (!resolved.HasValue()) {
return Result<std::vector<EntityIndex>>::Failure(
LoadFailure("invalid-load-target", load.location, "CLOAD", load.target,
"The load target must resolve unambiguously to one node or "
"one expanded node set."));
}
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 Result<std::vector<EntityIndex>>::Failure(LoadFailure(
"invalid-load-target", load.location, "CLOAD", load.target,
"The expanded node set must contain unique in-range stable node "
"identities."));
}
seen[node] = 1U;
nodes.push_back(node);
}
return Result<std::vector<EntityIndex>>::Success(std::move(nodes));
}
Status ValidateFiniteVector(const Vector& values,
const SourceLocation& location,
const std::string& identity) {
@@ -125,6 +99,35 @@ Status ValidateShellMoments(const Domain& domain, const Vector& full_load) {
Result<Vector> LoadAssembler::AssembleFullNodalLoad(const AnalysisModel& model,
const DofManager& dofs) {
const auto& active_loads = model.ActiveLoads();
const auto& owned_loads = model.Step().Loads();
if (active_loads.size() != owned_loads.size()) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-order", model.Step().Location(), "CLOAD",
model.Step().Name(),
"The active load view must include every sole-step load once."));
}
LoadView loads;
loads.reserve(active_loads.size());
for (std::size_t source_order = 0U; source_order < active_loads.size();
++source_order) {
const EntityIndex load_index = active_loads[source_order];
if (static_cast<std::size_t>(load_index) != source_order ||
load_index >= owned_loads.size()) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-order", model.Step().Location(), "CLOAD",
std::to_string(source_order),
"Active loads must retain complete stable source order."));
}
loads.push_back(owned_loads[load_index]);
}
return AssembleFullNodalLoad(model, dofs, loads);
}
Result<Vector> LoadAssembler::AssembleFullNodalLoad(const AnalysisModel& model,
const DofManager& dofs,
const LoadView& loads) {
const Domain& domain = model.GetDomain();
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode) {
@@ -165,52 +168,55 @@ Result<Vector> LoadAssembler::AssembleFullNodalLoad(const AnalysisModel& model,
}
}
const auto& active_loads = model.ActiveLoads();
const auto& loads = model.Step().loads;
if (active_loads.size() != loads.size()) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-order", model.Step().location, "CLOAD", model.Step().name,
"The active load view must include every sole-step load once."));
}
const SourceTargetIndex target_index = SourceTargetIndex::FromDomain(domain);
const SourceTargetResolver target_resolver{target_index};
const LoadContext context{domain, dofs, target_resolver};
std::vector<std::vector<LoadContribution>> contributions_by_load;
contributions_by_load.reserve(loads.size());
for (std::size_t source_order = 0U; source_order < loads.size();
++source_order) {
auto contributions =
loads[source_order].get().ComputeContributions(context);
if (!contributions.HasValue()) {
return Result<Vector>::Failure(contributions.GetStatus());
}
for (const auto& contribution : contributions.Value()) {
if (contribution.source_order != source_order) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-order", model.Step().Location(), "LOAD_ASSEMBLER",
std::to_string(contribution.source_order),
"Every contribution must retain its supplying load source "
"order."));
}
if (contribution.full_dof_index >= expected_full_count) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-index", model.Step().Location(), "LOAD_ASSEMBLER",
std::to_string(contribution.full_dof_index),
"A load contribution full-DOF index is outside the active "
"model."));
}
if (!std::isfinite(contribution.value)) {
return Result<Vector>::Failure(
LoadFailure("nonfinite-load-value", model.Step().Location(),
"LOAD_ASSEMBLER", std::to_string(source_order),
"A load contribution value must be finite."));
}
}
contributions_by_load.push_back(std::move(contributions.Value()));
}
Vector full_load{expected_full_count};
// Active load indices are required to be the original source order; this
// loop is therefore also the fixed floating-point accumulation order.
for (std::size_t source_order = 0U; source_order < active_loads.size();
++source_order) {
const EntityIndex load_index = active_loads[source_order];
if (static_cast<std::size_t>(load_index) != source_order ||
load_index >= loads.size()) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-order", model.Step().location, "CLOAD",
std::to_string(source_order),
"Active loads must retain complete stable source order."));
}
const auto& load = loads[load_index];
if (load.dof < 1 || load.dof > static_cast<int>(kDofsPerNode)) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-dof", load.location, "CLOAD", load.target,
"A nodal load component must be in the range 1 through 6."));
}
if (!std::isfinite(load.magnitude)) {
return Result<Vector>::Failure(
LoadFailure("nonfinite-load-value", load.location, "CLOAD",
load.target, "A nodal load magnitude must be finite."));
}
auto target = ResolveTarget(target_resolver, domain, load);
if (!target.HasValue()) {
return Result<Vector>::Failure(target.GetStatus());
}
const auto component = static_cast<DofComponent>(load.dof - 1);
for (const EntityIndex node : target.Value()) {
const std::size_t full_dof = dofs.FullDof(node, component);
const double accumulated = full_load[full_dof] + load.magnitude;
// Contribution validation completes before this fixed-order candidate
// accumulation begins, so failures cannot expose a partial global vector.
for (const auto& contributions : contributions_by_load) {
for (const auto& contribution : contributions) {
const std::size_t full_dof = contribution.full_dof_index;
const double accumulated = full_load[full_dof] + contribution.value;
if (!std::isfinite(accumulated)) {
return Result<Vector>::Failure(LoadFailure(
"nonfinite-load-accumulation", load.location, "CLOAD", load.target,
"nonfinite-load-accumulation", model.Step().Location(),
"LOAD_ASSEMBLER", std::to_string(full_dof),
"Source-order load accumulation produced a nonfinite value."));
}
full_load[full_dof] = accumulated;