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"
27template <
typename Scalar>
28ExitStatus interior_point(
29 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
30 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
32 const Options& options,
bool in_feasibility_restoration,
33#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
34 const Eigen::ArrayX<bool>& bound_constraint_mask,
36 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
37 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
38 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
39 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar& μ,
int& iterations);
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;
169 DenseVector fr_s = DenseVector::Ones(2 * num_eq);
171 DenseVector fr_y = DenseVector::Zero(num_eq);
174 DenseVector fr_z{2 * num_eq};
175 fr_z << fr_μ * p_e_0.cwiseInverse(), fr_μ * n_e_0.cwiseInverse();
180 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
181 DenseVector::Ones(2 * num_eq)};
183 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
184 static_cast<int>(fr_x.rows()),
185 static_cast<int>(fr_y.rows()),
186 static_cast<int>(fr_z.rows()),
187 [&](
const DenseVector& x_p) -> Scalar {
188 auto x = x_p.segment(0, num_vars);
195 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
196 ζ / Scalar(2) * diff.transpose() * D_r * diff;
198 [&](
const DenseVector& x_p) -> SparseVector {
199 auto x = x_p.segment(0, num_vars);
206 DenseVector g{x_p.rows()};
207 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
208 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
209 return g.sparseView();
211 [&](
const DenseVector& x_p,
const DenseVector& y_p,
212 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
213 auto x = x_p.segment(0, num_vars);
221 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
222 triplets.reserve(x_p.rows());
223 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
224 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
225 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
230 auto H_c = matrices.H_c(x, y);
231 H_c.resize(x_p.rows(), x_p.rows());
238 return d2f_dx2 + H_c;
240 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
241 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
242 return SparseMatrix{x_p.rows(), x_p.rows()};
244 [&](
const DenseVector& x_p) -> DenseVector {
245 auto x = x_p.segment(0, num_vars);
246 auto p_e = x_p.segment(num_vars, num_eq);
247 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
252 return matrices.c_e(x) - p_e + n_e;
254 [&](
const DenseVector& x_p) -> SparseMatrix {
255 auto x = x_p.segment(0, num_vars);
261 SparseMatrix A_e = matrices.A_e(x);
263 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
264 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
266 append_as_triplets(triplets, 0, 0, {A_e});
267 append_diagonal_as_triplets(
268 triplets, 0, num_vars,
269 DenseVector::Constant(num_eq, Scalar(-1)).eval());
270 append_diagonal_as_triplets(
271 triplets, 0, num_vars + num_eq,
272 DenseVector::Constant(num_eq, Scalar(1)).eval());
274 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
275 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
278 [&](
const DenseVector& x_p) -> DenseVector {
283 return x_p.segment(num_vars, 2 * num_eq);
285 [&](
const DenseVector& x_p) -> SparseMatrix {
291 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
292 triplets.reserve(2 * num_eq);
294 append_diagonal_as_triplets(
295 triplets, 0, num_vars,
296 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
298 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
299 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
304 auto status = interior_point<Scalar>(
305 fr_matrix_callbacks, iteration_callbacks, options,
true,
306#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
307 Eigen::ArrayX<bool>::Constant(2 * num_eq,
true),
309 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
311 x = fr_x.segment(0, x.rows());
313 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
314 auto g = matrices.g(x);
315 auto A_e = matrices.A_e(x);
317 y = lagrange_multiplier_estimate(g, A_e);
319 return ExitStatus::SUCCESS;
320 }
else if (status == ExitStatus::SUCCESS) {
321 return ExitStatus::LOCALLY_INFEASIBLE;
323 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
346template <
typename Scalar>
347ExitStatus feasibility_restoration(
348 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
349 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
351 const Options& options,
352#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
353 const Eigen::ArrayX<bool>& bound_constraint_mask,
355 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
356 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
357 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
358 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar μ,
int& iterations) {
379 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
380 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
381 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
382 using SparseVector = Eigen::SparseVector<Scalar>;
386 const auto& matrices = matrix_callbacks;
387 const auto& num_vars = matrices.num_decision_variables;
388 const auto& num_eq = matrices.num_equality_constraints;
389 const auto& num_ineq = matrices.num_inequality_constraints;
391 constexpr Scalar ρ(1e3);
393 const DenseVector c_e = matrices.c_e(x);
394 const DenseVector c_i = matrices.c_i(x);
396 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
397 (c_i - s).
template lpNorm<Eigen::Infinity>()});
398 const Scalar ζ = sqrt(fr_μ);
401 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
402 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, fr_μ);
405 const DiagonalMatrix D_r =
406 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
408 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
409 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
411 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
412 fr_s.segment(0, s.rows()) = s;
413 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq).setOnes();
415 DenseVector fr_y = DenseVector::Zero(c_e.rows());
418 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
419 fr_z << fr_μ * s.cwiseInverse(), fr_μ * p_e_0.cwiseInverse(),
420 fr_μ * n_e_0.cwiseInverse(), fr_μ * p_i_0.cwiseInverse(),
421 fr_μ * n_i_0.cwiseInverse();
426 DenseVector fr_d_c_i{c_i.rows() + 2 * num_eq + 2 * num_ineq};
427 fr_d_c_i << matrices.scaling.c_i,
428 DenseVector::Ones(2 * num_eq + 2 * num_ineq);
429 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
432 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
433 static_cast<int>(fr_x.rows()),
434 static_cast<int>(fr_y.rows()),
435 static_cast<int>(fr_z.rows()),
436 [&](
const DenseVector& x_p) -> Scalar {
437 auto x = x_p.segment(0, num_vars);
443 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
446 ζ / Scalar(2) * diff.transpose() * D_r * diff;
448 [&](
const DenseVector& x_p) -> SparseVector {
449 auto x = x_p.segment(0, num_vars);
458 DenseVector g{x_p.rows()};
459 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
460 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
461 return g.sparseView();
463 [&](
const DenseVector& x_p,
const DenseVector& y_p,
464 const DenseVector& z_p) -> SparseMatrix {
465 auto x = x_p.segment(0, num_vars);
467 auto z = z_p.segment(0, num_ineq);
476 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
477 triplets.reserve(x_p.rows());
478 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
479 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
480 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
485 auto H_c = matrices.H_c(x, y, z);
486 H_c.resize(x_p.rows(), x_p.rows());
495 return d2f_dx2 + H_c;
497 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
498 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
499 return SparseMatrix{x_p.rows(), x_p.rows()};
501 [&](
const DenseVector& x_p) -> DenseVector {
502 auto x = x_p.segment(0, num_vars);
503 auto p_e = x_p.segment(num_vars, num_eq);
504 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
509 return matrices.c_e(x) - p_e + n_e;
511 [&](
const DenseVector& x_p) -> SparseMatrix {
512 auto x = x_p.segment(0, num_vars);
518 SparseMatrix A_e = matrices.A_e(x);
520 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
521 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
523 append_as_triplets(triplets, 0, 0, {A_e});
524 append_diagonal_as_triplets(
525 triplets, 0, num_vars,
526 DenseVector::Constant(num_eq, Scalar(-1)).eval());
527 append_diagonal_as_triplets(
528 triplets, 0, num_vars + num_eq,
529 DenseVector::Constant(num_eq, Scalar(1)).eval());
531 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
532 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
535 [&](
const DenseVector& x_p) -> DenseVector {
536 auto x = x_p.segment(0, num_vars);
537 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
538 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
547 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
548 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
549 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
550 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
553 [&](
const DenseVector& x_p) -> SparseMatrix {
554 auto x = x_p.segment(0, num_vars);
564 SparseMatrix A_i = matrices.A_i(x);
566 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
567 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
570 append_as_triplets(triplets, 0, 0, {A_i});
573 append_diagonal_as_triplets(
574 triplets, num_ineq, num_vars,
575 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
578 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
581 SparseMatrix Z_col3{2 * num_eq, num_ineq};
582 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
583 {(-I_ineq).eval(), Z_col3, I_ineq});
586 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
587 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
588 {I_ineq, Z_col4, I_ineq});
590 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
591 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
596#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
597 Eigen::ArrayX<bool> fr_bound_constraint_mask{2 * num_eq + 3 * num_ineq};
598 fr_bound_constraint_mask.segment(0, num_ineq) = bound_constraint_mask;
599 fr_bound_constraint_mask.segment(num_ineq, 2 * num_eq + 2 * num_ineq) =
true;
602 auto status = interior_point<Scalar>(
603 fr_matrix_callbacks, iteration_callbacks, options,
true,
604#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
605 fr_bound_constraint_mask,
607 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
609 x = fr_x.segment(0, x.rows());
610 s = fr_s.segment(0, s.rows());
612 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
613 auto g = matrices.g(x);
614 auto A_e = matrices.A_e(x);
615 auto A_i = matrices.A_i(x);
617 auto [y_estimate, z_estimate] =
618 lagrange_multiplier_estimate(g, A_e, A_i, s, μ);
622 return ExitStatus::SUCCESS;
623 }
else if (status == ExitStatus::SUCCESS) {
624 return ExitStatus::LOCALLY_INFEASIBLE;
626 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
632#include "sleipnir/optimization/solver/interior_point.hpp"