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& μ,
int& iterations);
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)};
114template <
typename Scalar>
115ExitStatus feasibility_restoration(
116 const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
117 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
119 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
120 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
int& iterations) {
137 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
138 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
139 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
140 using SparseVector = Eigen::SparseVector<Scalar>;
144 const auto& matrices = matrix_callbacks;
145 const auto& num_vars = matrices.num_decision_variables;
146 const auto& num_eq = matrices.num_equality_constraints;
148 constexpr Scalar ρ(1e3);
149 const Scalar μ(options.tolerance / 10.0);
151 const DenseVector c_e = matrices.c_e(x);
153 Scalar fr_μ = std::max(μ, c_e.template lpNorm<Eigen::Infinity>());
154 const Scalar ζ = sqrt(fr_μ);
157 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
160 const DiagonalMatrix D_r =
161 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
163 DenseVector fr_x{num_vars + 2 * num_eq};
164 fr_x << x, p_e_0, n_e_0;
166 DenseVector fr_s = DenseVector::Ones(2 * num_eq);
168 DenseVector fr_y = DenseVector::Zero(num_eq);
171 DenseVector fr_z{2 * num_eq};
172 fr_z << fr_μ * p_e_0.cwiseInverse(), fr_μ * n_e_0.cwiseInverse();
177 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
178 DenseVector::Ones(2 * num_eq)};
180 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
181 static_cast<int>(fr_x.rows()),
182 static_cast<int>(fr_y.rows()),
183 static_cast<int>(fr_z.rows()),
184 [&](
const DenseVector& x_p) -> Scalar {
185 auto x = x_p.segment(0, num_vars);
192 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
193 ζ / Scalar(2) * diff.transpose() * D_r * diff;
195 [&](
const DenseVector& x_p) -> SparseVector {
196 auto x = x_p.segment(0, num_vars);
203 DenseVector g{x_p.rows()};
204 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
205 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
206 return g.sparseView();
208 [&](
const DenseVector& x_p,
const DenseVector& y_p,
209 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
210 auto x = x_p.segment(0, num_vars);
218 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
219 triplets.reserve(x_p.rows());
220 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
221 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
222 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
227 auto H_c = matrices.H_c(x, y);
228 H_c.resize(x_p.rows(), x_p.rows());
235 return d2f_dx2 + H_c;
237 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
238 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
239 return SparseMatrix{x_p.rows(), x_p.rows()};
241 [&](
const DenseVector& x_p) -> DenseVector {
242 auto x = x_p.segment(0, num_vars);
243 auto p_e = x_p.segment(num_vars, num_eq);
244 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
249 return matrices.c_e(x) - p_e + n_e;
251 [&](
const DenseVector& x_p) -> SparseMatrix {
252 auto x = x_p.segment(0, num_vars);
258 SparseMatrix A_e = matrices.A_e(x);
260 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
261 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
263 append_as_triplets(triplets, 0, 0, {A_e});
264 append_diagonal_as_triplets(
265 triplets, 0, num_vars,
266 DenseVector::Constant(num_eq, Scalar(-1)).eval());
267 append_diagonal_as_triplets(
268 triplets, 0, num_vars + num_eq,
269 DenseVector::Constant(num_eq, Scalar(1)).eval());
271 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
272 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
275 [&](
const DenseVector& x_p) -> DenseVector {
280 return x_p.segment(num_vars, 2 * num_eq);
282 [&](
const DenseVector& x_p) -> SparseMatrix {
288 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
289 triplets.reserve(2 * num_eq);
291 append_diagonal_as_triplets(
292 triplets, 0, num_vars,
293 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
295 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
296 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
301 auto status = interior_point<Scalar>(
302 fr_matrix_callbacks, iteration_callbacks, options,
true,
303#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
304 Eigen::ArrayX<bool>::Constant(2 * num_eq,
true),
306 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
308 x = fr_x.segment(0, x.rows());
310 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
311 auto g = matrices.g(x);
312 auto A_e = matrices.A_e(x);
314 y = lagrange_multiplier_estimate(g, A_e);
316 return ExitStatus::SUCCESS;
317 }
else if (status == ExitStatus::SUCCESS) {
318 return ExitStatus::LOCALLY_INFEASIBLE;
320 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
343template <
typename Scalar>
344ExitStatus feasibility_restoration(
345 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
346 std::span<std::function<
bool(
const IterationInfo<Scalar>& info)>>
348 const Options& options,
349#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
350 const Eigen::ArrayX<bool>& bound_constraint_mask,
352 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
353 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
354 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
355 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar μ,
int& iterations) {
376 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
377 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
378 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
379 using SparseVector = Eigen::SparseVector<Scalar>;
383 const auto& matrices = matrix_callbacks;
384 const auto& num_vars = matrices.num_decision_variables;
385 const auto& num_eq = matrices.num_equality_constraints;
386 const auto& num_ineq = matrices.num_inequality_constraints;
388 constexpr Scalar ρ(1e3);
390 const DenseVector c_e = matrices.c_e(x);
391 const DenseVector c_i = matrices.c_i(x);
393 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
394 (c_i - s).
template lpNorm<Eigen::Infinity>()});
395 const Scalar ζ = sqrt(fr_μ);
398 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
399 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, fr_μ);
402 const DiagonalMatrix D_r =
403 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
405 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
406 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
408 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
409 fr_s.segment(0, s.rows()) = s;
410 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq).setOnes();
412 DenseVector fr_y = DenseVector::Zero(c_e.rows());
415 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
416 fr_z << fr_μ * s.cwiseInverse(), fr_μ * p_e_0.cwiseInverse(),
417 fr_μ * n_e_0.cwiseInverse(), fr_μ * p_i_0.cwiseInverse(),
418 fr_μ * n_i_0.cwiseInverse();
423 DenseVector fr_d_c_i{c_i.rows() + 2 * num_eq + 2 * num_ineq};
424 fr_d_c_i << matrices.scaling.c_i,
425 DenseVector::Ones(2 * num_eq + 2 * num_ineq);
426 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
429 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
430 static_cast<int>(fr_x.rows()),
431 static_cast<int>(fr_y.rows()),
432 static_cast<int>(fr_z.rows()),
433 [&](
const DenseVector& x_p) -> Scalar {
434 auto x = x_p.segment(0, num_vars);
440 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
443 ζ / Scalar(2) * diff.transpose() * D_r * diff;
445 [&](
const DenseVector& x_p) -> SparseVector {
446 auto x = x_p.segment(0, num_vars);
455 DenseVector g{x_p.rows()};
456 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
457 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
458 return g.sparseView();
460 [&](
const DenseVector& x_p,
const DenseVector& y_p,
461 const DenseVector& z_p) -> SparseMatrix {
462 auto x = x_p.segment(0, num_vars);
464 auto z = z_p.segment(0, num_ineq);
473 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
474 triplets.reserve(x_p.rows());
475 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
476 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
477 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
482 auto H_c = matrices.H_c(x, y, z);
483 H_c.resize(x_p.rows(), x_p.rows());
492 return d2f_dx2 + H_c;
494 [&](
const DenseVector& x_p, [[maybe_unused]]
const DenseVector& y_p,
495 [[maybe_unused]]
const DenseVector& z_p) -> SparseMatrix {
496 return SparseMatrix{x_p.rows(), x_p.rows()};
498 [&](
const DenseVector& x_p) -> DenseVector {
499 auto x = x_p.segment(0, num_vars);
500 auto p_e = x_p.segment(num_vars, num_eq);
501 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
506 return matrices.c_e(x) - p_e + n_e;
508 [&](
const DenseVector& x_p) -> SparseMatrix {
509 auto x = x_p.segment(0, num_vars);
515 SparseMatrix A_e = matrices.A_e(x);
517 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
518 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
520 append_as_triplets(triplets, 0, 0, {A_e});
521 append_diagonal_as_triplets(
522 triplets, 0, num_vars,
523 DenseVector::Constant(num_eq, Scalar(-1)).eval());
524 append_diagonal_as_triplets(
525 triplets, 0, num_vars + num_eq,
526 DenseVector::Constant(num_eq, Scalar(1)).eval());
528 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
529 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
532 [&](
const DenseVector& x_p) -> DenseVector {
533 auto x = x_p.segment(0, num_vars);
534 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
535 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
544 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
545 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
546 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
547 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
550 [&](
const DenseVector& x_p) -> SparseMatrix {
551 auto x = x_p.segment(0, num_vars);
561 SparseMatrix A_i = matrices.A_i(x);
563 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
564 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
567 append_as_triplets(triplets, 0, 0, {A_i});
570 append_diagonal_as_triplets(
571 triplets, num_ineq, num_vars,
572 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
575 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
578 SparseMatrix Z_col3{2 * num_eq, num_ineq};
579 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
580 {(-I_ineq).eval(), Z_col3, I_ineq});
583 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
584 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
585 {I_ineq, Z_col4, I_ineq});
587 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
588 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
593#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
594 Eigen::ArrayX<bool> fr_bound_constraint_mask{2 * num_eq + 3 * num_ineq};
595 fr_bound_constraint_mask.segment(0, num_ineq) = bound_constraint_mask;
596 fr_bound_constraint_mask.segment(num_ineq, 2 * num_eq + 2 * num_ineq) =
true;
599 auto status = interior_point<Scalar>(
600 fr_matrix_callbacks, iteration_callbacks, options,
true,
601#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
602 fr_bound_constraint_mask,
604 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
606 x = fr_x.segment(0, x.rows());
607 s = fr_s.segment(0, s.rows());
609 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
610 auto g = matrices.g(x);
611 auto A_e = matrices.A_e(x);
612 auto A_i = matrices.A_i(x);
614 auto [y_estimate, z_estimate] =
615 lagrange_multiplier_estimate(g, A_e, A_i, s, μ);
619 return ExitStatus::SUCCESS;
620 }
else if (status == ExitStatus::SUCCESS) {
621 return ExitStatus::LOCALLY_INFEASIBLE;
623 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
629#include "sleipnir/optimization/solver/interior_point.hpp"