#include "fesa/analysis/solid_static_state.h" #include #include #include #include "fesa/elements/element_factory.h" #include "fesa/elements/hex8_element.h" namespace fesa { namespace { /// @brief Keeps state/control failures separate from input syntax failures. Status StateFailure(const char* code, const char* message) { return Status::Failure( FailureCategory::kSolver, {{Severity::kError, code, {}, "SOLID_STATIC", {}, message}}); } /// @brief Validates all components without a zero clamp or scale floor. template bool Finite(const Range& values) { for (double value : values) if (!std::isfinite(value)) return false; return true; } /// @brief Validates owned vectors including their physical full-space size. bool FiniteVector(const Vector& values, std::size_t size) { if (values.Size() != size) return false; for (std::size_t i = 0U; i < size; ++i) if (!std::isfinite(values[i])) return false; return true; } /// @brief Calculates the infinity norm after separate finite validation. double InfinityNorm(const Vector& values) { double norm = 0.0; for (std::size_t i = 0U; i < values.Size(); ++i) norm = (std::max)(norm, std::abs(values[i])); return norm; } /// @brief Compares immutable semantic and raw source identities. bool SameIdentity(const SourceEntityId& a, const SourceEntityId& b) { return a.instance_name == b.instance_name && a.source_label == b.source_label && a.source_label_text == b.source_label_text; } /// @brief Builds one raw criterion with the approved branch and physical unit. NewtonChannelDecision Decision(const char* name, double numerator, double denominator, NewtonChannelUnit unit) { const bool absolute = denominator == 0.0; const double threshold = absolute ? kNewtonZeroScaleThreshold : kNewtonRelativeThreshold; return { name, numerator, denominator, absolute ? NewtonConvergenceBranch::kZeroScale : NewtonConvergenceBranch::kRelative, threshold, absolute ? unit : NewtonChannelUnit::kDimensionless, absolute ? numerator <= threshold : numerator / denominator <= threshold}; } } // namespace Result EvaluateSolidStaticConvergence( const Vector& residual, const Vector& internal, const Vector& external, const Vector& correction, const Vector& increment) { const std::size_t size = residual.Size(); if (!FiniteVector(residual, size) || !FiniteVector(internal, size) || !FiniteVector(external, size) || !FiniteVector(correction, size) || !FiniteVector(increment, size)) return Result::Failure(StateFailure( "nonfinite-solid-newton-data", "Static channels require finite vectors of the same free dimension.")); const double external_norm = InfinityNorm(external); const double force_scale = external_norm == 0.0 ? 0.0 : (std::max)(external_norm, InfinityNorm(internal)); SolidStaticIterationRecord record; record.force_residual = Decision("residual_force", InfinityNorm(residual), force_scale, NewtonChannelUnit::kForce); record.translation_correction = Decision("correction_translation", InfinityNorm(correction), InfinityNorm(increment), NewtonChannelUnit::kLength); record.convergence_evaluated = true; return Result::Success(std::move(record)); } Result SolidStaticState::Create(const Domain& domain, const DofManager& dofs) { if (!domain.IsSolid() || domain.Steps().Size() != 1U || domain.Frequency() != nullptr || domain.Dynamic() != nullptr || dofs.Profile() != DofProfile::kSolidStructural || dofs.ComponentsPerNode() != 3U || dofs.FullDofCount() != 3U * domain.Nodes().size()) return Result::Failure( StateFailure("invalid-solid-static-state", "Static state requires one Solid static step and matching " "3N mapping.")); const Status mapping = dofs.ValidateInvariants(); if (!mapping.IsOk()) return Result::Failure(mapping); SolidStaticState state; state.initialized_ = true; state.nonlinear_ = domain.Steps()[0].IsNonlinearStatic(); state.constrained_dofs_ = dofs.ConstrainedDofs(); state.prescribed_values_ = dofs.PrescribedValues(); const std::size_t count = dofs.FullDofCount(); state.committed_.displacement = Vector{count}; state.committed_.internal_force = Vector{count}; state.committed_.external_force = Vector{count}; state.committed_.residual = Vector{count}; state.committed_.reaction = Vector{count}; for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) { state.committed_.nodal_internal_order.push_back( static_cast(node)); state.committed_.nodal_source_ids.push_back(domain.Nodes()[node].source_id); } // Capture the immutable point coordinates from certified zero-state kernels. // Later state commits cannot substitute another element or result location. for (std::size_t e = 0U; e < domain.Elements().Size(); ++e) { auto element = ElementFactory{}.Create(domain.Elements()[e], domain); if (!element.HasValue()) return Result::Failure(element.GetStatus()); const auto* hex = dynamic_cast(element.Value().get()); if (hex == nullptr) return Result::Failure(StateFailure( "invalid-solid-static-state", "Static state requires Hex8 kernels.")); auto response = hex->EvaluateLinear(Vector{24U}); if (!response.HasValue()) return Result::Failure(response.GetStatus()); state.reference_points_.insert(state.reference_points_.end(), response.Value().points.begin(), response.Value().points.end()); } return Result::Success(std::move(state)); } Status SolidStaticState::CommitAcceptedFrame(const SolidStaticFrame& frame) { const auto invalid = []() { return StateFailure( "invalid-solid-static-frame", "Static candidate identity, projection or recovery is invalid."); }; if (!initialized_ || frame.frame_index != accepted_frames_.size() || frame.increment_index == 0U || !std::isfinite(frame.load_factor) || !(frame.load_factor > committed_.load_factor) || frame.load_factor > 1.0 || frame.is_factor_one != (frame.load_factor == 1.0) || (nonlinear_ && (frame.increment_index != committed_.increment_index + 1U || frame.iteration_count == 0U || frame.iteration_count > 100U)) || (!nonlinear_ && (!accepted_frames_.empty() || frame.load_factor != 1.0 || frame.iteration_count != 0U))) return invalid(); const std::size_t count = committed_.displacement.Size(); if (frame.nodal_internal_order != committed_.nodal_internal_order || frame.nodal_source_ids.size() != committed_.nodal_source_ids.size() || !FiniteVector(frame.displacement, count) || !FiniteVector(frame.internal_force, count) || !FiniteVector(frame.external_force, count) || !FiniteVector(frame.residual, count) || !FiniteVector(frame.reaction, count) || frame.solid_results.size() != reference_points_.size() || !std::isfinite(frame.physical_strain_energy) || !Finite(frame.global_equilibrium) || !Finite(frame.balance.internal_force_sum) || !Finite(frame.balance.reference_internal_moment) || !Finite(frame.balance.current_internal_moment) || !Finite(frame.balance.free_residual_moment) || !Finite(frame.balance.linearization_moment) || !Finite(frame.balance.internal_force_scale) || !Finite(frame.balance.reference_moment_scale) || !Finite(frame.balance.current_moment_scale)) return invalid(); for (std::size_t node = 0U; node < frame.nodal_source_ids.size(); ++node) if (!SameIdentity(frame.nodal_source_ids[node], committed_.nodal_source_ids[node])) return invalid(); std::vector constrained(count, false); for (std::size_t c = 0U; c < constrained_dofs_.size(); ++c) { const auto full = constrained_dofs_[c]; constrained[full] = true; if (frame.displacement[full] != frame.load_factor * prescribed_values_[c]) return invalid(); } for (std::size_t i = 0U; i < count; ++i) if (frame.residual[i] != frame.internal_force[i] - frame.external_force[i] || frame.reaction[i] != (constrained[i] ? frame.residual[i] : 0.0)) return invalid(); for (std::size_t g = 0U; g < reference_points_.size(); ++g) { const auto& row = frame.solid_results[g]; const auto& reference = reference_points_[g]; if (row.element != reference.element || row.integration_point != reference.integration_point || row.natural_coordinates != reference.natural_coordinates || row.reference_coordinates != reference.reference_coordinates || !Finite(row.cauchy_stress) || !Finite(row.strain)) return invalid(); } // Prepare both copies before mutating committed history. SolidStaticFrame next = frame; accepted_frames_.push_back(frame); committed_ = std::move(next); return Status::Ok(); } const std::vector& SolidStaticState::AcceptedFrames() const noexcept { return accepted_frames_; } const SolidStaticFrame& SolidStaticState::CommittedState() const noexcept { return committed_; } const std::vector& SolidStaticState::IterationRecords() const noexcept { return iterations_; } } // namespace fesa