12#include <Eigen/SparseCore>
13#include <gch/small_vector.hpp>
15#include "sleipnir/optimization/solver/exit_status.hpp"
16#include "sleipnir/optimization/solver/interior_point_matrix_callbacks.hpp"
17#include "sleipnir/optimization/solver/iteration_info.hpp"
18#include "sleipnir/optimization/solver/options.hpp"
19#include "sleipnir/optimization/solver/util/all_finite.hpp"
20#include "sleipnir/optimization/solver/util/append_as_triplets.hpp"
21#include "sleipnir/optimization/solver/util/feasibility_restoration.hpp"
22#include "sleipnir/optimization/solver/util/filter.hpp"
23#include "sleipnir/optimization/solver/util/fraction_to_the_boundary_rule.hpp"
24#include "sleipnir/optimization/solver/util/is_locally_infeasible.hpp"
25#include "sleipnir/optimization/solver/util/kkt_error.hpp"
26#include "sleipnir/optimization/solver/util/regularized_ldlt.hpp"
27#include "sleipnir/util/assert.hpp"
28#include "sleipnir/util/print_diagnostics.hpp"
29#include "sleipnir/util/profiler.hpp"
30#include "sleipnir/util/scope_exit.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>;
75 DenseVector::Ones(matrix_callbacks.num_inequality_constraints);
76 DenseVector y = DenseVector::Zero(matrix_callbacks.num_equality_constraints);
78 DenseVector::Ones(matrix_callbacks.num_inequality_constraints);
81 return interior_point(matrix_callbacks, iteration_callbacks, options,
false,
82#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
83 bound_constraint_mask,
120template <
typename Scalar>
121ExitStatus interior_point(
122 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
123 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
125 const Options& options,
bool in_feasibility_restoration,
126#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
127 const Eigen::ArrayX<bool>& bound_constraint_mask,
129 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
130 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
131 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
132 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar& μ) {
133 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
134 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
135 using SparseVector = Eigen::SparseVector<Scalar>;
151 const auto solve_start_time = std::chrono::steady_clock::now();
153 gch::small_vector<SolveProfiler> solve_profilers;
154 solve_profilers.emplace_back(
"solver");
155 solve_profilers.emplace_back(
"↳ setup");
156 solve_profilers.emplace_back(
"↳ iteration");
157 solve_profilers.emplace_back(
" ↳ feasibility check");
158 solve_profilers.emplace_back(
" ↳ callbacks");
159 solve_profilers.emplace_back(
" ↳ KKT matrix build");
160 solve_profilers.emplace_back(
" ↳ KKT matrix decomp");
161 solve_profilers.emplace_back(
" ↳ KKT system solve");
162 solve_profilers.emplace_back(
" ↳ line search");
163 solve_profilers.emplace_back(
" ↳ SOC");
164 solve_profilers.emplace_back(
" ↳ feas. restoration");
165 solve_profilers.emplace_back(
" ↳ f(x)");
166 solve_profilers.emplace_back(
" ↳ ∇f(x)");
167 solve_profilers.emplace_back(
" ↳ ∇²ₓₓL");
168 solve_profilers.emplace_back(
" ↳ ∇²ₓₓL_c");
169 solve_profilers.emplace_back(
" ↳ cₑ(x)");
170 solve_profilers.emplace_back(
" ↳ ∂cₑ/∂x");
171 solve_profilers.emplace_back(
" ↳ cᵢ(x)");
172 solve_profilers.emplace_back(
" ↳ ∂cᵢ/∂x");
174 auto& solver_prof = solve_profilers[0];
175 auto& setup_prof = solve_profilers[1];
176 auto& inner_iter_prof = solve_profilers[2];
177 auto& feasibility_check_prof = solve_profilers[3];
178 auto& iter_callbacks_prof = solve_profilers[4];
179 auto& kkt_matrix_build_prof = solve_profilers[5];
180 auto& kkt_matrix_decomp_prof = solve_profilers[6];
181 auto& kkt_system_solve_prof = solve_profilers[7];
182 auto& line_search_prof = solve_profilers[8];
183 auto& soc_prof = solve_profilers[9];
184 auto& feasibility_restoration_prof = solve_profilers[10];
187#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
188 auto& f_prof = solve_profilers[11];
189 auto& g_prof = solve_profilers[12];
190 auto& H_prof = solve_profilers[13];
191 auto& H_c_prof = solve_profilers[14];
192 auto& c_e_prof = solve_profilers[15];
193 auto& A_e_prof = solve_profilers[16];
194 auto& c_i_prof = solve_profilers[17];
195 auto& A_i_prof = solve_profilers[18];
197 InteriorPointMatrixCallbacks<Scalar> matrices{
198 matrix_callbacks.num_decision_variables,
199 matrix_callbacks.num_equality_constraints,
200 matrix_callbacks.num_inequality_constraints,
201 [&](
const DenseVector& x) -> Scalar {
202 ScopedProfiler prof{f_prof};
203 return matrix_callbacks.f(x);
205 [&](
const DenseVector& x) -> SparseVector {
206 ScopedProfiler prof{g_prof};
207 return matrix_callbacks.g(x);
209 [&](
const DenseVector& x,
const DenseVector& y,
210 const DenseVector& z) -> SparseMatrix {
211 ScopedProfiler prof{H_prof};
212 return matrix_callbacks.H(x, y, z);
214 [&](
const DenseVector& x,
const DenseVector& y,
215 const DenseVector& z) -> SparseMatrix {
216 ScopedProfiler prof{H_c_prof};
217 return matrix_callbacks.H_c(x, y, z);
219 [&](
const DenseVector& x) -> DenseVector {
220 ScopedProfiler prof{c_e_prof};
221 return matrix_callbacks.c_e(x);
223 [&](
const DenseVector& x) -> SparseMatrix {
224 ScopedProfiler prof{A_e_prof};
225 return matrix_callbacks.A_e(x);
227 [&](
const DenseVector& x) -> DenseVector {
228 ScopedProfiler prof{c_i_prof};
229 return matrix_callbacks.c_i(x);
231 [&](
const DenseVector& x) -> SparseMatrix {
232 ScopedProfiler prof{A_i_prof};
233 return matrix_callbacks.A_i(x);
236 const auto& matrices = matrix_callbacks;
242 Scalar f = matrices.f(x);
243 SparseVector g = matrices.g(x);
244 SparseMatrix H = matrices.H(x, y, z);
245 DenseVector c_e = matrices.c_e(x);
246 SparseMatrix A_e = matrices.A_e(x);
247 DenseVector c_i = matrices.c_i(x);
248 SparseMatrix A_i = matrices.A_i(x);
251 slp_assert(g.rows() == matrices.num_decision_variables);
252 slp_assert(H.rows() == matrices.num_decision_variables);
253 slp_assert(H.cols() == matrices.num_decision_variables);
254 slp_assert(c_e.rows() == matrices.num_equality_constraints);
255 slp_assert(A_e.rows() == matrices.num_equality_constraints);
256 slp_assert(A_e.cols() == matrices.num_decision_variables);
257 slp_assert(c_i.rows() == matrices.num_inequality_constraints);
258 slp_assert(A_i.rows() == matrices.num_inequality_constraints);
259 slp_assert(A_i.cols() == matrices.num_decision_variables);
267 DenseVector trial_c_e;
268 DenseVector trial_c_i;
271 if (matrices.num_equality_constraints > matrices.num_decision_variables) {
272 if (options.diagnostics) {
273 print_too_few_dofs_error(c_e);
276 return ExitStatus::TOO_FEW_DOFS;
280 if (!isfinite(f) || !all_finite(g) || !all_finite(H) || !c_e.allFinite() ||
281 !all_finite(A_e) || !c_i.allFinite() || !all_finite(A_i)) {
282 return ExitStatus::NONFINITE_INITIAL_GUESS;
285#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
287 s = bound_constraint_mask.select(c_i, s);
293 const Scalar μ_min = Scalar(options.tolerance) / Scalar(10);
296 constexpr Scalar τ_min(0.99);
301 Filter<Scalar> filter{c_e.template lpNorm<1>() +
302 (c_i - s).
template lpNorm<1>()};
306 auto update_barrier_parameter_and_reset_filter = [&] {
308 constexpr Scalar κ_μ(0.2);
312 constexpr Scalar θ_μ(1.5);
320 μ = std::max(μ_min, std::min(κ_μ * μ, pow(μ, θ_μ)));
327 τ = std::max(τ_min, Scalar(1) - μ);
334 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
338 RegularizedLDLT<Scalar> solver{
339 matrices.num_decision_variables, matrices.num_equality_constraints,
340 in_feasibility_restoration ? Scalar(0) : Scalar(1e-10)};
343 constexpr Scalar α_reduction_factor(0.5);
344 constexpr Scalar α_min(1e-7);
346 int full_step_rejected_counter = 0;
349 Scalar E_0 = kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
350 g, A_e, c_e, A_i, c_i, s, y, z, Scalar(0));
355 scope_exit exit{[&] {
356 if (options.diagnostics) {
359 if (in_feasibility_restoration) {
363 if (iterations > 0) {
364 print_bottom_iteration_diagnostics();
366 print_solver_diagnostics(solve_profilers);
370 while (E_0 > Scalar(options.tolerance)) {
371 ScopedProfiler inner_iter_profiler{inner_iter_prof};
372 ScopedProfiler feasibility_check_profiler{feasibility_check_prof};
375 if (is_equality_locally_infeasible(A_e, c_e)) {
376 if (options.diagnostics) {
377 print_c_e_local_infeasibility_error(c_e);
380 return ExitStatus::LOCALLY_INFEASIBLE;
384 if (is_inequality_locally_infeasible(A_i, c_i)) {
385 if (options.diagnostics) {
386 print_c_i_local_infeasibility_error(c_i);
389 return ExitStatus::LOCALLY_INFEASIBLE;
393 if (x.template lpNorm<Eigen::Infinity>() > Scalar(1e10) || !x.allFinite() ||
394 s.template lpNorm<Eigen::Infinity>() > Scalar(1e10) || !s.allFinite()) {
395 return ExitStatus::DIVERGING_ITERATES;
398 feasibility_check_profiler.stop();
399 ScopedProfiler iter_callbacks_profiler{iter_callbacks_prof};
402 for (
const auto& callback : iteration_callbacks) {
403 if (callback({iterations, x, s, y, z, g, H, A_e, A_i})) {
404 return ExitStatus::CALLBACK_REQUESTED_STOP;
408 iter_callbacks_profiler.stop();
409 ScopedProfiler kkt_matrix_build_profiler{kkt_matrix_build_prof};
414 const SparseMatrix Σ{s.cwiseInverse().asDiagonal() * z.asDiagonal()};
420 const SparseMatrix top_left =
421 H + (A_i.transpose() * Σ * A_i).
template triangularView<Eigen::Lower>();
423 triplets.reserve(top_left.nonZeros() + A_e.nonZeros());
424 append_as_triplets(triplets, 0, 0, {top_left, A_e});
426 matrices.num_decision_variables + matrices.num_equality_constraints,
427 matrices.num_decision_variables + matrices.num_equality_constraints);
428 lhs.setFromSortedTriplets(triplets.begin(), triplets.end());
432 DenseVector rhs{x.rows() + y.rows()};
433 rhs.segment(0, x.rows()) =
434 -g + A_e.transpose() * y +
435 A_i.transpose() * (-Σ * c_i + μ * s.cwiseInverse() + z);
436 rhs.segment(x.rows(), y.rows()) = -c_e;
438 kkt_matrix_build_profiler.stop();
439 ScopedProfiler kkt_matrix_decomp_profiler{kkt_matrix_decomp_prof};
445 bool call_feasibility_restoration =
false;
451 if (solver.compute(lhs).info() != Eigen::Success) [[unlikely]] {
452 return ExitStatus::FACTORIZATION_FAILED;
455 kkt_matrix_decomp_profiler.stop();
456 ScopedProfiler kkt_system_solve_profiler{kkt_system_solve_prof};
458 auto compute_step = [&](Step& step) {
461 DenseVector p = solver.solve(rhs);
462 step.p_x = p.segment(0, x.rows());
463 step.p_y = -p.segment(x.rows(), y.rows());
467 step.p_s = c_i - s + A_i * step.p_x;
468 step.p_z = -Σ * c_i + μ * s.cwiseInverse() - Σ * A_i * step.p_x;
472 kkt_system_solve_profiler.stop();
473 ScopedProfiler line_search_profiler{line_search_prof};
476 α_max = fraction_to_the_boundary_rule<Scalar>(s, step.p_s, τ);
481 call_feasibility_restoration =
true;
485 α_z = fraction_to_the_boundary_rule<Scalar>(z, step.p_z, τ);
487 const FilterEntry<Scalar> current_entry{f, s, c_e, c_i, μ};
491 trial_x = x + α * step.p_x;
492 if (options.feasible_ipm && c_i.cwiseGreater(Scalar(0)).all()) {
499 trial_s = s + α * step.p_s;
501 trial_y = y + α_z * step.p_y;
502 trial_z = z + α_z * step.p_z;
504 trial_f = matrices.f(trial_x);
505 trial_c_e = matrices.c_e(trial_x);
506 trial_c_i = matrices.c_i(trial_x);
510 if (!isfinite(trial_f) || !trial_c_e.allFinite() ||
511 !trial_c_i.allFinite()) {
513 α *= α_reduction_factor;
516 call_feasibility_restoration =
true;
523 FilterEntry trial_entry{trial_f, trial_s, trial_c_e, trial_c_i, μ};
524 if (filter.try_add(current_entry, trial_entry, step.p_x, g, α)) {
529 Scalar prev_constraint_violation =
530 c_e.template lpNorm<1>() + (c_i - s).
template lpNorm<1>();
531 Scalar next_constraint_violation =
532 trial_c_e.template lpNorm<1>() +
533 (trial_c_i - trial_s).
template lpNorm<1>();
540 next_constraint_violation >= prev_constraint_violation) {
542 auto soc_step = step;
545 Scalar α_z_soc = α_z;
546 DenseVector c_e_soc = c_e;
548 bool step_acceptable =
false;
549 for (
int soc_iteration = 0; soc_iteration < 5 && !step_acceptable;
551 ScopedProfiler soc_profiler{soc_prof};
553 scope_exit soc_exit{[&] {
556 if (options.diagnostics) {
557 print_iteration_diagnostics(
559 step_acceptable ? IterationType::ACCEPTED_SOC
560 : IterationType::REJECTED_SOC,
561 soc_profiler.current_duration(),
562 kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
563 g, A_e, trial_c_e, A_i, trial_c_i, trial_s, trial_y,
566 trial_c_e.template lpNorm<1>() +
567 (trial_c_i - trial_s).template lpNorm<1>(),
568 trial_s.dot(trial_z), μ, solver.hessian_regularization(),
569 α_soc, Scalar(1), α_reduction_factor, α_z_soc);
579 c_e_soc = α_soc * c_e_soc + trial_c_e;
580 rhs.bottomRows(y.rows()) = -c_e_soc;
583 compute_step(soc_step);
587 α_soc = fraction_to_the_boundary_rule<Scalar>(s, soc_step.p_s, τ);
588 α_z_soc = fraction_to_the_boundary_rule<Scalar>(z, soc_step.p_z, τ);
590 trial_x = x + α_soc * soc_step.p_x;
591 trial_s = s + α_soc * soc_step.p_s;
592 trial_y = y + α_z_soc * soc_step.p_y;
593 trial_z = z + α_z_soc * soc_step.p_z;
595 trial_f = matrices.f(trial_x);
596 trial_c_e = matrices.c_e(trial_x);
597 trial_c_i = matrices.c_i(trial_x);
600 constexpr Scalar κ_soc(0.99);
604 next_constraint_violation =
605 trial_c_e.template lpNorm<1>() +
606 (trial_c_i - trial_s).
template lpNorm<1>();
607 if (next_constraint_violation > κ_soc * prev_constraint_violation) {
612 FilterEntry trial_entry{trial_f, trial_s, trial_c_e, trial_c_i, μ};
613 if (filter.try_add(current_entry, trial_entry, step.p_x, g, α)) {
617 step_acceptable =
true;
621 if (step_acceptable) {
631 ++full_step_rejected_counter;
638 if (full_step_rejected_counter >= 4 &&
639 filter.max_constraint_violation >
640 current_entry.constraint_violation / Scalar(10) &&
641 filter.last_rejection_due_to_filter()) {
642 filter.max_constraint_violation *= Scalar(0.1);
648 α *= α_reduction_factor;
653 Scalar current_kkt_error = kkt_error<Scalar, KKTErrorType::ONE_NORM>(
654 g, A_e, c_e, A_i, c_i, s, y, z, μ);
656 trial_x = x + α_max * step.p_x;
657 trial_s = s + α_max * step.p_s;
658 trial_y = y + α_z * step.p_y;
659 trial_z = z + α_z * step.p_z;
661 trial_f = matrices.f(trial_x);
662 trial_c_e = matrices.c_e(trial_x);
663 trial_c_i = matrices.c_i(trial_x);
665 Scalar next_kkt_error = kkt_error<Scalar, KKTErrorType::ONE_NORM>(
666 matrices.g(trial_x), matrices.A_e(trial_x), trial_c_e,
667 matrices.A_i(trial_x), trial_c_i, trial_s, trial_y, trial_z, μ);
670 if (next_kkt_error <= Scalar(0.999) * current_kkt_error) {
675 call_feasibility_restoration =
true;
680 line_search_profiler.stop();
682 if (call_feasibility_restoration) {
683 ScopedProfiler feasibility_restoration_profiler{
684 feasibility_restoration_prof};
687 if (in_feasibility_restoration) {
688 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
691 FilterEntry initial_entry{matrices.f(x), s, c_e, c_i, μ};
694 gch::small_vector<std::function<bool(
const IterationInfo<Scalar>& info)>>
696 for (
auto& callback : iteration_callbacks) {
697 callbacks.emplace_back(callback);
699 callbacks.emplace_back([&](
const IterationInfo<Scalar>& info) {
700 DenseVector trial_x =
701 info.x.segment(0, matrices.num_decision_variables);
702 DenseVector trial_s =
703 info.s.segment(0, matrices.num_inequality_constraints);
705 DenseVector trial_c_e = matrices.c_e(trial_x);
706 DenseVector trial_c_i = matrices.c_i(trial_x);
710 FilterEntry trial_entry{matrices.f(trial_x), trial_s, trial_c_e,
712 return trial_entry.constraint_violation <
713 Scalar(0.9) * initial_entry.constraint_violation &&
714 filter.try_add(initial_entry, trial_entry, trial_x - x, g, α);
716 auto status = feasibility_restoration<Scalar>(matrices, callbacks,
717 options, x, s, y, z, μ);
719 if (status != ExitStatus::SUCCESS) {
725 c_e = matrices.c_e(x);
726 c_i = matrices.c_i(x);
730 full_step_rejected_counter = 0;
752 for (
int row = 0; row < z.rows(); ++row) {
753 constexpr Scalar κ_Σ(1e10);
755 std::clamp(z[row], Scalar(1) / κ_Σ * μ / s[row], κ_Σ * μ / s[row]);
764 A_e = matrices.A_e(x);
765 A_i = matrices.A_i(x);
767 H = matrices.H(x, y, z);
770 E_0 = kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
771 g, A_e, c_e, A_i, c_i, s, y, z, Scalar(0));
774 if (E_0 > Scalar(options.tolerance)) {
776 constexpr Scalar κ_ε(10);
780 Scalar E_μ = kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(
781 g, A_e, c_e, A_i, c_i, s, y, z, μ);
782 while (μ > μ_min && E_μ <= κ_ε * μ) {
783 update_barrier_parameter_and_reset_filter();
784 E_μ = kkt_error<Scalar, KKTErrorType::INF_NORM_SCALED>(g, A_e, c_e, A_i,
789 inner_iter_profiler.stop();
791 if (options.diagnostics) {
792 print_iteration_diagnostics(
794 in_feasibility_restoration ? IterationType::FEASIBILITY_RESTORATION
795 : IterationType::NORMAL,
796 inner_iter_profiler.current_duration(), E_0, f,
797 c_e.template lpNorm<1>() + (c_i - s).template lpNorm<1>(), s.dot(z),
798 μ, solver.hessian_regularization(), α, α_max, α_reduction_factor,
805 if (iterations >= options.max_iterations) {
806 return ExitStatus::MAX_ITERATIONS_EXCEEDED;
810 if (std::chrono::steady_clock::now() - solve_start_time > options.timeout) {
811 return ExitStatus::TIMEOUT;
815 return ExitStatus::SUCCESS;
818extern template SLEIPNIR_DLLEXPORT ExitStatus
819interior_point(
const InteriorPointMatrixCallbacks<double>& matrix_callbacks,
820 std::span<std::function<
bool(
const IterationInfo<double>& info)>>
822 const Options& options,
823#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
824 const Eigen::ArrayX<bool>& bound_constraint_mask,
826 Eigen::Vector<double, Eigen::Dynamic>& x);