11#include <Eigen/SparseCore>
12#include <gch/small_vector.hpp>
14#include "sleipnir/optimization/solver/exit_status.hpp"
15#include "sleipnir/optimization/solver/iteration_info.hpp"
16#include "sleipnir/optimization/solver/options.hpp"
17#include "sleipnir/optimization/solver/sqp_matrix_callbacks.hpp"
18#include "sleipnir/optimization/solver/util/all_finite.hpp"
19#include "sleipnir/optimization/solver/util/append_as_triplets.hpp"
20#include "sleipnir/optimization/solver/util/feasibility_restoration.hpp"
21#include "sleipnir/optimization/solver/util/filter.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/profiler.hpp"
27#include "sleipnir/util/scope_exit.hpp"
28#include "sleipnir/util/symbol_exports.hpp"
54template <
typename Scalar>
55ExitStatus sqp(
const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
56 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
58 const Options& options,
59 Eigen::Vector<Scalar, Eigen::Dynamic>& x) {
60 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
62 DenseVector y = DenseVector::Zero(matrix_callbacks.num_equality_constraints);
64 return sqp(matrix_callbacks, iteration_callbacks, options, x, y);
89template <
typename Scalar>
90ExitStatus sqp(
const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
91 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
93 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
94 Eigen::Vector<Scalar, Eigen::Dynamic>& y) {
95 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
96 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
97 using SparseVector = Eigen::SparseVector<Scalar>;
109 const auto solve_start_time = std::chrono::steady_clock::now();
111 gch::small_vector<SolveProfiler> solve_profilers;
112 solve_profilers.emplace_back(
"solver");
113 solve_profilers.emplace_back(
"↳ setup");
114 solve_profilers.emplace_back(
"↳ iteration");
115 solve_profilers.emplace_back(
" ↳ callbacks");
116 solve_profilers.emplace_back(
" ↳ KKT matrix build");
117 solve_profilers.emplace_back(
" ↳ KKT matrix decomp");
118 solve_profilers.emplace_back(
" ↳ KKT system solve");
119 solve_profilers.emplace_back(
" ↳ line search");
120 solve_profilers.emplace_back(
" ↳ SOC");
121 solve_profilers.emplace_back(
" ↳ feas. restoration");
122 solve_profilers.emplace_back(
" ↳ f(x)");
123 solve_profilers.emplace_back(
" ↳ ∇f(x)");
124 solve_profilers.emplace_back(
" ↳ ∇²ₓₓL");
125 solve_profilers.emplace_back(
" ↳ ∇²ₓₓL_c");
126 solve_profilers.emplace_back(
" ↳ cₑ(x)");
127 solve_profilers.emplace_back(
" ↳ ∂cₑ/∂x");
129 auto& solver_prof = solve_profilers[0];
130 auto& setup_prof = solve_profilers[1];
131 auto& inner_iter_prof = solve_profilers[2];
132 auto& iter_callbacks_prof = solve_profilers[3];
133 auto& kkt_matrix_build_prof = solve_profilers[4];
134 auto& kkt_matrix_decomp_prof = solve_profilers[5];
135 auto& kkt_system_solve_prof = solve_profilers[6];
136 auto& line_search_prof = solve_profilers[7];
137 auto& soc_prof = solve_profilers[8];
138 auto& feasibility_restoration_prof = solve_profilers[9];
141#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
142 auto& f_prof = solve_profilers[10];
143 auto& g_prof = solve_profilers[11];
144 auto& H_prof = solve_profilers[12];
145 auto& H_c_prof = solve_profilers[13];
146 auto& c_e_prof = solve_profilers[14];
147 auto& A_e_prof = solve_profilers[15];
149 SQPMatrixCallbacks<Scalar> matrices{
150 matrix_callbacks.num_decision_variables,
151 matrix_callbacks.num_equality_constraints,
152 [&](
const DenseVector& x) -> Scalar {
153 ScopedProfiler prof{f_prof};
154 return matrix_callbacks.f(x);
156 [&](
const DenseVector& x) -> SparseVector {
157 ScopedProfiler prof{g_prof};
158 return matrix_callbacks.g(x);
160 [&](
const DenseVector& x,
const DenseVector& y) -> SparseMatrix {
161 ScopedProfiler prof{H_prof};
162 return matrix_callbacks.H(x, y);
164 [&](
const DenseVector& x,
const DenseVector& y) -> SparseMatrix {
165 ScopedProfiler prof{H_c_prof};
166 return matrix_callbacks.H_c(x, y);
168 [&](
const DenseVector& x) -> DenseVector {
169 ScopedProfiler prof{c_e_prof};
170 return matrix_callbacks.c_e(x);
172 [&](
const DenseVector& x) -> SparseMatrix {
173 ScopedProfiler prof{A_e_prof};
174 return matrix_callbacks.A_e(x);
176 matrix_callbacks.scaling};
178 const auto& matrices = matrix_callbacks;
184 Scalar f = matrices.f(x);
185 SparseVector g = matrices.g(x);
186 SparseMatrix H = matrices.H(x, y);
187 DenseVector c_e = matrices.c_e(x);
188 SparseMatrix A_e = matrices.A_e(x);
191 slp_assert(g.rows() == matrices.num_decision_variables);
192 slp_assert(H.rows() == matrices.num_decision_variables);
193 slp_assert(H.cols() == matrices.num_decision_variables);
194 slp_assert(c_e.rows() == matrices.num_equality_constraints);
195 slp_assert(A_e.rows() == matrices.num_equality_constraints);
196 slp_assert(A_e.cols() == matrices.num_decision_variables);
202 DenseVector trial_c_e;
205 if (matrices.num_equality_constraints > matrices.num_decision_variables) {
206 if (options.diagnostics) {
207 print_too_few_dofs_error(c_e);
210 return ExitStatus::TOO_FEW_DOFS;
214 if (!isfinite(f) || !all_finite(g) || !all_finite(H) || !c_e.allFinite() ||
216 return ExitStatus::NONFINITE_INITIAL_GUESS;
221 Filter<Scalar> filter{c_e.template lpNorm<1>()};
224 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
227 matrices.num_decision_variables + matrices.num_equality_constraints;
228 RegularizedLDLT<Scalar> solver{
230 H.nonZeros() + A_e.nonZeros() < 0.25 * lhs_rows * lhs_rows,
231 matrices.num_decision_variables, matrices.num_equality_constraints};
234 constexpr Scalar α_reduction_factor(0.5);
235 constexpr Scalar α_min(1e-7);
237 int full_step_rejected_counter = 0;
240 Scalar E_0 = unscaled_kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
241 matrices.scaling, g, A_e, c_e, y);
246 scope_exit exit{[&] {
247 if (options.diagnostics) {
249 if (iterations > 0) {
250 print_bottom_iteration_diagnostics();
252 print_solver_diagnostics(solve_profilers);
256 while (E_0 > Scalar(options.tolerance)) {
257 ScopedProfiler inner_iter_profiler{inner_iter_prof};
260 if (x.template lpNorm<Eigen::Infinity>() > Scalar(1e10) || !x.allFinite()) {
261 return ExitStatus::DIVERGING_ITERATES;
264 ScopedProfiler iter_callbacks_profiler{iter_callbacks_prof};
267 for (
const auto& callback : iteration_callbacks) {
268 if (callback({iterations, x, {}, y, {}, g, H, A_e, {}})) {
269 return ExitStatus::CALLBACK_REQUESTED_STOP;
273 iter_callbacks_profiler.stop();
274 ScopedProfiler kkt_matrix_build_profiler{kkt_matrix_build_prof};
281 triplets.reserve(H.nonZeros() + A_e.nonZeros());
282 append_as_triplets(triplets, 0, 0, {H, A_e});
284 matrices.num_decision_variables + matrices.num_equality_constraints,
285 matrices.num_decision_variables + matrices.num_equality_constraints);
286 lhs.setFromSortedTriplets(triplets.begin(), triplets.end());
290 DenseVector rhs{x.rows() + y.rows()};
291 rhs.segment(0, x.rows()) = -g + A_e.transpose() * y;
292 rhs.segment(x.rows(), y.rows()) = -c_e;
294 kkt_matrix_build_profiler.stop();
295 ScopedProfiler kkt_matrix_decomp_profiler{kkt_matrix_decomp_prof};
298 constexpr Scalar α_max(1);
300 bool call_feasibility_restoration =
false;
306 if (solver.compute(lhs).info() != Eigen::Success) [[unlikely]] {
307 return ExitStatus::FACTORIZATION_FAILED;
310 kkt_matrix_decomp_profiler.stop();
311 ScopedProfiler kkt_system_solve_profiler{kkt_system_solve_prof};
313 auto compute_step = [&](Step& step) {
316 DenseVector p = solver.solve(rhs);
317 step.p_x = p.segment(0, x.rows());
318 step.p_y = -p.segment(x.rows(), y.rows());
322 kkt_system_solve_profiler.stop();
323 ScopedProfiler line_search_profiler{line_search_prof};
327 const FilterEntry<Scalar> current_entry{f, c_e};
328 const Scalar D_ϕ = g.transpose() * step.p_x;
332 trial_x = x + α * step.p_x;
333 trial_y = y + α * step.p_y;
335 trial_f = matrices.f(trial_x);
336 trial_c_e = matrices.c_e(trial_x);
340 if (!isfinite(trial_f) || !trial_c_e.allFinite()) {
342 α *= α_reduction_factor;
345 call_feasibility_restoration =
true;
352 FilterEntry trial_entry{trial_f, trial_c_e};
353 if (filter.try_add(current_entry, trial_entry, D_ϕ, α)) {
358 Scalar prev_constraint_violation = c_e.template lpNorm<1>();
359 Scalar next_constraint_violation = trial_c_e.template lpNorm<1>();
366 next_constraint_violation >= prev_constraint_violation) {
368 auto soc_step = step;
371 DenseVector c_e_soc = c_e;
373 Scalar soc_constraint_violation = next_constraint_violation;
375 bool step_acceptable =
false;
376 for (
int soc_iteration = 0; soc_iteration < 5 && !step_acceptable;
378 ScopedProfiler soc_profiler{soc_prof};
380 scope_exit soc_exit{[&] {
383 if (options.diagnostics && step_acceptable) {
384 print_iteration_diagnostics(
385 iterations, IterationType::SECOND_ORDER_CORRECTION,
386 soc_profiler.current_duration(),
387 kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
388 g, A_e, trial_c_e, trial_y),
389 trial_f, trial_c_e.template lpNorm<1>(), Scalar(0), Scalar(0),
390 solver.hessian_regularization(),
391 solver.constraint_jacobian_regularization(),
392 soc_step.p_x.template lpNorm<Eigen::Infinity>(),
393 soc_step.p_y.template lpNorm<Eigen::Infinity>(), α_soc,
394 Scalar(1), α_reduction_factor, Scalar(1));
404 c_e_soc = α_soc * c_e_soc + trial_c_e;
405 rhs.bottomRows(y.rows()) = -c_e_soc;
408 compute_step(soc_step);
410 trial_x = x + α_soc * soc_step.p_x;
411 trial_y = y + α_soc * soc_step.p_y;
413 trial_f = matrices.f(trial_x);
414 trial_c_e = matrices.c_e(trial_x);
417 FilterEntry trial_entry{trial_f, trial_c_e};
418 if (filter.try_add(current_entry, trial_entry, D_ϕ, α)) {
421 step_acceptable =
true;
426 constexpr Scalar κ_soc(0.99);
430 next_constraint_violation = trial_c_e.template lpNorm<1>();
431 if (next_constraint_violation > κ_soc * soc_constraint_violation) {
435 soc_constraint_violation = next_constraint_violation;
438 if (step_acceptable) {
448 ++full_step_rejected_counter;
455 if (full_step_rejected_counter >= 4 &&
456 filter.max_constraint_violation >
457 current_entry.constraint_violation / Scalar(10) &&
458 filter.last_rejection_due_to_filter()) {
459 filter.max_constraint_violation *= Scalar(0.1);
465 α *= α_reduction_factor;
470 Scalar current_kkt_error =
471 kkt_error<Scalar, KKTErrorType::ONE_NORM>(g, A_e, c_e, y);
473 trial_x = x + α_max * step.p_x;
474 trial_y = y + α_max * step.p_y;
476 trial_f = matrices.f(trial_x);
477 trial_c_e = matrices.c_e(trial_x);
479 Scalar next_kkt_error = kkt_error<Scalar, KKTErrorType::ONE_NORM>(
480 matrices.g(trial_x), matrices.A_e(trial_x), trial_c_e, trial_y);
483 if (next_kkt_error <= Scalar(0.999) * current_kkt_error) {
488 call_feasibility_restoration =
true;
493 line_search_profiler.stop();
495 if (call_feasibility_restoration) {
496 ScopedProfiler feasibility_restoration_profiler{
497 feasibility_restoration_prof};
499 FilterEntry initial_entry{matrices.f(x), c_e};
502 gch::small_vector<std::function<bool(
const IterationInfo<Scalar>& info)>>
504 for (
auto& callback : iteration_callbacks) {
505 callbacks.emplace_back(callback);
507 callbacks.emplace_back([&](
const IterationInfo<Scalar>& info) {
508 DenseVector trial_x =
509 info.x.segment(0, matrices.num_decision_variables);
511 DenseVector trial_c_e = matrices.c_e(trial_x);
513 FilterEntry trial_entry{matrices.f(trial_x), trial_c_e};
514 const Scalar D_ϕ_restoration = g.transpose() * (trial_x - x);
518 return trial_entry.constraint_violation <
519 Scalar(0.9) * initial_entry.constraint_violation &&
520 filter.try_add(initial_entry, trial_entry, D_ϕ_restoration, α);
522 auto status = feasibility_restoration<Scalar>(matrices, callbacks,
523 options, x, y, iterations);
525 if (status != ExitStatus::SUCCESS) {
531 c_e = matrices.c_e(x);
535 full_step_rejected_counter = 0;
547 A_e = matrices.A_e(x);
549 H = matrices.H(x, y);
552 E_0 = unscaled_kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
553 matrices.scaling, g, A_e, c_e, y);
555 inner_iter_profiler.stop();
557 if (options.diagnostics) {
558 print_iteration_diagnostics(iterations, IterationType::NORMAL,
559 inner_iter_profiler.current_duration(), E_0,
560 f, c_e.template lpNorm<1>(), Scalar(0),
561 Scalar(0), solver.hessian_regularization(),
562 solver.constraint_jacobian_regularization(),
563 step.p_x.template lpNorm<Eigen::Infinity>(),
564 step.p_y.template lpNorm<Eigen::Infinity>(),
565 α, α_max, α_reduction_factor, α);
571 if (iterations >= options.max_iterations) {
572 return ExitStatus::MAX_ITERATIONS_EXCEEDED;
576 if (std::chrono::steady_clock::now() - solve_start_time > options.timeout) {
577 return ExitStatus::TIMEOUT;
581 return ExitStatus::SUCCESS;
584extern template SLEIPNIR_DLLEXPORT ExitStatus
585sqp(
const SQPMatrixCallbacks<double>& matrix_callbacks,
586 std::span<std::function<
bool(
const IterationInfo<double>& info)>>
588 const Options& options, Eigen::Vector<double, Eigen::Dynamic>& x);