Sleipnir C++ API
Loading...
Searching...
No Matches
kkt_error.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8
9#include <Eigen/Core>
10#include <Eigen/SparseCore>
11
12#include "sleipnir/optimization/solver/util/problem_scaling.hpp"
13
14// See docs/algorithms.md#Works_cited for citation definitions
15
16namespace slp {
17
19enum class KKTErrorType {
21 INF_NORM_SCALED,
23 ONE_NORM
24};
25
31template <typename Scalar, KKTErrorType T>
32Scalar kkt_error(const Eigen::VectorX<Scalar>& g) {
33 // The KKT conditions from docs/algorithms.md:
34 //
35 // ∇f = 0
36
37 if constexpr (T == KKTErrorType::INF_NORM_SCALED) {
38 return g.template lpNorm<Eigen::Infinity>();
39 } else if constexpr (T == KKTErrorType::ONE_NORM) {
40 return g.template lpNorm<1>();
41 }
42}
43
53template <typename Scalar, KKTErrorType T>
54Scalar kkt_error(const Eigen::VectorX<Scalar>& g,
55 const Eigen::SparseMatrix<Scalar>& A_e,
56 const Eigen::VectorX<Scalar>& c_e,
57 const Eigen::VectorX<Scalar>& y, Scalar tolerance) {
58 using std::pow;
59
60 // The KKT conditions from docs/algorithms.md:
61 //
62 // ∇f − Aₑᵀy = 0
63 // cₑ = 0
64
65 if constexpr (T == KKTErrorType::INF_NORM_SCALED) {
66 constexpr Scalar ε = std::numeric_limits<Scalar>::epsilon();
67
68 // The stationarity residual must not go below its rounding error
69 // floor κεω, where κ is how much the problem amplifies rounding
70 // error and ω is the size of the largest stationarity terms. If
71 // κεω > tol, the solver stalls instead of terminating.
72 //
73 // κ is unknown, so assume κ ≤ ε^(-3/8) (≈ 7.4e5 for double, 395 for
74 // float). Then the floor is at most
75 //
76 // κεω ≤ ε^(-3/8) εω = ε^(5/8) ω
77 //
78 // and dividing the residual by ε^(5/8) ω/tol makes it meet tol at the
79 // floor. Dividing by less than 1 would tighten tol instead, so
80 //
81 // s_d = max(1, ε^(5/8) ω/tol)
82
83 // ω = ‖|∇f| + |Aₑ|ᵀ|y|‖_∞
84 const Scalar ω = (g.cwiseAbs() + A_e.cwiseAbs().transpose() * y.cwiseAbs())
85 .template lpNorm<Eigen::Infinity>();
86
87 Scalar s_d = std::max(Scalar(1), pow(ε, Scalar(0.625)) * ω / tolerance);
88
89 // ‖∇f − Aₑᵀy‖_∞ / s_d
90 // ‖cₑ‖_∞
91 return std::max(
92 {(g - A_e.transpose() * y).template lpNorm<Eigen::Infinity>() / s_d,
93 c_e.template lpNorm<Eigen::Infinity>()});
94 } else if constexpr (T == KKTErrorType::ONE_NORM) {
95 return (g - A_e.transpose() * y).template lpNorm<1>() +
96 c_e.template lpNorm<1>();
97 }
98}
99
114template <typename Scalar, KKTErrorType T>
115Scalar kkt_error(const Eigen::VectorX<Scalar>& g,
116 const Eigen::SparseMatrix<Scalar>& A_e,
117 const Eigen::VectorX<Scalar>& c_e,
118 const Eigen::SparseMatrix<Scalar>& A_i,
119 const Eigen::VectorX<Scalar>& c_i,
120 const Eigen::VectorX<Scalar>& s,
121 const Eigen::VectorX<Scalar>& y,
122 const Eigen::VectorX<Scalar>& z, Scalar μ, Scalar tolerance) {
123 using std::pow;
124
125 // The KKT conditions from docs/algorithms.md:
126 //
127 // ∇f − Aₑᵀy − Aᵢᵀz = 0
128 // Sz − μe = 0
129 // cₑ = 0
130 // cᵢ − s = 0
131
132 if constexpr (T == KKTErrorType::INF_NORM_SCALED) {
133 constexpr Scalar ε = std::numeric_limits<Scalar>::epsilon();
134
135 // The stationarity residual must not go below its rounding error
136 // floor κεω, where κ is how much the problem amplifies rounding
137 // error and ω is the size of the largest stationarity terms. If
138 // κεω > tol, the solver stalls instead of terminating.
139 //
140 // κ is unknown, so assume κ ≤ ε^(-3/8) (≈ 7.4e5 for double, 395 for
141 // float). Then the floor is at most
142 //
143 // κεω ≤ ε^(-3/8) εω = ε^(5/8) ω
144 //
145 // and dividing the residual by ε^(5/8) ω/tol makes it meet tol at the
146 // floor. Dividing by less than 1 would tighten tol instead, so
147 //
148 // s_d = max(1, ε^(5/8) ω/tol)
149
150 // ω = ‖|∇f| + |Aₑ|ᵀ|y| + |Aᵢ|ᵀ|z|‖_∞
151 const Scalar ω = (g.cwiseAbs() + A_e.cwiseAbs().transpose() * y.cwiseAbs() +
152 A_i.cwiseAbs().transpose() * z.cwiseAbs())
153 .template lpNorm<Eigen::Infinity>();
154
155 Scalar s_d = std::max(Scalar(1), pow(ε, Scalar(0.625)) * ω / tolerance);
156
157 // s_c = max(sₘₐₓ, ‖z‖₁ / n) / sₘₐₓ
158 //
159 // See equation (5) of [2].
160 constexpr Scalar s_max(100);
161 Scalar s_c =
162 std::max(s_max, z.template lpNorm<1>() / Scalar(z.rows())) / s_max;
163
164 const auto S = s.asDiagonal();
165 const Eigen::VectorX<Scalar> μe =
166 Eigen::VectorX<Scalar>::Constant(s.rows(), μ);
167
168 // ‖∇f − Aₑᵀy − Aᵢᵀz‖_∞ / s_d
169 // ‖Sz − μe‖_∞ / s_c
170 // ‖cₑ‖_∞
171 // ‖cᵢ − s‖_∞
172 return std::max({(g - A_e.transpose() * y - A_i.transpose() * z)
173 .template lpNorm<Eigen::Infinity>() /
174 s_d,
175 (S * z - μe).template lpNorm<Eigen::Infinity>() / s_c,
176 c_e.template lpNorm<Eigen::Infinity>(),
177 (c_i - s).template lpNorm<Eigen::Infinity>()});
178 } else if constexpr (T == KKTErrorType::ONE_NORM) {
179 const auto S = s.asDiagonal();
180 const Eigen::VectorX<Scalar> μe =
181 Eigen::VectorX<Scalar>::Constant(s.rows(), μ);
182
183 return (g - A_e.transpose() * y - A_i.transpose() * z)
184 .template lpNorm<1>() +
185 (S * z - μe).template lpNorm<1>() + c_e.template lpNorm<1>() +
186 (c_i - s).template lpNorm<1>();
187 }
188}
189
196template <typename Scalar, KKTErrorType T>
197Scalar unscaled_kkt_error(const ProblemScaling<Scalar>& scaling,
198 const Eigen::VectorX<Scalar>& g) {
199 using DenseVector = Eigen::VectorX<Scalar>;
200
201 if (scaling.is_identity()) {
202 return kkt_error<Scalar, T>(g);
203 }
204
205 const DenseVector g_unscaled = (Scalar(1) / scaling.f) * g;
206
207 return kkt_error<Scalar, T>(g_unscaled);
208}
209
220template <typename Scalar, KKTErrorType T>
221Scalar unscaled_kkt_error(const ProblemScaling<Scalar>& scaling,
222 const Eigen::VectorX<Scalar>& g,
223 const Eigen::SparseMatrix<Scalar>& A_e,
224 const Eigen::VectorX<Scalar>& c_e,
225 const Eigen::VectorX<Scalar>& y, Scalar tolerance) {
226 using DenseVector = Eigen::VectorX<Scalar>;
227 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
228
229 if (scaling.is_identity()) {
230 return kkt_error<Scalar, T>(g, A_e, c_e, y, tolerance);
231 }
232
233 const Scalar inv_d_f = Scalar(1) / scaling.f;
234 const DenseVector inv_d_c_e = scaling.c_e.cwiseInverse();
235
236 const DenseVector g_unscaled = inv_d_f * g;
237 const SparseMatrix A_e_unscaled = inv_d_c_e.asDiagonal() * A_e;
238 const DenseVector c_e_unscaled = inv_d_c_e.cwiseProduct(c_e);
239 const DenseVector y_unscaled = scaling.c_e.cwiseProduct(y) * inv_d_f;
240
241 return kkt_error<Scalar, T>(g_unscaled, A_e_unscaled, c_e_unscaled,
242 y_unscaled, tolerance);
243}
244
260template <typename Scalar, KKTErrorType T>
261Scalar unscaled_kkt_error(
262 const ProblemScaling<Scalar>& scaling, const Eigen::VectorX<Scalar>& g,
263 const Eigen::SparseMatrix<Scalar>& A_e, const Eigen::VectorX<Scalar>& c_e,
264 const Eigen::SparseMatrix<Scalar>& A_i, const Eigen::VectorX<Scalar>& c_i,
265 const Eigen::VectorX<Scalar>& s, const Eigen::VectorX<Scalar>& y,
266 const Eigen::VectorX<Scalar>& z, Scalar μ, Scalar tolerance) {
267 using DenseVector = Eigen::VectorX<Scalar>;
268 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
269
270 if (scaling.is_identity()) {
271 return kkt_error<Scalar, T>(g, A_e, c_e, A_i, c_i, s, y, z, μ, tolerance);
272 }
273
274 const Scalar inv_d_f = Scalar(1) / scaling.f;
275 const DenseVector inv_d_c_e = scaling.c_e.cwiseInverse();
276 const DenseVector inv_d_c_i = scaling.c_i.cwiseInverse();
277
278 const DenseVector g_unscaled = inv_d_f * g;
279 const SparseMatrix A_e_unscaled = inv_d_c_e.asDiagonal() * A_e;
280 const DenseVector c_e_unscaled = inv_d_c_e.cwiseProduct(c_e);
281 const SparseMatrix A_i_unscaled = inv_d_c_i.asDiagonal() * A_i;
282 const DenseVector c_i_unscaled = inv_d_c_i.cwiseProduct(c_i);
283 const DenseVector s_unscaled = inv_d_c_i.cwiseProduct(s);
284 const DenseVector y_unscaled = scaling.c_e.cwiseProduct(y) * inv_d_f;
285 const DenseVector z_unscaled = scaling.c_i.cwiseProduct(z) * inv_d_f;
286 const Scalar μ_unscaled = inv_d_f * μ;
287
288 return kkt_error<Scalar, T>(g_unscaled, A_e_unscaled, c_e_unscaled,
289 A_i_unscaled, c_i_unscaled, s_unscaled,
290 y_unscaled, z_unscaled, μ_unscaled, tolerance);
291}
292
293} // namespace slp