14#include <Eigen/SparseCore>
16#include "sleipnir/optimization/solver/exit_status.hpp"
17#include "sleipnir/optimization/solver/iteration_info.hpp"
18#include "sleipnir/optimization/solver/newton_matrix_callbacks.hpp"
19#include "sleipnir/optimization/solver/options.hpp"
20#include "sleipnir/optimization/solver/util/all_finite.hpp"
21#include "sleipnir/optimization/solver/util/filter.hpp"
22#include "sleipnir/optimization/solver/util/kkt_error.hpp"
23#include "sleipnir/optimization/solver/util/kkt_solver.hpp"
24#include "sleipnir/optimization/solver/util/time_trace.hpp"
25#include "sleipnir/util/assert.hpp"
26#include "sleipnir/util/print_diagnostics.hpp"
27#include "sleipnir/util/symbol_exports.hpp"
28#include "sleipnir/util/time_tracer.hpp"
52template <
typename Scalar>
54 const NewtonMatrixCallbacks<Scalar>& matrix_callbacks,
55 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
57 const Options<Scalar>& options, Eigen::VectorX<Scalar>& x) {
58 using DenseVector = Eigen::VectorX<Scalar>;
59 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
60 using SparseVector = Eigen::SparseVector<Scalar>;
64 const auto solve_start_time = std::chrono::steady_clock::now();
66 using enum SolveTimeTracerID;
71 SolveDiagnostics<newton_time_trace> diagnostics{options.diagnostics,
75#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
76 std::optional<NewtonMatrixCallbacks<Scalar>> traced_matrices;
77 if (options.diagnostics) {
78 traced_matrices.emplace(matrix_callbacks.num_decision_variables,
79 traced(diagnostics[COST], matrix_callbacks.f),
80 traced(diagnostics[GRADIENT], matrix_callbacks.g),
81 traced(diagnostics[HESSIAN], matrix_callbacks.H),
82 matrix_callbacks.scaling);
84 const auto& matrices = traced_matrices ? *traced_matrices : matrix_callbacks;
86 const auto& matrices = matrix_callbacks;
89 ScopedTimeTracer setup_tracer{diagnostics[SETUP]};
91 Scalar f = matrices.f(x);
92 SparseVector g = matrices.g(x);
93 SparseMatrix H = matrices.H(x);
96 slp_assert(g.rows() == matrices.num_decision_variables);
97 slp_assert(H.rows() == matrices.num_decision_variables);
98 slp_assert(H.cols() == matrices.num_decision_variables);
105 if (!isfinite(f) || !all_finite(g) || !all_finite(H)) {
106 return ExitStatus::NONFINITE_INITIAL_GUESS;
109 Filter<Scalar> filter;
111 KKTSolver<Scalar> solver{
113 H.nonZeros() < 0.25 * H.size(), matrices.num_decision_variables, 0};
116 constexpr Scalar α_reduction_factor(0.5);
121 Scalar fallback_kkt_error = std::numeric_limits<Scalar>::infinity();
124 Scalar E_0 = unscaled_kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
125 matrices.scaling, g);
131 IterateDiagnosticsTable table{options.diagnostics, E_0, f,
132 Scalar(0), Scalar(0), Scalar(0)};
134 while (E_0 > options.tolerance) {
135 ScopedTimeTracer inner_iter_tracer{diagnostics[ITERATION]};
138 if (x.template lpNorm<Eigen::Infinity>() > Scalar(1e10) || !x.allFinite()) {
139 return ExitStatus::DIVERGING_ITERATES;
142 PhaseTimeTracer phase_tracer{diagnostics[CALLBACKS]};
145 for (
const auto& callback : iteration_callbacks) {
146 if (callback({iterations, x, {}, {}, {}, g, H, {}, {}})) {
147 return ExitStatus::CALLBACK_REQUESTED_STOP;
151 phase_tracer.next(diagnostics[KKT_MATRIX_DECOMP]);
158 phase_tracer.next(diagnostics[KKT_SYSTEM_SOLVE]);
160 DenseVector p_x = solver.solve(-g);
162 phase_tracer.next(diagnostics[LINE_SEARCH]);
164 constexpr Scalar α_max(1);
166 const FilterEntry<Scalar> current_entry{f};
167 const Scalar D_ϕ = g.transpose() * p_x;
169 const Scalar α_min = filter.min_step_size(current_entry, D_ϕ);
187 const Scalar α_tiny = Scalar(10) * std::numeric_limits<Scalar>::epsilon() /
188 (p_x.array().abs() / (Scalar(1) + x.array().abs()))
190 .template lpNorm<Eigen::Infinity>();
194 trial_x = x + α * p_x;
196 trial_f = matrices.f(trial_x);
199 if (!isfinite(trial_f)) {
201 α *= α_reduction_factor;
203 if (α < α_min || α < α_tiny) {
204 return ExitStatus::LINE_SEARCH_FAILED;
210 if (filter.try_add(current_entry, FilterEntry{trial_f}, D_ϕ, α)) {
216 α *= α_reduction_factor;
220 if (α < α_min || α < α_tiny) {
221 Scalar current_kkt_error = kkt_error<Scalar, KKTErrorType::ONE_NORM>(g);
223 trial_x = x + α_max * p_x;
225 trial_f = matrices.f(trial_x);
227 Scalar next_kkt_error =
228 kkt_error<Scalar, KKTErrorType::ONE_NORM>(matrices.g(trial_x));
232 if (next_kkt_error <=
233 Scalar(0.999) * std::min(current_kkt_error, fallback_kkt_error)) {
235 fallback_kkt_error = next_kkt_error;
240 return ExitStatus::LINE_SEARCH_FAILED;
256 E_0 = unscaled_kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
257 matrices.scaling, g);
259 inner_iter_tracer.stop();
261 if (options.diagnostics) {
262 print_iterate_diagnostics(
263 iterations, IterationType::NORMAL,
264 inner_iter_tracer.current_duration(), E_0, f, Scalar(0), Scalar(0),
265 Scalar(0), solver.hessian_regularization(),
266 solver.constraint_jacobian_regularization(),
267 p_x.template lpNorm<Eigen::Infinity>(), Scalar(1), α, α_max,
268 α_reduction_factor, Scalar(1));
274 if (iterations >= options.max_iterations) {
275 return ExitStatus::MAX_ITERATIONS_EXCEEDED;
279 if (std::chrono::steady_clock::now() - solve_start_time > options.timeout) {
280 return ExitStatus::TIMEOUT;
284 if (!isfinite(E_0)) {
285 return ExitStatus::DIVERGING_ITERATES;
287 return ExitStatus::SUCCESS;
291extern template SLEIPNIR_DLLEXPORT ExitStatus
292newton(
const NewtonMatrixCallbacks<double>& matrix_callbacks,
293 std::span<std::function<
bool(
const IterationInfo<double>& info)>>
295 const Options<double>& options, Eigen::VectorX<double>& x);