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/sqp_matrix_callbacks.hpp"
21#include "sleipnir/optimization/solver/util/append_as_triplets.hpp"
22#include "sleipnir/optimization/solver/util/lagrange_multiplier_estimate.hpp"
23#include "sleipnir/optimization/solver/util/problem_scaling.hpp"
24#include "sleipnir/util/print_diagnostics.hpp"
28template <
typename Scalar>
29ExitStatus interior_point(
30 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
31 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
33 const Options& options,
bool in_feasibility_restoration,
34#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
35 const Eigen::ArrayX<bool>& bound_constraint_mask,
37 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
38 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
39 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
40 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar& μ,
int& iterations);
50template <
typename Scalar>
51std::tuple<Eigen::Vector<Scalar, Eigen::Dynamic>,
52 Eigen::Vector<Scalar, Eigen::Dynamic>>
53compute_p_n(
const Eigen::Vector<Scalar, Eigen::Dynamic>& c, Scalar ρ,
85 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
89 DenseVector p{c.rows()};
90 DenseVector n{c.rows()};
91 for (
int row = 0; row < p.rows(); ++row) {
93 Scalar _b = ρ * c[row] - μ;
94 Scalar _c = -μ * c[row] / Scalar(2);
96 n[row] = (-_b + sqrt(_b * _b - Scalar(4) * _a * _c)) / (Scalar(2) * _a);
97 p[row] = c[row] + n[row];
100 return {std::move(p), std::move(n)};
118template <
typename Scalar>
119ExitStatus feasibility_restoration(
120 const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
121 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
123 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
124 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
int& iterations) {
141 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
142 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
143 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
144 using SparseVector = Eigen::SparseVector<Scalar>;
148 const auto& matrices = matrix_callbacks;
149 const auto& num_vars = matrices.num_decision_variables;
150 const auto& num_eq = matrices.num_equality_constraints;
152 constexpr Scalar ρ(1e3);
153 const Scalar μ(options.tolerance / 10.0);
155 const DenseVector c_e = matrices.c_e(x);
157 Scalar fr_μ = std::max(μ, c_e.template lpNorm<Eigen::Infinity>());
158 const Scalar ζ = sqrt(fr_μ);
161 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
164 const DiagonalMatrix D_r =
165 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
167 DenseVector fr_x{num_vars + 2 * num_eq};
168 fr_x << x, p_e_0, n_e_0;
170 DenseVector fr_s = DenseVector::Ones(2 * num_eq);
172 DenseVector fr_y = DenseVector::Zero(num_eq);
175 DenseVector fr_z{2 * num_eq};
176 fr_z << fr_μ * p_e_0.cwiseInverse(), fr_μ * n_e_0.cwiseInverse();
181 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
182 DenseVector::Ones(2 * num_eq)};
184 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
185 static_cast<int>(fr_x.rows()),
186 static_cast<int>(fr_y.rows()),
187 static_cast<int>(fr_z.rows()),
188 [&](
const DenseVector& x_p) -> Scalar {
189 auto x = x_p.segment(0, num_vars);
196 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
197 ζ / Scalar(2) * diff.transpose() * D_r * diff;
199 [&](
const DenseVector& x_p) -> SparseVector {
200 auto x = x_p.segment(0, num_vars);
207 DenseVector g{x_p.rows()};
208 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
209 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
210 return g.sparseView();
212 [&](
const DenseVector& x_p,
const DenseVector& y_p,
213 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
214 auto x = x_p.segment(0, num_vars);
222 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
223 triplets.reserve(x_p.rows());
224 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
225 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
226 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
231 auto H_c = matrices.H_c(x, y);
232 H_c.conservativeResize(x_p.rows(), x_p.rows());
239 return d2f_dx2 + H_c;
241 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
242 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
243 return SparseMatrix{x_p.rows(), x_p.rows()};
245 [&](
const DenseVector& x_p) -> DenseVector {
246 auto x = x_p.segment(0, num_vars);
247 auto p_e = x_p.segment(num_vars, num_eq);
248 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
253 return matrices.c_e(x) - p_e + n_e;
255 [&](
const DenseVector& x_p) -> SparseMatrix {
256 auto x = x_p.segment(0, num_vars);
262 SparseMatrix A_e = matrices.A_e(x);
264 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
265 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
267 append_as_triplets(triplets, 0, 0, {A_e});
268 append_diagonal_as_triplets(
269 triplets, 0, num_vars,
270 DenseVector::Constant(num_eq, Scalar(-1)).eval());
271 append_diagonal_as_triplets(
272 triplets, 0, num_vars + num_eq,
273 DenseVector::Constant(num_eq, Scalar(1)).eval());
275 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
276 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
279 [&](
const DenseVector& x_p) -> DenseVector {
284 return x_p.segment(num_vars, 2 * num_eq);
286 [&](
const DenseVector& x_p) -> SparseMatrix {
292 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
293 triplets.reserve(2 * num_eq);
295 append_diagonal_as_triplets(
296 triplets, 0, num_vars,
297 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
299 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
300 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
305 auto status = interior_point<Scalar>(
306 fr_matrix_callbacks, iteration_callbacks, options,
true,
307#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
308 Eigen::ArrayX<bool>::Constant(2 * num_eq,
true),
310 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
312 x = fr_x.segment(0, x.rows());
314 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
315 auto g = matrices.g(x);
316 auto A_e = matrices.A_e(x);
318 y = lagrange_multiplier_estimate(g, A_e);
320 return ExitStatus::SUCCESS;
321 }
else if (status == ExitStatus::SUCCESS) {
328 DenseVector c_e = matrices.c_e(x);
329 if (matrices.scaling.c_e.size() > 0) {
330 c_e = matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e);
333 if (c_e.template lpNorm<Eigen::Infinity>() > Scalar(options.tolerance)) {
334 if (options.diagnostics) {
335 print_c_e_local_infeasibility_error(c_e, Scalar(options.tolerance));
338 return ExitStatus::LOCALLY_INFEASIBLE;
341 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
343 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
366template <
typename Scalar>
367ExitStatus feasibility_restoration(
368 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
369 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
371 const Options& options,
372#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
373 const Eigen::ArrayX<bool>& bound_constraint_mask,
375 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
376 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
377 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
378 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar μ,
int& iterations) {
399 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
400 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
401 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
402 using SparseVector = Eigen::SparseVector<Scalar>;
406 const auto& matrices = matrix_callbacks;
407 const auto& num_vars = matrices.num_decision_variables;
408 const auto& num_eq = matrices.num_equality_constraints;
409 const auto& num_ineq = matrices.num_inequality_constraints;
411 constexpr Scalar ρ(1e3);
413 const DenseVector c_e = matrices.c_e(x);
414 const DenseVector c_i = matrices.c_i(x);
416 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
417 (c_i - s).
template lpNorm<Eigen::Infinity>()});
418 const Scalar ζ = sqrt(fr_μ);
421 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
422 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, fr_μ);
425 const DiagonalMatrix D_r =
426 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
428 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
429 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
431 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
432 fr_s.segment(0, s.rows()) = s;
433 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq).setOnes();
435 DenseVector fr_y = DenseVector::Zero(c_e.rows());
438 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
439 fr_z << fr_μ * s.cwiseInverse(), fr_μ * p_e_0.cwiseInverse(),
440 fr_μ * n_e_0.cwiseInverse(), fr_μ * p_i_0.cwiseInverse(),
441 fr_μ * n_i_0.cwiseInverse();
446 DenseVector fr_d_c_i{c_i.rows() + 2 * num_eq + 2 * num_ineq};
447 fr_d_c_i << matrices.scaling.c_i,
448 DenseVector::Ones(2 * num_eq + 2 * num_ineq);
449 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
452 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
453 static_cast<int>(fr_x.rows()),
454 static_cast<int>(fr_y.rows()),
455 static_cast<int>(fr_z.rows()),
456 [&](
const DenseVector& x_p) -> Scalar {
457 auto x = x_p.segment(0, num_vars);
463 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
466 ζ / Scalar(2) * diff.transpose() * D_r * diff;
468 [&](
const DenseVector& x_p) -> SparseVector {
469 auto x = x_p.segment(0, num_vars);
478 DenseVector g{x_p.rows()};
479 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
480 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
481 return g.sparseView();
483 [&](
const DenseVector& x_p,
const DenseVector& y_p,
484 const DenseVector& z_p) -> SparseMatrix {
485 auto x = x_p.segment(0, num_vars);
487 auto z = z_p.segment(0, num_ineq);
496 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
497 triplets.reserve(x_p.rows());
498 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
499 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
500 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
505 auto H_c = matrices.H_c(x, y, z);
506 H_c.conservativeResize(x_p.rows(), x_p.rows());
515 return d2f_dx2 + H_c;
517 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
518 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
519 return SparseMatrix{x_p.rows(), x_p.rows()};
521 [&](
const DenseVector& x_p) -> DenseVector {
522 auto x = x_p.segment(0, num_vars);
523 auto p_e = x_p.segment(num_vars, num_eq);
524 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
529 return matrices.c_e(x) - p_e + n_e;
531 [&](
const DenseVector& x_p) -> SparseMatrix {
532 auto x = x_p.segment(0, num_vars);
538 SparseMatrix A_e = matrices.A_e(x);
540 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
541 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
543 append_as_triplets(triplets, 0, 0, {A_e});
544 append_diagonal_as_triplets(
545 triplets, 0, num_vars,
546 DenseVector::Constant(num_eq, Scalar(-1)).eval());
547 append_diagonal_as_triplets(
548 triplets, 0, num_vars + num_eq,
549 DenseVector::Constant(num_eq, Scalar(1)).eval());
551 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
552 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
555 [&](
const DenseVector& x_p) -> DenseVector {
556 auto x = x_p.segment(0, num_vars);
557 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
558 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
567 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
568 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
569 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
570 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
573 [&](
const DenseVector& x_p) -> SparseMatrix {
574 auto x = x_p.segment(0, num_vars);
584 SparseMatrix A_i = matrices.A_i(x);
586 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
587 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
590 append_as_triplets(triplets, 0, 0, {A_i});
593 append_diagonal_as_triplets(
594 triplets, num_ineq, num_vars,
595 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
598 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
601 SparseMatrix Z_col3{2 * num_eq, num_ineq};
602 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
603 {(-I_ineq).eval(), Z_col3, I_ineq});
606 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
607 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
608 {I_ineq, Z_col4, I_ineq});
610 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
611 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
616#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
617 Eigen::ArrayX<bool> fr_bound_constraint_mask{2 * num_eq + 3 * num_ineq};
618 fr_bound_constraint_mask.segment(0, num_ineq) = bound_constraint_mask;
619 fr_bound_constraint_mask.segment(num_ineq, 2 * num_eq + 2 * num_ineq) =
true;
622 auto status = interior_point<Scalar>(
623 fr_matrix_callbacks, iteration_callbacks, options,
true,
624#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
625 fr_bound_constraint_mask,
627 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
629 x = fr_x.segment(0, x.rows());
630 s = fr_s.segment(0, s.rows());
632 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
633 auto g = matrices.g(x);
634 auto A_e = matrices.A_e(x);
635 auto A_i = matrices.A_i(x);
637 auto [y_estimate, z_estimate] =
638 lagrange_multiplier_estimate(g, A_e, A_i, s, μ);
642 return ExitStatus::SUCCESS;
643 }
else if (status == ExitStatus::SUCCESS) {
650 DenseVector c_e = matrices.c_e(x);
651 if (matrices.scaling.c_e.size() > 0) {
652 c_e = matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e);
655 DenseVector c_i = matrices.c_i(x);
656 if (matrices.scaling.c_i.size() > 0) {
657 c_i = matrices.scaling.c_i.cwiseInverse().cwiseProduct(c_i);
662 const DenseVector c_i_violation = (-c_i).cwiseMax(Scalar(0));
664 const bool c_e_violated =
665 c_e.template lpNorm<Eigen::Infinity>() > Scalar(options.tolerance);
666 const bool c_i_violated = c_i_violation.template lpNorm<Eigen::Infinity>() >
667 Scalar(options.tolerance);
669 if (c_e_violated || c_i_violated) {
670 if (options.diagnostics) {
672 print_c_e_local_infeasibility_error(c_e, Scalar(options.tolerance));
675 print_c_i_local_infeasibility_error(c_i, Scalar(options.tolerance));
679 return ExitStatus::LOCALLY_INFEASIBLE;
682 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
684 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
690#include "sleipnir/optimization/solver/interior_point.hpp"