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& options,
bool in_feasibility_restoration,
32#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
33 const Eigen::ArrayX<bool>& bound_constraint_mask,
35 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
36 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
37 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
38 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar& μ,
49template <
typename Scalar>
50std::tuple<Eigen::Vector<Scalar, Eigen::Dynamic>,
51 Eigen::Vector<Scalar, Eigen::Dynamic>>
52compute_p_n(
const Eigen::Vector<Scalar, Eigen::Dynamic>& c, Scalar ρ,
84 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
88 DenseVector p{c.rows()};
89 DenseVector n{c.rows()};
90 for (
int row = 0; row < p.rows(); ++row) {
92 Scalar _b = ρ * c[row] - μ;
93 Scalar _c = -μ * c[row] / Scalar(2);
95 n[row] = (-_b + sqrt(_b * _b - Scalar(4) * _a * _c)) / (Scalar(2) * _a);
96 p[row] = c[row] + n[row];
99 return {std::move(p), std::move(n)};
117template <
typename Scalar>
118ExitStatus feasibility_restoration(
119 const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
120 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
122 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
123 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
int& iterations) {
140 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
141 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
142 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
143 using SparseVector = Eigen::SparseVector<Scalar>;
147 const auto& matrices = matrix_callbacks;
148 const auto& num_vars = matrices.num_decision_variables;
149 const auto& num_eq = matrices.num_equality_constraints;
151 constexpr Scalar ρ(1e3);
152 const Scalar μ(options.tolerance / 10.0);
154 const DenseVector c_e = matrices.c_e(x);
156 Scalar fr_μ = std::max(μ, c_e.template lpNorm<Eigen::Infinity>());
157 const Scalar ζ = sqrt(fr_μ);
160 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
163 const DiagonalMatrix D_r =
164 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
166 DenseVector fr_x{num_vars + 2 * num_eq};
167 fr_x << x, p_e_0, n_e_0;
170 DenseVector fr_s{2 * num_eq};
171 fr_s << p_e_0, n_e_0;
173 DenseVector fr_y = DenseVector::Zero(num_eq);
176 DenseVector fr_z{2 * num_eq};
177 fr_z << fr_μ * p_e_0.cwiseInverse(), fr_μ * n_e_0.cwiseInverse();
182 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
183 DenseVector::Ones(2 * num_eq)};
185 IPMMatrixCallbacks<Scalar> fr_matrix_callbacks{
186 static_cast<int>(fr_x.rows()),
187 static_cast<int>(fr_y.rows()),
188 static_cast<int>(fr_z.rows()),
189 [&](
const DenseVector& x_p) -> Scalar {
190 auto x = x_p.segment(0, num_vars);
197 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
198 ζ / Scalar(2) * diff.transpose() * D_r * diff;
200 [&](
const DenseVector& x_p) -> SparseVector {
201 auto x = x_p.segment(0, num_vars);
208 DenseVector g{x_p.rows()};
209 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
210 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
211 return g.sparseView();
213 [&](
const DenseVector& x_p,
const DenseVector& y_p,
214 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
215 auto x = x_p.segment(0, num_vars);
223 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
224 triplets.reserve(x_p.rows());
225 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
226 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
227 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
232 auto H_c = matrices.H_c(x, y);
233 H_c.conservativeResize(x_p.rows(), x_p.rows());
240 return d2f_dx2 + H_c;
242 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
243 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
244 return SparseMatrix{x_p.rows(), x_p.rows()};
246 [&](
const DenseVector& x_p) -> DenseVector {
247 auto x = x_p.segment(0, num_vars);
248 auto p_e = x_p.segment(num_vars, num_eq);
249 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
254 return matrices.c_e(x) - p_e + n_e;
256 [&](
const DenseVector& x_p) -> SparseMatrix {
257 auto x = x_p.segment(0, num_vars);
263 SparseMatrix A_e = matrices.A_e(x);
265 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
266 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
268 append_as_triplets(triplets, 0, 0, {A_e});
269 append_diagonal_as_triplets(
270 triplets, 0, num_vars,
271 DenseVector::Constant(num_eq, Scalar(-1)).eval());
272 append_diagonal_as_triplets(
273 triplets, 0, num_vars + num_eq,
274 DenseVector::Constant(num_eq, Scalar(1)).eval());
276 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
277 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
280 [&](
const DenseVector& x_p) -> DenseVector {
285 return x_p.segment(num_vars, 2 * num_eq);
287 [&](
const DenseVector& x_p) -> SparseMatrix {
293 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
294 triplets.reserve(2 * num_eq);
296 append_diagonal_as_triplets(
297 triplets, 0, num_vars,
298 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
300 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
301 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
307 ipm<Scalar>(fr_matrix_callbacks, iteration_callbacks, options,
true,
308#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
309 Eigen::ArrayX<bool>::Constant(2 * num_eq,
true),
311 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
313 x = fr_x.segment(0, x.rows());
315 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
316 auto g = matrices.g(x);
317 auto A_e = matrices.A_e(x);
323 return ExitStatus::SUCCESS;
324 }
else if (status == ExitStatus::SUCCESS) {
331 DenseVector c_e = matrices.c_e(x);
332 if (matrices.scaling.c_e.size() > 0) {
333 c_e = matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e);
336 if (c_e.template lpNorm<Eigen::Infinity>() > Scalar(options.tolerance)) {
337 if (options.diagnostics) {
338 print_c_e_local_infeasibility_error(c_e, Scalar(options.tolerance));
341 return ExitStatus::LOCALLY_INFEASIBLE;
344 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
346 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
369template <
typename Scalar>
370ExitStatus feasibility_restoration(
371 const IPMMatrixCallbacks<Scalar>& matrix_callbacks,
372 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
374 const Options& options,
375#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
376 const Eigen::ArrayX<bool>& bound_constraint_mask,
378 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
379 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
380 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
381 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar μ,
int& iterations) {
402 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
403 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
404 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
405 using SparseVector = Eigen::SparseVector<Scalar>;
409 const auto& matrices = matrix_callbacks;
410 const auto& num_vars = matrices.num_decision_variables;
411 const auto& num_eq = matrices.num_equality_constraints;
412 const auto& num_ineq = matrices.num_inequality_constraints;
414 constexpr Scalar ρ(1e3);
416 const DenseVector c_e = matrices.c_e(x);
417 const DenseVector c_i = matrices.c_i(x);
419 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
420 (c_i - s).
template lpNorm<Eigen::Infinity>()});
421 const Scalar ζ = sqrt(fr_μ);
424 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
425 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, fr_μ);
428 const DiagonalMatrix D_r =
429 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
431 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
432 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
438 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
439 fr_s.segment(0, s.rows()) = s;
440 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq) << p_e_0, n_e_0, p_i_0,
443 DenseVector fr_y = DenseVector::Zero(c_e.rows());
446 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
447 fr_z << fr_μ * s.cwiseInverse(), fr_μ * p_e_0.cwiseInverse(),
448 fr_μ * n_e_0.cwiseInverse(), fr_μ * p_i_0.cwiseInverse(),
449 fr_μ * n_i_0.cwiseInverse();
454 DenseVector fr_d_c_i{c_i.rows() + 2 * num_eq + 2 * num_ineq};
455 fr_d_c_i << matrices.scaling.c_i,
456 DenseVector::Ones(2 * num_eq + 2 * num_ineq);
457 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
460 IPMMatrixCallbacks<Scalar> fr_matrix_callbacks{
461 static_cast<int>(fr_x.rows()),
462 static_cast<int>(fr_y.rows()),
463 static_cast<int>(fr_z.rows()),
464 [&](
const DenseVector& x_p) -> Scalar {
465 auto x = x_p.segment(0, num_vars);
471 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
474 ζ / Scalar(2) * diff.transpose() * D_r * diff;
476 [&](
const DenseVector& x_p) -> SparseVector {
477 auto x = x_p.segment(0, num_vars);
486 DenseVector g{x_p.rows()};
487 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
488 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
489 return g.sparseView();
491 [&](
const DenseVector& x_p,
const DenseVector& y_p,
492 const DenseVector& z_p) -> SparseMatrix {
493 auto x = x_p.segment(0, num_vars);
495 auto z = z_p.segment(0, num_ineq);
504 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
505 triplets.reserve(x_p.rows());
506 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
507 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
508 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
513 auto H_c = matrices.H_c(x, y, z);
514 H_c.conservativeResize(x_p.rows(), x_p.rows());
523 return d2f_dx2 + H_c;
525 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
526 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
527 return SparseMatrix{x_p.rows(), x_p.rows()};
529 [&](
const DenseVector& x_p) -> DenseVector {
530 auto x = x_p.segment(0, num_vars);
531 auto p_e = x_p.segment(num_vars, num_eq);
532 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
537 return matrices.c_e(x) - p_e + n_e;
539 [&](
const DenseVector& x_p) -> SparseMatrix {
540 auto x = x_p.segment(0, num_vars);
546 SparseMatrix A_e = matrices.A_e(x);
548 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
549 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
551 append_as_triplets(triplets, 0, 0, {A_e});
552 append_diagonal_as_triplets(
553 triplets, 0, num_vars,
554 DenseVector::Constant(num_eq, Scalar(-1)).eval());
555 append_diagonal_as_triplets(
556 triplets, 0, num_vars + num_eq,
557 DenseVector::Constant(num_eq, Scalar(1)).eval());
559 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
560 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
563 [&](
const DenseVector& x_p) -> DenseVector {
564 auto x = x_p.segment(0, num_vars);
565 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
566 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
575 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
576 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
577 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
578 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
581 [&](
const DenseVector& x_p) -> SparseMatrix {
582 auto x = x_p.segment(0, num_vars);
592 SparseMatrix A_i = matrices.A_i(x);
594 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
595 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
598 append_as_triplets(triplets, 0, 0, {A_i});
601 append_diagonal_as_triplets(
602 triplets, num_ineq, num_vars,
603 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
606 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
609 SparseMatrix Z_col3{2 * num_eq, num_ineq};
610 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
611 {(-I_ineq).eval(), Z_col3, I_ineq});
614 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
615 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
616 {I_ineq, Z_col4, I_ineq});
618 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
619 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
624#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
625 Eigen::ArrayX<bool> fr_bound_constraint_mask{2 * num_eq + 3 * num_ineq};
626 fr_bound_constraint_mask.segment(0, num_ineq) = bound_constraint_mask;
627 fr_bound_constraint_mask.segment(num_ineq, 2 * num_eq + 2 * num_ineq) =
true;
631 ipm<Scalar>(fr_matrix_callbacks, iteration_callbacks, options,
true,
632#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
633 fr_bound_constraint_mask,
635 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
637 x = fr_x.segment(0, x.rows());
639 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
640 const DenseVector s_old = s;
650 const DenseVector c_i = matrices.c_i(x);
651 s = (c_i.array() >= Scalar(1e-2)).select(c_i, Scalar(1));
665 const DenseVector Σ = z.cwiseQuotient(s_old);
666 const DenseVector p_s = s - s_old;
667 z += μ * s_old.cwiseInverse() - z - Σ.cwiseProduct(p_s);
668 if ((z.array() <= Scalar(0)).any() || (z.array() > Scalar(1e3)).any()) {
672 return ExitStatus::SUCCESS;
673 }
else if (status == ExitStatus::SUCCESS) {
681 s = fr_s.segment(0, s.rows());
683 DenseVector c_e = matrices.c_e(x);
684 if (matrices.scaling.c_e.size() > 0) {
685 c_e = matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e);
688 DenseVector c_i = matrices.c_i(x);
689 if (matrices.scaling.c_i.size() > 0) {
690 c_i = matrices.scaling.c_i.cwiseInverse().cwiseProduct(c_i);
695 const DenseVector c_i_violation = (-c_i).cwiseMax(Scalar(0));
697 const bool c_e_violated =
698 c_e.template lpNorm<Eigen::Infinity>() > Scalar(options.tolerance);
699 const bool c_i_violated = c_i_violation.template lpNorm<Eigen::Infinity>() >
700 Scalar(options.tolerance);
702 if (c_e_violated || c_i_violated) {
703 if (options.diagnostics) {
705 print_c_e_local_infeasibility_error(c_e, Scalar(options.tolerance));
708 print_c_i_local_infeasibility_error(c_i, Scalar(options.tolerance));
712 return ExitStatus::LOCALLY_INFEASIBLE;
715 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
717 s = fr_s.segment(0, s.rows());
718 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
724#include "sleipnir/optimization/solver/ipm.hpp"