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);
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;
165 DenseVector fr_s = DenseVector::Ones(2 * num_eq);
167 DenseVector fr_y = DenseVector::Zero(num_eq);
170 DenseVector fr_z{2 * num_eq};
171 fr_z << fr_μ * p_e_0.cwiseInverse(), fr_μ * n_e_0.cwiseInverse();
176 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
177 DenseVector::Ones(2 * num_eq)};
179 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
180 static_cast<int>(fr_x.rows()),
181 static_cast<int>(fr_y.rows()),
182 static_cast<int>(fr_z.rows()),
183 [&](
const DenseVector& x_p) -> Scalar {
184 auto x = x_p.segment(0, num_vars);
191 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
192 ζ / Scalar(2) * diff.transpose() * D_r * diff;
194 [&](
const DenseVector& x_p) -> SparseVector {
195 auto x = x_p.segment(0, num_vars);
202 DenseVector g{x_p.rows()};
203 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
204 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
205 return g.sparseView();
207 [&](
const DenseVector& x_p,
const DenseVector& y_p,
208 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
209 auto x = x_p.segment(0, num_vars);
217 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
218 triplets.reserve(x_p.rows());
219 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
220 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
221 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
226 auto H_c = matrices.H_c(x, y);
227 H_c.resize(x_p.rows(), x_p.rows());
234 return d2f_dx2 + H_c;
236 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
237 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
238 return SparseMatrix{x_p.rows(), x_p.rows()};
240 [&](
const DenseVector& x_p) -> DenseVector {
241 auto x = x_p.segment(0, num_vars);
242 auto p_e = x_p.segment(num_vars, num_eq);
243 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
248 return matrices.c_e(x) - p_e + n_e;
250 [&](
const DenseVector& x_p) -> SparseMatrix {
251 auto x = x_p.segment(0, num_vars);
257 SparseMatrix A_e = matrices.A_e(x);
259 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
260 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
262 append_as_triplets(triplets, 0, 0, {A_e});
263 append_diagonal_as_triplets(
264 triplets, 0, num_vars,
265 DenseVector::Constant(num_eq, Scalar(-1)).eval());
266 append_diagonal_as_triplets(
267 triplets, 0, num_vars + num_eq,
268 DenseVector::Constant(num_eq, Scalar(1)).eval());
270 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
271 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
274 [&](
const DenseVector& x_p) -> DenseVector {
279 return x_p.segment(num_vars, 2 * num_eq);
281 [&](
const DenseVector& x_p) -> SparseMatrix {
287 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
288 triplets.reserve(2 * num_eq);
290 append_diagonal_as_triplets(
291 triplets, 0, num_vars,
292 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
294 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
295 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
300 auto status = interior_point<Scalar>(fr_matrix_callbacks, iteration_callbacks,
302#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
305 fr_x, fr_s, fr_y, fr_z, fr_μ);
307 x = fr_x.segment(0, x.rows());
309 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
310 auto g = matrices.g(x);
311 auto A_e = matrices.A_e(x);
313 y = lagrange_multiplier_estimate(g, A_e);
315 return ExitStatus::SUCCESS;
316 }
else if (status == ExitStatus::SUCCESS) {
317 return ExitStatus::LOCALLY_INFEASIBLE;
319 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
341template <
typename Scalar>
342ExitStatus feasibility_restoration(
343 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
344 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
346 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
347 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
348 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
349 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar μ) {
370 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
371 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
372 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
373 using SparseVector = Eigen::SparseVector<Scalar>;
377 const auto& matrices = matrix_callbacks;
378 const auto& num_vars = matrices.num_decision_variables;
379 const auto& num_eq = matrices.num_equality_constraints;
380 const auto& num_ineq = matrices.num_inequality_constraints;
382 constexpr Scalar ρ(1e3);
384 const DenseVector c_e = matrices.c_e(x);
385 const DenseVector c_i = matrices.c_i(x);
387 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
388 (c_i - s).
template lpNorm<Eigen::Infinity>()});
389 const Scalar ζ = sqrt(fr_μ);
392 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
393 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, fr_μ);
396 const DiagonalMatrix D_r =
397 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
399 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
400 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
402 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
403 fr_s.segment(0, s.rows()) = s;
404 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq).setOnes();
406 DenseVector fr_y = DenseVector::Zero(c_e.rows());
409 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
410 fr_z << fr_μ * s.cwiseInverse(), fr_μ * p_e_0.cwiseInverse(),
411 fr_μ * n_e_0.cwiseInverse(), fr_μ * p_i_0.cwiseInverse(),
412 fr_μ * n_i_0.cwiseInverse();
417 DenseVector fr_d_c_i{c_i.rows() + 2 * num_eq + 2 * num_ineq};
418 fr_d_c_i << matrices.scaling.c_i,
419 DenseVector::Ones(2 * num_eq + 2 * num_ineq);
420 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
423 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
424 static_cast<int>(fr_x.rows()),
425 static_cast<int>(fr_y.rows()),
426 static_cast<int>(fr_z.rows()),
427 [&](
const DenseVector& x_p) -> Scalar {
428 auto x = x_p.segment(0, num_vars);
434 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
437 ζ / Scalar(2) * diff.transpose() * D_r * diff;
439 [&](
const DenseVector& x_p) -> SparseVector {
440 auto x = x_p.segment(0, num_vars);
449 DenseVector g{x_p.rows()};
450 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
451 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
452 return g.sparseView();
454 [&](
const DenseVector& x_p,
const DenseVector& y_p,
455 const DenseVector& z_p) -> SparseMatrix {
456 auto x = x_p.segment(0, num_vars);
458 auto z = z_p.segment(0, num_ineq);
467 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
468 triplets.reserve(x_p.rows());
469 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
470 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
471 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
476 auto H_c = matrices.H_c(x, y, z);
477 H_c.resize(x_p.rows(), x_p.rows());
486 return d2f_dx2 + H_c;
488 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
489 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
490 return SparseMatrix{x_p.rows(), x_p.rows()};
492 [&](
const DenseVector& x_p) -> DenseVector {
493 auto x = x_p.segment(0, num_vars);
494 auto p_e = x_p.segment(num_vars, num_eq);
495 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
500 return matrices.c_e(x) - p_e + n_e;
502 [&](
const DenseVector& x_p) -> SparseMatrix {
503 auto x = x_p.segment(0, num_vars);
509 SparseMatrix A_e = matrices.A_e(x);
511 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
512 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
514 append_as_triplets(triplets, 0, 0, {A_e});
515 append_diagonal_as_triplets(
516 triplets, 0, num_vars,
517 DenseVector::Constant(num_eq, Scalar(-1)).eval());
518 append_diagonal_as_triplets(
519 triplets, 0, num_vars + num_eq,
520 DenseVector::Constant(num_eq, Scalar(1)).eval());
522 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
523 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
526 [&](
const DenseVector& x_p) -> DenseVector {
527 auto x = x_p.segment(0, num_vars);
528 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
529 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
538 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
539 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
540 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
541 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
544 [&](
const DenseVector& x_p) -> SparseMatrix {
545 auto x = x_p.segment(0, num_vars);
555 SparseMatrix A_i = matrices.A_i(x);
557 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
558 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
561 append_as_triplets(triplets, 0, 0, {A_i});
564 append_diagonal_as_triplets(
565 triplets, num_ineq, num_vars,
566 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
569 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
572 SparseMatrix Z_col3{2 * num_eq, num_ineq};
573 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
574 {(-I_ineq).eval(), Z_col3, I_ineq});
577 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
578 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
579 {I_ineq, Z_col4, I_ineq});
581 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
582 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
587 auto status = interior_point<Scalar>(fr_matrix_callbacks, iteration_callbacks,
589#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
592 fr_x, fr_s, fr_y, fr_z, fr_μ);
594 x = fr_x.segment(0, x.rows());
595 s = fr_s.segment(0, s.rows());
597 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
598 auto g = matrices.g(x);
599 auto A_e = matrices.A_e(x);
600 auto A_i = matrices.A_i(x);
602 auto [y_estimate, z_estimate] =
603 lagrange_multiplier_estimate(g, A_e, A_i, s, μ);
607 return ExitStatus::SUCCESS;
608 }
else if (status == ExitStatus::SUCCESS) {
609 return ExitStatus::LOCALLY_INFEASIBLE;
611 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
617#include "sleipnir/optimization/solver/interior_point.hpp"