13#include <Eigen/SparseCore>
14#include <gch/small_vector.hpp>
16#include "sleipnir/optimization/solver/exit_status.hpp"
17#include "sleipnir/optimization/solver/ipm_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/problem_scaling.hpp"
23#include "sleipnir/util/print_diagnostics.hpp"
27template <
typename Scalar>
28ExitStatus ipm(
const IPMMatrixCallbacks<Scalar>& matrix_callbacks,
29 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
31 const Options<Scalar>& options,
bool in_feasibility_restoration,
32#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
33 const Eigen::ArrayX<bool>& bound_constraint_mask,
35 Eigen::VectorX<Scalar>& x, Eigen::VectorX<Scalar>& s,
36 Eigen::VectorX<Scalar>& y, Eigen::VectorX<Scalar>& z, Scalar& μ,
47template <
typename Scalar>
48std::tuple<Eigen::VectorX<Scalar>, Eigen::VectorX<Scalar>> compute_p_n(
49 const Eigen::VectorX<Scalar>& c, Scalar ρ, Scalar μ) {
80 using DenseVector = Eigen::VectorX<Scalar>;
84 DenseVector p{c.rows()};
85 DenseVector n{c.rows()};
86 for (
int row = 0; row < p.rows(); ++row) {
88 Scalar _b = ρ * c[row] - μ;
89 Scalar _c = -μ * c[row] / Scalar(2);
91 n[row] = (-_b + sqrt(_b * _b - Scalar(4) * _a * _c)) / (Scalar(2) * _a);
92 p[row] = c[row] + n[row];
95 return {std::move(p), std::move(n)};
113template <
typename Scalar>
114ExitStatus feasibility_restoration(
115 const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
116 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
118 const Options<Scalar>& options, Eigen::VectorX<Scalar>& x,
119 Eigen::VectorX<Scalar>& y,
int& iterations) {
136 using DenseVector = Eigen::VectorX<Scalar>;
137 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
138 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
139 using SparseVector = Eigen::SparseVector<Scalar>;
143 const auto& matrices = matrix_callbacks;
144 const auto& num_vars = matrices.num_decision_variables;
145 const auto& num_eq = matrices.num_equality_constraints;
147 constexpr Scalar ρ(1e3);
148 const Scalar μ = options.tolerance / Scalar(10);
150 const DenseVector c_e = matrices.c_e(x);
152 Scalar fr_μ = std::max(μ, c_e.template lpNorm<Eigen::Infinity>());
153 const Scalar ζ = sqrt(fr_μ);
156 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
159 const DiagonalMatrix D_r =
160 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
162 DenseVector fr_x{num_vars + 2 * num_eq};
163 fr_x << x, p_e_0, n_e_0;
166 DenseVector fr_s{2 * num_eq};
167 fr_s << p_e_0, n_e_0;
169 DenseVector fr_y = DenseVector::Zero(num_eq);
172 DenseVector fr_z{2 * num_eq};
173 fr_z << fr_μ * p_e_0.cwiseInverse(), fr_μ * n_e_0.cwiseInverse();
178 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
179 DenseVector::Ones(2 * num_eq)};
181 IPMMatrixCallbacks<Scalar> fr_matrix_callbacks{
182 static_cast<int>(fr_x.rows()),
183 static_cast<int>(fr_y.rows()),
184 static_cast<int>(fr_z.rows()),
185 [&](
const DenseVector& x_p) -> Scalar {
186 auto x = x_p.segment(0, num_vars);
193 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
194 ζ / Scalar(2) * diff.transpose() * D_r * diff;
196 [&](
const DenseVector& x_p) -> SparseVector {
197 auto x = x_p.segment(0, num_vars);
204 DenseVector g{x_p.rows()};
205 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
206 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
207 return g.sparseView();
209 [&](
const DenseVector& x_p,
const DenseVector& y_p,
210 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
211 auto x = x_p.segment(0, num_vars);
219 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
220 triplets.reserve(x_p.rows());
221 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
222 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
223 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
228 auto H_c = matrices.H_c(x, y);
229 H_c.conservativeResize(x_p.rows(), x_p.rows());
236 return d2f_dx2 + H_c;
238 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
239 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
240 return SparseMatrix{x_p.rows(), x_p.rows()};
242 [&](
const DenseVector& x_p) -> DenseVector {
243 auto x = x_p.segment(0, num_vars);
244 auto p_e = x_p.segment(num_vars, num_eq);
245 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
250 return matrices.c_e(x) - p_e + n_e;
252 [&](
const DenseVector& x_p) -> SparseMatrix {
253 auto x = x_p.segment(0, num_vars);
259 SparseMatrix A_e = matrices.A_e(x);
261 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
262 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
264 append_as_triplets(triplets, 0, 0, {A_e});
265 append_diagonal_as_triplets(
266 triplets, 0, num_vars,
267 DenseVector::Constant(num_eq, Scalar(-1)).eval());
268 append_diagonal_as_triplets(
269 triplets, 0, num_vars + num_eq,
270 DenseVector::Constant(num_eq, Scalar(1)).eval());
272 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
273 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
276 [&](
const DenseVector& x_p) -> DenseVector {
281 return x_p.segment(num_vars, 2 * num_eq);
283 [&](
const DenseVector& x_p) -> SparseMatrix {
289 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
290 triplets.reserve(2 * num_eq);
292 append_diagonal_as_triplets(
293 triplets, 0, num_vars,
294 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
296 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
297 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
303 ipm<Scalar>(fr_matrix_callbacks, iteration_callbacks, options,
true,
304#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
305 Eigen::ArrayX<bool>::Constant(2 * num_eq,
true),
307 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
309 x = fr_x.segment(0, x.rows());
313 DenseVector c_e_unscaled;
314 bool c_e_violated =
false;
315 if (status == ExitStatus::SUCCESS) {
316 c_e_unscaled = matrices.c_e(x);
317 if (matrices.scaling.c_e.size() > 0) {
319 matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e_unscaled);
323 c_e_unscaled.template lpNorm<Eigen::Infinity>() > options.tolerance;
328 if (status == ExitStatus::CALLBACK_REQUESTED_STOP ||
329 (status == ExitStatus::SUCCESS && !c_e_violated)) {
334 return ExitStatus::SUCCESS;
335 }
else if (status == ExitStatus::SUCCESS) {
342 if (options.diagnostics) {
343 print_c_e_local_infeasibility_error(c_e_unscaled, options.tolerance);
346 return ExitStatus::LOCALLY_INFEASIBLE;
348 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
371template <
typename Scalar>
372ExitStatus feasibility_restoration(
373 const IPMMatrixCallbacks<Scalar>& matrix_callbacks,
374 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
376 const Options<Scalar>& options,
377#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
378 const Eigen::ArrayX<bool>& bound_constraint_mask,
380 Eigen::VectorX<Scalar>& x, Eigen::VectorX<Scalar>& s,
381 Eigen::VectorX<Scalar>& y, Eigen::VectorX<Scalar>& z, Scalar μ,
403 using DenseVector = Eigen::VectorX<Scalar>;
404 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
405 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
406 using SparseVector = Eigen::SparseVector<Scalar>;
410 const auto& matrices = matrix_callbacks;
411 const auto& num_vars = matrices.num_decision_variables;
412 const auto& num_eq = matrices.num_equality_constraints;
413 const auto& num_ineq = matrices.num_inequality_constraints;
415 constexpr Scalar ρ(1e3);
417 const DenseVector c_e = matrices.c_e(x);
418 const DenseVector c_i = matrices.c_i(x);
420 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
421 (c_i - s).
template lpNorm<Eigen::Infinity>()});
422 const Scalar ζ = sqrt(fr_μ);
425 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
426 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, fr_μ);
429 const DiagonalMatrix D_r =
430 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
432 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
433 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
439 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
440 fr_s.segment(0, s.rows()) = s;
441 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq) << p_e_0, n_e_0, p_i_0,
444 DenseVector fr_y = DenseVector::Zero(c_e.rows());
447 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
448 fr_z << fr_μ * s.cwiseInverse(), fr_μ * p_e_0.cwiseInverse(),
449 fr_μ * n_e_0.cwiseInverse(), fr_μ * p_i_0.cwiseInverse(),
450 fr_μ * n_i_0.cwiseInverse();
455 DenseVector fr_d_c_i{c_i.rows() + 2 * num_eq + 2 * num_ineq};
456 fr_d_c_i << matrices.scaling.c_i,
457 DenseVector::Ones(2 * num_eq + 2 * num_ineq);
458 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
461 IPMMatrixCallbacks<Scalar> fr_matrix_callbacks{
462 static_cast<int>(fr_x.rows()),
463 static_cast<int>(fr_y.rows()),
464 static_cast<int>(fr_z.rows()),
465 [&](
const DenseVector& x_p) -> Scalar {
466 auto x = x_p.segment(0, num_vars);
472 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
475 ζ / Scalar(2) * diff.transpose() * D_r * diff;
477 [&](
const DenseVector& x_p) -> SparseVector {
478 auto x = x_p.segment(0, num_vars);
487 DenseVector g{x_p.rows()};
488 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
489 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
490 return g.sparseView();
492 [&](
const DenseVector& x_p,
const DenseVector& y_p,
493 const DenseVector& z_p) -> SparseMatrix {
494 auto x = x_p.segment(0, num_vars);
496 auto z = z_p.segment(0, num_ineq);
505 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
506 triplets.reserve(x_p.rows());
507 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
508 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
509 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
514 auto H_c = matrices.H_c(x, y, z);
515 H_c.conservativeResize(x_p.rows(), x_p.rows());
524 return d2f_dx2 + H_c;
526 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
527 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
528 return SparseMatrix{x_p.rows(), x_p.rows()};
530 [&](
const DenseVector& x_p) -> DenseVector {
531 auto x = x_p.segment(0, num_vars);
532 auto p_e = x_p.segment(num_vars, num_eq);
533 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
538 return matrices.c_e(x) - p_e + n_e;
540 [&](
const DenseVector& x_p) -> SparseMatrix {
541 auto x = x_p.segment(0, num_vars);
547 SparseMatrix A_e = matrices.A_e(x);
549 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
550 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
552 append_as_triplets(triplets, 0, 0, {A_e});
553 append_diagonal_as_triplets(
554 triplets, 0, num_vars,
555 DenseVector::Constant(num_eq, Scalar(-1)).eval());
556 append_diagonal_as_triplets(
557 triplets, 0, num_vars + num_eq,
558 DenseVector::Constant(num_eq, Scalar(1)).eval());
560 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
561 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
564 [&](
const DenseVector& x_p) -> DenseVector {
565 auto x = x_p.segment(0, num_vars);
566 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
567 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
576 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
577 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
578 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
579 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
582 [&](
const DenseVector& x_p) -> SparseMatrix {
583 auto x = x_p.segment(0, num_vars);
593 SparseMatrix A_i = matrices.A_i(x);
595 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
596 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
599 append_as_triplets(triplets, 0, 0, {A_i});
602 append_diagonal_as_triplets(
603 triplets, num_ineq, num_vars,
604 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
607 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
610 SparseMatrix Z_col3{2 * num_eq, num_ineq};
611 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
612 {(-I_ineq).eval(), Z_col3, I_ineq});
615 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
616 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
617 {I_ineq, Z_col4, I_ineq});
619 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
620 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
625#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
626 Eigen::ArrayX<bool> fr_bound_constraint_mask{2 * num_eq + 3 * num_ineq};
627 fr_bound_constraint_mask.segment(0, num_ineq) = bound_constraint_mask;
628 fr_bound_constraint_mask.segment(num_ineq, 2 * num_eq + 2 * num_ineq) =
true;
632 ipm<Scalar>(fr_matrix_callbacks, iteration_callbacks, options,
true,
633#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
634 fr_bound_constraint_mask,
636 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
638 x = fr_x.segment(0, x.rows());
642 DenseVector c_e_unscaled;
643 DenseVector c_i_unscaled;
644 bool c_e_violated =
false;
645 bool c_i_violated =
false;
646 if (status == ExitStatus::SUCCESS) {
647 c_e_unscaled = matrices.c_e(x);
648 if (matrices.scaling.c_e.size() > 0) {
650 matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e_unscaled);
653 c_i_unscaled = matrices.c_i(x);
654 if (matrices.scaling.c_i.size() > 0) {
656 matrices.scaling.c_i.cwiseInverse().cwiseProduct(c_i_unscaled);
662 c_e_unscaled.template lpNorm<Eigen::Infinity>() > options.tolerance;
664 (-c_i_unscaled).cwiseMax(Scalar(0)).
template lpNorm<Eigen::Infinity>() >
670 if (status == ExitStatus::CALLBACK_REQUESTED_STOP ||
671 (status == ExitStatus::SUCCESS && !c_e_violated && !c_i_violated)) {
672 const DenseVector s_old = s;
685 const DenseVector c_i = matrices.c_i(x);
686 s = (c_i.array() >= Scalar(1e-2)).select(c_i, fr_s.segment(0, s.rows()));
700 const DenseVector Σ = z.cwiseQuotient(s_old);
701 const DenseVector p_s = s - s_old;
702 z += μ * s_old.cwiseInverse() - z - Σ.cwiseProduct(p_s);
703 if ((z.array() <= Scalar(0)).any() || (z.array() > Scalar(1e3)).any()) {
707 return ExitStatus::SUCCESS;
708 }
else if (status == ExitStatus::SUCCESS) {
716 s = fr_s.segment(0, s.rows());
718 if (options.diagnostics) {
720 print_c_e_local_infeasibility_error(c_e_unscaled, options.tolerance);
723 print_c_i_local_infeasibility_error(c_i_unscaled, options.tolerance);
727 return ExitStatus::LOCALLY_INFEASIBLE;
729 s = fr_s.segment(0, s.rows());
730 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
736#include "sleipnir/optimization/solver/ipm.hpp"