Sleipnir C++ API
Loading...
Searching...
No Matches
feasibility_restoration.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <algorithm>
6#include <cmath>
7#include <functional>
8#include <span>
9#include <tuple>
10#include <utility>
11
12#include <Eigen/Core>
13#include <Eigen/SparseCore>
14#include <gch/small_vector.hpp>
15
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"
24#include "sleipnir/util/print_diagnostics.hpp"
25
26namespace slp {
27
28template <typename Scalar>
29ExitStatus interior_point(
30 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
31 std::span<std::function<bool(const IterationInfo<Scalar>& info)>>
32 iteration_callbacks,
33 const Options& options, bool in_feasibility_restoration,
34#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
35 const Eigen::ArrayX<bool>& bound_constraint_mask,
36#endif
37 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
38 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
39 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
40 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar& μ, int& iterations);
41
50template <typename Scalar>
51std::tuple<Eigen::Vector<Scalar, Eigen::Dynamic>,
52 Eigen::Vector<Scalar, Eigen::Dynamic>>
53compute_p_n(const Eigen::Vector<Scalar, Eigen::Dynamic>& c, Scalar ρ,
54 Scalar μ) {
55 // From equation (33) of [2]:
56 // ______________________
57 // μ − ρ c(x) /(μ − ρ c(x))² μ c(x)
58 // n = −−−−−−−−−− + / (−−−−−−−−−−) + −−−−−− (1)
59 // 2ρ √ ( 2ρ ) 2ρ
60 //
61 // The quadratic formula:
62 // ________
63 // -b + √b² - 4ac
64 // x = −−−−−−−−−−−−−− (2)
65 // 2a
66 //
67 // Rearrange (1) to fit the quadratic formula better:
68 // _________________________
69 // μ - ρ c(x) + √(μ - ρ c(x))² + 2ρ μ c(x)
70 // n = −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
71 // 2ρ
72 //
73 // Solve for coefficients:
74 //
75 // a = ρ (3)
76 // b = ρ c(x) - μ (4)
77 //
78 // -4ac = 2ρ μ c(x)
79 // -4(ρ)c = 2ρ μ c(x)
80 // -4c = 2μ c(x)
81 // c = -μ c(x)/2 (5)
82 //
83 // p = c(x) + n (6)
84
85 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
86
87 using std::sqrt;
88
89 DenseVector p{c.rows()};
90 DenseVector n{c.rows()};
91 for (int row = 0; row < p.rows(); ++row) {
92 Scalar _a = ρ;
93 Scalar _b = ρ * c[row] - μ;
94 Scalar _c = -μ * c[row] / Scalar(2);
95
96 n[row] = (-_b + sqrt(_b * _b - Scalar(4) * _a * _c)) / (Scalar(2) * _a);
97 p[row] = c[row] + n[row];
98 }
99
100 return {std::move(p), std::move(n)};
101}
102
118template <typename Scalar>
119ExitStatus feasibility_restoration(
120 const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
121 std::span<std::function<bool(const IterationInfo<Scalar>& info)>>
122 iteration_callbacks,
123 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
124 Eigen::Vector<Scalar, Eigen::Dynamic>& y, int& iterations) {
125 // Feasibility restoration
126 //
127 // min ρ Σ (pₑ + nₑ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
128 // x
129 // pₑ,nₑ
130 //
131 // s.t. cₑ(x) - pₑ + nₑ = 0
132 // pₑ ≥ 0
133 // nₑ ≥ 0
134 //
135 // where ρ = 1000, ζ = √μ where μ is the barrier parameter, xᵣ is original
136 // iterate before feasibility restoration, and Dᵣ is a scaling matrix defined
137 // by
138 //
139 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
140
141 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
142 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
143 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
144 using SparseVector = Eigen::SparseVector<Scalar>;
145
146 using std::sqrt;
147
148 const auto& matrices = matrix_callbacks;
149 const auto& num_vars = matrices.num_decision_variables;
150 const auto& num_eq = matrices.num_equality_constraints;
151
152 constexpr Scalar ρ(1e3);
153 const Scalar μ(options.tolerance / 10.0);
154
155 const DenseVector c_e = matrices.c_e(x);
156
157 Scalar fr_μ = std::max(μ, c_e.template lpNorm<Eigen::Infinity>());
158 const Scalar ζ = sqrt(fr_μ);
159
160 const auto& x_r = x;
161 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
162
163 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
164 const DiagonalMatrix D_r =
165 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
166
167 DenseVector fr_x{num_vars + 2 * num_eq};
168 fr_x << x, p_e_0, n_e_0;
169
170 DenseVector fr_s = DenseVector::Ones(2 * num_eq);
171
172 DenseVector fr_y = DenseVector::Zero(num_eq);
173
174 // Force the duals to start with perfect complementarity with the slacks
175 DenseVector fr_z{2 * num_eq};
176 fr_z << fr_μ * p_e_0.cwiseInverse(), fr_μ * n_e_0.cwiseInverse();
177
178 // Inherit the parent problem's scaling for the constraints, and use no
179 // scaling for the cost function since it has changed. The new rows introduced
180 // are not scaled.
181 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
182 DenseVector::Ones(2 * num_eq)};
183
184 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
185 static_cast<int>(fr_x.rows()),
186 static_cast<int>(fr_y.rows()),
187 static_cast<int>(fr_z.rows()),
188 [&](const DenseVector& x_p) -> Scalar {
189 auto x = x_p.segment(0, num_vars);
190
191 // Cost function
192 //
193 // ρ Σ (pₑ + nₑ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
194
195 auto diff = x - x_r;
196 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
197 ζ / Scalar(2) * diff.transpose() * D_r * diff;
198 },
199 [&](const DenseVector& x_p) -> SparseVector {
200 auto x = x_p.segment(0, num_vars);
201
202 // Cost function gradient
203 //
204 // [ζDᵣ(x − xᵣ)]
205 // [ ρ ]
206 // [ ρ ]
207 DenseVector g{x_p.rows()};
208 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
209 g.segment(num_vars, 2 * num_eq).setConstant(ρ);
210 return g.sparseView();
211 },
212 [&](const DenseVector& x_p, const DenseVector& y_p,
213 [[maybe_unused]] const DenseVector& z_p) -> SparseMatrix {
214 auto x = x_p.segment(0, num_vars);
215 const auto& y = y_p;
216
217 // Cost function Hessian
218 //
219 // [ζDᵣ 0 0]
220 // [ 0 0 0]
221 // [ 0 0 0]
222 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
223 triplets.reserve(x_p.rows());
224 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
225 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
226 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
227
228 // Constraint part of original problem's Lagrangian Hessian
229 //
230 // −∇ₓₓ²yᵀcₑ(x)
231 auto H_c = matrices.H_c(x, y);
232 H_c.conservativeResize(x_p.rows(), x_p.rows());
233
234 // Lagrangian Hessian
235 //
236 // [ζDᵣ 0 0]
237 // [ 0 0 0] − ∇ₓₓ²yᵀcₑ(x)
238 // [ 0 0 0]
239 return d2f_dx2 + H_c;
240 },
241 [&](const DenseVector& x_p, [[maybe_unused]] const DenseVector& y_p,
242 [[maybe_unused]] const DenseVector& z_p) -> SparseMatrix {
243 return SparseMatrix{x_p.rows(), x_p.rows()};
244 },
245 [&](const DenseVector& x_p) -> DenseVector {
246 auto x = x_p.segment(0, num_vars);
247 auto p_e = x_p.segment(num_vars, num_eq);
248 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
249
250 // Equality constraints
251 //
252 // cₑ(x) - pₑ + nₑ = 0
253 return matrices.c_e(x) - p_e + n_e;
254 },
255 [&](const DenseVector& x_p) -> SparseMatrix {
256 auto x = x_p.segment(0, num_vars);
257
258 // Equality constraint Jacobian
259 //
260 // [Aₑ −I I]
261
262 SparseMatrix A_e = matrices.A_e(x);
263
264 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
265 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
266
267 append_as_triplets(triplets, 0, 0, {A_e});
268 append_diagonal_as_triplets(
269 triplets, 0, num_vars,
270 DenseVector::Constant(num_eq, Scalar(-1)).eval());
271 append_diagonal_as_triplets(
272 triplets, 0, num_vars + num_eq,
273 DenseVector::Constant(num_eq, Scalar(1)).eval());
274
275 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
276 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
277 return A_e_p;
278 },
279 [&](const DenseVector& x_p) -> DenseVector {
280 // Inequality constraints
281 //
282 // pₑ ≥ 0
283 // nₑ ≥ 0
284 return x_p.segment(num_vars, 2 * num_eq);
285 },
286 [&](const DenseVector& x_p) -> SparseMatrix {
287 // Inequality constraint Jacobian
288 //
289 // [0 I 0]
290 // [0 0 I]
291
292 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
293 triplets.reserve(2 * num_eq);
294
295 append_diagonal_as_triplets(
296 triplets, 0, num_vars,
297 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
298
299 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
300 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
301 return A_i_p;
302 },
303 fr_scaling};
304
305 auto status = interior_point<Scalar>(
306 fr_matrix_callbacks, iteration_callbacks, options, true,
307#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
308 Eigen::ArrayX<bool>::Constant(2 * num_eq, true),
309#endif
310 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
311
312 x = fr_x.segment(0, x.rows());
313
314 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
315 auto g = matrices.g(x);
316 auto A_e = matrices.A_e(x);
317
318 y = lagrange_multiplier_estimate(g, A_e);
319
320 return ExitStatus::SUCCESS;
321 } else if (status == ExitStatus::SUCCESS) {
322 // Feasibility restoration converged to a minimizer of the constraint
323 // violation. If the constraint violation is still above the tolerance,
324 // that minimizer is a certificate of local infeasibility. Declaring local
325 // infeasibility anywhere else risks false positives (e.g., a
326 // point-in-time test can reject iterates the solver would otherwise
327 // escape). See section 3.3, p. 14 of [2].
328 DenseVector c_e = matrices.c_e(x);
329 if (matrices.scaling.c_e.size() > 0) {
330 c_e = matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e);
331 }
332
333 if (c_e.template lpNorm<Eigen::Infinity>() > Scalar(options.tolerance)) {
334 if (options.diagnostics) {
335 print_c_e_local_infeasibility_error(c_e, Scalar(options.tolerance));
336 }
337
338 return ExitStatus::LOCALLY_INFEASIBLE;
339 }
340
341 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
342 } else {
343 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
344 }
345}
346
366template <typename Scalar>
367ExitStatus feasibility_restoration(
368 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
369 std::span<std::function<bool(const IterationInfo<Scalar>& info)>>
370 iteration_callbacks,
371 const Options& options,
372#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
373 const Eigen::ArrayX<bool>& bound_constraint_mask,
374#endif
375 Eigen::Vector<Scalar, Eigen::Dynamic>& x,
376 Eigen::Vector<Scalar, Eigen::Dynamic>& s,
377 Eigen::Vector<Scalar, Eigen::Dynamic>& y,
378 Eigen::Vector<Scalar, Eigen::Dynamic>& z, Scalar μ, int& iterations) {
379 // Feasibility restoration
380 //
381 // min ρ Σ (pₑ + nₑ + pᵢ + nᵢ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
382 // x
383 // pₑ,nₑ
384 // pᵢ,nᵢ
385 //
386 // s.t. cₑ(x) - pₑ + nₑ = 0
387 // cᵢ(x) - pᵢ + nᵢ ≥ 0
388 // pₑ ≥ 0
389 // nₑ ≥ 0
390 // pᵢ ≥ 0
391 // nᵢ ≥ 0
392 //
393 // where ρ = 1000, ζ = √μ where μ is the barrier parameter, xᵣ is original
394 // iterate before feasibility restoration, and Dᵣ is a scaling matrix defined
395 // by
396 //
397 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
398
399 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
400 using DiagonalMatrix = Eigen::DiagonalMatrix<Scalar, Eigen::Dynamic>;
401 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
402 using SparseVector = Eigen::SparseVector<Scalar>;
403
404 using std::sqrt;
405
406 const auto& matrices = matrix_callbacks;
407 const auto& num_vars = matrices.num_decision_variables;
408 const auto& num_eq = matrices.num_equality_constraints;
409 const auto& num_ineq = matrices.num_inequality_constraints;
410
411 constexpr Scalar ρ(1e3);
412
413 const DenseVector c_e = matrices.c_e(x);
414 const DenseVector c_i = matrices.c_i(x);
415
416 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
417 (c_i - s).template lpNorm<Eigen::Infinity>()});
418 const Scalar ζ = sqrt(fr_μ);
419
420 const auto& x_r = x;
421 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, fr_μ);
422 const auto [p_i_0, n_i_0] = compute_p_n((c_i - s).eval(), ρ, fr_μ);
423
424 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
425 const DiagonalMatrix D_r =
426 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
427
428 DenseVector fr_x{num_vars + 2 * num_eq + 2 * num_ineq};
429 fr_x << x, p_e_0, n_e_0, p_i_0, n_i_0;
430
431 DenseVector fr_s{s.rows() + 2 * num_eq + 2 * num_ineq};
432 fr_s.segment(0, s.rows()) = s;
433 fr_s.segment(s.rows(), 2 * num_eq + 2 * num_ineq).setOnes();
434
435 DenseVector fr_y = DenseVector::Zero(c_e.rows());
436
437 // Force the duals to start with perfect complementarity with the slacks
438 DenseVector fr_z{c_i.rows() + 2 * num_eq + 2 * num_ineq};
439 fr_z << fr_μ * s.cwiseInverse(), fr_μ * p_e_0.cwiseInverse(),
440 fr_μ * n_e_0.cwiseInverse(), fr_μ * p_i_0.cwiseInverse(),
441 fr_μ * n_i_0.cwiseInverse();
442
443 // Inherit the parent problem's scaling for the constraints, and use no
444 // scaling for the cost function since it has changed. The new rows introduced
445 // are not scaled.
446 DenseVector fr_d_c_i{c_i.rows() + 2 * num_eq + 2 * num_ineq};
447 fr_d_c_i << matrices.scaling.c_i,
448 DenseVector::Ones(2 * num_eq + 2 * num_ineq);
449 const ProblemScaling<Scalar> fr_scaling{Scalar(1), matrices.scaling.c_e,
450 fr_d_c_i};
451
452 InteriorPointMatrixCallbacks<Scalar> fr_matrix_callbacks{
453 static_cast<int>(fr_x.rows()),
454 static_cast<int>(fr_y.rows()),
455 static_cast<int>(fr_z.rows()),
456 [&](const DenseVector& x_p) -> Scalar {
457 auto x = x_p.segment(0, num_vars);
458
459 // Cost function
460 //
461 // ρ Σ (pₑ + nₑ + pᵢ + nᵢ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
462 auto diff = x - x_r;
463 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
464 .array()
465 .sum() +
466 ζ / Scalar(2) * diff.transpose() * D_r * diff;
467 },
468 [&](const DenseVector& x_p) -> SparseVector {
469 auto x = x_p.segment(0, num_vars);
470
471 // Cost function gradient
472 //
473 // [ζDᵣ(x − xᵣ)]
474 // [ ρ ]
475 // [ ρ ]
476 // [ ρ ]
477 // [ ρ ]
478 DenseVector g{x_p.rows()};
479 g.segment(0, num_vars) = ζ * D_r * (x - x_r);
480 g.segment(num_vars, 2 * num_eq + 2 * num_ineq).setConstant(ρ);
481 return g.sparseView();
482 },
483 [&](const DenseVector& x_p, const DenseVector& y_p,
484 const DenseVector& z_p) -> SparseMatrix {
485 auto x = x_p.segment(0, num_vars);
486 const auto& y = y_p;
487 auto z = z_p.segment(0, num_ineq);
488
489 // Cost function Hessian
490 //
491 // [ζDᵣ 0 0 0 0]
492 // [ 0 0 0 0 0]
493 // [ 0 0 0 0 0]
494 // [ 0 0 0 0 0]
495 // [ 0 0 0 0 0]
496 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
497 triplets.reserve(x_p.rows());
498 append_as_triplets(triplets, 0, 0, {SparseMatrix{ζ * D_r}});
499 SparseMatrix d2f_dx2{x_p.rows(), x_p.rows()};
500 d2f_dx2.setFromSortedTriplets(triplets.begin(), triplets.end());
501
502 // Constraint part of original problem's Lagrangian Hessian
503 //
504 // −∇ₓₓ²yᵀcₑ(x) − ∇ₓₓ²zᵀcᵢ(x)
505 auto H_c = matrices.H_c(x, y, z);
506 H_c.conservativeResize(x_p.rows(), x_p.rows());
507
508 // Lagrangian Hessian
509 //
510 // [ζDᵣ 0 0 0 0]
511 // [ 0 0 0 0 0]
512 // [ 0 0 0 0 0] − ∇ₓₓ²yᵀcₑ(x) − ∇ₓₓ²zᵀcᵢ(x)
513 // [ 0 0 0 0 0]
514 // [ 0 0 0 0 0]
515 return d2f_dx2 + H_c;
516 },
517 [&](const DenseVector& x_p, [[maybe_unused]] const DenseVector& y_p,
518 [[maybe_unused]] const DenseVector& z_p) -> SparseMatrix {
519 return SparseMatrix{x_p.rows(), x_p.rows()};
520 },
521 [&](const DenseVector& x_p) -> DenseVector {
522 auto x = x_p.segment(0, num_vars);
523 auto p_e = x_p.segment(num_vars, num_eq);
524 auto n_e = x_p.segment(num_vars + num_eq, num_eq);
525
526 // Equality constraints
527 //
528 // cₑ(x) - pₑ + nₑ = 0
529 return matrices.c_e(x) - p_e + n_e;
530 },
531 [&](const DenseVector& x_p) -> SparseMatrix {
532 auto x = x_p.segment(0, num_vars);
533
534 // Equality constraint Jacobian
535 //
536 // [Aₑ −I I 0 0]
537
538 SparseMatrix A_e = matrices.A_e(x);
539
540 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
541 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
542
543 append_as_triplets(triplets, 0, 0, {A_e});
544 append_diagonal_as_triplets(
545 triplets, 0, num_vars,
546 DenseVector::Constant(num_eq, Scalar(-1)).eval());
547 append_diagonal_as_triplets(
548 triplets, 0, num_vars + num_eq,
549 DenseVector::Constant(num_eq, Scalar(1)).eval());
550
551 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
552 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
553 return A_e_p;
554 },
555 [&](const DenseVector& x_p) -> DenseVector {
556 auto x = x_p.segment(0, num_vars);
557 auto p_i = x_p.segment(num_vars + 2 * num_eq, num_ineq);
558 auto n_i = x_p.segment(num_vars + 2 * num_eq + num_ineq, num_ineq);
559
560 // Inequality constraints
561 //
562 // cᵢ(x) - pᵢ + nᵢ ≥ 0
563 // pₑ ≥ 0
564 // nₑ ≥ 0
565 // pᵢ ≥ 0
566 // nᵢ ≥ 0
567 DenseVector c_i_p{c_i.rows() + 2 * num_eq + 2 * num_ineq};
568 c_i_p.segment(0, num_ineq) = matrices.c_i(x) - p_i + n_i;
569 c_i_p.segment(p_i.rows(), 2 * num_eq + 2 * num_ineq) =
570 x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq);
571 return c_i_p;
572 },
573 [&](const DenseVector& x_p) -> SparseMatrix {
574 auto x = x_p.segment(0, num_vars);
575
576 // Inequality constraint Jacobian
577 //
578 // [Aᵢ 0 0 −I I]
579 // [0 I 0 0 0]
580 // [0 0 I 0 0]
581 // [0 0 0 I 0]
582 // [0 0 0 0 I]
583
584 SparseMatrix A_i = matrices.A_i(x);
585
586 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
587 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
588
589 // Column 0
590 append_as_triplets(triplets, 0, 0, {A_i});
591
592 // Columns 1 and 2
593 append_diagonal_as_triplets(
594 triplets, num_ineq, num_vars,
595 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
596
597 SparseMatrix I_ineq{
598 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
599
600 // Column 3
601 SparseMatrix Z_col3{2 * num_eq, num_ineq};
602 append_as_triplets(triplets, 0, num_vars + 2 * num_eq,
603 {(-I_ineq).eval(), Z_col3, I_ineq});
604
605 // Column 4
606 SparseMatrix Z_col4{2 * num_eq + num_ineq, num_ineq};
607 append_as_triplets(triplets, 0, num_vars + 2 * num_eq + num_ineq,
608 {I_ineq, Z_col4, I_ineq});
609
610 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
611 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
612 return A_i_p;
613 },
614 fr_scaling};
615
616#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
617 Eigen::ArrayX<bool> fr_bound_constraint_mask{2 * num_eq + 3 * num_ineq};
618 fr_bound_constraint_mask.segment(0, num_ineq) = bound_constraint_mask;
619 fr_bound_constraint_mask.segment(num_ineq, 2 * num_eq + 2 * num_ineq) = true;
620#endif
621
622 auto status = interior_point<Scalar>(
623 fr_matrix_callbacks, iteration_callbacks, options, true,
624#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
625 fr_bound_constraint_mask,
626#endif
627 fr_x, fr_s, fr_y, fr_z, fr_μ, iterations);
628
629 x = fr_x.segment(0, x.rows());
630 s = fr_s.segment(0, s.rows());
631
632 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
633 auto g = matrices.g(x);
634 auto A_e = matrices.A_e(x);
635 auto A_i = matrices.A_i(x);
636
637 auto [y_estimate, z_estimate] =
638 lagrange_multiplier_estimate(g, A_e, A_i, s, μ);
639 y = y_estimate;
640 z = z_estimate;
641
642 return ExitStatus::SUCCESS;
643 } else if (status == ExitStatus::SUCCESS) {
644 // Feasibility restoration converged to a minimizer of the constraint
645 // violation. If the constraint violation is still above the tolerance,
646 // that minimizer is a certificate of local infeasibility. Declaring local
647 // infeasibility anywhere else risks false positives (e.g., a
648 // point-in-time test can reject iterates the solver would otherwise
649 // escape). See section 3.3, p. 14 of [2].
650 DenseVector c_e = matrices.c_e(x);
651 if (matrices.scaling.c_e.size() > 0) {
652 c_e = matrices.scaling.c_e.cwiseInverse().cwiseProduct(c_e);
653 }
654
655 DenseVector c_i = matrices.c_i(x);
656 if (matrices.scaling.c_i.size() > 0) {
657 c_i = matrices.scaling.c_i.cwiseInverse().cwiseProduct(c_i);
658 }
659
660 // Inequality constraints cᵢ(x) ≥ 0 are only violated where they're
661 // negative
662 const DenseVector c_i_violation = (-c_i).cwiseMax(Scalar(0));
663
664 const bool c_e_violated =
665 c_e.template lpNorm<Eigen::Infinity>() > Scalar(options.tolerance);
666 const bool c_i_violated = c_i_violation.template lpNorm<Eigen::Infinity>() >
667 Scalar(options.tolerance);
668
669 if (c_e_violated || c_i_violated) {
670 if (options.diagnostics) {
671 if (c_e_violated) {
672 print_c_e_local_infeasibility_error(c_e, Scalar(options.tolerance));
673 }
674 if (c_i_violated) {
675 print_c_i_local_infeasibility_error(c_i, Scalar(options.tolerance));
676 }
677 }
678
679 return ExitStatus::LOCALLY_INFEASIBLE;
680 }
681
682 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
683 } else {
684 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
685 }
686}
687
688} // namespace slp
689
690#include "sleipnir/optimization/solver/interior_point.hpp"