#include "fesa/loads/concentrated_nodal_load.h" #include #include #include #include #include #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 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, std::optional amplitude_index, std::string amplitude_name_raw, SourceLocation amplitude_location) : target_{std::move(target)}, source_order_{source_order}, location_{std::move(location)}, source_dof_{source_dof}, amplitude_index_{amplitude_index}, amplitude_name_raw_{std::move(amplitude_name_raw)}, amplitude_location_{std::move(amplitude_location)} { if (source_dof >= 1 && source_dof <= static_cast(kDofsPerNode)) { global_components_[static_cast(source_dof - 1)] = magnitude; } } Result> ConcentratedNodalLoad::ComputeContributions(const LoadContext& context) const { const std::string& identity = target_.target_name_or_label; const std::size_t components = context.dof_manager.ComponentsPerNode(); if (context.dof_manager.Profile() == DofProfile::kThermal || (source_dof_ != 0 && source_dof_ > static_cast(components))) { return Result>::Failure(LoadFailure( "invalid-load-dof", location_, identity, "The source load component is inactive for this physical profile.")); } for (std::size_t component = components; component < kDofsPerNode; ++component) { if (global_components_[component] != 0.0) { return Result>::Failure( LoadFailure("invalid-load-dof", location_, identity, "A Solid load cannot contain rotational components.")); } } double amplitude = 1.0; if (amplitude_index_) { if (!context.domain.IsSolid() || context.domain.Dynamic() == nullptr || *amplitude_index_ >= context.domain.Amplitudes().size() || !std::isfinite(context.step_time) || context.step_time < 0.0) { return Result>::Failure( LoadFailure("invalid-load-amplitude", location_, identity, "Amplitude requires a valid Solid dynamic endpoint.")); } amplitude = context.domain.Amplitudes()[*amplitude_index_].Evaluate( context.step_time); } if (source_dof_ != 0 && (source_dof_ < 1 || source_dof_ > static_cast(kDofsPerNode))) { return Result>::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>::Failure( LoadFailure("nonfinite-load-value", location_, identity, "A nodal load magnitude must be finite.")); } } if (target_.entity_kind != SourceEntityKind::kNode) { return Result>::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>::Failure(LoadFailure( "invalid-load-target", location_, identity, "The load target must resolve unambiguously to one node or one " "expanded node set.")); } std::vector seen(context.domain.Nodes().size(), 0U); std::vector contributions; contributions.reserve(resolved.Value().size() * components); for (const auto& target : resolved.Value()) { const EntityIndex node = target.entity_index; if (node >= context.domain.Nodes().size() || seen[node] != 0U) { return Result>::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 < components; ++component) { const double value = global_components_[component] * amplitude; if (!std::isfinite(value)) { return Result>::Failure( LoadFailure("nonfinite-load-value", location_, identity, "Sampled load magnitude must remain finite.")); } try { contributions.push_back( {source_order_, context.dof_manager.FullDof(node, static_cast(component)), value}); } catch (const std::out_of_range&) { return Result>::Failure(LoadFailure( "invalid-load-target", location_, identity, "The resolved load target must have all six full DOFs.")); } } } return Result>::Success( std::move(contributions)); } const SourceTargetQuery& ConcentratedNodalLoad::Target() const noexcept { return target_; } const std::array& 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