12#include <Eigen/SparseCore>
13#include <gch/small_vector.hpp>
15#include "sleipnir/optimization/solver/exit_status.hpp"
16#include "sleipnir/optimization/solver/iteration_info.hpp"
17#include "sleipnir/optimization/solver/options.hpp"
18#include "sleipnir/optimization/solver/sqp_matrix_callbacks.hpp"
19#include "sleipnir/optimization/solver/util/error_estimate.hpp"
20#include "sleipnir/optimization/solver/util/filter.hpp"
21#include "sleipnir/optimization/solver/util/is_locally_infeasible.hpp"
22#include "sleipnir/optimization/solver/util/kkt_error.hpp"
23#include "sleipnir/optimization/solver/util/regularized_ldlt.hpp"
24#include "sleipnir/util/assert.hpp"
25#include "sleipnir/util/print_diagnostics.hpp"
26#include "sleipnir/util/scope_exit.hpp"
27#include "sleipnir/util/scoped_profiler.hpp"
28#include "sleipnir/util/solve_profiler.hpp"
29#include "sleipnir/util/symbol_exports.hpp"
55template <
typename Scalar>
56ExitStatus sqp(
const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
57 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
59 const Options& options,
60 Eigen::Vector<Scalar, Eigen::Dynamic>& x) {
61 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
62 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
63 using SparseVector = Eigen::SparseVector<Scalar>;
75 const auto solve_start_time = std::chrono::steady_clock::now();
77 gch::small_vector<SolveProfiler> solve_profilers;
78 solve_profilers.emplace_back(
"solver");
79 solve_profilers.emplace_back(
" ↳ setup");
80 solve_profilers.emplace_back(
" ↳ iteration");
81 solve_profilers.emplace_back(
" ↳ feasibility ✓");
82 solve_profilers.emplace_back(
" ↳ iter callbacks");
83 solve_profilers.emplace_back(
" ↳ KKT matrix build");
84 solve_profilers.emplace_back(
" ↳ KKT matrix decomp");
85 solve_profilers.emplace_back(
" ↳ KKT system solve");
86 solve_profilers.emplace_back(
" ↳ line search");
87 solve_profilers.emplace_back(
" ↳ SOC");
88 solve_profilers.emplace_back(
" ↳ next iter prep");
89 solve_profilers.emplace_back(
" ↳ f(x)");
90 solve_profilers.emplace_back(
" ↳ ∇f(x)");
91 solve_profilers.emplace_back(
" ↳ ∇²ₓₓL");
92 solve_profilers.emplace_back(
" ↳ cₑ(x)");
93 solve_profilers.emplace_back(
" ↳ ∂cₑ/∂x");
95 auto& solver_prof = solve_profilers[0];
96 auto& setup_prof = solve_profilers[1];
97 auto& inner_iter_prof = solve_profilers[2];
98 auto& feasibility_check_prof = solve_profilers[3];
99 auto& iter_callbacks_prof = solve_profilers[4];
100 auto& kkt_matrix_build_prof = solve_profilers[5];
101 auto& kkt_matrix_decomp_prof = solve_profilers[6];
102 auto& kkt_system_solve_prof = solve_profilers[7];
103 auto& line_search_prof = solve_profilers[8];
104 auto& soc_prof = solve_profilers[9];
105 auto& next_iter_prep_prof = solve_profilers[10];
108#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
109 auto& f_prof = solve_profilers[11];
110 auto& g_prof = solve_profilers[12];
111 auto& H_prof = solve_profilers[13];
112 auto& c_e_prof = solve_profilers[14];
113 auto& A_e_prof = solve_profilers[15];
115 SQPMatrixCallbacks<Scalar> matrices{
116 [&](
const DenseVector& x) -> Scalar {
117 ScopedProfiler prof{f_prof};
118 return matrix_callbacks.f(x);
120 [&](
const DenseVector& x) -> SparseVector {
121 ScopedProfiler prof{g_prof};
122 return matrix_callbacks.g(x);
124 [&](
const DenseVector& x,
const DenseVector& y) -> SparseMatrix {
125 ScopedProfiler prof{H_prof};
126 return matrix_callbacks.H(x, y);
128 [&](
const DenseVector& x) -> DenseVector {
129 ScopedProfiler prof{c_e_prof};
130 return matrix_callbacks.c_e(x);
132 [&](
const DenseVector& x) -> SparseMatrix {
133 ScopedProfiler prof{A_e_prof};
134 return matrix_callbacks.A_e(x);
137 const auto& matrices = matrix_callbacks;
143 Scalar f = matrices.f(x);
144 DenseVector c_e = matrices.c_e(x);
146 int num_decision_variables = x.rows();
147 int num_equality_constraints = c_e.rows();
150 if (num_equality_constraints > num_decision_variables) {
151 if (options.diagnostics) {
152 print_too_few_dofs_error(c_e);
155 return ExitStatus::TOO_FEW_DOFS;
158 SparseVector g = matrices.g(x);
159 SparseMatrix A_e = matrices.A_e(x);
161 DenseVector y = DenseVector::Zero(num_equality_constraints);
163 SparseMatrix H = matrices.H(x, y);
166 slp_assert(g.rows() == num_decision_variables);
167 slp_assert(A_e.rows() == num_equality_constraints);
168 slp_assert(A_e.cols() == num_decision_variables);
169 slp_assert(H.rows() == num_decision_variables);
170 slp_assert(H.cols() == num_decision_variables);
173 if (!isfinite(f) || !c_e.allFinite()) {
174 return ExitStatus::NONFINITE_INITIAL_COST_OR_CONSTRAINTS;
179 Filter<Scalar> filter;
182 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
184 RegularizedLDLT<Scalar> solver{num_decision_variables,
185 num_equality_constraints};
188 constexpr Scalar α_reduction_factor(0.5);
189 constexpr Scalar α_min(1e-7);
191 int full_step_rejected_counter = 0;
194 Scalar E_0 = std::numeric_limits<Scalar>::infinity();
199 scope_exit exit{[&] {
200 if (options.diagnostics) {
202 if (iterations > 0) {
203 print_bottom_iteration_diagnostics();
205 print_solver_diagnostics(solve_profilers);
209 while (E_0 > Scalar(options.tolerance)) {
210 ScopedProfiler inner_iter_profiler{inner_iter_prof};
211 ScopedProfiler feasibility_check_profiler{feasibility_check_prof};
214 if (is_equality_locally_infeasible(A_e, c_e)) {
215 if (options.diagnostics) {
216 print_c_e_local_infeasibility_error(c_e);
219 return ExitStatus::LOCALLY_INFEASIBLE;
223 if (x.template lpNorm<Eigen::Infinity>() > Scalar(1e10) || !x.allFinite()) {
224 return ExitStatus::DIVERGING_ITERATES;
227 feasibility_check_profiler.stop();
228 ScopedProfiler iter_callbacks_profiler{iter_callbacks_prof};
231 for (
const auto& callback : iteration_callbacks) {
232 if (callback({iterations, x, g, H, A_e, SparseMatrix{}})) {
233 return ExitStatus::CALLBACK_REQUESTED_STOP;
237 iter_callbacks_profiler.stop();
238 ScopedProfiler kkt_matrix_build_profiler{kkt_matrix_build_prof};
245 triplets.reserve(H.nonZeros() + A_e.nonZeros());
246 for (
int col = 0; col < H.cols(); ++col) {
248 for (
typename SparseMatrix::InnerIterator it{H, col}; it; ++it) {
249 triplets.emplace_back(it.row(), it.col(), it.value());
252 for (
typename SparseMatrix::InnerIterator it{A_e, col}; it; ++it) {
253 triplets.emplace_back(H.rows() + it.row(), it.col(), it.value());
256 SparseMatrix lhs(num_decision_variables + num_equality_constraints,
257 num_decision_variables + num_equality_constraints);
258 lhs.setFromSortedTriplets(triplets.begin(), triplets.end());
262 DenseVector rhs{x.rows() + y.rows()};
263 rhs.segment(0, x.rows()) = -g + A_e.transpose() * y;
264 rhs.segment(x.rows(), y.rows()) = -c_e;
266 kkt_matrix_build_profiler.stop();
267 ScopedProfiler kkt_matrix_decomp_profiler{kkt_matrix_decomp_prof};
270 constexpr Scalar α_max(1);
277 if (solver.compute(lhs).info() != Eigen::Success) [[unlikely]] {
278 return ExitStatus::FACTORIZATION_FAILED;
281 kkt_matrix_decomp_profiler.stop();
282 ScopedProfiler kkt_system_solve_profiler{kkt_system_solve_prof};
284 auto compute_step = [&](Step& step) {
287 DenseVector p = solver.solve(rhs);
288 step.p_x = p.segment(0, x.rows());
289 step.p_y = -p.segment(x.rows(), y.rows());
293 kkt_system_solve_profiler.stop();
294 ScopedProfiler line_search_profiler{line_search_prof};
300 DenseVector trial_x = x + α * step.p_x;
301 DenseVector trial_y = y + α * step.p_y;
303 Scalar trial_f = matrices.f(trial_x);
304 DenseVector trial_c_e = matrices.c_e(trial_x);
308 if (!isfinite(trial_f) || !trial_c_e.allFinite()) {
310 α *= α_reduction_factor;
313 return ExitStatus::LINE_SEARCH_FAILED;
319 if (filter.try_add(FilterEntry{trial_f, trial_c_e}, α)) {
324 Scalar prev_constraint_violation = c_e.template lpNorm<1>();
325 Scalar next_constraint_violation = trial_c_e.template lpNorm<1>();
332 next_constraint_violation >= prev_constraint_violation) {
334 auto soc_step = step;
337 DenseVector c_e_soc = c_e;
339 bool step_acceptable =
false;
340 for (
int soc_iteration = 0; soc_iteration < 5 && !step_acceptable;
342 ScopedProfiler soc_profiler{soc_prof};
344 scope_exit soc_exit{[&] {
347 if (options.diagnostics) {
348 print_iteration_diagnostics(
350 step_acceptable ? IterationType::ACCEPTED_SOC
351 : IterationType::REJECTED_SOC,
352 soc_profiler.current_duration(),
353 error_estimate<Scalar>(g, A_e, trial_c_e, trial_y), trial_f,
354 trial_c_e.template lpNorm<1>(), Scalar(0), Scalar(0),
355 solver.hessian_regularization(), α_soc, Scalar(1),
356 α_reduction_factor, Scalar(1));
366 c_e_soc = α_soc * c_e_soc + trial_c_e;
367 rhs.bottomRows(y.rows()) = -c_e_soc;
370 compute_step(soc_step);
372 trial_x = x + α_soc * soc_step.p_x;
373 trial_y = y + α_soc * soc_step.p_y;
375 trial_f = matrices.f(trial_x);
376 trial_c_e = matrices.c_e(trial_x);
379 constexpr Scalar κ_soc(0.99);
383 next_constraint_violation = trial_c_e.template lpNorm<1>();
384 if (next_constraint_violation > κ_soc * prev_constraint_violation) {
389 if (filter.try_add(FilterEntry{trial_f, trial_c_e}, α)) {
392 step_acceptable =
true;
396 if (step_acceptable) {
406 ++full_step_rejected_counter;
413 if (full_step_rejected_counter >= 4 &&
414 filter.max_constraint_violation >
415 filter.back().constraint_violation / Scalar(10)) {
416 filter.max_constraint_violation *= Scalar(0.1);
422 α *= α_reduction_factor;
427 Scalar current_kkt_error = kkt_error<Scalar>(g, A_e, c_e, y);
429 trial_x = x + α_max * step.p_x;
430 trial_y = y + α_max * step.p_y;
432 trial_c_e = matrices.c_e(trial_x);
434 Scalar next_kkt_error = kkt_error<Scalar>(
435 matrices.g(trial_x), matrices.A_e(trial_x), trial_c_e, trial_y);
438 if (next_kkt_error <= Scalar(0.999) * current_kkt_error) {
445 return ExitStatus::LINE_SEARCH_FAILED;
449 line_search_profiler.stop();
453 full_step_rejected_counter = 0;
463 A_e = matrices.A_e(x);
465 H = matrices.H(x, y);
467 ScopedProfiler next_iter_prep_profiler{next_iter_prep_prof};
469 c_e = matrices.c_e(x);
472 E_0 = error_estimate<Scalar>(g, A_e, c_e, y);
474 next_iter_prep_profiler.stop();
475 inner_iter_profiler.stop();
477 if (options.diagnostics) {
478 print_iteration_diagnostics(iterations, IterationType::NORMAL,
479 inner_iter_profiler.current_duration(), E_0,
480 f, c_e.template lpNorm<1>(), Scalar(0),
481 Scalar(0), solver.hessian_regularization(), α,
482 α_max, α_reduction_factor, α);
488 if (iterations >= options.max_iterations) {
489 return ExitStatus::MAX_ITERATIONS_EXCEEDED;
493 if (std::chrono::steady_clock::now() - solve_start_time > options.timeout) {
494 return ExitStatus::TIMEOUT;
498 return ExitStatus::SUCCESS;
501extern template SLEIPNIR_DLLEXPORT ExitStatus
502sqp(
const SQPMatrixCallbacks<double>& matrix_callbacks,
503 std::span<std::function<
bool(
const IterationInfo<double>& info)>>
505 const Options& options, Eigen::Vector<double, Eigen::Dynamic>& x);