12#include <gch/small_vector.hpp>
14#include "sleipnir/optimization/solver/exit_status.hpp"
15#include "sleipnir/optimization/solver/interior_point_matrix_callbacks.hpp"
16#include "sleipnir/optimization/solver/iteration_info.hpp"
17#include "sleipnir/optimization/solver/options.hpp"
18#include "sleipnir/optimization/solver/sqp_matrix_callbacks.hpp"
19#include "sleipnir/optimization/solver/util/append_as_triplets.hpp"
20#include "sleipnir/optimization/solver/util/lagrange_multiplier_estimate.hpp"
24template <
typename Scalar>
25ExitStatus interior_point(
26 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
27 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
29 const Options& options,
bool in_feasibility_restoration,
30#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
31 const Eigen::ArrayX<bool>& bound_constraint_mask,
33 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
34 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
35 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
36 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar& μ);
46template <
typename Scalar>
47std::tuple<Eigen::Vector<Scalar, Eigen::Dynamic>,
48 Eigen::Vector<Scalar, Eigen::Dynamic>>
49compute_p_n(
const Eigen::Vector<Scalar, Eigen::Dynamic>& c, Scalar ρ,
81 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
85 DenseVector p{c.rows()};
86 DenseVector n{c.rows()};
87 for (
int row = 0; row < p.rows(); ++row) {
89 Scalar _b = ρ * c[row] - μ;
90 Scalar _c = -μ * c[row] / Scalar(2);
92 n[row] = (-_b + sqrt(_b * _b - Scalar(4) * _a * _c)) / (Scalar(2) * _a);
93 p[row] = c[row] + n[row];
96 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& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
119 Eigen::Vector<Scalar, Eigen::Dynamic>& y) {
136 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
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 / 10.0);
149 const Scalar ζ = sqrt(μ);
151 const DenseVector c_e = matrices.c_e(x);
154 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, μ);
157 const DiagonalMatrix D_r =
158 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
160 DenseVector fr_x{num_vars + 2 * num_eq};
161 fr_x << x, p_e_0, n_e_0;
163 DenseVector fr_s = DenseVector::Ones(2 * num_eq);
165 DenseVector fr_y = DenseVector::Zero(num_eq);
167 DenseVector fr_z{2 * num_eq};
168 fr_z << μ * p_e_0.cwiseInverse(), μ * n_e_0.cwiseInverse();
170 Scalar fr_μ = std::max(μ, c_e.template lpNorm<Eigen::Infinity>());
172 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
173 static_cast<int>(fr_x.rows()),
174 static_cast<int>(fr_y.rows()),
175 static_cast<int>(fr_z.rows()),
176 [&](
const DenseVector& x_p) -> Scalar {
177 auto x = x_p.segment(0, num_vars);
184 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
185 ζ / Scalar(2) * diff.transpose() * D_r * diff;
187 [&](
const DenseVector& x_p) -> SparseVector {
188 auto x = x_p.segment(0, num_vars);
195 DenseVector g{x_p.rows()};
196 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
197 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
198 return g.sparseView();
200 [&](
const DenseVector& x_p,
const DenseVector& y_p,
201 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
202 auto x = x_p.segment(0, num_vars);
210 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
211 triplets.reserve(x_p.rows());
212 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
213 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
214 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
219 auto H_c = matrices.H_c(x, y);
220 H_c.resize(x_p.rows(), x_p.rows());
227 return d2f_dx2 + H_c;
229 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
230 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
231 return SparseMatrix{x_p.rows(), x_p.rows()};
233 [&](
const DenseVector& x_p) -> DenseVector {
234 auto x = x_p.segment(0, num_vars);
235 auto p_e = x_p.segment(num_vars, num_eq);
236 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
241 return matrices.c_e(x) - p_e + n_e;
243 [&](
const DenseVector& x_p) -> SparseMatrix {
244 auto x = x_p.segment(0, num_vars);
250 SparseMatrix A_e = matrices.A_e(x);
252 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
253 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
255 append_as_triplets(triplets, 0, 0, {A_e});
256 append_diagonal_as_triplets(
257 triplets, 0, num_vars,
258 DenseVector::Constant(num_eq, Scalar(-1)).eval());
259 append_diagonal_as_triplets(
260 triplets, 0, num_vars + num_eq,
261 DenseVector::Constant(num_eq, Scalar(1)).eval());
263 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
264 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
267 [&](
const DenseVector& x_p) -> DenseVector {
272 return x_p.segment(num_vars, 2 * num_eq);
274 [&](
const DenseVector& x_p) -> SparseMatrix {
280 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
281 triplets.reserve(2 * num_eq);
283 append_diagonal_as_triplets(
284 triplets, 0, num_vars,
285 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
287 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
288 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
292 auto status = interior_point<Scalar>(fr_matrix_callbacks, iteration_callbacks,
294#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
297 fr_x, fr_s, fr_y, fr_z, fr_μ);
299 x = fr_x.segment(0, x.rows());
301 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
302 auto g = matrices.g(x);
303 auto A_e = matrices.A_e(x);
305 y = lagrange_multiplier_estimate(g, A_e);
307 return ExitStatus::SUCCESS;
308 }
else if (status == ExitStatus::SUCCESS) {
309 return ExitStatus::LOCALLY_INFEASIBLE;
311 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
333template <
typename Scalar>
334ExitStatus feasibility_restoration(
335 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
336 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
338 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
339 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
340 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
341 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar μ) {
362 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
363 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
364 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
365 using SparseVector = Eigen::SparseVector<Scalar>;
369 const auto& matrices = matrix_callbacks;
370 const auto& num_vars = matrices.num_decision_variables;
371 const auto& num_eq = matrices.num_equality_constraints;
372 const auto& num_ineq = matrices.num_inequality_constraints;
374 constexpr Scalar ρ(1e3);
375 const Scalar ζ = sqrt(μ);
377 const DenseVector c_e = matrices.c_e(x);
378 const DenseVector c_i = matrices.c_i(x);
381 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, μ);
382 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, μ);
385 const DiagonalMatrix D_r =
386 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
388 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
389 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
391 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
392 fr_s.segment(0, s.rows()) = s;
393 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq).setOnes();
395 DenseVector fr_y = DenseVector::Zero(c_e.rows());
397 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
398 fr_z << z.cwiseMin(ρ), μ * p_e_0.cwiseInverse(), μ * n_e_0.cwiseInverse(),
399 μ * p_i_0.cwiseInverse(), μ * n_i_0.cwiseInverse();
401 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
402 (c_i - s).
template lpNorm<Eigen::Infinity>()});
404 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
405 static_cast<int>(fr_x.rows()),
406 static_cast<int>(fr_y.rows()),
407 static_cast<int>(fr_z.rows()),
408 [&](
const DenseVector& x_p) -> Scalar {
409 auto x = x_p.segment(0, num_vars);
415 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
418 ζ / Scalar(2) * diff.transpose() * D_r * diff;
420 [&](
const DenseVector& x_p) -> SparseVector {
421 auto x = x_p.segment(0, num_vars);
430 DenseVector g{x_p.rows()};
431 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
432 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
433 return g.sparseView();
435 [&](
const DenseVector& x_p,
const DenseVector& y_p,
436 const DenseVector& z_p) -> SparseMatrix {
437 auto x = x_p.segment(0, num_vars);
439 auto z = z_p.segment(0, num_ineq);
448 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
449 triplets.reserve(x_p.rows());
450 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
451 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
452 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
457 auto H_c = matrices.H_c(x, y, z);
458 H_c.resize(x_p.rows(), x_p.rows());
467 return d2f_dx2 + H_c;
469 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
470 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
471 return SparseMatrix{x_p.rows(), x_p.rows()};
473 [&](
const DenseVector& x_p) -> DenseVector {
474 auto x = x_p.segment(0, num_vars);
475 auto p_e = x_p.segment(num_vars, num_eq);
476 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
481 return matrices.c_e(x) - p_e + n_e;
483 [&](
const DenseVector& x_p) -> SparseMatrix {
484 auto x = x_p.segment(0, num_vars);
490 SparseMatrix A_e = matrices.A_e(x);
492 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
493 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
495 append_as_triplets(triplets, 0, 0, {A_e});
496 append_diagonal_as_triplets(
497 triplets, 0, num_vars,
498 DenseVector::Constant(num_eq, Scalar(-1)).eval());
499 append_diagonal_as_triplets(
500 triplets, 0, num_vars + num_eq,
501 DenseVector::Constant(num_eq, Scalar(1)).eval());
503 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
504 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
507 [&](
const DenseVector& x_p) -> DenseVector {
508 auto x = x_p.segment(0, num_vars);
509 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
510 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
519 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
520 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
521 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
522 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
525 [&](
const DenseVector& x_p) -> SparseMatrix {
526 auto x = x_p.segment(0, num_vars);
536 SparseMatrix A_i = matrices.A_i(x);
538 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
539 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
542 append_as_triplets(triplets, 0, 0, {A_i});
545 append_diagonal_as_triplets(
546 triplets, num_ineq, num_vars,
547 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
550 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
553 SparseMatrix Z_col3{2 * num_eq, num_ineq};
554 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
555 {(-I_ineq).eval(), Z_col3, I_ineq});
558 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
559 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
560 {I_ineq, Z_col4, I_ineq});
562 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
563 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
567 auto status = interior_point<Scalar>(fr_matrix_callbacks, iteration_callbacks,
569#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
572 fr_x, fr_s, fr_y, fr_z, fr_μ);
574 x = fr_x.segment(0, x.rows());
575 s = fr_s.segment(0, s.rows());
577 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
578 auto g = matrices.g(x);
579 auto A_e = matrices.A_e(x);
580 auto A_i = matrices.A_i(x);
582 auto [y_estimate, z_estimate] =
583 lagrange_multiplier_estimate(g, A_e, A_i, s, μ);
587 return ExitStatus::SUCCESS;
588 }
else if (status == ExitStatus::SUCCESS) {
589 return ExitStatus::LOCALLY_INFEASIBLE;
591 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
597#include "sleipnir/optimization/solver/interior_point.hpp"