#include "fesa/analysis/solid_hht_dynamic_procedure.h" #include "equilibrium_algorithm.h" #include "analysis/solid_hht_core.h" #include "solid_hht_integrator.h" #include #include #include #include #include #include #include #include #include #include #include "fesa/analysis/hht_time_integration.h" #include "fesa/assembly/load_assembler.h" #include "fesa/assembly/parallel_for.h" #include "fesa/constraints/essential_constraint_policy.h" #include "fesa/elements/element_factory.h" #include "fesa/elements/hex8_element.h" #include "fesa/math/sparse_matrix.h" #include "fesa/solvers/linear/linear_solver.h" #include "fesa/solvers/linear/tangent_solver.h" #include "solid_hht_force_identity.h" #include "solid_hht_high_low.h" namespace fesa { namespace { using math_internal::Abs; using math_internal::Add; using math_internal::Compare; using math_internal::Divide; using math_internal::HighLow; using math_internal::IsFinite; using math_internal::IsNormalized; using math_internal::Multiply; using math_internal::Negate; using math_internal::Rounded; using math_internal::Subtract; HighLow Pair(const Vector& high, const Vector& low, std::size_t i) { return {high[i], low[i]}; } void Store(HighLow value, double* high, double* low) { *high = value.high; *low = value.low; } void Store(HighLow value, Vector* high, Vector* low, std::size_t i) { Store(value, &(*high)[i], &(*low)[i]); } bool Valid(HighLow value) { return IsFinite(value) && IsNormalized(value); } HighLow Maximum(HighLow left, HighLow right) { if (!Valid(left) || !Valid(right)) return math_internal::InvalidHighLow(); return Compare(left, right) >= 0 ? left : right; } void InitializeLowVectors(SolidDynamicAcceptedFrameState* f, std::size_t size) { for (auto* v : {&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}) *v = Vector(size); } struct PairedTangent { std::vector values; }; /// @brief Reports source-context procedure failure without committing a trial. Status Failure(const Domain& domain, const char* code, const std::string& message) { return Status::Failure( FailureCategory::kSolver, {{Severity::kError, code, domain.Dynamic() == nullptr ? SourceLocation{} : domain.Dynamic()->dynamic_location, "DYNAMIC", domain.Dynamic() == nullptr ? "" : domain.Dynamic()->source_step_name_raw, message}}); } Status CheckArithmeticEnvironment(const Domain& domain) { return math_internal::ArithmeticEnvironmentSupported() ? Status::Ok() : Failure(domain, "solid-dynamic-arithmetic-environment", "Paired HHT requires nearest rounding and gradual " "underflow, including after backend callbacks."); } /// @brief Owns immutable kernels and the stable full-space translation scatter. struct RuntimeSolid { std::unique_ptr owner; std::unique_ptr solid; std::vector scatter; }; /// @brief Creates checked kernels in declaration order, with no mutable element /// state. Result> BuildSolids(const Domain& domain, const DofManager& dofs) { std::vector solids; ElementFactory factory; for (std::size_t e = 0; e < domain.Elements().Size(); ++e) { auto owner = factory.Create(domain.Elements()[e], domain); if (!owner.HasValue()) return Result>::Failure(owner.GetStatus()); auto solid = factory.BindSolidTrial(*owner.Value()); if (solid == nullptr) return Result>::Failure( Failure(domain, "solid-dynamic-element-family", "HHT requires exclusively Hex8 kernels.")); auto scatter = dofs.ElementScatter(solid->DofLayout()); if (!scatter.HasValue()) return Result>::Failure(scatter.GetStatus()); solids.push_back( {std::move(owner.Value()), std::move(solid), std::move(scatter.Value())}); } return Result>::Success(std::move(solids)); } /// @brief Preserves all 576 structural entries in stable element/local order. void AppendMatrix(const Matrix& local, const RuntimeSolid& solid, std::size_t e, std::vector* coo) { for (std::size_t i = 0; i < 24; ++i) for (std::size_t j = 0; j < 24; ++j) coo->push_back( {solid.scatter[i], solid.scatter[j], local(i, j), e, 24 * i + j}); } /// @brief Assembles constant reference row-sum lumped mass once. Result AssembleMass(const std::vector& solids, const DofManager& dofs) { std::vector coo; coo.reserve(solids.size() * 576); for (std::size_t e = 0; e < solids.size(); ++e) { auto mass = solids[e].solid->ComputeMass(); if (!mass.HasValue()) return Result::Failure(mass.GetStatus()); AppendMatrix(mass.Value().values, solids[e], e, &coo); } return SparseMatrix::FromCoo(dofs.FullDofCount(), dofs.FullDofCount(), std::move(coo), dofs.GetSparsePattern()); } /// @brief Evaluates independent local trials, then deterministically reduces /// recovery. Result EvaluateSolids(const Domain& domain, const std::vector& solids, const DofManager& dofs, const ParallelFor& parallel, SolidDynamicAcceptedFrameState* f) { const auto environment = CheckArithmeticEnvironment(domain); if (!environment.IsOk()) return Result::Failure(environment); if (!detail::ValidSolidHhtPair(f->displacement, f->low.displacement, dofs.FullDofCount())) return Result::Failure( Failure(domain, "solid-dynamic-invalid-paired-state", "Local evaluation requires complete normalized pairs.")); std::vector>> responses( solids.size()); parallel.Execute(solids.size(), [&](std::size_t e) { Vector local(24), low(24); for (std::size_t i = 0; i < 24; ++i) { local[i] = f->displacement[solids[e].scatter[i]]; low[i] = f->low.displacement[solids[e].scatter[i]]; } responses[e] = domain.Dynamic()->nlgeom ? solids[e].solid->EvaluateNonlinearHighLow(local, low) : solids[e].solid->EvaluateLinearHighLow(local, low); }); const auto reduction_environment = CheckArithmeticEnvironment(domain); if (!reduction_environment.IsOk()) return Result::Failure(reduction_environment); const auto& pattern = dofs.GetSparsePattern(); PairedTangent tangent; tangent.values.resize(pattern.column_indices.size()); f->internal_force = Vector(dofs.FullDofCount()); f->low.internal_force = Vector(dofs.FullDofCount()); f->strain_energy = 0; f->low.strain_energy = 0; f->solid_results.clear(); for (std::size_t e = 0; e < solids.size(); ++e) { if (!responses[e]) return Result::Failure( Failure(domain, "solid-dynamic-local-execution", "Local worker did not evaluate its index.")); if (!responses[e]->HasValue()) return Result::Failure(responses[e]->GetStatus()); const auto& r = responses[e]->Value(); for (std::size_t i = 0; i < 24; ++i) { const auto row = solids[e].scatter[i]; const auto force = Add(Pair(f->internal_force, f->low.internal_force, row), Pair(r.high.internal_force, r.internal_force_low, i)); if (!Valid(force)) return Result::Failure( Failure(domain, "solid-dynamic-nonfinite-assembly", "Paired internal force reduction failed.")); Store(force, &f->internal_force, &f->low.internal_force, row); for (std::size_t j = 0; j < 24; ++j) { const auto column = solids[e].scatter[j]; const auto begin = pattern.column_indices.begin() + pattern.row_offsets[row]; const auto end = pattern.column_indices.begin() + pattern.row_offsets[row + 1]; const auto found = std::lower_bound(begin, end, column); if (found == end || *found != column) return Result::Failure( Failure(domain, "solid-dynamic-sparse-pattern", "Local tangent is outside the fixed sparse pattern.")); const auto k = static_cast(found - pattern.column_indices.begin()); tangent.values[k] = Add(tangent.values[k], HighLow(r.high.tangent(i, j), r.tangent_low(i, j))); if (!Valid(tangent.values[k])) return Result::Failure( Failure(domain, "solid-dynamic-nonfinite-assembly", "Paired tangent reduction failed.")); } } const auto energy = Add(HighLow(f->strain_energy, f->low.strain_energy), HighLow(r.high.strain_energy, r.strain_energy_low)); if (!Valid(energy)) return Result::Failure( Failure(domain, "solid-dynamic-nonfinite-assembly", "Paired strain energy reduction failed.")); Store(energy, &f->strain_energy, &f->low.strain_energy); f->solid_results.insert(f->solid_results.end(), r.high.points.begin(), r.high.points.end()); } return Result::Success(std::move(tangent)); } /// @brief Returns the finite positive reference nodal bounding-box diagonal. Result ReferenceLength(const Domain& domain) { auto lo = domain.Nodes().front().coordinates; auto hi = lo; for (const auto& node : domain.Nodes()) for (std::size_t c = 0; c < 3; ++c) { lo[c] = (std::min)(lo[c], node.coordinates[c]); hi[c] = (std::max)(hi[c], node.coordinates[c]); } const double length = std::hypot(std::hypot(hi[0] - lo[0], hi[1] - lo[1]), hi[2] - lo[2]); if (!std::isfinite(length) || length <= 0) return Result::Failure( Failure(domain, "solid-dynamic-reference-length", "Reference length is not finite positive.")); return Result::Success(length); } /// @brief Forms M/(beta*h^2)+(1+alpha)*Kt in the unchanged full sparse pattern. Result EffectiveTangent(const SparseMatrix& m, const PairedTangent& k, const DofManager& dofs, double h, const HhtParameters& p) { std::vector coo; coo.reserve(m.Values().size()); const auto denominator = Multiply(Multiply(HighLow(p.beta), HighLow(h)), HighLow(h)); const auto coefficient = Add(HighLow(1), HighLow(p.alpha)); for (std::size_t i = 0; i < m.Rows(); ++i) for (std::size_t j = m.RowOffsets()[i]; j < m.RowOffsets()[i + 1]; ++j) { const auto value = Add(Divide(HighLow(m.Values()[j]), denominator), Multiply(coefficient, k.values[j])); if (!Valid(value)) return Result::Failure( detail::SolidHhtPairedKinematicsFailure()); coo.push_back({i, m.ColumnIndices()[j], Rounded(value), 0, j}); } return SparseMatrix::FromCoo(m.Rows(), m.Columns(), std::move(coo), dofs.GetSparsePattern()); } using Paired3 = std::array; void CrossAdd(const Paired3& x, const Paired3& force, Paired3* sum) { for (std::size_t c = 0; c < 3; ++c) { const auto j = (c + 1) % 3; const auto k = (c + 2) % 3; (*sum)[c] = Add((*sum)[c], Subtract(Multiply(x[j], force[k]), Multiply(x[k], force[j]))); } } bool Store3(const Paired3& values, std::array* high, std::array* low) { for (std::size_t c = 0; c < 3; ++c) { if (!Valid(values[c])) return false; Store(values[c], &(*high)[c], &(*low)[c]); } return true; } /// @brief Recovers full physical endpoint quantities before any publication or /// commit. The constant Float64 mass coefficients are lifted exactly once. Status RecoverEndpoint(const Domain& domain, const DofManager& dofs, const SparseMatrix& mass, const SolidDynamicAcceptedFrameState* old, HighLow initial_energy, SolidDynamicAcceptedFrameState* f, SolidDynamicIterationEvidence* e) { auto inertia = detail::MultiplySolidHhtMassHighLow(mass, f->acceleration, f->low.acceleration); auto momentum = detail::MultiplySolidHhtMassHighLow(mass, f->velocity, f->low.velocity); if (!inertia.HasValue()) return inertia.GetStatus(); if (!momentum.HasValue()) return momentum.GetStatus(); f->inertia_force = std::move(inertia.Value().high); f->low.inertia_force = std::move(inertia.Value().low); const auto size = dofs.FullDofCount(); for (auto* v : {&f->residual, &f->low.residual, &f->reaction, &f->low.reaction, &f->residual_scale, &f->low.residual_scale}) *v = Vector(size); HighLow numerator, scale_maximum, metric; for (std::size_t i = 0; i < size; ++i) { const auto ma = Pair(f->inertia_force, f->low.inertia_force, i); const auto internal = Pair(f->internal_force, f->low.internal_force, i); const auto external = Pair(f->external_force, f->low.external_force, i); const auto residual = Subtract(Add(ma, internal), external); const auto scale = Add(Add(Abs(ma), Abs(internal)), Abs(external)); if (!Valid(residual) || !Valid(scale)) return Failure(domain, "solid-dynamic-nonfinite-residual", "Physical paired force arithmetic failed."); Store(residual, &f->residual, &f->low.residual, i); Store(scale, &f->residual_scale, &f->low.residual_scale, i); } for (auto i : dofs.ConstrainedDofs()) { f->reaction[i] = f->residual[i]; f->low.reaction[i] = f->low.residual[i]; } for (auto i : dofs.FreeDofs()) { const auto residual = Pair(f->residual, f->low.residual, i); const auto scale = Pair(f->residual_scale, f->low.residual_scale, i); numerator = Maximum(numerator, Abs(residual)); scale_maximum = Maximum(scale_maximum, scale); if (Compare(scale, HighLow(0)) == 0) { e->zero_scale_exact = e->zero_scale_exact && Compare(residual, HighLow(0)) == 0; } else { const auto ratio = Divide(Abs(residual), scale); if (!Valid(ratio)) return Failure(domain, "solid-dynamic-nonfinite-residual", "Physical paired force ratio is unsupported."); if (Compare(ratio, metric) > 0) { metric = ratio; e->worst_full_dof = i; e->worst_force_terms = { Rounded(residual), Rounded(Pair(f->inertia_force, f->low.inertia_force, i)), Rounded(Pair(f->internal_force, f->low.internal_force, i)), Rounded(Pair(f->external_force, f->low.external_force, i)), Rounded(scale)}; } } } if (!Valid(numerator) || !Valid(scale_maximum) || !Valid(metric)) return Failure(domain, "solid-dynamic-nonfinite-residual", "Physical paired evidence is nonfinite."); e->residual_numerator = Rounded(numerator); e->residual_scale = Rounded(scale_maximum); e->residual_metric = Rounded(metric); e->residual_pass = e->zero_scale_exact && Compare(metric, HighLow(1e-10)) <= 0; HighLow kinetic; HighLow work = old == nullptr ? HighLow(0) : HighLow(old->external_work, old->low.external_work); for (std::size_t i = 0; i < size; ++i) { kinetic = Add(kinetic, Multiply(Pair(f->velocity, f->low.velocity, i), Pair(momentum.Value().high, momentum.Value().low, i))); if (old != nullptr) { const auto average = Multiply(HighLow(.5), Add(Pair(old->external_force, old->low.external_force, i), Pair(f->external_force, f->low.external_force, i))); const auto delta = Subtract(Pair(f->displacement, f->low.displacement, i), Pair(old->displacement, old->low.displacement, i)); work = Add(work, Multiply(average, delta)); } } kinetic = Multiply(HighLow(.5), kinetic); const auto error = Subtract( Subtract(Add(kinetic, HighLow(f->strain_energy, f->low.strain_energy)), initial_energy), work); if (!Valid(kinetic) || !Valid(work) || !Valid(error)) return Failure(domain, "solid-dynamic-nonfinite-energy", "Paired energy or endpoint work is unsupported."); Store(kinetic, &f->kinetic_energy, &f->low.kinetic_energy); Store(work, &f->external_work, &f->low.external_work); Store(error, &f->energy_balance_error, &f->low.energy_balance_error); Paired3 linear{}, angular{}, linear_rate{}, angular_rate{}, applied_moment{}, linear_equilibrium{}, current_internal_moment{}, reference_moment{}, free_residual_moment{}; for (std::size_t n = 0; n < domain.Nodes().size(); ++n) { Paired3 x{}, reference{}, mv{}, ma{}, applied{}, internal{}, free_residual{}, u{}; for (std::size_t c = 0; c < 3; ++c) { const auto i = 3 * n + c; reference[c] = HighLow(domain.Nodes()[n].coordinates[c]); u[c] = Pair(f->displacement, f->low.displacement, i); x[c] = Add(reference[c], u[c]); mv[c] = Pair(momentum.Value().high, momentum.Value().low, i); ma[c] = Pair(f->inertia_force, f->low.inertia_force, i); internal[c] = Pair(f->internal_force, f->low.internal_force, i); applied[c] = Add(Pair(f->external_force, f->low.external_force, i), Pair(f->reaction, f->low.reaction, i)); free_residual[c] = Subtract(Pair(f->residual, f->low.residual, i), Pair(f->reaction, f->low.reaction, i)); linear[c] = Add(linear[c], mv[c]); linear_rate[c] = Add(linear_rate[c], ma[c]); linear_equilibrium[c] = Add(linear_equilibrium[c], Subtract(applied[c], ma[c])); } CrossAdd(x, mv, &angular); CrossAdd(x, ma, &angular_rate); CrossAdd(x, applied, &applied_moment); CrossAdd(u, internal, ¤t_internal_moment); CrossAdd(reference, internal, &reference_moment); CrossAdd(x, free_residual, &free_residual_moment); } if (!Store3(linear, &f->linear_momentum, &f->low.linear_momentum) || !Store3(angular, &f->angular_momentum, &f->low.angular_momentum) || !Store3(linear_rate, &f->linear_momentum_rate, &f->low.linear_momentum_rate) || !Store3(angular_rate, &f->angular_momentum_rate, &f->low.angular_momentum_rate) || !Store3(current_internal_moment, &f->linearization_current_internal_moment, &f->low.linearization_current_internal_moment) || !Store3(reference_moment, &f->reference_internal_moment, &f->low.reference_internal_moment) || !Store3(free_residual_moment, &f->current_free_residual_moment, &f->low.current_free_residual_moment)) return Failure( domain, "solid-dynamic-nonfinite-momentum", "Paired momentum or current/reference moment is unsupported."); for (std::size_t c = 0; c < 3; ++c) { const auto moment = Subtract(applied_moment[c], angular_rate[c]); if (!Valid(linear_equilibrium[c]) || !Valid(moment)) return Failure(domain, "solid-dynamic-nonfinite-equilibrium", "Paired global equilibrium is unsupported."); Store(linear_equilibrium[c], &f->global_equilibrium[c], &f->low.global_equilibrium[c]); Store(moment, &f->global_equilibrium[3 + c], &f->low.global_equilibrium[3 + c]); } return Status::Ok(); } /// @brief Uses full paired same-unit uncancelled formula terms. HighLow UpdateMetric(HighLow left, std::initializer_list terms) { HighLow sum; auto scale = Abs(left); for (const auto x : terms) { sum = Add(sum, x); scale = Add(scale, Abs(x)); } const auto defect = Abs(Subtract(left, sum)); if (!Valid(defect) || !Valid(scale)) return math_internal::InvalidHighLow(); if (Compare(scale, HighLow(0)) == 0) return Compare(defect, HighLow(0)) == 0 ? HighLow(0) : math_internal::InvalidHighLow(); return Divide(defect, scale); } HighLow CheckUpdates(const SolidDynamicAcceptedFrameState& old, const SolidDynamicAcceptedFrameState& f, const HhtParameters& p, double h) { HighLow metric; const auto step = HighLow(h); const auto hh = Multiply(step, step); for (std::size_t i = 0; i < f.displacement.Size(); ++i) { const auto A = Pair(f.algorithmic_acceleration, f.low.algorithmic_acceleration, i); const auto oldA = Pair(old.algorithmic_acceleration, old.low.algorithmic_acceleration, i); const auto oldv = Pair(old.velocity, old.low.velocity, i); metric = Maximum( metric, UpdateMetric( Pair(f.displacement, f.low.displacement, i), {Pair(old.displacement, old.low.displacement, i), Multiply(step, oldv), Multiply(Multiply(hh, Subtract(HighLow(.5), HighLow(p.beta))), oldA), Multiply(Multiply(hh, HighLow(p.beta)), A)})); metric = Maximum( metric, UpdateMetric( Pair(f.velocity, f.low.velocity, i), {oldv, Multiply(Multiply(step, Subtract(HighLow(1), HighLow(p.gamma))), oldA), Multiply(Multiply(step, HighLow(p.gamma)), A)})); metric = Maximum( metric, UpdateMetric(A, {Multiply(Add(HighLow(1), HighLow(p.alpha)), Pair(f.acceleration, f.low.acceleration, i)), Negate(Multiply(HighLow(p.alpha), Pair(old.acceleration, old.low.acceleration, i)))})); } return metric; } std::array FreshCorrectionMetrics(const Vector& du, double length, const HhtParameters& p) { HighLow q; for (std::size_t i = 0; i < du.Size(); ++i) q = Maximum(q, Divide(Abs(HighLow(du[i])), HighLow(length))); return { q, Divide(Multiply(HighLow(p.gamma), q), HighLow(p.beta)), Divide(q, Multiply(Add(HighLow(1), HighLow(p.alpha)), HighLow(p.beta))), Divide(q, HighLow(p.beta))}; } std::array ObservedCorrectionMetrics( const SolidDynamicAcceptedFrameState& before, const SolidDynamicAcceptedFrameState& after, double h, double length) { std::array metric{}; const auto hh = Multiply(HighLow(h), HighLow(h)); for (std::size_t i = 0; i < after.displacement.Size(); ++i) { metric[0] = Maximum( metric[0], Divide(Abs(Subtract( Pair(after.displacement, after.low.displacement, i), Pair(before.displacement, before.low.displacement, i))), HighLow(length))); metric[1] = Maximum( metric[1], Divide(Multiply(HighLow(h), Abs(Subtract( Pair(after.velocity, after.low.velocity, i), Pair(before.velocity, before.low.velocity, i)))), HighLow(length))); metric[2] = Maximum( metric[2], Divide(Multiply(hh, Abs(Subtract(Pair(after.acceleration, after.low.acceleration, i), Pair(before.acceleration, before.low.acceleration, i)))), HighLow(length))); metric[3] = Maximum( metric[3], Divide(Multiply(hh, Abs(Subtract( Pair(after.algorithmic_acceleration, after.low.algorithmic_acceleration, i), Pair(before.algorithmic_acceleration, before.low.algorithmic_acceleration, i)))), HighLow(length))); } return metric; } bool CorrectionPass(const std::array& metrics) { return std::all_of(metrics.begin(), metrics.end(), [](HighLow x) { return Valid(x) && Compare(x, HighLow(0)) >= 0 && Compare(x, HighLow(1e-10)) <= 0; }); } } // namespace SolidHhtDynamicProcedureExecutor::SolidHhtDynamicProcedureExecutor( const ParallelFor& parallel_for, LinearSolver& mass_solver, TangentSolver& tangent_solver) : parallel_for_(parallel_for), mass_solver_(mass_solver), tangent_solver_(tangent_solver) {} Result SolidHhtDynamicProcedureExecutor::Execute( const Domain& domain) const { auto model = AnalysisModel::Create(domain); if (!model.HasValue()) return Result::Failure(model.GetStatus()); auto dofs = DofManager::Create(model.Value()); if (!dofs.HasValue()) return Result::Failure(dofs.GetStatus()); auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value()); if (!state.HasValue()) return Result::Failure(state.GetStatus()); auto status = ExecuteInto(domain, state.Value()); if (!status.IsOk()) return Result::Failure(status); return Result::Success( {std::move(dofs.Value()), std::move(state.Value())}); } Status SolidHhtDynamicProcedureExecutor::ExecuteInto( const Domain& domain, SolidDynamicAnalysisState& state) const { return ExecuteIntoForVerification(domain, state, -0.05); } Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification( const Domain& domain, SolidDynamicAnalysisState& state, double alpha) const { const detail::PairedSolidHhtIntegrator integrator; NewtonStepAlgorithm algorithm; return ExecuteSolidHhtCore(domain, state, alpha, parallel_for_, mass_solver_, tangent_solver_, integrator, algorithm); } Status ExecuteSolidHhtCore( const Domain& domain, SolidDynamicAnalysisState& state, double alpha, const ParallelFor& parallel_for, LinearSolver& mass_solver, TangentSolver& tangent_solver, const detail::SolidHhtIntegrator& integrator, EquilibriumAlgorithm& algorithm) { SolidDynamicIterationRecord record; auto fail = [&](const Status& failure) { record.stage = "failed"; record.outcome = "failed"; record.failure_code = failure.Diagnostics().empty() ? "solid-dynamic-failure" : failure.Diagnostics().front().code; state.RecordIteration(record); return failure; }; if (!state.IsInitialized() || !state.AcceptedFrames().empty() || !domain.IsSolid() || domain.Dynamic() == nullptr) return fail(Failure( domain, "solid-dynamic-invalid-execution", "Execution requires an initialized empty Solid dynamic state.")); auto environment = CheckArithmeticEnvironment(domain); if (!environment.IsOk()) return fail(environment); auto model = AnalysisModel::Create(domain); if (!model.HasValue()) return fail(model.GetStatus()); auto dofs_result = DofManager::Create(model.Value()); if (!dofs_result.HasValue()) return fail(dofs_result.GetStatus()); const auto& dofs = dofs_result.Value(); auto solids = BuildSolids(domain, dofs); if (!solids.HasValue()) return fail(solids.GetStatus()); auto mass = AssembleMass(solids.Value(), dofs); if (!mass.HasValue()) return fail(mass.GetStatus()); EssentialConstraintPolicy partition; auto mass_partition = partition.Partition(mass.Value(), dofs); if (!mass_partition.HasValue()) return fail(mass_partition.GetStatus()); if (dofs.FreeDofCount() != 0) { const auto status = mass_solver.Factorize(mass_partition.Value().k_ff); if (!status.IsOk()) return fail(status); environment = CheckArithmeticEnvironment(domain); if (!environment.IsOk()) return fail(environment); } auto parameters = MakeHhtParameters(alpha); if (!parameters.HasValue()) return fail(parameters.GetStatus()); const auto p = parameters.Value(); auto length = ReferenceLength(domain); if (!length.HasValue()) return fail(length.GetStatus()); const double h = domain.Dynamic()->initial_increment; SolidDynamicAcceptedFrameState initial; initial.is_initial = true; for (std::size_t n = 0; n < domain.Nodes().size(); ++n) { initial.nodal_internal_order.push_back(static_cast(n)); initial.nodal_source_ids.push_back(domain.Nodes()[n].source_id); } for (auto* v : {&initial.displacement, &initial.velocity, &initial.acceleration, &initial.algorithmic_acceleration}) *v = Vector(dofs.FullDofCount()); InitializeLowVectors(&initial, dofs.FullDofCount()); auto force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs, 0); if (!force.HasValue()) return fail(force.GetStatus()); initial.external_force = std::move(force.Value()); auto tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for, &initial); if (!tangent.HasValue()) return fail(tangent.GetStatus()); SolidDynamicIterationEvidence evidence; bool initial_converged = false; for (std::uint32_t refinement = 0; refinement <= 100; ++refinement) { initial.algorithmic_acceleration = initial.acceleration; initial.low.algorithmic_acceleration = initial.low.acceleration; evidence = {}; evidence.correction_pass = evidence.update_pass = true; auto status = RecoverEndpoint(domain, dofs, mass.Value(), nullptr, HighLow(0), &initial, &evidence); if (!status.IsOk()) return fail(status); if (evidence.residual_pass) { initial_converged = true; break; } record.stage = "initial-mass"; record.outcome = "residual-refinement"; record.evidence = evidence; record.evidence.iteration_index = refinement; state.RecordIteration(record); if (refinement == 100) break; Vector rhs(dofs.FreeDofCount()), a(dofs.FreeDofCount()); for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) { const auto i = dofs.FreeDofs()[j]; rhs[j] = Rounded(Negate(Pair(initial.residual, initial.low.residual, i))); } status = mass_solver.Solve(rhs, a); if (!status.IsOk()) return fail(status); environment = CheckArithmeticEnvironment(domain); if (!environment.IsOk()) return fail(environment); if (a.Size() != dofs.FreeDofCount()) return fail( Failure(domain, "solid-dynamic-invalid-backend-result", "Initial mass solve returned incompatible acceleration.")); for (std::size_t j = 0; j < a.Size(); ++j) { if (!std::isfinite(a[j])) return fail( Failure(domain, "solid-dynamic-invalid-backend-result", "Initial mass solve returned nonfinite acceleration.")); const auto i = dofs.FreeDofs()[j]; const auto value = Add(Pair(initial.acceleration, initial.low.acceleration, i), HighLow(a[j] == 0 ? 0.0 : a[j])); if (!Valid(value)) return fail(detail::SolidHhtPairedKinematicsFailure()); Store(value, &initial.acceleration, &initial.low.acceleration, i); } } if (!initial_converged) return fail(Failure(domain, "solid-dynamic-initial-mass-nonconvergence", "Physical initial equilibrium did not converge in100 " "mass refinements.")); initial.iterations.push_back(evidence); auto status = state.CommitAcceptedFrame(initial); if (!status.IsOk()) { record.evidence = evidence; return fail(status); } record.stage = "accepted"; record.outcome = "initial"; record.evidence = evidence; state.RecordIteration(record); const auto initial_energy = Add(HighLow(initial.strain_energy, initial.low.strain_energy), HighLow(initial.kinetic_energy, initial.low.kinetic_energy)); for (std::uint32_t increment = 1; increment <= domain.Dynamic()->increment_count; ++increment) { const auto& old = state.CommittedState(); record = {}; record.increment_index = increment; record.time = static_cast(increment) * h; status = detail::RunSolidHhtStep( integrator, old, h, p, fail, [&](const detail::SolidHhtIntegrator& step_integrator, const detail::SolidHhtPairedPredictor& predictor) -> Result { auto candidate = old; candidate.frame_index = candidate.increment_index = increment; candidate.iteration_count = 0; candidate.time = record.time; candidate.is_initial = false; candidate.iterations.clear(); // Begin from certified accepted geometry, not an unchecked predictor. force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs, candidate.time); if (!force.HasValue()) return Result::Failure(fail(force.GetStatus())); candidate.external_force = std::move(force.Value()); candidate.low.external_force = Vector(dofs.FullDofCount()); std::array correction{}, fresh_correction{}; using StepResult = Result>; const auto report_failure = fail; const auto record_iteration = [&](const auto& value) { state.RecordIteration(value); }; SolidHhtStep iteration_step( [&](std::uint32_t iteration) -> StepResult { const auto fail = [&](const Status& failure) { return StepResult::Failure(report_failure(failure)); }; record.stage = "evaluation"; record.outcome = "paired-candidate"; record.evidence = {}; record.evidence.iteration_index = iteration; status = detail::SetSolidHhtRates(step_integrator, old, predictor, h, p, &candidate); if (!status.IsOk()) return fail(status); tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for, &candidate); if (!tangent.HasValue()) return fail(tangent.GetStatus()); status = RecoverEndpoint(domain, dofs, mass.Value(), &old, initial_energy, &candidate, &record.evidence); if (!status.IsOk()) return fail(status); auto MA = detail::MultiplySolidHhtMassHighLow( mass.Value(), candidate.algorithmic_acceleration, candidate.low.algorithmic_acceleration); if (!MA.HasValue()) return fail(MA.GetStatus()); Vector rhs(dofs.FreeDofCount()); HighLow identity_metric, original_norm, solved_norm, old_norm; for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) { const auto i = dofs.FreeDofs()[j]; detail::SolidHhtPairedForceIdentity identity; if (!detail::EvaluateSolidHhtHighLowForceIdentity( mass.Value(), i, Pair(MA.Value().high, MA.Value().low, i), candidate, old, p.alpha, &identity)) return fail( Failure(domain, "solid-dynamic-nonfinite-residual", "Paired weighted force arithmetic is unsupported.")); rhs[j] = Rounded(Negate(identity.solved)); identity_metric = Maximum(identity_metric, identity.metric); original_norm = Maximum(original_norm, Abs(identity.original)); solved_norm = Maximum(solved_norm, Abs(identity.solved)); old_norm = Maximum(old_norm, Abs(Pair(old.residual, old.low.residual, i))); if (i == record.evidence.worst_full_dof) record.evidence.worst_weighted_terms = { Rounded(identity.original), Rounded(identity.solved), Rounded(Pair(old.residual, old.low.residual, i)), Rounded(identity.scale)}; } if (!Valid(identity_metric) || !Valid(original_norm) || !Valid(solved_norm) || !Valid(old_norm) || Compare(identity_metric, HighLow(1e-12)) > 0) return fail( Failure(domain, "solid-dynamic-weighted-algebra-inconsistency", "Paired original and solved forces disagree beyond " "their uncancelled scale.")); record.evidence.weighted_equivalence_metric = Rounded(identity_metric); record.evidence.weighted_residual_numerator = Rounded(original_norm); record.evidence.recombined_residual_numerator = Rounded(solved_norm); record.evidence.old_residual_numerator = Rounded(old_norm); for (std::size_t c = 0; c < 4; ++c) { if (!Valid(correction[c]) || !Valid(fresh_correction[c])) return fail(detail::SolidHhtPairedKinematicsFailure()); record.evidence.correction_metrics[c] = Rounded(correction[c]); } record.evidence.correction_pass = CorrectionPass(correction) && CorrectionPass(fresh_correction); const auto update = CheckUpdates(old, candidate, p, h); if (!Valid(update)) return fail(detail::SolidHhtPairedKinematicsFailure()); record.evidence.update_metric = Rounded(update); record.evidence.update_pass = Compare(update, HighLow(1e-12)) <= 0; candidate.iterations.push_back(record.evidence); candidate.iteration_count = iteration; if (record.evidence.residual_pass && record.evidence.correction_pass && record.evidence.update_pass) { return StepResult::Success(candidate); } record_iteration(record); if (iteration == 100) return fail(Failure( domain, "solid-dynamic-nonconvergence", "Paired HHT endpoint did not converge in100 Newton solves.")); auto effective = EffectiveTangent(mass.Value(), tangent.Value(), dofs, h, p); if (!effective.HasValue()) return fail(effective.GetStatus()); auto reduced = partition.Partition(effective.Value(), dofs); if (!reduced.HasValue()) return fail(reduced.GetStatus()); Vector du(dofs.FreeDofCount()); if (dofs.FreeDofCount() != 0) { status = tangent_solver.FactorizeFresh(reduced.Value().k_ff); if (!status.IsOk()) return fail(status); environment = CheckArithmeticEnvironment(domain); if (!environment.IsOk()) return fail(environment); status = tangent_solver.SolveCorrection(rhs, du); if (!status.IsOk()) return fail(status); environment = CheckArithmeticEnvironment(domain); if (!environment.IsOk()) return fail(environment); } if (du.Size() != dofs.FreeDofCount()) return fail( Failure(domain, "solid-dynamic-invalid-backend-result", "Tangent solve returned incompatible correction.")); for (std::size_t j = 0; j < du.Size(); ++j) if (!std::isfinite(du[j])) return fail( Failure(domain, "solid-dynamic-invalid-backend-result", "Tangent solve returned nonfinite correction.")); fresh_correction = FreshCorrectionMetrics(du, length.Value(), p); const auto before = candidate; for (std::size_t j = 0; j < du.Size(); ++j) { const auto i = dofs.FreeDofs()[j]; const auto value = Add(Pair(candidate.displacement, candidate.low.displacement, i), HighLow(du[j] == 0 ? 0.0 : du[j])); if (!Valid(value)) return fail(detail::SolidHhtPairedKinematicsFailure()); Store(value, &candidate.displacement, &candidate.low.displacement, i); } status = detail::SetSolidHhtRates(step_integrator, old, predictor, h, p, &candidate); if (!status.IsOk()) return fail(status); correction = ObservedCorrectionMetrics(before, candidate, h, length.Value()); return StepResult::Success(std::nullopt); }, 100U); const Status solved = iteration_step.Solve(algorithm); if (!solved.IsOk()) return Result::Failure(solved); if (!iteration_step.HasCandidate()) return Result::Failure( fail(Failure(domain, "solid-dynamic-nonconvergence", "No complete paired endpoint was accepted."))); return Result::Success( iteration_step.TakeCandidate()); }, [&](SolidDynamicAcceptedFrameState&& accepted_candidate) -> Status { status = state.CommitAcceptedFrame(accepted_candidate); if (!status.IsOk()) return fail(status); record.stage = "accepted"; record.outcome = "increment"; state.RecordIteration(record); return Status::Ok(); }); if (!status.IsOk()) return status; } return Status::Ok(); } } // namespace fesa