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 <span>
8#include <tuple>
9#include <utility>
10
11#include <Eigen/Core>
12#include <gch/small_vector.hpp>
13
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"
21
22namespace slp {
23
24template <typename Scalar>
25ExitStatus interior_point(
26 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
27 std::span<std::function<bool(const IterationInfo<Scalar>& info)>>
28 iteration_callbacks,
29 const Options& options, bool in_feasibility_restoration,
30#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
31 const Eigen::ArrayX<bool>& bound_constraint_mask,
32#endif
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& μ);
37
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 ρ,
50 Scalar μ) {
51 // From equation (33) of [2]:
52 // ______________________
53 // μ − ρ c(x) /(μ − ρ c(x))² μ c(x)
54 // n = −−−−−−−−−− + / (−−−−−−−−−−) + −−−−−− (1)
55 // 2ρ √ ( 2ρ ) 2ρ
56 //
57 // The quadratic formula:
58 // ________
59 // -b + √b² - 4ac
60 // x = −−−−−−−−−−−−−− (2)
61 // 2a
62 //
63 // Rearrange (1) to fit the quadratic formula better:
64 // _________________________
65 // μ - ρ c(x) + √(μ - ρ c(x))² + 2ρ μ c(x)
66 // n = −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
67 // 2ρ
68 //
69 // Solve for coefficients:
70 //
71 // a = ρ (3)
72 // b = ρ c(x) - μ (4)
73 //
74 // -4ac = 2ρ μ c(x)
75 // -4(ρ)c = 2ρ μ c(x)
76 // -4c = 2μ c(x)
77 // c = -μ c(x)/2 (5)
78 //
79 // p = c(x) + n (6)
80
81 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
82
83 using std::sqrt;
84
85 DenseVector p{c.rows()};
86 DenseVector n{c.rows()};
87 for (int row = 0; row < p.rows(); ++row) {
88 Scalar _a = ρ;
89 Scalar _b = ρ * c[row] - μ;
90 Scalar _c = -μ * c[row] / Scalar(2);
91
92 n[row] = (-_b + sqrt(_b * _b - Scalar(4) * _a * _c)) / (Scalar(2) * _a);
93 p[row] = c[row] + n[row];
94 }
95
96 return {std::move(p), std::move(n)};
97}
98
113template <typename Scalar>
114ExitStatus feasibility_restoration(
115 const SQPMatrixCallbacks<Scalar>& matrix_callbacks,
116 std::span<std::function<bool(const IterationInfo<Scalar>& info)>>
117 iteration_callbacks,
118 const Options& options, Eigen::Vector<Scalar, Eigen::Dynamic>& x,
119 Eigen::Vector<Scalar, Eigen::Dynamic>& y) {
120 // Feasibility restoration
121 //
122 // min ρ Σ (pₑ + nₑ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
123 // x
124 // pₑ,nₑ
125 //
126 // s.t. cₑ(x) - pₑ + nₑ = 0
127 // pₑ ≥ 0
128 // nₑ ≥ 0
129 //
130 // where ρ = 1000, ζ = √μ where μ is the barrier parameter, xᵣ is original
131 // iterate before feasibility restoration, and Dᵣ is a scaling matrix defined
132 // by
133 //
134 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
135
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>;
140
141 using std::sqrt;
142
143 const auto& matrices = matrix_callbacks;
144 const auto& num_vars = matrices.num_decision_variables;
145 const auto& num_eq = matrices.num_equality_constraints;
146
147 constexpr Scalar ρ(1e3);
148 const Scalar μ(options.tolerance / 10.0);
149 const Scalar ζ = sqrt(μ);
150
151 const DenseVector c_e = matrices.c_e(x);
152
153 const auto& x_r = x;
154 const auto [p_e_0, n_e_0] = compute_p_n(c_e, ρ, μ);
155
156 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
157 const DiagonalMatrix D_r =
158 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
159
160 DenseVector fr_x{num_vars + 2 * num_eq};
161 fr_x << x, p_e_0, n_e_0;
162
163 DenseVector fr_s = DenseVector::Ones(2 * num_eq);
164
165 DenseVector fr_y = DenseVector::Zero(num_eq);
166
167 DenseVector fr_z{2 * num_eq};
168 fr_z << μ * p_e_0.cwiseInverse(), μ * n_e_0.cwiseInverse();
169
170 Scalar fr_μ = std::max(μ, c_e.template lpNorm<Eigen::Infinity>());
171
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);
178
179 // Cost function
180 //
181 // ρ Σ (pₑ + nₑ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
182
183 auto diff = x - x_r;
184 return ρ * x_p.segment(num_vars, 2 * num_eq).array().sum() +
185 ζ / Scalar(2) * diff.transpose() * D_r * diff;
186 },
187 [&](const DenseVector& x_p) -> SparseVector {
188 auto x = x_p.segment(0, num_vars);
189
190 // Cost function gradient
191 //
192 // [ζDᵣ(x − xᵣ)]
193 // [ ρ ]
194 // [ ρ ]
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();
199 },
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);
203 const auto& y = y_p;
204
205 // Cost function Hessian
206 //
207 // [ζDᵣ 0 0]
208 // [ 0 0 0]
209 // [ 0 0 0]
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());
215
216 // Constraint part of original problem's Lagrangian Hessian
217 //
218 // −∇ₓₓ²yᵀcₑ(x)
219 auto H_c = matrices.H_c(x, y);
220 H_c.resize(x_p.rows(), x_p.rows());
221
222 // Lagrangian Hessian
223 //
224 // [ζDᵣ 0 0]
225 // [ 0 0 0] − ∇ₓₓ²yᵀcₑ(x)
226 // [ 0 0 0]
227 return d2f_dx2 + H_c;
228 },
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()};
232 },
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);
237
238 // Equality constraints
239 //
240 // cₑ(x) - pₑ + nₑ = 0
241 return matrices.c_e(x) - p_e + n_e;
242 },
243 [&](const DenseVector& x_p) -> SparseMatrix {
244 auto x = x_p.segment(0, num_vars);
245
246 // Equality constraint Jacobian
247 //
248 // [Aₑ −I I]
249
250 SparseMatrix A_e = matrices.A_e(x);
251
252 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
253 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
254
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());
262
263 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
264 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
265 return A_e_p;
266 },
267 [&](const DenseVector& x_p) -> DenseVector {
268 // Inequality constraints
269 //
270 // pₑ ≥ 0
271 // nₑ ≥ 0
272 return x_p.segment(num_vars, 2 * num_eq);
273 },
274 [&](const DenseVector& x_p) -> SparseMatrix {
275 // Inequality constraint Jacobian
276 //
277 // [0 I 0]
278 // [0 0 I]
279
280 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
281 triplets.reserve(2 * num_eq);
282
283 append_diagonal_as_triplets(
284 triplets, 0, num_vars,
285 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
286
287 SparseMatrix A_i_p{2 * num_eq, x_p.rows()};
288 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
289 return A_i_p;
290 }};
291
292 auto status = interior_point<Scalar>(fr_matrix_callbacks, iteration_callbacks,
293 options, true,
294#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
295 {},
296#endif
297 fr_x, fr_s, fr_y, fr_z, fr_μ);
298
299 x = fr_x.segment(0, x.rows());
300
301 if (status == ExitStatus::CALLBACK_REQUESTED_STOP) {
302 auto g = matrices.g(x);
303 auto A_e = matrices.A_e(x);
304
305 y = lagrange_multiplier_estimate(g, A_e);
306
307 return ExitStatus::SUCCESS;
308 } else if (status == ExitStatus::SUCCESS) {
309 return ExitStatus::LOCALLY_INFEASIBLE;
310 } else {
311 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
312 }
313}
314
333template <typename Scalar>
334ExitStatus feasibility_restoration(
335 const InteriorPointMatrixCallbacks<Scalar>& matrix_callbacks,
336 std::span<std::function<bool(const IterationInfo<Scalar>& info)>>
337 iteration_callbacks,
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 μ) {
342 // Feasibility restoration
343 //
344 // min ρ Σ (pₑ + nₑ + pᵢ + nᵢ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
345 // x
346 // pₑ,nₑ
347 // pᵢ,nᵢ
348 //
349 // s.t. cₑ(x) - pₑ + nₑ = 0
350 // cᵢ(x) - pᵢ + nᵢ ≥ 0
351 // pₑ ≥ 0
352 // nₑ ≥ 0
353 // pᵢ ≥ 0
354 // nᵢ ≥ 0
355 //
356 // where ρ = 1000, ζ = √μ where μ is the barrier parameter, xᵣ is original
357 // iterate before feasibility restoration, and Dᵣ is a scaling matrix defined
358 // by
359 //
360 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
361
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>;
366
367 using std::sqrt;
368
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;
373
374 constexpr Scalar ρ(1e3);
375 const Scalar ζ = sqrt(μ);
376
377 const DenseVector c_e = matrices.c_e(x);
378 const DenseVector c_i = matrices.c_i(x);
379
380 const auto& x_r = 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(), ρ, μ);
383
384 // Dᵣ = diag(min(1, 1/xᵣ[i]²) for i in x.rows())
385 const DiagonalMatrix D_r =
386 x.cwiseSquare().cwiseInverse().cwiseMin(Scalar(1)).asDiagonal();
387
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;
390
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();
394
395 DenseVector fr_y = DenseVector::Zero(c_e.rows());
396
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();
400
401 Scalar fr_μ = std::max({μ, c_e.template lpNorm<Eigen::Infinity>(),
402 (c_i - s).template lpNorm<Eigen::Infinity>()});
403
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);
410
411 // Cost function
412 //
413 // ρ Σ (pₑ + nₑ + pᵢ + nᵢ) + ζ/2 (x - xᵣ)ᵀDᵣ(x - xᵣ)
414 auto diff = x - x_r;
415 return ρ * x_p.segment(num_vars, 2 * num_eq + 2 * num_ineq)
416 .array()
417 .sum() +
418 ζ / Scalar(2) * diff.transpose() * D_r * diff;
419 },
420 [&](const DenseVector& x_p) -> SparseVector {
421 auto x = x_p.segment(0, num_vars);
422
423 // Cost function gradient
424 //
425 // [ζDᵣ(x − xᵣ)]
426 // [ ρ ]
427 // [ ρ ]
428 // [ ρ ]
429 // [ ρ ]
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();
434 },
435 [&](const DenseVector& x_p, const DenseVector& y_p,
436 const DenseVector& z_p) -> SparseMatrix {
437 auto x = x_p.segment(0, num_vars);
438 const auto& y = y_p;
439 auto z = z_p.segment(0, num_ineq);
440
441 // Cost function Hessian
442 //
443 // [ζDᵣ 0 0 0 0]
444 // [ 0 0 0 0 0]
445 // [ 0 0 0 0 0]
446 // [ 0 0 0 0 0]
447 // [ 0 0 0 0 0]
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());
453
454 // Constraint part of original problem's Lagrangian Hessian
455 //
456 // −∇ₓₓ²yᵀcₑ(x) − ∇ₓₓ²zᵀcᵢ(x)
457 auto H_c = matrices.H_c(x, y, z);
458 H_c.resize(x_p.rows(), x_p.rows());
459
460 // Lagrangian Hessian
461 //
462 // [ζDᵣ 0 0 0 0]
463 // [ 0 0 0 0 0]
464 // [ 0 0 0 0 0] − ∇ₓₓ²yᵀcₑ(x) − ∇ₓₓ²zᵀcᵢ(x)
465 // [ 0 0 0 0 0]
466 // [ 0 0 0 0 0]
467 return d2f_dx2 + H_c;
468 },
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()};
472 },
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);
477
478 // Equality constraints
479 //
480 // cₑ(x) - pₑ + nₑ = 0
481 return matrices.c_e(x) - p_e + n_e;
482 },
483 [&](const DenseVector& x_p) -> SparseMatrix {
484 auto x = x_p.segment(0, num_vars);
485
486 // Equality constraint Jacobian
487 //
488 // [Aₑ −I I 0 0]
489
490 SparseMatrix A_e = matrices.A_e(x);
491
492 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
493 triplets.reserve(A_e.nonZeros() + 2 * num_eq);
494
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());
502
503 SparseMatrix A_e_p{A_e.rows(), x_p.rows()};
504 A_e_p.setFromSortedTriplets(triplets.begin(), triplets.end());
505 return A_e_p;
506 },
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);
511
512 // Inequality constraints
513 //
514 // cᵢ(x) - pᵢ + nᵢ ≥ 0
515 // pₑ ≥ 0
516 // nₑ ≥ 0
517 // pᵢ ≥ 0
518 // nᵢ ≥ 0
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);
523 return c_i_p;
524 },
525 [&](const DenseVector& x_p) -> SparseMatrix {
526 auto x = x_p.segment(0, num_vars);
527
528 // Inequality constraint Jacobian
529 //
530 // [Aᵢ 0 0 −I I]
531 // [0 I 0 0 0]
532 // [0 0 I 0 0]
533 // [0 0 0 I 0]
534 // [0 0 0 0 I]
535
536 SparseMatrix A_i = matrices.A_i(x);
537
538 gch::small_vector<Eigen::Triplet<Scalar>> triplets;
539 triplets.reserve(A_i.nonZeros() + 2 * num_eq + 4 * num_ineq);
540
541 // Column 0
542 append_as_triplets(triplets, 0, 0, {A_i});
543
544 // Columns 1 and 2
545 append_diagonal_as_triplets(
546 triplets, num_ineq, num_vars,
547 DenseVector::Constant(2 * num_eq, Scalar(1)).eval());
548
549 SparseMatrix I_ineq{
550 DenseVector::Constant(num_ineq, Scalar(1)).asDiagonal()};
551
552 // Column 3
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});
556
557 // Column 4
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});
561
562 SparseMatrix A_i_p{2 * num_eq + 3 * num_ineq, x_p.rows()};
563 A_i_p.setFromSortedTriplets(triplets.begin(), triplets.end());
564 return A_i_p;
565 }};
566
567 auto status = interior_point<Scalar>(fr_matrix_callbacks, iteration_callbacks,
568 options, true,
569#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
570 {},
571#endif
572 fr_x, fr_s, fr_y, fr_z, fr_μ);
573
574 x = fr_x.segment(0, x.rows());
575 s = fr_s.segment(0, s.rows());
576
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);
581
582 auto [y_estimate, z_estimate] =
583 lagrange_multiplier_estimate(g, A_e, A_i, s, μ);
584 y = y_estimate;
585 z = z_estimate;
586
587 return ExitStatus::SUCCESS;
588 } else if (status == ExitStatus::SUCCESS) {
589 return ExitStatus::LOCALLY_INFEASIBLE;
590 } else {
591 return ExitStatus::FEASIBILITY_RESTORATION_FAILED;
592 }
593}
594
595} // namespace slp
596
597#include "sleipnir/optimization/solver/interior_point.hpp"