#include "fesa/analysis/solid_dynamic_analysis_state.h" #include #include #include #include #include "fesa/elements/element_factory.h" #include "fesa/elements/hex8_element.h" #include "math/high_low_arithmetic.h" namespace fesa { namespace { /// @brief Rejects a candidate without changing observable history. Status Failure(const std::string& message) { return Status::Failure(FailureCategory::kSolver, {{Severity::kError, "solid-dynamic-invalid-state", {}, "DYNAMIC", {}, message}}); } /// @brief Checks a physical record without replacing nonfinite quantities. template bool Finite(const std::array& values) { return std::all_of(values.begin(), values.end(), [](double x) { return std::isfinite(x); }); } bool ValidPair(double high, double low) { const math_internal::HighLow value(high, low); return math_internal::IsFinite(value) && math_internal::IsNormalized(value); } bool ValidPair(const Vector& high, const Vector& low, std::size_t size) { if (high.Size() != size || low.Size() != size) return false; for (std::size_t i = 0; i < size; ++i) if (!ValidPair(high[i], low[i])) return false; return true; } template bool ValidPair(const std::array& high, const std::array& low) { for (std::size_t i = 0; i < N; ++i) if (!ValidPair(high[i], low[i])) return false; return true; } /// @brief Preserves canonical and raw source identity independently of indices. bool SameId(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; } } // namespace Result SolidDynamicAnalysisState::Create( const Domain& domain, const DofManager& dofs) { const auto* step = domain.Dynamic(); if (!domain.IsSolid() || step == nullptr || dofs.Profile() != DofProfile::kSolidStructural || dofs.FullDofCount() != 3 * domain.Nodes().size() || !dofs.ValidateInvariants().IsOk() || step->increment_count == 0 || !std::isfinite(step->initial_increment) || step->initial_increment <= 0 || !std::isfinite(step->time_period) || static_cast(step->increment_count) * step->initial_increment != step->time_period || step->increment_count > step->maximum_increments) { return Result::Failure( Failure("Solid HHT requires a valid 3N exact fixed schedule.")); } for (std::size_t i = 0; i < dofs.PrescribedValues().Size(); ++i) if (dofs.PrescribedValues()[i] != 0) return Result::Failure( Failure("Dynamic prescribed translations must be exactly zero.")); SolidDynamicAnalysisState state; state.full_dof_count_ = dofs.FullDofCount(); state.increment_count_ = step->increment_count; state.time_increment_ = step->initial_increment; state.constrained_dofs_ = dofs.ConstrainedDofs(); for (const auto& node : domain.Nodes()) state.source_ids_.push_back(node.source_id); ElementFactory factory; for (std::size_t e = 0; e < domain.Elements().Size(); ++e) { auto runtime = factory.Create(domain.Elements()[e], domain); if (!runtime.HasValue()) return Result::Failure(runtime.GetStatus()); const auto* solid = dynamic_cast(runtime.Value().get()); if (solid == nullptr) return Result::Failure(Failure( "Dynamic Solid inventory contains a different element family.")); auto zero = solid->EvaluateLinear(Vector(24)); if (!zero.HasValue()) return Result::Failure(zero.GetStatus()); state.point_inventory_.insert(state.point_inventory_.end(), zero.Value().points.begin(), zero.Value().points.end()); } state.initialized_ = true; return Result::Success(std::move(state)); } Status SolidDynamicAnalysisState::CommitAcceptedFrame( const SolidDynamicAcceptedFrameState& f) { if (!math_internal::ArithmeticEnvironmentSupported()) return Failure( "Paired state commit requires nearest rounding and gradual " "underflow."); const std::size_t index = accepted_frames_.size(); if (!initialized_ || index > increment_count_ || f.frame_index != index || f.increment_index != index || f.is_initial != (index == 0) || f.time != static_cast(index) * time_increment_ || f.iteration_count > 100 || (index == 0 && f.iteration_count != 0) || f.nodal_internal_order.size() != source_ids_.size() || f.nodal_source_ids.size() != source_ids_.size() || f.solid_results.size() != point_inventory_.size()) { return Failure( "Incomplete or out-of-order Solid endpoint inventory/schedule."); } for (std::size_t n = 0; n < source_ids_.size(); ++n) if (f.nodal_internal_order[n] != n || !SameId(f.nodal_source_ids[n], source_ids_[n])) return Failure("Solid nodal source identity differs from its inventory."); const std::array high{ &f.displacement, &f.velocity, &f.acceleration, &f.algorithmic_acceleration, &f.inertia_force, &f.internal_force, &f.external_force, &f.residual, &f.reaction, &f.residual_scale}; const std::array low{ &f.low.displacement, &f.low.velocity, &f.low.acceleration, &f.low.algorithmic_acceleration, &f.low.inertia_force, &f.low.internal_force, &f.low.external_force, &f.low.residual, &f.low.reaction, &f.low.residual_scale}; for (std::size_t i = 0; i < high.size(); ++i) if (!ValidPair(*high[i], *low[i], full_dof_count_)) return Failure( "Solid endpoint pairs are incomplete, unnormalized or " "nonfinite."); using namespace math_internal; for (std::size_t i = 0; i < full_dof_count_; ++i) { const bool constrained = std::binary_search(constrained_dofs_.begin(), constrained_dofs_.end(), i); const HighLow inertia(f.inertia_force[i], f.low.inertia_force[i]); const HighLow internal(f.internal_force[i], f.low.internal_force[i]); const HighLow external(f.external_force[i], f.low.external_force[i]); const HighLow residual(f.residual[i], f.low.residual[i]); const HighLow scale(f.residual_scale[i], f.low.residual_scale[i]); const HighLow reaction(f.reaction[i], f.low.reaction[i]); const auto expected_residual = Subtract(Add(inertia, internal), external); const auto expected_scale = Add(Add(Abs(inertia), Abs(internal)), Abs(external)); if (!IsFinite(expected_residual) || !IsFinite(expected_scale) || Compare(residual, expected_residual) != 0 || Compare(scale, expected_scale) != 0 || Compare(reaction, constrained ? residual : HighLow(0)) != 0) return Failure( "Physical reaction/residual/uncancelled force scale disagree."); if (constrained && (f.displacement[i] != 0 || f.low.displacement[i] != 0 || f.velocity[i] != 0 || f.low.velocity[i] != 0 || f.acceleration[i] != 0 || f.low.acceleration[i] != 0 || f.algorithmic_acceleration[i] != 0 || f.low.algorithmic_acceleration[i] != 0)) return Failure( "Prescribed dynamic translations and rates must be exact zero."); if (!constrained) { if (Compare(scale, HighLow(0)) < 0) return Failure("Negative physical force scale."); if (Compare(scale, HighLow(0)) == 0) { if (Compare(residual, HighLow(0)) != 0) return Failure("Physical zero-scale residual must be exactly zero."); } else { const auto ratio = Divide(Abs(residual), scale); if (!IsFinite(ratio) || !IsNormalized(ratio) || Compare(ratio, HighLow(1e-10)) > 0) return Failure("Physical free endpoint equilibrium is incomplete."); } } if (index == 0 && (f.displacement[i] != 0 || f.velocity[i] != 0 || f.low.displacement[i] != 0 || f.low.velocity[i] != 0 || Compare(HighLow(f.acceleration[i], f.low.acceleration[i]), HighLow(f.algorithmic_acceleration[i], f.low.algorithmic_acceleration[i])) != 0)) return Failure( "Initial Solid displacement/velocity must be zero and A=a."); } for (std::size_t i = 0; i < point_inventory_.size(); ++i) { const auto& row = f.solid_results[i]; const auto& expected = point_inventory_[i]; if (row.element != expected.element || row.integration_point != expected.integration_point || row.natural_coordinates != expected.natural_coordinates || row.reference_coordinates != expected.reference_coordinates || !Finite(row.cauchy_stress) || !Finite(row.strain)) return Failure( "Solid point recovery has invalid identity or finite values."); } if (!ValidPair(f.strain_energy, f.low.strain_energy) || !ValidPair(f.kinetic_energy, f.low.kinetic_energy) || !ValidPair(f.external_work, f.low.external_work) || !ValidPair(f.energy_balance_error, f.low.energy_balance_error)) return Failure("Nonfinite or unnormalized Solid energy/work pair."); if ((index == 0 && (f.external_work != 0 || f.low.external_work != 0 || f.energy_balance_error != 0 || f.low.energy_balance_error != 0)) || Compare(HighLow(f.kinetic_energy, f.low.kinetic_energy), HighLow(0)) < 0) return Failure( "Initial work/error or kinetic energy violates its definition."); if (!ValidPair(f.linear_momentum, f.low.linear_momentum) || !ValidPair(f.angular_momentum, f.low.angular_momentum) || !ValidPair(f.linear_momentum_rate, f.low.linear_momentum_rate) || !ValidPair(f.angular_momentum_rate, f.low.angular_momentum_rate) || !ValidPair(f.linearization_current_internal_moment, f.low.linearization_current_internal_moment) || !ValidPair(f.reference_internal_moment, f.low.reference_internal_moment) || !ValidPair(f.current_free_residual_moment, f.low.current_free_residual_moment)) return Failure("Nonfinite or unnormalized physical momentum/moment pair."); if (!ValidPair(f.global_equilibrium, f.low.global_equilibrium) || f.iterations.empty()) return Failure("Missing finite equilibrium or iteration evidence."); for (std::size_t i = 0; i < f.iterations.size(); ++i) { const auto& e = f.iterations[i]; if (e.iteration_index != i || !Finite(e.correction_metrics) || !Finite(e.worst_force_terms) || !Finite(e.worst_weighted_terms)) return Failure("Incomplete or nonfinite Newton correction evidence."); for (double x : {e.residual_numerator, e.residual_scale, e.residual_metric, e.weighted_residual_numerator, e.update_metric, e.old_residual_numerator, e.recombined_residual_numerator, e.weighted_equivalence_metric}) if (!std::isfinite(x) || x < 0) return Failure("Invalid raw Newton residual/update evidence."); for (double x : e.correction_metrics) if (x < 0) return Failure("Invalid correction scale."); } const auto& last = f.iterations.back(); if (f.iterations.size() != static_cast(f.iteration_count) + 1 || !last.residual_pass || !last.correction_pass || !last.update_pass || !last.zero_scale_exact || last.residual_metric > 1e-10 || last.update_metric > 1e-12 || last.weighted_equivalence_metric > 1e-12 || std::any_of(last.correction_metrics.begin(), last.correction_metrics.end(), [](double x) { return x > 1e-10; })) return Failure( "Candidate does not carry complete accepted convergence evidence."); accepted_frames_.push_back(f); return Status::Ok(); } Result SolidDynamicAnalysisState::BeginTrialFromCommitted() const { if (accepted_frames_.empty()) return Result::Failure( Failure("No accepted Solid endpoint exists.")); return Result::Success( accepted_frames_.back()); } const std::vector& SolidDynamicAnalysisState::AcceptedFrames() const noexcept { return accepted_frames_; } const SolidDynamicAcceptedFrameState& SolidDynamicAnalysisState::CommittedState() const { if (accepted_frames_.empty()) throw std::logic_error("No accepted Solid endpoint."); return accepted_frames_.back(); } bool SolidDynamicAnalysisState::IsInitialized() const noexcept { return initialized_; } void SolidDynamicAnalysisState::RecordIteration( SolidDynamicIterationRecord record) { iteration_records_.push_back(std::move(record)); } const std::vector& SolidDynamicAnalysisState::IterationRecords() const noexcept { return iteration_records_; } } // namespace fesa