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 <limits>
7
8#include <Eigen/Cholesky>
9#include <Eigen/Core>
10
11#include "sleipnir/optimization/solver/util/inertia.hpp"
12
13namespace slp {
14
20template <typename Scalar>
22 public:
24 using DenseMatrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
26 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
27
34 DenseKKTSolver(int num_decision_variables, int num_equality_constraints)
35 : m_num_decision_variables{num_decision_variables},
36 m_num_equality_constraints{num_equality_constraints} {}
37
45 DenseKKTSolver(int num_decision_variables, int num_equality_constraints,
46 Scalar γ_min)
47 : m_num_decision_variables{num_decision_variables},
48 m_num_equality_constraints{num_equality_constraints},
49 m_γ_min{γ_min} {}
50
54 Eigen::ComputationInfo info() const { return m_info; }
55
61 m_info = m_solver.compute(lhs).info();
62
63 if (m_info == Eigen::Success) {
64 auto D = m_solver.vectorD();
65
66 // If the inertia is ideal and D from LDLT is sufficiently far from zero,
67 // don't regularize the system
68 if (Inertia{D} == ideal_inertia &&
69 (D.cwiseAbs().array() >= Scalar(1e-4)).all()) {
70 m_prev_δ = Scalar(0);
71 m_prev_γ = Scalar(0);
72 return *this;
73 }
74 }
75
76 // We'll give lhs the correct inertia by adding [δI, 0; 0, −γI] where δ and
77 // γ regularize the Hessian and equality constraint Jacobian respectively.
78
79 // If the previous δ was zero, attempt a small value. Otherwise, attempt a
80 // smaller value than the previous δ so δ trends downward.
81 Scalar δ = m_prev_δ == Scalar(0)
82 ? Scalar(1e-4)
83 : std::max(m_prev_δ / Scalar(2),
84 std::numeric_limits<Scalar>::epsilon());
85
86 // Start γ at the minimum to minimize equality constraint Jacobian
87 // distortion.
88 Scalar γ = m_γ_min;
89
90 while (true) {
91 m_info = m_solver.compute(lhs + regularization(δ, γ)).info();
92
93 if (m_info == Eigen::Success) {
94 Inertia inertia{m_solver.vectorD()};
95
96 if (inertia == ideal_inertia) {
97 // If the inertia is ideal, report success
98 m_prev_δ = δ;
99 m_prev_γ = γ;
100 return *this;
101 } else if (inertia.zero > 0) {
102 if (γ == Scalar(0)) {
103 // If there's zero eigenvalues and γ = 0, increase γ to potentially
104 // compensate for a rank-deficient equality constraint Jacobian
105 γ = Scalar(1e-10);
106 } else {
107 // If there's zero eigenvalues and γ > 0, increase δ and γ to drive
108 // all eigenvalues away from zero
109 δ *= Scalar(10);
110 γ *= Scalar(10);
111 }
112 } else if (inertia.negative > ideal_inertia.negative) {
113 // If there's too many negative eigenvalues, increase δ to add more
114 // positive eigenvalues
115 δ *= Scalar(10);
116 } else if (inertia.positive > ideal_inertia.positive) {
117 // If there's too many positive eigenvalues, increase γ to add more
118 // negative eigenvalues
119 γ = γ == Scalar(0) ? Scalar(1e-10) : γ * Scalar(10);
120 }
121 } else {
122 // If the decomposition failed, increase δ and γ to drive all
123 // eigenvalues away from zero
124 δ *= Scalar(10);
125 γ = γ == Scalar(0) ? Scalar(1e-10) : γ * Scalar(10);
126 }
127
128 // If the lhs perturbation is too high, report failure. This can be caused
129 // by ill-conditioning.
130 if (δ > Scalar(1e20) || γ > Scalar(1e20)) {
131 m_info = Eigen::NumericalIssue;
132 m_prev_δ = δ;
133 m_prev_γ = γ;
134 return *this;
135 }
136 }
137 }
138
143 template <typename Rhs>
144 DenseVector solve(const Eigen::MatrixBase<Rhs>& rhs) const {
145 return m_solver.solve(rhs);
146 }
147
152 template <typename Rhs>
153 DenseVector solve(const Eigen::SparseMatrixBase<Rhs>& rhs) const {
154 return m_solver.solve(rhs.toDense());
155 }
156
160 Scalar hessian_regularization() const { return m_prev_δ; }
161
165 Scalar constraint_jacobian_regularization() const { return m_prev_γ; }
166
167 private:
168 using Solver = Eigen::LDLT<DenseMatrix>;
169
170 Solver m_solver;
171
172 Eigen::ComputationInfo m_info = Eigen::Success;
173
175 int m_num_decision_variables = 0;
176
178 int m_num_equality_constraints = 0;
179
181 Scalar m_γ_min{1e-10};
182
184 Inertia ideal_inertia{m_num_decision_variables, m_num_equality_constraints,
185 0};
186
188 Scalar m_prev_δ{0};
189
191 Scalar m_prev_γ{0};
192
201 DenseMatrix regularization(Scalar δ, Scalar γ) const {
202 DenseVector vec{m_num_decision_variables + m_num_equality_constraints};
203 vec.segment(0, m_num_decision_variables).setConstant(δ);
204 vec.segment(m_num_decision_variables, m_num_equality_constraints)
205 .setConstant(-γ);
206
207 return vec.asDiagonal().toDenseMatrix();
208 }
209};
210
211} // namespace slp
Definition dense_kkt_solver.hpp:21
Scalar constraint_jacobian_regularization() const
Definition dense_kkt_solver.hpp:165
DenseKKTSolver(int num_decision_variables, int num_equality_constraints, Scalar γ_min)
Definition dense_kkt_solver.hpp:45
Scalar hessian_regularization() const
Definition dense_kkt_solver.hpp:160
Eigen::Vector< Scalar, Eigen::Dynamic > DenseVector
Type alias for dense vector.
Definition dense_kkt_solver.hpp:26
Eigen::ComputationInfo info() const
Definition dense_kkt_solver.hpp:54
DenseKKTSolver(int num_decision_variables, int num_equality_constraints)
Definition dense_kkt_solver.hpp:34
DenseVector solve(const Eigen::SparseMatrixBase< Rhs > &rhs) const
Definition dense_kkt_solver.hpp:153
DenseVector solve(const Eigen::MatrixBase< Rhs > &rhs) const
Definition dense_kkt_solver.hpp:144
DenseKKTSolver & compute(const DenseMatrix &lhs)
Definition dense_kkt_solver.hpp:60
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > DenseMatrix
Type alias for dense matrix.
Definition dense_kkt_solver.hpp:24
Definition inertia.hpp:14
int positive
The number of positive eigenvalues.
Definition inertia.hpp:17
int negative
The number of negative eigenvalues.
Definition inertia.hpp:19
Definition intrusive_shared_ptr.hpp:27