feat(cpp-object-oriented-modular-refactoring): step 18 - load-hierarchy
This commit is contained in:
@@ -20,6 +20,7 @@ add_library(
|
||||
io/abaqus/domain_mapper.cpp
|
||||
io/abaqus/input_reader.cpp
|
||||
io/hdf5/hdf5_results_writer.cpp
|
||||
loads/concentrated_nodal_load.cpp
|
||||
materials/isotropic_linear_elastic_material.cpp
|
||||
math/dense_blas_internal.cpp
|
||||
math/matrix.cpp
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace fesa {
|
||||
|
||||
Result<AnalysisModel> AnalysisModel::Create(const Domain& domain) {
|
||||
if (domain.Steps().empty()) {
|
||||
if (domain.Steps().Empty()) {
|
||||
return Result<AnalysisModel>::Failure(
|
||||
Status::Failure(FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
@@ -16,12 +16,12 @@ Result<AnalysisModel> AnalysisModel::Create(const Domain& domain) {
|
||||
"0",
|
||||
"AnalysisModel requires exactly one static step."}}));
|
||||
}
|
||||
if (domain.Steps().size() > 1U) {
|
||||
if (domain.Steps().Size() > 1U) {
|
||||
const auto& second_step = domain.Steps()[1];
|
||||
return Result<AnalysisModel>::Failure(
|
||||
Status::Failure(FailureCategory::kInput,
|
||||
{{Severity::kError, "unsupported-multiple-step",
|
||||
second_step.location, "STEP", second_step.name,
|
||||
second_step.Location(), "STEP", second_step.Name(),
|
||||
"AnalysisModel does not support multiple steps."}}));
|
||||
}
|
||||
return Result<AnalysisModel>::Success(AnalysisModel{domain});
|
||||
@@ -29,8 +29,8 @@ Result<AnalysisModel> AnalysisModel::Create(const Domain& domain) {
|
||||
|
||||
const Domain& AnalysisModel::GetDomain() const noexcept { return *domain_; }
|
||||
|
||||
const StaticStepDefinition& AnalysisModel::Step() const noexcept {
|
||||
return domain_->Steps().front();
|
||||
const StepDefinition& AnalysisModel::Step() const noexcept {
|
||||
return domain_->Steps()[0U];
|
||||
}
|
||||
|
||||
const std::vector<EntityIndex>& AnalysisModel::ActiveElements() const noexcept {
|
||||
@@ -101,10 +101,10 @@ AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t index = 0U; index < Step().boundaries.size(); ++index) {
|
||||
for (std::size_t index = 0U; index < Step().Boundaries().size(); ++index) {
|
||||
active_boundary_conditions_.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
for (std::size_t index = 0U; index < Step().loads.size(); ++index) {
|
||||
for (std::size_t index = 0U; index < Step().Loads().size(); ++index) {
|
||||
active_loads_.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -180,7 +180,8 @@ Status DofManager::BuildLayouts(const AnalysisModel& analysis_model,
|
||||
std::vector<std::optional<double>> prescribed_by_full_dof(full_count);
|
||||
for (const EntityIndex boundary_index :
|
||||
analysis_model.ActiveBoundaryConditions()) {
|
||||
const auto& boundary = analysis_model.Step().boundaries.at(boundary_index);
|
||||
const auto& boundary =
|
||||
analysis_model.Step().Boundaries().at(boundary_index);
|
||||
const auto target = ExpandBoundaryTarget(target_resolver, boundary);
|
||||
for (const EntityIndex node : target) {
|
||||
for (int component = boundary.first_dof; component <= boundary.last_dof;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "fesa/loads/concentrated_nodal_load.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t kDofsPerNode = 6U;
|
||||
|
||||
Status LoadFailure(const std::string& code, const SourceLocation& location,
|
||||
const std::string& identity, const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError, code, location, "CLOAD", identity, message}});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConcentratedNodalLoad::ConcentratedNodalLoad(
|
||||
SourceTargetQuery target, std::array<double, 6> global_components,
|
||||
const std::size_t source_order)
|
||||
: target_{std::move(target)},
|
||||
global_components_{global_components},
|
||||
source_order_{source_order} {}
|
||||
|
||||
ConcentratedNodalLoad::ConcentratedNodalLoad(SourceTargetQuery target,
|
||||
const int source_dof,
|
||||
const double magnitude,
|
||||
const std::size_t source_order,
|
||||
SourceLocation location)
|
||||
: target_{std::move(target)},
|
||||
source_order_{source_order},
|
||||
location_{std::move(location)},
|
||||
source_dof_{source_dof} {
|
||||
if (source_dof >= 1 && source_dof <= static_cast<int>(kDofsPerNode)) {
|
||||
global_components_[static_cast<std::size_t>(source_dof - 1)] = magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::vector<LoadContribution>>
|
||||
ConcentratedNodalLoad::ComputeContributions(const LoadContext& context) const {
|
||||
const std::string& identity = target_.target_name_or_label;
|
||||
if (source_dof_ != 0 &&
|
||||
(source_dof_ < 1 || source_dof_ > static_cast<int>(kDofsPerNode))) {
|
||||
return Result<std::vector<LoadContribution>>::Failure(LoadFailure(
|
||||
"invalid-load-dof", location_, identity,
|
||||
"A nodal load component must be in the range 1 through 6."));
|
||||
}
|
||||
for (const double component : global_components_) {
|
||||
if (!std::isfinite(component)) {
|
||||
return Result<std::vector<LoadContribution>>::Failure(
|
||||
LoadFailure("nonfinite-load-value", location_, identity,
|
||||
"A nodal load magnitude must be finite."));
|
||||
}
|
||||
}
|
||||
if (target_.entity_kind != SourceEntityKind::kNode) {
|
||||
return Result<std::vector<LoadContribution>>::Failure(
|
||||
LoadFailure("invalid-load-target", location_, identity,
|
||||
"A concentrated nodal load requires a node target."));
|
||||
}
|
||||
|
||||
auto resolved = context.target_resolver.Resolve(target_);
|
||||
if (!resolved.HasValue()) {
|
||||
return Result<std::vector<LoadContribution>>::Failure(LoadFailure(
|
||||
"invalid-load-target", location_, identity,
|
||||
"The load target must resolve unambiguously to one node or one "
|
||||
"expanded node set."));
|
||||
}
|
||||
|
||||
std::vector<unsigned char> seen(context.domain.Nodes().size(), 0U);
|
||||
std::vector<LoadContribution> contributions;
|
||||
contributions.reserve(resolved.Value().size() * kDofsPerNode);
|
||||
for (const auto& target : resolved.Value()) {
|
||||
const EntityIndex node = target.entity_index;
|
||||
if (node >= context.domain.Nodes().size() || seen[node] != 0U) {
|
||||
return Result<std::vector<LoadContribution>>::Failure(LoadFailure(
|
||||
"invalid-load-target", location_, identity,
|
||||
"The expanded node set must contain unique in-range stable node "
|
||||
"identities."));
|
||||
}
|
||||
seen[node] = 1U;
|
||||
for (std::size_t component = 0U; component < kDofsPerNode; ++component) {
|
||||
try {
|
||||
contributions.push_back(
|
||||
{source_order_,
|
||||
context.dof_manager.FullDof(node,
|
||||
static_cast<DofComponent>(component)),
|
||||
global_components_[component]});
|
||||
} catch (const std::out_of_range&) {
|
||||
return Result<std::vector<LoadContribution>>::Failure(LoadFailure(
|
||||
"invalid-load-target", location_, identity,
|
||||
"The resolved load target must have all six full DOFs."));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result<std::vector<LoadContribution>>::Success(
|
||||
std::move(contributions));
|
||||
}
|
||||
|
||||
const SourceTargetQuery& ConcentratedNodalLoad::Target() const noexcept {
|
||||
return target_;
|
||||
}
|
||||
|
||||
const std::array<double, 6>& ConcentratedNodalLoad::GlobalComponents()
|
||||
const noexcept {
|
||||
return global_components_;
|
||||
}
|
||||
|
||||
std::size_t ConcentratedNodalLoad::SourceOrder() const noexcept {
|
||||
return source_order_;
|
||||
}
|
||||
|
||||
const SourceLocation& ConcentratedNodalLoad::Location() const noexcept {
|
||||
return location_;
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
@@ -1,11 +1,66 @@
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
StepDefinition::StepDefinition(StaticStepDefinition definition)
|
||||
: name_{std::move(definition.name)},
|
||||
boundaries_{std::move(definition.boundaries)},
|
||||
initial_increment_{definition.initial_increment},
|
||||
time_period_{definition.time_period},
|
||||
minimum_increment_{definition.minimum_increment},
|
||||
maximum_increment_{definition.maximum_increment},
|
||||
location_{std::move(definition.location)} {
|
||||
loads_.reserve(definition.loads.size());
|
||||
loads_view_.reserve(definition.loads.size());
|
||||
for (std::size_t source_order = 0U; source_order < definition.loads.size();
|
||||
++source_order) {
|
||||
auto& load = definition.loads[source_order];
|
||||
auto owned = std::make_unique<ConcentratedNodalLoad>(
|
||||
SourceTargetQuery{SourceEntityKind::kNode, "", load.target}, load.dof,
|
||||
load.magnitude, source_order, std::move(load.location));
|
||||
loads_view_.push_back(std::cref(*owned));
|
||||
concentrated_loads_view_.Add(*owned);
|
||||
loads_.push_back(std::move(owned));
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& StepDefinition::Name() const noexcept { return name_; }
|
||||
|
||||
const std::vector<BoundaryCondition>& StepDefinition::Boundaries()
|
||||
const noexcept {
|
||||
return boundaries_;
|
||||
}
|
||||
|
||||
const LoadView& StepDefinition::Loads() const noexcept { return loads_view_; }
|
||||
|
||||
const DomainCollectionView<ConcentratedNodalLoad>&
|
||||
StepDefinition::ConcentratedLoads() const noexcept {
|
||||
return concentrated_loads_view_;
|
||||
}
|
||||
|
||||
double StepDefinition::InitialIncrement() const noexcept {
|
||||
return initial_increment_;
|
||||
}
|
||||
|
||||
double StepDefinition::TimePeriod() const noexcept { return time_period_; }
|
||||
|
||||
double StepDefinition::MinimumIncrement() const noexcept {
|
||||
return minimum_increment_;
|
||||
}
|
||||
|
||||
double StepDefinition::MaximumIncrement() const noexcept {
|
||||
return maximum_increment_;
|
||||
}
|
||||
|
||||
const SourceLocation& StepDefinition::Location() const noexcept {
|
||||
return location_;
|
||||
}
|
||||
|
||||
Result<Domain> Domain::Create(ModelDefinition definition) {
|
||||
return Result<Domain>::Success(Domain{std::move(definition)});
|
||||
}
|
||||
@@ -66,8 +121,8 @@ const std::vector<ElementSet>& Domain::ElementSets() const noexcept {
|
||||
return definition_.element_sets;
|
||||
}
|
||||
|
||||
const std::vector<StaticStepDefinition>& Domain::Steps() const noexcept {
|
||||
return definition_.steps;
|
||||
const DomainCollectionView<StepDefinition>& Domain::Steps() const noexcept {
|
||||
return steps_view_;
|
||||
}
|
||||
|
||||
const std::vector<Diagnostic>& Domain::Warnings() const noexcept {
|
||||
@@ -131,6 +186,15 @@ Domain::Domain(ModelDefinition definition)
|
||||
element_definitions_.push_back(std::move(owned));
|
||||
}
|
||||
definition_.shell_elements.clear();
|
||||
|
||||
step_definitions_.reserve(definition_.steps.size());
|
||||
for (auto& step : definition_.steps) {
|
||||
auto owned =
|
||||
std::unique_ptr<StepDefinition>(new StepDefinition(std::move(step)));
|
||||
steps_view_.Add(*owned);
|
||||
step_definitions_.push_back(std::move(owned));
|
||||
}
|
||||
definition_.steps.clear();
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/elements/element_factory.h"
|
||||
#include "fesa/loads/concentrated_nodal_load.h"
|
||||
#include "fesa/math/vector3.h"
|
||||
#include "fesa/model/source_target_resolver.h"
|
||||
|
||||
@@ -345,11 +346,12 @@ Status AppendShellRows(const SourceLocation& location,
|
||||
|
||||
Result<std::vector<EntityIndex>> ResolveLoadTarget(
|
||||
const SourceTargetResolver& resolver, const Domain& domain,
|
||||
const NodalLoad& load) {
|
||||
auto resolved = resolver.Resolve({SourceEntityKind::kNode, "", load.target});
|
||||
const ConcentratedNodalLoad& load) {
|
||||
auto resolved = resolver.Resolve(load.Target());
|
||||
if (!resolved.HasValue()) {
|
||||
return RecoveryResultFailure<std::vector<EntityIndex>>(
|
||||
"invalid-node-station-entity", load.location, load.target,
|
||||
"invalid-node-station-entity", load.Location(),
|
||||
load.Target().target_name_or_label,
|
||||
"A station-eligibility load target must resolve unambiguously.");
|
||||
}
|
||||
std::vector<EntityIndex> nodes;
|
||||
@@ -359,7 +361,8 @@ Result<std::vector<EntityIndex>> ResolveLoadTarget(
|
||||
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,
|
||||
"invalid-node-station-entity", load.Location(),
|
||||
load.Target().target_name_or_label,
|
||||
"A station-eligibility node set must contain unique valid nodes.");
|
||||
}
|
||||
seen[node] = 1U;
|
||||
@@ -789,33 +792,46 @@ ResultRecovery::NormalizeSectionResultantsToNodeStations(
|
||||
}
|
||||
|
||||
std::vector<unsigned char> loaded_nodes(domain.Nodes().size(), 0U);
|
||||
if (model.ActiveLoads().size() != model.Step().loads.size()) {
|
||||
const auto& loads = model.Step().ConcentratedLoads();
|
||||
if (model.ActiveLoads().size() != loads.Size()) {
|
||||
return RecoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"invalid-node-station-entity", model.Step().location, model.Step().name,
|
||||
"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()) {
|
||||
if (load_index != order || load_index >= loads.Size()) {
|
||||
return RecoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"invalid-node-station-entity", model.Step().location,
|
||||
"invalid-node-station-entity", model.Step().Location(),
|
||||
std::to_string(load_index),
|
||||
"Active loads must remain in stable source order.");
|
||||
}
|
||||
const auto& load = model.Step().loads[load_index];
|
||||
if (!std::isfinite(load.magnitude)) {
|
||||
const auto& load = loads[load_index];
|
||||
if (load.SourceOrder() != order) {
|
||||
return RecoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"nonfinite-node-station-value", load.location, load.target,
|
||||
"Station eligibility requires finite concentrated loads.");
|
||||
"invalid-node-station-entity", load.Location(),
|
||||
std::to_string(load.SourceOrder()),
|
||||
"Station eligibility requires stable load source order.");
|
||||
}
|
||||
bool has_nonzero_component = false;
|
||||
for (const double component : load.GlobalComponents()) {
|
||||
if (!std::isfinite(component)) {
|
||||
return RecoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"nonfinite-node-station-value", load.Location(),
|
||||
load.Target().target_name_or_label,
|
||||
"Station eligibility requires finite concentrated loads.");
|
||||
}
|
||||
has_nonzero_component = has_nonzero_component || component != 0.0;
|
||||
}
|
||||
auto targets = ResolveLoadTarget(target_resolver, domain, load);
|
||||
if (!targets.HasValue()) {
|
||||
return Result<std::vector<NodeStationResultRow>>::Failure(
|
||||
targets.GetStatus());
|
||||
}
|
||||
if (load.magnitude != 0.0) {
|
||||
if (has_nonzero_component) {
|
||||
for (const EntityIndex node : targets.Value()) {
|
||||
loaded_nodes[node] = 1U;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user