Sleipnir C++ API
Loading...
Searching...
No Matches
dense_kkt_solver.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8
9#include <Eigen/Cholesky>
10#include <Eigen/Core>
11
12#include "sleipnir/optimization/solver/util/inertia.hpp"
13
14namespace slp {
15
21template <typename Scalar>
23 public:
25 using DenseMatrix = Eigen::MatrixX<Scalar>;
27 using DenseVector = Eigen::VectorX<Scalar>;
28
35 DenseKKTSolver(int num_decision_variables, int num_equality_constraints)
36 : m_num_decision_variables{num_decision_variables},
37 m_num_equality_constraints{num_equality_constraints} {}
38
46 DenseKKTSolver(int num_decision_variables, int num_equality_constraints,
47 Scalar γ_min)
48 : m_num_decision_variables{num_decision_variables},
49 m_num_equality_constraints{num_equality_constraints},
50 m_γ_min{γ_min} {}
51
55 Eigen::ComputationInfo info() const { return m_info; }
56
62 // Bunch-Kaufman pivoting is backward stable for indefinite matrices, so the
63 // unregularized system is accepted whenever its inertia is ideal
64 m_lhs = lhs;
65 m_info = m_solver.compute(m_lhs).info();
66 if (m_info == Eigen::Success && inertia() == ideal_inertia) {
67 m_prev_δ = Scalar(0);
68 m_prev_γ = Scalar(0);
69 return *this;
70 }
71
72 // We'll give lhs the correct inertia by adding [δI, 0; 0, −γI] where δ and
73 // γ regularize the Hessian and equality constraint Jacobian respectively.
74
75 // The minimum nonzero Hessian regularization
76 constexpr Scalar δ_min(1e-10);
77
78 // The maximum constraint regularization. Larger values distort the equality
79 // constraint Jacobian more than they help a rank-deficient one.
80 constexpr Scalar γ_max(1e-8);
81
82 // The initial nonzero constraint regularization
83 constexpr Scalar γ_initial(1e-10);
84
85 // If the previous δ was zero, attempt a small value. Otherwise, attempt a
86 // smaller value than the previous δ so δ trends downward. δ has a floor
87 // because when regularization is needed, δ is the only curvature in some
88 // directions, so the KKT matrix's smallest eigenvalues shrink with δ. Too
89 // small a δ makes the matrix ill-conditioned, which amplifies the
90 // factorization's rounding error in the solution, and eventually puts those
91 // eigenvalues within ε of zero, where the inertia check counts them as
92 // zero.
93 Scalar δ = m_prev_δ == Scalar(0) ? Scalar(1e-4)
94 : std::max(m_prev_δ / Scalar(2), δ_min);
95
96 // Start γ at the minimum to minimize equality constraint Jacobian
97 // distortion.
98 Scalar γ = m_γ_min;
99
100 while (true) {
101 m_lhs = lhs + regularization(δ, γ);
102 m_info = m_solver.compute(m_lhs).info();
103
104 if (m_info == Eigen::Success) {
105 Inertia inertia = this->inertia();
106
107 if (inertia == ideal_inertia) {
108 // If the inertia is ideal, report success
109 m_prev_δ = δ;
110 m_prev_γ = γ;
111 return *this;
112 } else if (inertia.zero > 0) {
113 if (γ == Scalar(0)) {
114 // If there's zero eigenvalues and γ = 0, increase γ to potentially
115 // compensate for a rank-deficient equality constraint Jacobian
116 γ = γ_initial;
117 } else {
118 // If there's zero eigenvalues and γ > 0, increase δ and γ to drive
119 // all eigenvalues away from zero
120 δ *= Scalar(10);
121 γ = std::min(γ * Scalar(10), γ_max);
122 }
123 } else if (inertia.negative > ideal_inertia.negative) {
124 // If there's too many negative eigenvalues, increase δ to add more
125 // positive eigenvalues
126 δ *= Scalar(10);
127 } else if (inertia.positive > ideal_inertia.positive) {
128 // The KKT matrix has at least as many negative eigenvalues as
129 // equality constraints if γ > 0 (from the −γI block) or if the
130 // equality constraint Jacobian has full row rank (as in feasibility
131 // restoration, where γ = 0). Too many positive eigenvalues can then
132 // only come from rounding error, so increase δ and γ to drive all
133 // eigenvalues away from zero.
134 //
135 // Unlike LDLT, Bunch-Kaufman pivoting avoids small pivots, so
136 // there's no need to find which block the error came from.
137 δ *= Scalar(10);
138 γ = std::min(γ * Scalar(10), γ_max);
139 }
140 } else {
141 // If the decomposition failed, increase δ and γ to drive all
142 // eigenvalues away from zero
143 δ *= Scalar(10);
144 γ = γ == Scalar(0) ? γ_initial : std::min(γ * Scalar(10), γ_max);
145 }
146
147 // If the lhs perturbation is too high, report failure. This can be caused
148 // by ill-conditioning.
149 if (δ > Scalar(1e20) || γ > Scalar(1e20)) {
150 m_info = Eigen::NumericalIssue;
151 m_prev_δ = δ;
152 m_prev_γ = γ;
153 return *this;
154 }
155 }
156 }
157
162 template <typename Rhs>
163 DenseVector solve(const Eigen::MatrixBase<Rhs>& rhs) const {
164 DenseVector x = m_solver.solve(rhs);
165
166 // The factorization is backward stable, but the matrix's conditioning
167 // amplifies its rounding error in the solution, so apply iterative
168 // refinement with the same factorization
169 constexpr int max_refinement_steps = 3;
170 for (int i = 0; i < max_refinement_steps; ++i) {
171 DenseVector r = rhs - m_lhs.template selfadjointView<Eigen::Lower>() * x;
172 if (r.norm() <= Scalar(1e-12) * rhs.norm()) {
173 break;
174 }
175 x += m_solver.solve(r);
176 }
177
178 return x;
179 }
180
185 template <typename Rhs>
186 DenseVector solve(const Eigen::SparseMatrixBase<Rhs>& rhs) const {
187 return solve(rhs.toDense());
188 }
189
193 Scalar hessian_regularization() const { return m_prev_δ; }
194
198 Scalar constraint_jacobian_regularization() const { return m_prev_γ; }
199
200 private:
201 using Solver = Eigen::BunchKaufman<DenseMatrix>;
202
203 Solver m_solver;
204
206 DenseMatrix m_lhs;
207
208 Eigen::ComputationInfo m_info = Eigen::Success;
209
211 int m_num_decision_variables = 0;
212
214 int m_num_equality_constraints = 0;
215
217 Scalar m_γ_min{1e-10};
218
220 Inertia ideal_inertia{m_num_decision_variables, m_num_equality_constraints,
221 0};
222
224 Scalar m_prev_δ{0};
225
227 Scalar m_prev_γ{0};
228
237 Inertia inertia() const {
238 using std::abs;
239 using std::hypot;
240
241 constexpr Scalar ε = std::numeric_limits<Scalar>::epsilon();
242
243 const auto D = m_solver.vectorD();
244 const auto& D_sub = m_solver.subDiagonal();
245
246 Inertia inertia;
247 auto count = [&](Scalar λ) {
248 if (λ > ε) {
249 ++inertia.positive;
250 } else if (λ < -ε) {
251 ++inertia.negative;
252 } else {
253 ++inertia.zero;
254 }
255 };
256
257 for (int k = 0; k < D.size(); ++k) {
258 if (k + 1 < D.size() && D_sub[k] != Scalar(0)) {
259 // The 2x2 block [a b; b c] has eigenvalues (a + c)/2 ± r where
260 // r = √(((a − c)/2)² + b²)
261 const Scalar a = D[k];
262 const Scalar b = D_sub[k];
263 const Scalar c = D[k + 1];
264 const Scalar mid = (a + c) / Scalar(2);
265 const Scalar r = hypot((a - c) / Scalar(2), abs(b));
266 count(mid + r);
267 count(mid - r);
268 ++k;
269 } else {
270 count(D[k]);
271 }
272 }
273
274 return inertia;
275 }
276
285 DenseMatrix regularization(Scalar δ, Scalar γ) const {
286 DenseVector vec{m_num_decision_variables + m_num_equality_constraints};
287 vec.segment(0, m_num_decision_variables).setConstant(δ);
288 vec.segment(m_num_decision_variables, m_num_equality_constraints)
289 .setConstant(-γ);
290
291 return vec.asDiagonal().toDenseMatrix();
292 }
293};
294
295} // namespace slp
Definition dense_kkt_solver.hpp:22
Scalar constraint_jacobian_regularization() const
Definition dense_kkt_solver.hpp:198
DenseKKTSolver(int num_decision_variables, int num_equality_constraints, Scalar γ_min)
Definition dense_kkt_solver.hpp:46
Scalar hessian_regularization() const
Definition dense_kkt_solver.hpp:193
Eigen::ComputationInfo info() const
Definition dense_kkt_solver.hpp:55
DenseKKTSolver(int num_decision_variables, int num_equality_constraints)
Definition dense_kkt_solver.hpp:35
Eigen::VectorX< Scalar > DenseVector
Type alias for dense vector.
Definition dense_kkt_solver.hpp:27
Eigen::MatrixX< Scalar > DenseMatrix
Type alias for dense matrix.
Definition dense_kkt_solver.hpp:25
DenseVector solve(const Eigen::SparseMatrixBase< Rhs > &rhs) const
Definition dense_kkt_solver.hpp:186
DenseVector solve(const Eigen::MatrixBase< Rhs > &rhs) const
Definition dense_kkt_solver.hpp:163
DenseKKTSolver & compute(const DenseMatrix &lhs)
Definition dense_kkt_solver.hpp:61
Definition inertia.hpp:11
int zero
The number of zero eigenvalues.
Definition inertia.hpp:18
int positive
The number of positive eigenvalues.
Definition inertia.hpp:14
int negative
The number of negative eigenvalues.
Definition inertia.hpp:16