13#include <Eigen/SparseCore>
14#include <gch/small_vector.hpp>
16#include "sleipnir/optimization/solver/exit_status.hpp"
17#include "sleipnir/optimization/solver/interior_point_matrix_callbacks.hpp"
18#include "sleipnir/optimization/solver/iteration_info.hpp"
19#include "sleipnir/optimization/solver/options.hpp"
20#include "sleipnir/optimization/solver/util/error_estimate.hpp"
21#include "sleipnir/optimization/solver/util/filter.hpp"
22#include "sleipnir/optimization/solver/util/fraction_to_the_boundary_rule.hpp"
23#include "sleipnir/optimization/solver/util/is_locally_infeasible.hpp"
24#include "sleipnir/optimization/solver/util/kkt_error.hpp"
25#include "sleipnir/optimization/solver/util/regularized_ldlt.hpp"
26#include "sleipnir/util/assert.hpp"
27#include "sleipnir/util/print_diagnostics.hpp"
28#include "sleipnir/util/scope_exit.hpp"
29#include "sleipnir/util/scoped_profiler.hpp"
30#include "sleipnir/util/solve_profiler.hpp"
31#include "sleipnir/util/symbol_exports.hpp"
62template <
typename Scalar>
63ExitStatus interior_point(
64 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
65 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
67 const Options& options,
68#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
69 const Eigen::ArrayX<bool>& bound_constraint_mask,
71 Eigen::Vector<Scalar, Eigen::Dynamic>& x) {
72 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
73 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
74 using SparseVector = Eigen::SparseVector<Scalar>;
90 const auto solve_start_time = std::chrono::steady_clock::now();
92 gch::small_vector<SolveProfiler> solve_profilers;
93 solve_profilers.emplace_back(
"solver");
94 solve_profilers.emplace_back(
" ↳ setup");
95 solve_profilers.emplace_back(
" ↳ iteration");
96 solve_profilers.emplace_back(
" ↳ feasibility ✓");
97 solve_profilers.emplace_back(
" ↳ iter callbacks");
98 solve_profilers.emplace_back(
" ↳ KKT matrix build");
99 solve_profilers.emplace_back(
" ↳ KKT matrix decomp");
100 solve_profilers.emplace_back(
" ↳ KKT system solve");
101 solve_profilers.emplace_back(
" ↳ line search");
102 solve_profilers.emplace_back(
" ↳ SOC");
103 solve_profilers.emplace_back(
" ↳ next iter prep");
104 solve_profilers.emplace_back(
" ↳ f(x)");
105 solve_profilers.emplace_back(
" ↳ ∇f(x)");
106 solve_profilers.emplace_back(
" ↳ ∇²ₓₓL");
107 solve_profilers.emplace_back(
" ↳ cₑ(x)");
108 solve_profilers.emplace_back(
" ↳ ∂cₑ/∂x");
109 solve_profilers.emplace_back(
" ↳ cᵢ(x)");
110 solve_profilers.emplace_back(
" ↳ ∂cᵢ/∂x");
112 auto& solver_prof = solve_profilers[0];
113 auto& setup_prof = solve_profilers[1];
114 auto& inner_iter_prof = solve_profilers[2];
115 auto& feasibility_check_prof = solve_profilers[3];
116 auto& iter_callbacks_prof = solve_profilers[4];
117 auto& kkt_matrix_build_prof = solve_profilers[5];
118 auto& kkt_matrix_decomp_prof = solve_profilers[6];
119 auto& kkt_system_solve_prof = solve_profilers[7];
120 auto& line_search_prof = solve_profilers[8];
121 auto& soc_prof = solve_profilers[9];
122 auto& next_iter_prep_prof = solve_profilers[10];
125#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
126 auto& f_prof = solve_profilers[11];
127 auto& g_prof = solve_profilers[12];
128 auto& H_prof = solve_profilers[13];
129 auto& c_e_prof = solve_profilers[14];
130 auto& A_e_prof = solve_profilers[15];
131 auto& c_i_prof = solve_profilers[16];
132 auto& A_i_prof = solve_profilers[17];
134 InteriorPointMatrixCallbacks<Scalar> matrices{
135 [&](
const DenseVector& x) -> Scalar {
136 ScopedProfiler prof{f_prof};
137 return matrix_callbacks.f(x);
139 [&](
const DenseVector& x) -> SparseVector {
140 ScopedProfiler prof{g_prof};
141 return matrix_callbacks.g(x);
143 [&](
const DenseVector& x,
const DenseVector& y,
144 const DenseVector& z) -> SparseMatrix {
145 ScopedProfiler prof{H_prof};
146 return matrix_callbacks.H(x, y, z);
148 [&](
const DenseVector& x) -> DenseVector {
149 ScopedProfiler prof{c_e_prof};
150 return matrix_callbacks.c_e(x);
152 [&](
const DenseVector& x) -> SparseMatrix {
153 ScopedProfiler prof{A_e_prof};
154 return matrix_callbacks.A_e(x);
156 [&](
const DenseVector& x) -> DenseVector {
157 ScopedProfiler prof{c_i_prof};
158 return matrix_callbacks.c_i(x);
160 [&](
const DenseVector& x) -> SparseMatrix {
161 ScopedProfiler prof{A_i_prof};
162 return matrix_callbacks.A_i(x);
165 const auto& matrices = matrix_callbacks;
171 Scalar f = matrices.f(x);
172 DenseVector c_e = matrices.c_e(x);
173 DenseVector c_i = matrices.c_i(x);
175 int num_decision_variables = x.rows();
176 int num_equality_constraints = c_e.rows();
177 int num_inequality_constraints = c_i.rows();
180 if (num_equality_constraints > num_decision_variables) {
181 if (options.diagnostics) {
182 print_too_few_dofs_error(c_e);
185 return ExitStatus::TOO_FEW_DOFS;
188 SparseVector g = matrices.g(x);
189 SparseMatrix A_e = matrices.A_e(x);
190 SparseMatrix A_i = matrices.A_i(x);
192 DenseVector s = DenseVector::Ones(num_inequality_constraints);
193#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
195 s = bound_constraint_mask.select(c_i, s);
197 DenseVector y = DenseVector::Zero(num_equality_constraints);
198 DenseVector z = DenseVector::Ones(num_inequality_constraints);
200 SparseMatrix H = matrices.H(x, y, z);
203 slp_assert(g.rows() == num_decision_variables);
204 slp_assert(A_e.rows() == num_equality_constraints);
205 slp_assert(A_e.cols() == num_decision_variables);
206 slp_assert(A_i.rows() == num_inequality_constraints);
207 slp_assert(A_i.cols() == num_decision_variables);
208 slp_assert(H.rows() == num_decision_variables);
209 slp_assert(H.cols() == num_decision_variables);
212 if (!isfinite(f) || !c_e.allFinite() || !c_i.allFinite()) {
213 return ExitStatus::NONFINITE_INITIAL_COST_OR_CONSTRAINTS;
219 const Scalar μ_min = Scalar(options.tolerance) / Scalar(10);
225 constexpr Scalar τ_min(0.99);
230 Filter<Scalar> filter;
234 auto update_barrier_parameter_and_reset_filter = [&] {
236 constexpr Scalar κ_μ(0.2);
240 constexpr Scalar θ_μ(1.5);
248 μ = std::max(μ_min, std::min(κ_μ * μ, pow(μ, θ_μ)));
255 τ = std::max(τ_min, Scalar(1) - μ);
262 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
264 RegularizedLDLT<Scalar> solver{num_decision_variables,
265 num_equality_constraints};
268 constexpr Scalar α_reduction_factor(0.5);
269 constexpr Scalar α_min(1e-7);
271 int full_step_rejected_counter = 0;
274 Scalar E_0 = std::numeric_limits<Scalar>::infinity();
279 scope_exit exit{[&] {
280 if (options.diagnostics) {
282 if (iterations > 0) {
283 print_bottom_iteration_diagnostics();
285 print_solver_diagnostics(solve_profilers);
289 while (E_0 > Scalar(options.tolerance)) {
290 ScopedProfiler inner_iter_profiler{inner_iter_prof};
291 ScopedProfiler feasibility_check_profiler{feasibility_check_prof};
294 if (is_equality_locally_infeasible(A_e, c_e)) {
295 if (options.diagnostics) {
296 print_c_e_local_infeasibility_error(c_e);
299 return ExitStatus::LOCALLY_INFEASIBLE;
303 if (is_inequality_locally_infeasible(A_i, c_i)) {
304 if (options.diagnostics) {
305 print_c_i_local_infeasibility_error(c_i);
308 return ExitStatus::LOCALLY_INFEASIBLE;
312 if (x.template lpNorm<Eigen::Infinity>() > Scalar(1e10) || !x.allFinite() ||
313 s.template lpNorm<Eigen::Infinity>() > Scalar(1e10) || !s.allFinite()) {
314 return ExitStatus::DIVERGING_ITERATES;
317 feasibility_check_profiler.stop();
318 ScopedProfiler iter_callbacks_profiler{iter_callbacks_prof};
321 for (
const auto& callback : iteration_callbacks) {
322 if (callback({iterations, x, g, H, A_e, A_i})) {
323 return ExitStatus::CALLBACK_REQUESTED_STOP;
327 iter_callbacks_profiler.stop();
328 ScopedProfiler kkt_matrix_build_profiler{kkt_matrix_build_prof};
333 const SparseMatrix Σ{s.cwiseInverse().asDiagonal() * z.asDiagonal()};
339 const SparseMatrix top_left =
340 H + (A_i.transpose() * Σ * A_i).
template triangularView<Eigen::Lower>();
342 triplets.reserve(top_left.nonZeros() + A_e.nonZeros());
343 for (
int col = 0; col < H.cols(); ++col) {
345 for (
typename SparseMatrix::InnerIterator it{top_left, col}; it; ++it) {
346 triplets.emplace_back(it.row(), it.col(), it.value());
349 for (
typename SparseMatrix::InnerIterator it{A_e, col}; it; ++it) {
350 triplets.emplace_back(H.rows() + it.row(), it.col(), it.value());
353 SparseMatrix lhs(num_decision_variables + num_equality_constraints,
354 num_decision_variables + num_equality_constraints);
355 lhs.setFromSortedTriplets(triplets.begin(), triplets.end());
359 DenseVector rhs{x.rows() + y.rows()};
360 rhs.segment(0, x.rows()) =
361 -g + A_e.transpose() * y +
362 A_i.transpose() * (-Σ * c_i + μ * s.cwiseInverse() + z);
363 rhs.segment(x.rows(), y.rows()) = -c_e;
365 kkt_matrix_build_profiler.stop();
366 ScopedProfiler kkt_matrix_decomp_profiler{kkt_matrix_decomp_prof};
377 if (solver.compute(lhs).info() != Eigen::Success) [[unlikely]] {
378 return ExitStatus::FACTORIZATION_FAILED;
381 kkt_matrix_decomp_profiler.stop();
382 ScopedProfiler kkt_system_solve_profiler{kkt_system_solve_prof};
384 auto compute_step = [&](Step& step) {
387 DenseVector p = solver.solve(rhs);
388 step.p_x = p.segment(0, x.rows());
389 step.p_y = -p.segment(x.rows(), y.rows());
393 step.p_s = c_i - s + A_i * step.p_x;
394 step.p_z = -Σ * c_i + μ * s.cwiseInverse() - Σ * A_i * step.p_x;
398 kkt_system_solve_profiler.stop();
399 ScopedProfiler line_search_profiler{line_search_prof};
402 α_max = fraction_to_the_boundary_rule<Scalar>(s, step.p_s, τ);
407 return ExitStatus::LINE_SEARCH_FAILED;
411 α_z = fraction_to_the_boundary_rule<Scalar>(z, step.p_z, τ);
415 DenseVector trial_x = x + α * step.p_x;
416 DenseVector trial_y = y + α_z * step.p_y;
417 DenseVector trial_z = z + α_z * step.p_z;
419 Scalar trial_f = matrices.f(trial_x);
420 DenseVector trial_c_e = matrices.c_e(trial_x);
421 DenseVector trial_c_i = matrices.c_i(trial_x);
425 if (!isfinite(trial_f) || !trial_c_e.allFinite() ||
426 !trial_c_i.allFinite()) {
428 α *= α_reduction_factor;
431 return ExitStatus::LINE_SEARCH_FAILED;
437 if (options.feasible_ipm && c_i.cwiseGreater(Scalar(0)).all()) {
444 trial_s = s + α * step.p_s;
448 if (filter.try_add(FilterEntry{trial_f, trial_s, trial_c_e, trial_c_i, μ},
454 Scalar prev_constraint_violation =
455 c_e.template lpNorm<1>() + (c_i - s).
template lpNorm<1>();
456 Scalar next_constraint_violation =
457 trial_c_e.template lpNorm<1>() +
458 (trial_c_i - trial_s).
template lpNorm<1>();
465 next_constraint_violation >= prev_constraint_violation) {
467 auto soc_step = step;
470 Scalar α_z_soc = α_z;
471 DenseVector c_e_soc = c_e;
473 bool step_acceptable =
false;
474 for (
int soc_iteration = 0; soc_iteration < 5 && !step_acceptable;
476 ScopedProfiler soc_profiler{soc_prof};
478 scope_exit soc_exit{[&] {
481 if (options.diagnostics) {
482 print_iteration_diagnostics(
484 step_acceptable ? IterationType::ACCEPTED_SOC
485 : IterationType::REJECTED_SOC,
486 soc_profiler.current_duration(),
487 error_estimate<Scalar>(g, A_e, trial_c_e, trial_y), trial_f,
488 trial_c_e.template lpNorm<1>() +
489 (trial_c_i - trial_s).template lpNorm<1>(),
490 trial_s.dot(trial_z), μ, solver.hessian_regularization(),
491 α_soc, Scalar(1), α_reduction_factor, α_z_soc);
501 c_e_soc = α_soc * c_e_soc + trial_c_e;
502 rhs.bottomRows(y.rows()) = -c_e_soc;
505 compute_step(soc_step);
508 α_soc = fraction_to_the_boundary_rule<Scalar>(s, soc_step.p_s, τ);
509 trial_x = x + α_soc * soc_step.p_x;
510 trial_s = s + α_soc * soc_step.p_s;
513 α_z_soc = fraction_to_the_boundary_rule<Scalar>(z, soc_step.p_z, τ);
514 trial_y = y + α_z_soc * soc_step.p_y;
515 trial_z = z + α_z_soc * soc_step.p_z;
517 trial_f = matrices.f(trial_x);
518 trial_c_e = matrices.c_e(trial_x);
519 trial_c_i = matrices.c_i(trial_x);
522 constexpr Scalar κ_soc(0.99);
526 next_constraint_violation =
527 trial_c_e.template lpNorm<1>() +
528 (trial_c_i - trial_s).
template lpNorm<1>();
529 if (next_constraint_violation > κ_soc * prev_constraint_violation) {
535 FilterEntry{trial_f, trial_s, trial_c_e, trial_c_i, μ}, α)) {
539 step_acceptable =
true;
543 if (step_acceptable) {
553 ++full_step_rejected_counter;
560 if (full_step_rejected_counter >= 4 &&
561 filter.max_constraint_violation >
562 filter.back().constraint_violation / Scalar(10)) {
563 filter.max_constraint_violation *= Scalar(0.1);
569 α *= α_reduction_factor;
574 Scalar current_kkt_error =
575 kkt_error<Scalar>(g, A_e, c_e, A_i, c_i, s, y, z, μ);
577 trial_x = x + α_max * step.p_x;
578 trial_s = s + α_max * step.p_s;
580 trial_y = y + α_z * step.p_y;
581 trial_z = z + α_z * step.p_z;
583 trial_c_e = matrices.c_e(trial_x);
584 trial_c_i = matrices.c_i(trial_x);
586 Scalar next_kkt_error = kkt_error<Scalar>(
587 matrices.g(trial_x), matrices.A_e(trial_x), matrices.c_e(trial_x),
588 matrices.A_i(trial_x), trial_c_i, trial_s, trial_y, trial_z, μ);
591 if (next_kkt_error <= Scalar(0.999) * current_kkt_error) {
598 return ExitStatus::LINE_SEARCH_FAILED;
602 line_search_profiler.stop();
606 full_step_rejected_counter = 0;
631 for (
int row = 0; row < z.rows(); ++row) {
632 constexpr Scalar κ_Σ(1e10);
634 std::clamp(z[row], Scalar(1) / κ_Σ * μ / s[row], κ_Σ * μ / s[row]);
639 A_e = matrices.A_e(x);
640 A_i = matrices.A_i(x);
642 H = matrices.H(x, y, z);
644 ScopedProfiler next_iter_prep_profiler{next_iter_prep_prof};
646 c_e = matrices.c_e(x);
647 c_i = matrices.c_i(x);
650 E_0 = error_estimate<Scalar>(g, A_e, c_e, A_i, c_i, s, y, z, Scalar(0));
653 if (E_0 > Scalar(options.tolerance)) {
655 constexpr Scalar κ_ε(10);
659 Scalar E_μ = error_estimate<Scalar>(g, A_e, c_e, A_i, c_i, s, y, z, μ);
660 while (μ > μ_min && E_μ <= κ_ε * μ) {
661 update_barrier_parameter_and_reset_filter();
662 E_μ = error_estimate<Scalar>(g, A_e, c_e, A_i, c_i, s, y, z, μ);
666 next_iter_prep_profiler.stop();
667 inner_iter_profiler.stop();
669 if (options.diagnostics) {
670 print_iteration_diagnostics(
671 iterations, IterationType::NORMAL,
672 inner_iter_profiler.current_duration(), E_0, f,
673 c_e.template lpNorm<1>() + (c_i - s).template lpNorm<1>(), s.dot(z),
674 μ, solver.hessian_regularization(), α, α_max, α_reduction_factor,
681 if (iterations >= options.max_iterations) {
682 return ExitStatus::MAX_ITERATIONS_EXCEEDED;
686 if (std::chrono::steady_clock::now() - solve_start_time > options.timeout) {
687 return ExitStatus::TIMEOUT;
691 return ExitStatus::SUCCESS;
694extern template SLEIPNIR_DLLEXPORT ExitStatus
695interior_point(
const InteriorPointMatrixCallbacks<double>& matrix_callbacks,
696 std::span<std::function<
bool(
const IterationInfo<double>& info)>>
698 const Options& options,
699#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
700 const Eigen::ArrayX<bool>& bound_constraint_mask,
702 Eigen::Vector<double, Eigen::Dynamic>& x);