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