#include "analysis/hht_reduced_procedure.h" #include "analysis/hht_reduced_integrator.h" #include "equilibrium_algorithm.h" #include #include #include #include #include #include #include "analysis/hht_dynamic_internal.h" #include "analysis/hht_reduced_increment.h" #include "analysis/hht_reduced_internal.h" #include "fesa/analysis/analysis_model.h" #include "fesa/analysis/newton_iteration_log.h" #include "fesa/solvers/linear/tangent_solver.h" namespace fesa { namespace { using namespace hht_internal; using namespace hht_reduced_internal; Status Failure(const Domain& domain, const std::string& code, const std::string& message) { return Status::Failure( FailureCategory::kSolver, {{Severity::kError, code, domain.Dynamic()->dynamic_location, "DYNAMIC", domain.Dynamic()->source_step_name_raw, message}}); } Status WithProcedureContext(const Domain& domain, const Status& status, const char* stage, const std::uint32_t increment, const double time) { std::vector diagnostics = status.Diagnostics(); std::ostringstream message; message << "stage=" << stage << ", step=" << domain.Dynamic()->source_step_name_raw << ", increment=" << increment << ", time=" << time << "."; diagnostics.push_back({Severity::kError, "dynamic-procedure-context", domain.Dynamic()->dynamic_location, "DYNAMIC", domain.Dynamic()->source_step_name_raw, message.str()}); if (const auto category = status.Category(); category.has_value()) return Status::Failure(*category, std::move(diagnostics)); return Status::Failure(std::move(diagnostics)); } bool UpdateEqual(const double left, const std::initializer_list terms) { double right = 0.0; double scale = 0.0; for (const double term : terms) { right += term; scale += std::abs(term); } const double defect = std::abs(left - right); return std::isfinite(defect) && std::isfinite(scale) && (scale == 0.0 ? defect == 0.0 : defect / scale <= 1e-12); } bool PrivateCorrectionEqual(const FixedChart& chart, const std::size_t coordinate, const Vector& full_algorithmic, const Vector& private_algorithmic, const Vector& private_physical, const double alpha) { if (!UpdateEqual((1.0 + alpha) * private_physical[coordinate], {private_algorithmic[coordinate]})) return false; const auto& column = chart.p_columns[coordinate]; if (!column.fixed_plane && !UpdateEqual(full_algorithmic[column.support[0U].dof], {private_algorithmic[coordinate]})) return false; double sum = 0.0; double compensation = 0.0; double scale = std::abs(private_algorithmic[coordinate]); for (const auto& entry : column.support) { const double term = entry.value * full_algorithmic[entry.dof]; const double next = sum + term; compensation += std::abs(sum) >= std::abs(term) ? (sum - next) + term : (term - next) + sum; sum = next; scale += std::abs(term); } const double defect = std::abs((sum + compensation) - private_algorithmic[coordinate]); return std::isfinite(scale) && std::isfinite(defect) && (scale == 0.0 ? defect == 0.0 : defect / scale <= 1e-12); } double AdditionLowPart(const double left, const double right, const double sum) { const double right_virtual = sum - left; return (left - (sum - right_virtual)) + (right - right_virtual); } void AdvanceCoordinate(const double delta, double* high, double* low) { const double sum = *high + delta; const double error = AdditionLowPart(*high, delta, sum); const double low_sum = *low + error; *high = sum + low_sum; *low = AdditionLowPart(sum, low_sum, *high); } void AddIncrementTraceTerm(const double value, double* sum, double* compensation) { const double next = *sum + value; if (std::abs(*sum) >= std::abs(value)) *compensation += (*sum - next) + value; else *compensation += (value - next) + *sum; *sum = next; } bool GlobalBasisEqual(const FixedChart& chart, const Vector& full, const Vector& p, const Vector& z) { Vector sums{chart.full_dof_count}; Vector compensations{chart.full_dof_count}; Vector scales{chart.full_dof_count}; const auto add = [&](const std::vector& columns, const Vector& coordinates) { for (std::size_t i = 0U; i < coordinates.Size(); ++i) for (const auto& entry : columns[i].support) { const double term = entry.value * coordinates[i]; AddIncrementTraceTerm(term, &sums[entry.dof], &compensations[entry.dof]); scales[entry.dof] += std::abs(term); } }; add(chart.p_columns, p); add(chart.z_columns, z); for (std::size_t i = 0U; i < full.Size(); ++i) { const double defect = std::abs(full[i] - (sums[i] + compensations[i])); if (!std::isfinite(defect) || !std::isfinite(scales[i]) || (scales[i] == 0.0 ? defect != 0.0 : defect / scales[i] > 1e-12)) return false; } return true; } void ResidualRecord(const DofManager& dofs, const DynamicAcceptedFrameState& frame, DynamicNewtonIterationLogRecord* record) { record->residual_numerator = {}; record->residual_scale = {}; for (const auto full : dofs.FreeDofs()) { const auto channel = full % 6U < 3U ? 0U : 1U; record->residual_numerator[channel] = (std::max)(record->residual_numerator[channel], std::abs(frame.residual[full])); record->residual_scale[channel] = (std::max)(record->residual_scale[channel], frame.residual_scale[full]); } record->residual_pass = frame.free_residual_metrics[0U] <= 1e-10 && frame.free_residual_metrics[1U] <= 1e-10; } void CorrectionRecord(const Vector& coordinate, const Vector& velocity, const Vector& acceleration, const Vector& full_algorithmic, const Vector& reduced_algorithmic, const Vector& reduced_physical, const FixedChart& chart, const DofManager& dofs, const double length, const double h, const double alpha, DynamicNewtonIterationLogRecord* record) { record->coordinate_correction = {}; record->velocity_correction = {}; record->acceleration_correction = {}; record->algorithmic_correction = {}; for (const auto full : dofs.FreeDofs()) { const auto channel = full % 6U < 3U ? 0U : 1U; const double scale = channel == 0U ? length : 1.0; record->coordinate_correction[channel] = (std::max)(record->coordinate_correction[channel], std::abs(coordinate[full]) / scale); record->velocity_correction[channel] = (std::max)(record->velocity_correction[channel], std::abs(h * velocity[full]) / scale); record->acceleration_correction[channel] = (std::max)(record->acceleration_correction[channel], std::abs(h * h * acceleration[full]) / scale); record->algorithmic_correction[channel] = (std::max)(record->algorithmic_correction[channel], std::abs(h * h * full_algorithmic[full]) / scale); } record->correction_pass = true; for (std::size_t channel = 0U; channel < 2U; ++channel) { record->correction_pass = record->correction_pass && record->coordinate_correction[channel] <= 1e-10 && record->velocity_correction[channel] <= 1e-10 && record->acceleration_correction[channel] <= 1e-10 && record->algorithmic_correction[channel] <= 1e-10; } if (reduced_algorithmic.Size() != chart.p_columns.size() || reduced_physical.Size() != chart.p_columns.size()) { record->correction_pass = false; return; } for (std::size_t i = 0U; i < chart.p_dofs.size(); ++i) record->correction_pass = record->correction_pass && PrivateCorrectionEqual(chart, i, full_algorithmic, reduced_algorithmic, reduced_physical, alpha); } bool ExactlyEqual(const Vector& left, const Vector& right) { if (left.Size() != right.Size()) return false; for (std::size_t i = 0U; i < left.Size(); ++i) if (left[i] != right[i]) return false; return true; } Status CheckReducedUpdates(const Domain& domain, const DynamicAcceptedFrameState& old, const DynamicAcceptedFrameState& frame, const Vector& predictor_coordinates, const Vector& predictor_velocity, const Vector& physical_coordinates, const Vector& physical_velocity, const Vector& physical_acceleration, const Vector& algebraic_coordinates, const Vector& algebraic_velocity, const Vector& algebraic_acceleration, const Vector& algorithmic_acceleration, const Vector& old_private_coordinates, const Vector& old_private_velocity, const Vector& old_private_physical_acceleration, const Vector& old_private_algorithmic_acceleration, const Vector& increment_trace, const Vector& increment_trace_compensation, const Vector& increment_trace_absolute_sum, const FixedChart& chart, const Vector& chosen_start_coordinates, const Vector& coordinate_low, const HhtParameters& parameters, double h) { if (!GlobalBasisEqual(chart, frame.generalized_coordinates, physical_coordinates, algebraic_coordinates) || !GlobalBasisEqual(chart, frame.velocity, physical_velocity, algebraic_velocity) || !GlobalBasisEqual(chart, frame.acceleration, physical_acceleration, algebraic_acceleration)) return Failure(domain, "reduced-dynamic-update-inconsistency", "Global HHT reconstruction differs from its actual sparse basis products."); for (std::size_t i = 0U; i < physical_coordinates.Size(); ++i) { const auto inconsistent = [&](const char* reason) { const auto pair_trace = CheckPairIncrement(physical_coordinates[i], coordinate_low[i], old_private_coordinates[i], {increment_trace[i], increment_trace_compensation[i]}, increment_trace_absolute_sum[i]); std::ostringstream message; message << std::setprecision(17) << reason << " i=" << i << ", coordinate_label=" << chart.p_dofs[i] << ", channel=" << PhysicalCoordinateChannel(chart, i) << ", old_p=" << old_private_coordinates[i] << ", start_p=" << chosen_start_coordinates[i] << ", p=" << physical_coordinates[i] << ", p_low=" << coordinate_low[i] << ", actual_increment=" << physical_coordinates[i] - old_private_coordinates[i] << ", trace_sum=" << increment_trace[i] << ", trace_compensation=" << increment_trace_compensation[i] << ", trace_scale=" << increment_trace_absolute_sum[i] << ", pair_trace_defect=" << pair_trace.defect << ", pair_trace_ratio=" << pair_trace.ratio << ", pair_storage=" << pair_trace.storage_pass << ", representation_distance=" << std::abs(coordinate_low[i]) << ", pp=" << predictor_coordinates[i] << ", h_vold=" << h * old_private_velocity[i] << ", h2_old_Ar=" << h * h * (0.5 - parameters.beta) * old_private_algorithmic_acceleration[i] << ", beta_h2_Ar=" << parameters.beta * h * h * algorithmic_acceleration[i] << "."; return Failure(domain, "reduced-dynamic-update-inconsistency", message.str()); }; if (!UpdateEqual(physical_coordinates[i], {predictor_coordinates[i], parameters.beta * h * h * algorithmic_acceleration[i]}) || !UpdateEqual(physical_velocity[i], {predictor_velocity[i], parameters.gamma * h * algorithmic_acceleration[i]}) || !UpdateEqual((1.0 + parameters.alpha) * physical_acceleration[i], {algorithmic_acceleration[i], parameters.alpha * old_private_physical_acceleration[i]})) { return inconsistent("Reduced HHT private history or public auxiliary rate is inconsistent."); } const double velocity_term = h * old_private_velocity[i]; const double old_rate_term = h * h * (0.5 - parameters.beta) * old_private_algorithmic_acceleration[i]; const double new_rate_term = h * h * parameters.beta * algorithmic_acceleration[i]; const auto whole = CheckPairIncrement(physical_coordinates[i], coordinate_low[i], old_private_coordinates[i], {velocity_term, old_rate_term, new_rate_term}, std::abs(velocity_term) + std::abs(old_rate_term) + std::abs(new_rate_term)); if (!whole.storage_pass) return inconsistent("Reduced HHT coordinate high is not the RN-even pair value."); if (!whole.arithmetic_pass) { return inconsistent("Reduced HHT resolved private update is inconsistent."); } const auto requested = CheckPairIncrement(physical_coordinates[i], coordinate_low[i], old_private_coordinates[i], {increment_trace[i], increment_trace_compensation[i]}, increment_trace_absolute_sum[i]); if (!requested.arithmetic_pass) { return inconsistent("Reduced HHT increment trace is inconsistent."); } } for (std::size_t i = 0U; i < frame.algorithmic_acceleration.Size(); ++i) if (!UpdateEqual(frame.algorithmic_acceleration[i], {(1.0 + parameters.alpha) * frame.acceleration[i], -parameters.alpha * old.acceleration[i]})) return Failure(domain, "reduced-dynamic-update-inconsistency", "Public HHT auxiliary acceleration is inconsistent."); return Status::Ok(); } // Private nonvirtual primitive: keep velocity assignment before acceleration // evaluation, including per-index position retries. No accepted state changes. void EvaluateReducedShellRate(double predictor_velocity, double algorithmic_acceleration, double old_acceleration, double h, const HhtParameters& parameters, double* velocity, double* acceleration) { *velocity = predictor_velocity + parameters.gamma * h * algorithmic_acceleration; *acceleration = (algorithmic_acceleration + parameters.alpha * old_acceleration) / (1.0 + parameters.alpha); } class ReducedCoordinateShellHhtIntegrator final : public ShellHhtIntegrator { public: ReducedShellPredictor Predict(const ReducedHistory& history, double h, const HhtParameters& parameters) const override { Vector pp{history.p.Size()}, vpp{history.p.Size()}; for (std::size_t i = 0U; i < history.p.Size(); ++i) { pp[i] = history.p[i] + h * history.velocity[i] + h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i]; vpp[i] = history.velocity[i] + h * (1.0 - parameters.gamma) * history.algorithmic_acceleration[i]; } Vector coordinate_low{history.p.Size()}; Vector increment_trace{history.p.Size()}; Vector increment_trace_compensation{history.p.Size()}; Vector increment_trace_absolute_sum{history.p.Size()}; for (std::size_t i = 0U; i < history.p.Size(); ++i) { const double velocity_term = h * history.velocity[i]; const double old_acceleration_term = h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i]; const double first_sum = history.p[i] + velocity_term; coordinate_low[i] = AdditionLowPart(history.p[i], velocity_term, first_sum) + AdditionLowPart(first_sum, old_acceleration_term, pp[i]); AddIncrementTraceTerm(velocity_term, &increment_trace[i], &increment_trace_compensation[i]); AddIncrementTraceTerm(old_acceleration_term, &increment_trace[i], &increment_trace_compensation[i]); increment_trace_absolute_sum[i] = std::abs(velocity_term) + std::abs(old_acceleration_term); } return {std::move(pp), std::move(vpp), std::move(coordinate_low), std::move(increment_trace), std::move(increment_trace_compensation), std::move(increment_trace_absolute_sum)}; } void EvaluateRates(const ReducedShellRateRequest& request, Vector& trial_v, Vector& trial_a) const override { const auto& history = request.history; const auto& trial_algorithmic = request.trial_algorithmic; const auto& vpp = request.vpp; const auto& parameters = request.parameters; const double h = request.h; for (std::size_t i = 0U; i < history.p.Size(); ++i) { if (request.copy_translation_rates && PhysicalCoordinateChannel(request.chart, i) == 0U) { trial_v[i] = request.retained_velocity[i]; trial_a[i] = request.retained_acceleration[i]; } else { EvaluateReducedShellRate( vpp[i], trial_algorithmic[i], history.acceleration[i], h, parameters, &trial_v[i], &trial_a[i]); } } } Result ReconstructPhysicalRates(const FixedChart& chart, const EmbeddingRoot& root, const Vector& velocity, const Vector& acceleration, TangentSolver& solver) const override { return hht_reduced_internal::ReconstructPhysicalRates(chart, root, velocity, acceleration, solver); } }; struct Trial { DynamicAssembly assembly; EmbeddingRoot root; Vector p{0U}; Vector velocity{0U}; Vector acceleration{0U}; Vector algorithmic_acceleration{0U}; Vector algebraic_velocity{0U}; Vector algebraic_acceleration{0U}; }; } // namespace Result ExecuteReducedHht( const Domain& domain, NewtonIterationLogWriter& log, const ParallelFor& parallel_for, LinearSolver& positive_solver, TangentSolver& tangent_solver) { const ReducedCoordinateShellHhtIntegrator integrator; NewtonStepAlgorithm algorithm; return ExecuteReducedHht(domain, log, parallel_for, positive_solver, tangent_solver, integrator, algorithm); } Result ExecuteReducedHht( const Domain& domain, NewtonIterationLogWriter& log, const ParallelFor& parallel_for, LinearSolver& positive_solver, TangentSolver& tangent_solver, const ShellHhtIntegrator& integrator, EquilibriumAlgorithm& algorithm) { if (domain.Dynamic() == nullptr) return Result::Failure(Status::Failure( FailureCategory::kModel, {{Severity::kError, "invalid-dynamic-step", {}, "DYNAMIC", {}, "HHT requires exactly one dynamic step."}})); auto model = AnalysisModel::Create(domain); if (!model.HasValue()) return Result::Failure(model.GetStatus()); auto dofs_result = DofManager::Create(model.Value()); if (!dofs_result.HasValue()) return Result::Failure(dofs_result.GetStatus()); const auto& dofs = dofs_result.Value(); auto state_result = DynamicAnalysisState::Create(domain, dofs); if (!state_result.HasValue()) return Result::Failure(state_result.GetStatus()); auto state = std::move(state_result.Value()); auto shells = BuildRuntimeShells(domain, dofs); if (!shells.HasValue()) return Result::Failure(shells.GetStatus()); auto length_result = ReferenceLength(domain); if (!length_result.HasValue()) return Result::Failure(length_result.GetStatus()); const double length = length_result.Value(); auto loads = AssembleLoads(domain, dofs); if (!loads.HasValue()) return Result::Failure(loads.GetStatus()); auto parameters_result = MakeHhtParameters(domain.Dynamic()->alpha); if (!parameters_result.HasValue()) return Result::Failure(parameters_result.GetStatus()); const HhtParameters parameters = parameters_result.Value(); const double h = domain.Dynamic()->initial_increment; const Vector zero{dofs.FullDofCount()}; DynamicAcceptedFrameState frame{}; frame.is_initial = true; frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht; for (auto* values : {&frame.generalized_coordinates, &frame.velocity, &frame.acceleration, &frame.algorithmic_acceleration}) *values = Vector(dofs.FullDofCount()); frame.external_force = loads.Value()[0U]; auto initial = EvaluateAssembly(domain, dofs, shells.Value(), frame, loads.Value()[1U], h, parameters, parallel_for, true); if (!initial.HasValue()) return Result::Failure( WithProcedureContext(domain, initial.GetStatus(), "initial", 0U, 0.0)); const auto null_nodes = NullNodes(dofs, initial.Value().rotations); auto initial_acceleration = InitializeAcceleration(domain, dofs, initial.Value(), length, positive_solver, tangent_solver); if (!initial_acceleration.HasValue()) return Result::Failure( WithProcedureContext(domain, initial_acceleration.GetStatus(), "initial", 0U, 0.0)); frame.acceleration = initial_acceleration.Value(); frame.algorithmic_acceleration = initial_acceleration.Value(); initial = EvaluateAssembly(domain, dofs, shells.Value(), frame, loads.Value()[1U], h, parameters, parallel_for, true); if (!initial.HasValue()) return Result::Failure( WithProcedureContext(domain, initial.GetStatus(), "initial", 0U, 0.0)); frame = initial.Value().frame; frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht; auto chart = BuildFixedChart(dofs, initial.Value().rotations); if (!chart.HasValue()) return Result::Failure( WithProcedureContext(domain, chart.GetStatus(), "initial", 0U, 0.0)); auto p0 = ProjectPhysical(chart.Value(), frame.generalized_coordinates); auto vp0 = ProjectPhysical(chart.Value(), frame.velocity); auto ap0 = ProjectPhysical(chart.Value(), frame.acceleration); auto z0 = ProjectAlgebraic(chart.Value(), frame.generalized_coordinates); for (const auto* projected : {&p0, &vp0, &ap0, &z0}) if (!projected->HasValue()) return Result::Failure(projected->GetStatus()); ReducedHistory history{std::move(p0.Value()), std::move(vp0.Value()), ap0.Value(), ap0.Value(), std::move(z0.Value())}; auto initial_p_acceleration = Lift(chart.Value(), history.acceleration, Vector{chart.Value().z_columns.size()}); if (!initial_p_acceleration.HasValue()) return Result::Failure(initial_p_acceleration.GetStatus()); bool position_geometry_failure = false; const auto position_operators = [&](const DynamicAcceptedFrameState& template_frame, const Vector& q) -> Result { auto position = template_frame; position.generalized_coordinates = q; position.velocity = zero; position.acceleration = zero; position.algorithmic_acceleration = zero; auto assembly = EvaluateAssembly(domain, dofs, shells.Value(), position, loads.Value()[1U], h, parameters, parallel_for, false); if (!assembly.HasValue()) { if (!assembly.GetStatus().Diagnostics().empty()) { const auto& code = assembly.GetStatus().Diagnostics().front().code; position_geometry_failure = code == "invalid-nonlinear-shell-geometry" || code == "invalid-nonlinear-shell-director" || code == "invalid-nonlinear-inertia-geometry" || code == "invalid-nonlinear-inertia-director"; if (code == "invalid-dynamic-rotation") { const double margin = std::sqrt(std::numeric_limits::epsilon()); const double pi = std::acos(-1.0); for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) { double squared = 0.0; for (std::size_t component = 0U; component < 3U; ++component) { const double value = q[6U * node + 3U + component]; squared += value * value; } const double angle = std::sqrt(squared); if (!std::isfinite(angle) || !(pi - angle > margin)) { position_geometry_failure = true; break; } } } } return Result::Failure(assembly.GetStatus()); } return EvaluateConstraintOperators(domain, dofs, shells.Value(), assembly.Value(), chart.Value(), zero, zero); }; auto root0 = SolveEmbeddingRoot(chart.Value(), history.p, history.z, [&](const Vector& q) { return position_operators(frame, q); }, tangent_solver); if (!root0.HasValue()) return Result::Failure( WithProcedureContext(domain, root0.GetStatus(), "inner-position", 0U, 0.0)); auto rates0 = integrator.ReconstructPhysicalRates(chart.Value(), root0.Value(), history.velocity, history.acceleration, tangent_solver); if (!rates0.HasValue()) return Result::Failure( WithProcedureContext(domain, rates0.GetStatus(), "inner-rates", 0U, 0.0)); // Frame zero remains the independently recovered physical state. Its // position and velocity must be represented exactly; acceleration is // certified below by the existing hidden-rate and positive-mass evidence, // rather than requiring two independent solves to round identically. if (!ExactlyEqual(root0.Value().q, frame.generalized_coordinates) || !ExactlyEqual(rates0.Value().velocity, frame.velocity)) return Result::Failure( Failure(domain, "dynamic-initial-consistency", "The initialized full HHT position or velocity is not exactly representable by the fixed reduced chart.")); auto reconstructed_zero = frame; reconstructed_zero.generalized_coordinates = root0.Value().q; reconstructed_zero.velocity = rates0.Value().velocity; reconstructed_zero.acceleration = rates0.Value().acceleration; reconstructed_zero.algorithmic_acceleration = rates0.Value().acceleration; auto reconstructed_assembly = EvaluateAssembly( domain, dofs, shells.Value(), reconstructed_zero, loads.Value()[1U], h, parameters, parallel_for, true); if (!reconstructed_assembly.HasValue()) return Result::Failure(WithProcedureContext( domain, reconstructed_assembly.GetStatus(), "initial", 0U, 0.0)); Status reconstructed_status = CompleteConstraintEvidence( domain, dofs, reconstructed_assembly.Value(), length, null_nodes, positive_solver, tangent_solver, &reconstructed_zero); if (!reconstructed_status.IsOk()) return Result::Failure(WithProcedureContext( domain, reconstructed_status, "initial", 0U, 0.0)); reconstructed_status = CompleteEquilibrium(domain, dofs, reconstructed_assembly.Value(), &reconstructed_zero); if (!reconstructed_status.IsOk()) return Result::Failure(WithProcedureContext( domain, reconstructed_status, "initial", 0U, 0.0)); if (!(reconstructed_assembly.Value().frame.free_residual_metrics[0U] <= 1.0e-10 && reconstructed_assembly.Value().frame.free_residual_metrics[1U] <= 1.0e-10)) { const auto& reconstructed = reconstructed_assembly.Value().frame; DynamicNewtonIterationLogRecord initial_record{}; ResidualRecord(dofs, reconstructed, &initial_record); std::ostringstream message; message << std::setprecision(17) << "The reconstructed frame-zero physical residual is not acceptable; " << "initial_u=" << frame.free_residual_metrics[0U] << ", initial_phi=" << frame.free_residual_metrics[1U]; for (std::size_t channel = 0U; channel < 2U; ++channel) { std::size_t worst = 0U; double maximum = -1.0; for (const auto full : dofs.FreeDofs()) { if ((full % 6U < 3U ? 0U : 1U) == channel && std::abs(reconstructed.residual[full]) > maximum) { worst = full; maximum = std::abs(reconstructed.residual[full]); } } message << "; channel=" << channel << ", ratio=" << reconstructed.free_residual_metrics[channel] << ", numerator=" << initial_record.residual_numerator[channel] << ", scale=" << initial_record.residual_scale[channel]; if (maximum >= 0.0) message << ", full=" << worst << ", node=" << worst / 6U << ", residual=" << reconstructed.residual[worst] << ", row_scale=" << reconstructed.residual_scale[worst] << ", original_residual=" << frame.residual[worst] << ", q=" << reconstructed.generalized_coordinates[worst] << ", v=" << reconstructed.velocity[worst] << ", a=" << reconstructed.acceleration[worst] << ", original_a=" << frame.acceleration[worst] << ", delta_a=" << reconstructed.acceleration[worst] - frame.acceleration[worst]; } Vector acceleration_delta{frame.acceleration.Size()}; for (std::size_t full = 0U; full < acceleration_delta.Size(); ++full) acceleration_delta[full] = reconstructed.acceleration[full] - frame.acceleration[full]; const auto mass_delta = initial.Value().mass.Multiply(acceleration_delta); const auto graph = GraphJacobian(root0.Value().operators, chart.Value()); const auto hessian_velocity = ContractConstraintHessian( root0.Value().operators, frame.velocity, frame.velocity); // Failure-only evidence is bounded to sixteen canonical null rows. C09's // eight-node covariance fixture is fully represented, without state edits. const auto count = (std::min)(root0.Value().operators.rows.size(), std::size_t{16U}); for (std::size_t row = 0U; row < count; ++row) { const auto node = chart.Value().null_nodes[row]; const auto& null = initial.Value().rotations[node].drill_null; const auto& primitive = root0.Value().operators.rows[row]; Vector projected_gradient{frame.acceleration.Size()}; for (std::size_t component = 0U; component < 3U; ++component) { const auto full = 6U * node + 3U + component; for (std::size_t index = initial.Value().internal_tangent.RowOffsets()[full]; index < initial.Value().internal_tangent.RowOffsets()[full + 1U]; ++index) projected_gradient[initial.Value().internal_tangent.ColumnIndices()[index]] += null[component] * initial.Value().internal_tangent.Values()[index]; } double original_dot = 0.0, reconstructed_dot = 0.0, projected_dot = 0.0; double time_velocity = 0.0; for (const auto& entry : primitive.time_gradient) time_velocity += entry.value * frame.velocity[entry.dof]; double partition_rhs = -hessian_velocity[row] - 2.0 * time_velocity - primitive.time_second; std::array translation_sum{}, projected_translation_sum{}; for (std::size_t full = 0U; full < frame.acceleration.Size(); ++full) { projected_dot += projected_gradient[full] * frame.acceleration[full]; if (full % 6U < 3U) projected_translation_sum[full % 6U] += projected_gradient[full]; } for (const auto& entry : primitive.gradient) { original_dot += entry.value * frame.acceleration[entry.dof]; reconstructed_dot += entry.value * reconstructed.acceleration[entry.dof]; partition_rhs -= entry.value * initial_p_acceleration.Value()[entry.dof]; if (entry.dof % 6U < 3U) translation_sum[entry.dof % 6U] += entry.value; } message << "\ninitial-null row=" << row << ", node=" << node << ", z_label=" << chart.Value().z_dofs[row] << ", old_psi_a=" << original_dot << ", reconstructed_psi_a=" << reconstructed_dot << ", nH_old_a=" << projected_dot << ", partition_rhs=" << partition_rhs << ", old_r2=" << original_dot + hessian_velocity[row] + 2.0 * time_velocity + primitive.time_second; for (std::size_t component = 0U; component < 3U; ++component) { const auto full = 6U * node + 3U + component; message << ", n" << component << '=' << null[component] << ", old_a" << component << '=' << frame.acceleration[full] << ", reconstructed_a" << component << '=' << reconstructed.acceleration[full] << ", delta_E" << component << '=' << reconstructed.residual[full] - frame.residual[full] << ", M_delta_a" << component << '=' << mass_delta[full] << ", translation_sum" << component << '=' << translation_sum[component] << ", nH_translation_sum" << component << '=' << projected_translation_sum[component]; } message << ", G="; for (std::size_t column = 0U; column < graph.Columns(); ++column) if (graph(row, column) != 0.0) message << '(' << column << ':' << graph(row, column) << ')'; message << ", gradient_dof_value_olda_nH="; for (const auto& entry : primitive.gradient) message << '(' << entry.dof << ':' << entry.value << ':' << frame.acceleration[entry.dof] << ':' << projected_gradient[entry.dof] << ')'; } return Result::Failure( Failure(domain, "dynamic-initial-consistency", message.str())); } Status status = CompleteConstraintEvidence(domain, dofs, initial.Value(), length, null_nodes, positive_solver, tangent_solver, &frame); if (!status.IsOk()) return Result::Failure( WithProcedureContext(domain, status, "initial", 0U, 0.0)); status = CompleteEquilibrium(domain, dofs, initial.Value(), &frame); if (!status.IsOk()) return Result::Failure( WithProcedureContext(domain, status, "initial", 0U, 0.0)); auto graph_regularity = CheckGraphRegularity(initial.Value(), chart.Value(), root0.Value().operators, tangent_solver); if (!graph_regularity.HasValue()) return Result::Failure(WithProcedureContext( domain, graph_regularity.GetStatus(), "initial", 0U, 0.0)); if (!(frame.free_residual_metrics[0U] <= 1.0e-10 && frame.free_residual_metrics[1U] <= 1.0e-10)) return Result::Failure( Failure(domain, "dynamic-initial-consistency", "The initialized frame-zero physical residual is not acceptable.")); const double initial_energy = frame.kinetic_energy + frame.physical_strain_energy + frame.drilling_energy; status = state.CommitAcceptedFrame(frame); if (!status.IsOk()) return Result::Failure(status); DynamicNewtonIterationLogRecord record{}; record.stage = "accepted"; record.outcome = "initial"; record.correction_pass = true; ResidualRecord(dofs, frame, &record); status = log.Write(record); if (status.IsOk()) status = log.Flush(); if (!status.IsOk()) return Result::Failure(status); struct ReducedStepCandidate { DynamicAcceptedFrameState frame; ReducedHistory history; }; for (std::uint32_t increment = 1U; increment <= domain.Dynamic()->increment_count; ++increment) { const auto& old = state.CommittedState(); const auto report_failure = [&](const Status& failure, const char* stage) { const Status contextual = WithProcedureContext( domain, failure, stage, increment, record.time); record.stage = stage; record.outcome = "failed"; record.failure_code = contextual.Diagnostics().empty() ? "dynamic-solver-failure" : contextual.Diagnostics().front().code; const Status written = log.Write(record); return written.IsOk() ? contextual : written; }; status = RunReducedHhtStep( integrator, history, h, parameters, [&](const ShellHhtIntegrator& step_integrator, ReducedShellPredictor predictor) -> Result { Vector pp = std::move(predictor.pp); Vector vpp = std::move(predictor.vpp); DynamicAcceptedFrameState trial_frame = old; trial_frame.is_initial = false; trial_frame.frame_index = increment; trial_frame.increment_index = increment; trial_frame.time = static_cast(increment) * h; trial_frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht; Vector trial_algorithmic{history.p.Size()}; Vector trial_p = pp; Vector coordinate_low = std::move(predictor.coordinate_low); Vector chosen_start_p = pp; Vector increment_trace = std::move(predictor.increment_trace); Vector increment_trace_compensation = std::move(predictor.increment_trace_compensation); Vector increment_trace_absolute_sum = std::move(predictor.increment_trace_absolute_sum); Vector seed = history.z; record = {}; record.increment_index = increment; record.time = trial_frame.time; record.correction_pass = true; bool translation_private_algorithmic_pass = true; bool copy_translation_rates = false; Vector retained_velocity{history.p.Size()}; Vector retained_acceleration{history.p.Size()}; using StepResult = Result>; const auto write_iteration = [&](const DynamicNewtonIterationLogRecord& value) { return log.Write(value); }; ReducedShellHhtStep iteration_step( [&](std::uint32_t iteration) -> StepResult { record.iteration_index = iteration; record.stage = "evaluation"; record.outcome = "candidate"; const auto fail = [&](const Status& failure, const char* stage = "outer") -> StepResult { return StepResult::Failure(report_failure(failure, stage)); }; ReducedShellTrialRates reduced_rates{ Vector{history.p.Size()}, Vector{history.p.Size()}}; step_integrator.EvaluateRates( {chart.Value(), history, trial_algorithmic, vpp, copy_translation_rates, retained_velocity, retained_acceleration, h, parameters}, reduced_rates.velocity, reduced_rates.acceleration); Vector trial_v = std::move(reduced_rates.velocity); Vector trial_a = std::move(reduced_rates.acceleration); const auto evaluate_trial = [&](const Vector& physical_p, const Vector& physical_v, const Vector& physical_a, const Vector& root_seed) -> Result { auto root = SolveEmbeddingRoot( chart.Value(), physical_p, root_seed, [&](const Vector& q) { return position_operators(trial_frame, q); }, tangent_solver); if (!root.HasValue()) return Result::Failure(WithProcedureContext( domain, root.GetStatus(), "inner-position", increment, trial_frame.time)); auto rates = step_integrator.ReconstructPhysicalRates( chart.Value(), root.Value(), physical_v, physical_a, tangent_solver); if (!rates.HasValue()) return Result::Failure( WithProcedureContext(domain, rates.GetStatus(), "inner-rates", increment, trial_frame.time)); auto candidate = trial_frame; candidate.generalized_coordinates = root.Value().q; candidate.velocity = rates.Value().velocity; candidate.acceleration = rates.Value().acceleration; candidate.algorithmic_acceleration = Vector(dofs.FullDofCount()); for (std::size_t i = 0U; i < candidate.algorithmic_acceleration.Size(); ++i) candidate.algorithmic_acceleration[i] = (1.0 + parameters.alpha) * candidate.acceleration[i] - parameters.alpha * old.acceleration[i]; candidate.iteration_count = iteration; auto assembly = EvaluateAssembly(domain, dofs, shells.Value(), candidate, loads.Value()[1U], h, parameters, parallel_for, true); if (!assembly.HasValue()) return Result::Failure( WithProcedureContext(domain, assembly.GetStatus(), "outer", increment, trial_frame.time)); assembly.Value().frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht; return Result::Success( {std::move(assembly.Value()), std::move(root.Value()), physical_p, physical_v, physical_a, trial_algorithmic, std::move(rates.Value().z_velocity), std::move(rates.Value().z_acceleration)}); }; position_geometry_failure = false; auto trial = evaluate_trial(trial_p, trial_v, trial_a, seed); if (!trial.HasValue() && iteration == 0U && position_geometry_failure) { for (std::size_t i = 0U; i < trial_p.Size(); ++i) { trial_algorithmic[i] = (history.p[i] - pp[i]) / (parameters.beta * h * h); trial_p[i] = history.p[i]; chosen_start_p[i] = history.p[i]; coordinate_low[i] = 0.0; increment_trace[i] = 0.0; increment_trace_compensation[i] = 0.0; increment_trace_absolute_sum[i] = 0.0; } step_integrator.EvaluateRates( {chart.Value(), history, trial_algorithmic, vpp, false, retained_velocity, retained_acceleration, h, parameters}, trial_v, trial_a); trial = evaluate_trial(trial_p, trial_v, trial_a, seed); } if (!trial.HasValue()) return fail(trial.GetStatus()); if (iteration == 0U && !ExactlyEqual(trial_p, history.p)) { Vector old_algorithmic{trial_p.Size()}; Vector old_velocity{trial_p.Size()}; Vector old_acceleration{trial_p.Size()}; for (std::size_t i = 0U; i < trial_p.Size(); ++i) { old_algorithmic[i] = (history.p[i] - pp[i]) / (parameters.beta * h * h); } step_integrator.EvaluateRates( {chart.Value(), history, old_algorithmic, vpp, false, retained_velocity, retained_acceleration, h, parameters}, old_velocity, old_acceleration); auto old_trial = evaluate_trial(history.p, old_velocity, old_acceleration, seed); if (!old_trial.HasValue()) return fail(old_trial.GetStatus()); const auto reduced_residual = [&](const Trial& value) { double norm = 0.0; for (const auto full : dofs.FreeDofs()) { const double scale = full % 6U < 3U ? length : 1.0; norm = (std::max)(norm, scale * std::abs( value.assembly.frame.residual[full])); } if (!std::isfinite(norm)) return std::numeric_limits::infinity(); return norm; }; const double old_residual = reduced_residual(old_trial.Value()); const double predictor_residual = reduced_residual(trial.Value()); if (!std::isfinite(old_residual) || !std::isfinite(predictor_residual)) return fail( Failure(domain, "dynamic-invalid-state", "The scaled initial reduced residual is nonfinite.")); if (old_residual < predictor_residual) { trial = std::move(old_trial); trial_p = history.p; chosen_start_p = history.p; coordinate_low = Vector(history.p.Size()); increment_trace = Vector(history.p.Size()); increment_trace_compensation = Vector(history.p.Size()); increment_trace_absolute_sum = Vector(history.p.Size()); trial_v = old_velocity; trial_a = old_acceleration; trial_algorithmic = old_algorithmic; } } auto candidate = trial.Value().assembly.frame; auto operators = EvaluateConstraintOperators( domain, dofs, shells.Value(), trial.Value().assembly, chart.Value(), zero, zero); if (!operators.HasValue()) return fail(operators.GetStatus()); ResidualRecord(dofs, candidate, &record); if (record.residual_pass && record.correction_pass) { status = CompleteConstraintEvidence( domain, dofs, trial.Value().assembly, length, null_nodes, positive_solver, tangent_solver, &candidate); if (!status.IsOk()) return fail(status, "accepted"); status = CompleteEquilibrium(domain, dofs, trial.Value().assembly, &candidate); if (!status.IsOk()) return fail(status, "accepted"); graph_regularity = CheckGraphRegularity(trial.Value().assembly, chart.Value(), operators.Value(), tangent_solver); if (!graph_regularity.HasValue()) return fail(graph_regularity.GetStatus(), "accepted"); status = CheckReducedUpdates( domain, old, candidate, pp, vpp, trial_p, trial_v, trial_a, trial.Value().root.z, trial.Value().algebraic_velocity, trial.Value().algebraic_acceleration, trial_algorithmic, history.p, history.velocity, history.acceleration, history.algorithmic_acceleration, increment_trace, increment_trace_compensation, increment_trace_absolute_sum, chart.Value(), chosen_start_p, coordinate_low, parameters, h); if (!status.IsOk()) return fail(status, "accepted"); candidate.external_work = 0.0; for (std::size_t i = 0U; i < dofs.FullDofCount(); ++i) candidate.external_work += candidate.external_force[i] * candidate.generalized_coordinates[i]; candidate.energy_defect = candidate.kinetic_energy + candidate.physical_strain_energy + candidate.drilling_energy - initial_energy - candidate.external_work; return StepResult::Success(ReducedStepCandidate{ std::move(candidate), {trial_p, trial_v, trial_a, trial_algorithmic, trial.Value().root.z}}); } if (!CanApplyOuterCorrection(iteration)) { double full_hidden = 0.0; double analytic_hidden = 0.0; double inertia_hidden = 0.0; double consistency = 0.0; for (std::size_t row = 0U; row < chart.Value().null_nodes.size(); ++row) { const auto node = chart.Value().null_nodes[row]; const auto& null = trial.Value().assembly.rotations[node].drill_null; double full = 0.0; double inertia = 0.0; for (std::size_t component = 0U; component < 3U; ++component) { const auto full_dof = 6U * node + 3U + component; full += null[component] * candidate.residual[full_dof]; inertia += null[component] * candidate.inertia_force[full_dof]; } const double analytic = operators.Value().rows[row].value; full_hidden = (std::max)(full_hidden, std::abs(full)); analytic_hidden = (std::max)(analytic_hidden, std::abs(analytic)); inertia_hidden = (std::max)(inertia_hidden, std::abs(inertia)); consistency = (std::max)(consistency, std::abs(full - analytic - inertia)); } std::ostringstream message; const auto maximum = [](const Vector& values) { double result = 0.0; for (std::size_t i = 0U; i < values.Size(); ++i) result = (std::max)(result, std::abs(values[i])); return result; }; Vector position_correction{trial_algorithmic.Size()}; Vector velocity_correction{trial_algorithmic.Size()}; for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) { position_correction[i] = parameters.beta * h * h * trial_algorithmic[i]; velocity_correction[i] = parameters.gamma * h * trial_algorithmic[i]; } message << "HHT exhausted 100 corrections without satisfying every " "endpoint check; " << "stage=outer, outer_iteration=" << iteration << ", graph_iteration=" << trial.Value().root.graph_iteration_count << ", graph_residual=" << trial.Value().root.graph_residual_infinity << ", graph_relative=" << trial.Value().root.graph_residual_relative << ", graph_correction=" << trial.Value().root.graph_correction_infinity << ", max_NtE=" << full_hidden << ", max_analytic_psi=" << analytic_hidden << ", max_NtInertia=" << inertia_hidden << ", max_NtE_minus_psi_minus_NtInertia=" << consistency << ", max_pp=" << maximum(pp) << ", max_beta_h2_trial_Ar=" << maximum(position_correction) << ", max_trial_p=" << maximum(trial_p) << ", max_vp_predictor=" << maximum(vpp) << ", max_gamma_h_trial_Ar=" << maximum(velocity_correction) << ", max_trial_v=" << maximum(trial_v) << ", max_physical_v=" << maximum(trial.Value().velocity) << "."; return fail( Failure(domain, "dynamic-nonconvergence", message.str())); } status = write_iteration(record); if (!status.IsOk()) return StepResult::Failure(status); const double velocity_rate = parameters.gamma / (parameters.beta * h); const double acceleration_rate = 1.0 / ((1.0 + parameters.alpha) * parameters.beta * h * h); auto system = AssembleAugmentedReducedSystem( trial.Value().assembly, chart.Value(), operators.Value(), velocity_rate, acceleration_rate, length, h); if (!system.HasValue()) return fail(system.GetStatus()); const bool angular_only = ShouldRefineAngular( chart.Value(), candidate.free_residual_metrics, {record.coordinate_correction[0U], record.velocity_correction[0U], record.acceleration_correction[0U], record.algorithmic_correction[0U]}, translation_private_algorithmic_pass); auto selected = SelectAugmentedSolve(system.Value(), chart.Value(), angular_only); if (!selected.HasValue()) return fail(selected.GetStatus()); const auto linear_failure = [&](const Status& failure) { return fail(failure, angular_only ? "angular-newton-linear-solve" : "full-newton-linear-solve"); }; Vector selected_solution{selected.Value().rhs.Size()}; if (selected_solution.Size() > 0U) { status = tangent_solver.FactorizeFresh(selected.Value().tangent); if (!status.IsOk()) return linear_failure(status); status = tangent_solver.SolveCorrection(selected.Value().rhs, selected_solution); if (!status.IsOk()) return linear_failure(status); status = CheckLinearSolve(selected.Value().tangent, selected.Value().rhs, selected_solution); if (!status.IsOk()) return linear_failure(status); } auto solution = EmbedAugmentedSolution( selected.Value(), selected_solution, system.Value().rhs.Size()); if (!solution.HasValue()) return fail(solution.GetStatus()); auto correction = MapAugmentedCorrection( chart.Value(), system.Value(), solution.Value(), velocity_rate, acceleration_rate); if (!correction.HasValue()) return fail(correction.GetStatus()); Vector full_algorithmic{dofs.FullDofCount()}; Vector reduced_algorithmic{correction.Value().p.Size()}; Vector reduced_physical{correction.Value().p.Size()}; for (std::size_t i = 0U; i < full_algorithmic.Size(); ++i) full_algorithmic[i] = (1.0 + parameters.alpha) * correction.Value().acceleration[i]; for (std::size_t i = 0U; i < reduced_algorithmic.Size(); ++i) { reduced_algorithmic[i] = correction.Value().p[i] / (parameters.beta * h * h); reduced_physical[i] = acceleration_rate * correction.Value().p[i]; } CorrectionRecord(correction.Value().coordinate, correction.Value().velocity, correction.Value().acceleration, full_algorithmic, reduced_algorithmic, reduced_physical, chart.Value(), dofs, length, h, parameters.alpha, &record); translation_private_algorithmic_pass = true; for (std::size_t i = 0U; i < chart.Value().p_dofs.size(); ++i) if (PhysicalCoordinateChannel(chart.Value(), i) == 0U) translation_private_algorithmic_pass = translation_private_algorithmic_pass && PrivateCorrectionEqual(chart.Value(), i, full_algorithmic, reduced_algorithmic, reduced_physical, parameters.alpha); copy_translation_rates = angular_only; if (angular_only) { retained_velocity = trial_v; retained_acceleration = trial_a; } for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) { if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue; trial_algorithmic[i] += reduced_algorithmic[i]; } for (std::size_t i = 0U; i < trial_p.Size(); ++i) { if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue; AdvanceCoordinate(correction.Value().p[i], &trial_p[i], &coordinate_low[i]); } for (std::size_t i = 0U; i < increment_trace.Size(); ++i) { if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue; AddIncrementTraceTerm(correction.Value().p[i], &increment_trace[i], &increment_trace_compensation[i]); increment_trace_absolute_sum[i] += std::abs(correction.Value().p[i]); } seed = trial.Value().root.z; for (std::size_t i = 0U; i < seed.Size(); ++i) seed[i] += correction.Value().z[i]; return StepResult::Success(std::nullopt); }, kMaximumOuterCorrections); const Status solved = iteration_step.Solve(algorithm); if (!solved.IsOk()) return Result::Failure(solved); if (!iteration_step.HasCandidate()) return Result::Failure( Failure(domain, "dynamic-nonconvergence", "Dynamic increment was not accepted.")); return Result::Success( iteration_step.TakeCandidate()); }, [&](ReducedStepCandidate&& accepted_candidate) -> Status { status = state.CommitAcceptedFrame(accepted_candidate.frame); if (!status.IsOk()) return report_failure(status, "accepted"); history = std::move(accepted_candidate.history); record.stage = "accepted"; record.outcome = "accepted"; status = log.Write(record); if (status.IsOk()) status = log.Flush(); return status; }); if (!status.IsOk()) return Result::Failure(status); } return Result::Success( {std::move(state), domain.Warnings()}); } } // namespace fesa