Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
DPR1EigenSolver.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// This Source Code Form is subject to the terms of the Mozilla
5// Public License v. 2.0. If a copy of the MPL was not distributed
6// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
7// SPDX-FileCopyrightText: The Eigen Authors
8// SPDX-License-Identifier: MPL-2.0
9
10#ifndef EIGEN_STRUCTURED_DPR1_EIGEN_SOLVER_H
11#define EIGEN_STRUCTURED_DPR1_EIGEN_SOLVER_H
12
13// IWYU pragma: private
14#include "./InternalHeaderCheck.h"
15
16namespace Eigen {
17
72template <typename RealScalar_>
74 public:
75 using RealScalar = RealScalar_;
76 using Scalar = RealScalar;
77 using Index = Eigen::Index;
78 using VectorType = Matrix<RealScalar, Dynamic, 1>;
79 using MatrixType = Matrix<RealScalar, Dynamic, Dynamic>;
80
81 static_assert(std::is_same<RealScalar, float>::value || std::is_same<RealScalar, double>::value ||
82 std::is_same<RealScalar, long double>::value,
83 "DPR1EigenSolver supports only float, double, and long double scalar types.");
84
86 DPR1EigenSolver() = default;
87
90 DPR1EigenSolver(const VectorType& d, RealScalar rho, const VectorType& z, int options = ComputeEigenvectors) {
91 compute(d, rho, z, options);
92 }
93
95 DPR1EigenSolver& compute(const VectorType& d, RealScalar rho, const VectorType& z, int options = ComputeEigenvectors);
96
98 const VectorType& eigenvalues() const {
99 eigen_assert(m_isInitialized && "DPR1EigenSolver is not initialized.");
100 return m_eivalues;
101 }
102
105 const MatrixType& eigenvectors() const {
106 eigen_assert(m_isInitialized && "DPR1EigenSolver is not initialized.");
107 eigen_assert(m_vectorsComputed && "eigenvectors were not computed");
108 return m_eivec;
109 }
110
116 eigen_assert(m_isInitialized && "DPR1EigenSolver is not initialized.");
117 return m_info;
118 }
119
120 private:
121 // A deflation-stage Givens rotation acting on working rows (i, j).
122 struct Rotation {
123 Index i, j;
124 RealScalar c, s;
125 };
126
130 static RealScalar secular(const VectorType& delta, const VectorType& zeta2, RealScalar rho, RealScalar tau) {
131 return RealScalar(1) + rho * (zeta2.array() / (delta.array() - tau)).sum();
132 }
133
134 VectorType m_eivalues;
135 MatrixType m_eivec;
136 bool m_isInitialized = false;
137 bool m_vectorsComputed = false;
139};
140
141template <typename RealScalar_>
143 const VectorType& z, int options) {
144 const Index n = d.size();
145 eigen_assert(z.size() == n && "d and z must have the same size");
146 eigen_assert((options & ~EigVecMask) == 0 && (options & EigVecMask) != EigVecMask && "invalid option parameter");
147 const bool computeVectors = (options & ComputeEigenvectors) == ComputeEigenvectors;
148 m_vectorsComputed = false;
149 m_info = Success;
150
151 m_eivalues.resize(n);
152 if (computeVectors) m_eivec.setIdentity(n, n);
153
154 // Non-finite input would break the sorting comparator (not a strict weak
155 // order under NaN) and silently deflate everything; reject it up front.
156 if (!(d.allFinite() && z.allFinite() && (numext::isfinite)(rho))) {
157 m_eivalues.setConstant(NumTraits<RealScalar>::quiet_NaN());
158 m_info = InvalidInput;
159 m_isInitialized = true;
160 return *this;
161 }
162 if (n == 0) {
163 m_vectorsComputed = computeVectors;
164 m_isInitialized = true;
165 return *this;
166 }
167
168 const bool negated = rho < RealScalar(0);
169 const VectorType dW = negated ? VectorType(-d) : d;
170
171 // pi maps each sorted working index to its input row.
172 std::vector<Index> pi;
173 pi.reserve(static_cast<std::size_t>(n));
174 for (Index i = 0; i < n; ++i) pi.push_back(i);
175 std::stable_sort(pi.begin(), pi.end(), [&dW](Index a, Index b) { return dW[a] < dW[b]; });
176 VectorType ds(n), zs(n);
177 for (Index i = 0; i < n; ++i) {
178 ds[i] = dW[pi[static_cast<std::size_t>(i)]];
179 zs[i] = z[pi[static_cast<std::size_t>(i)]];
180 }
181
182 // Normalize z and absorb ||z||^2 into rho, then scale the problem by the exact
183 // power of two s = 2^-scaleExp that brings max(||D||_inf, rho ||z||^2) into
184 // [1/2, 1): eig(sD + s rho zz^T) = s eig(D + rho zz^T).
185 const RealScalar znorm = zs.stableNorm();
186 if (znorm > RealScalar(0)) zs /= znorm;
187 RealScalar rhoW = rho == RealScalar(0) || znorm == RealScalar(0) ? RealScalar(0) : (numext::abs(rho) * znorm) * znorm;
188 if (!(numext::isfinite)(rhoW)) {
189 // An overflowing update would make the deflation tolerance infinite and
190 // silently deflate it away.
191 m_eivalues.setConstant(NumTraits<RealScalar>::quiet_NaN());
192 m_info = InvalidInput;
193 m_isInitialized = true;
194 return *this;
195 }
196 EIGEN_USING_STD(frexp)
197 EIGEN_USING_STD(ldexp)
198 RealScalar scaledNorm = numext::maxi(ds.cwiseAbs().maxCoeff(), rhoW); // in [1/2, 1) once scaled
199 int scaleExp = 0;
200 if (scaledNorm > RealScalar(0)) scaledNorm = frexp(scaledNorm, &scaleExp);
201 ds = ds.array().ldexp(-scaleExp).matrix();
202 rhoW = ldexp(rhoW, -scaleExp);
203
204 // Backward-error budget: dropping a coupling of size <= tol perturbs the
205 // matrix by O(tol), like LAPACK's xLAED2. Using max(|d|_inf, rho*||z||^2) as
206 // the scale is the unscaled-problem generalization of xLAED2's criterion: the
207 // perturbation stays O(eps * (||D|| + rho ||z||^2)), i.e. backward stable in
208 // the data.
209 const RealScalar tol = RealScalar(8) * NumTraits<RealScalar>::epsilon() * scaledNorm;
210
211 std::vector<Rotation> rotations;
212 std::vector<bool> deflated;
213
214 if (rhoW <= tol) {
215 deflated.assign(static_cast<std::size_t>(n), true);
216 } else {
217 // Negligible z_i leaves d_i as an eigenvalue.
218 deflated.reserve(static_cast<std::size_t>(n));
219 for (Index i = 0; i < n; ++i) deflated.push_back(rhoW * numext::abs(zs[i]) <= tol);
220 // The dropped off-diagonal coupling is |c*s*(ds[i]-ds[p])| <= tol.
221 Index p = -1;
222 for (Index i = 0; i < n; ++i) {
223 if (deflated[static_cast<std::size_t>(i)]) continue;
224 if (p >= 0) {
225 const RealScalar r = numext::hypot(zs[p], zs[i]);
226 const RealScalar c = zs[i] / r, s = zs[p] / r; // zeroes the earlier entry
227 const RealScalar gap = ds[i] - ds[p];
228 if (numext::abs(c * s * gap) <= tol) {
229 // Rotated diagonal (c^2 a + s^2 b, s^2 a + c^2 b), a = ds[p], b = ds[i], with c^2 + s^2 = 1
230 // applied exactly and the smaller weight w = min(c^2, s^2) <= 1/2 on the gap:
231 // |s| <= |c|: (a + s^2 gap, b - s^2 gap), otherwise (b - c^2 gap, a + c^2 gap).
232 // Equal poles (gap = 0) keep their value, which the weighted sums lose to the rounding of
233 // c^2 + s^2, and the rounding of a wide gap enters scaled by w.
234 const RealScalar left = ds[p], right = ds[i];
235 if (numext::abs(s) <= numext::abs(c)) {
236 const RealScalar shift = s * s * gap;
237 ds[p] = left + shift;
238 ds[i] = right - shift;
239 } else {
240 const RealScalar shift = c * c * gap;
241 ds[p] = right - shift;
242 ds[i] = left + shift;
243 }
244 zs[i] = r;
245 zs[p] = RealScalar(0);
246 deflated[static_cast<std::size_t>(p)] = true;
247 // Recorded for both options, keeping computeVectors out of the loops that produce the
248 // eigenvalues: GCC unswitches this loop on it, and IBM double-double operations are not
249 // commutative, so the two copies' operand orders can round differently.
250 rotations.push_back(Rotation{p, i, c, s});
251 }
252 }
253 if (!deflated[static_cast<std::size_t>(i)]) p = i;
254 }
255 }
256
257 std::vector<Index> sub; // working positions of the surviving poles
258 for (Index i = 0; i < n; ++i)
259 if (!deflated[static_cast<std::size_t>(i)]) sub.push_back(i);
260 const Index m = static_cast<Index>(sub.size());
261
262 VectorType lambdaW = ds;
263
264 MatrixType subVectors; // m x m secular eigenvectors (in subproblem coordinates)
265
266 if (m > 0) {
267 VectorType delta(m), zeta(m), zeta2(m);
268 std::vector<Index> shiftIndex(static_cast<std::size_t>(m));
269 VectorType tau(m);
270 for (Index a = 0; a < m; ++a) {
271 delta[a] = ds[sub[static_cast<std::size_t>(a)]];
272 zeta[a] = zs[sub[static_cast<std::size_t>(a)]];
273 }
274 zeta2.array() = zeta.array() * zeta.array();
275 const RealScalar zeta2sum = zeta2.sum();
276
277 // Bisection stops when the bracket has collapsed to adjacent floating-point
278 // numbers. The backstop covers exponent_range + 2*digits halvings, enough
279 // to shrink a unit-width bracket to a subnormal root offset and resolve it.
280 const int digits =
281 (std::numeric_limits<RealScalar>::digits > 0) ? static_cast<int>(std::numeric_limits<RealScalar>::digits) : 128;
282 const int expRange = (std::numeric_limits<RealScalar>::max_exponent > std::numeric_limits<RealScalar>::min_exponent)
283 ? static_cast<int>(std::numeric_limits<RealScalar>::max_exponent) -
284 static_cast<int>(std::numeric_limits<RealScalar>::min_exponent)
285 : 16 * digits;
286 const int maxBisect = expRange + 2 * digits + 32;
287 VectorType lam(m), dsh(m); // dsh is refilled from scratch each root
288 for (Index k = 0; k < m; ++k) {
289 // Choose the shift pole and the bracket, entirely in shifted coordinates.
290 RealScalar lo, hi;
291 Index shift;
292 if (k + 1 == m) {
293 // Last root: it lies in (delta_m, delta_m + rho*|zeta|^2]; never form
294 // the unshifted right end (it can round to the pole itself).
295 shift = k;
296 lo = RealScalar(0);
297 hi = rhoW * zeta2sum;
298 } else {
299 // Interior root: the secular function is increasing between the poles,
300 // so its sign at the midpoint picks the nearer pole as the shift.
301 const RealScalar left = delta[k], right = delta[k + 1];
302 const RealScalar mid = left + (right - left) / RealScalar(2);
303 if (secular(delta, zeta2, rhoW, mid) > RealScalar(0)) {
304 shift = k;
305 lo = RealScalar(0);
306 hi = mid - left;
307 } else {
308 shift = k + 1;
309 lo = mid - right; // negative
310 hi = RealScalar(0);
311 }
312 }
313 const RealScalar shiftVal = delta[shift];
314 dsh.array() = delta.array() - shiftVal;
315
316 // Bisection: g(lo) < 0 < g(hi) by the pole signs (for shift = k the
317 // function tends to -inf as tau -> 0+, for shift = k+1 to +inf as
318 // tau -> 0-). The endpoints are never evaluated.
319 RealScalar a0 = lo, b0 = hi;
320 bool converged = false;
321 for (int iter = 0; iter < maxBisect; ++iter) {
322 const RealScalar t = a0 + (b0 - a0) / RealScalar(2);
323 if (t == a0 || t == b0) {
324 converged = true; // interval fully resolved
325 break;
326 }
327 if (secular(dsh, zeta2, rhoW, t) > RealScalar(0))
328 b0 = t;
329 else
330 a0 = t;
331 }
332 if (!converged) m_info = NoConvergence;
333 const RealScalar t = a0 + (b0 - a0) / RealScalar(2);
334 shiftIndex[static_cast<std::size_t>(k)] = shift;
335 tau[k] = t;
336 lam[k] = shiftVal + t;
337 }
338
339 // ---- Gu-Eisenstat weights: the z-vector for which lam are exact roots ----
340 // zhat_i^2 = prod_j (lam_j - delta_i) / (rho * prod_{j != i} (delta_j - delta_i)),
341 // with every distance lam_j - delta_i formed as (delta_shift(j) - delta_i) + tau_j.
342 // Numerator and denominator factors are paired so the running product stays O(1).
343 VectorType zhat(m);
344 for (Index i = 0; i < m; ++i) {
345 RealScalar acc = ((delta[shiftIndex[static_cast<std::size_t>(i)]] - delta[i]) + tau[i]) / rhoW;
346 for (Index j = 0; j < m; ++j) {
347 if (j == i) continue;
348 const RealScalar num = (delta[shiftIndex[static_cast<std::size_t>(j)]] - delta[i]) + tau[j];
349 acc *= num / (delta[j] - delta[i]);
350 }
351 zhat[i] = numext::abs(acc) > RealScalar(0) ? RealScalar(numext::sqrt(numext::abs(acc))) : RealScalar(0);
352 if (zeta[i] < RealScalar(0)) zhat[i] = -zhat[i];
353 }
354
355 // ---- secular eigenvectors from the Gu-Eisenstat weights ----
356 if (computeVectors) {
357 subVectors.resize(m, m);
358 for (Index j = 0; j < m; ++j) {
359 const Index sj = shiftIndex[static_cast<std::size_t>(j)];
360 if (tau[j] == RealScalar(0)) {
361 // The root coincides with its shift pole to working precision (a
362 // fully underflowed bracket): the eigenvector is that pole's axis.
363 subVectors.col(j).setZero();
364 subVectors(sj, j) = RealScalar(1);
365 continue;
366 }
367 // Divide by the pole distances delta_i - lam_j, each formed as
368 // (delta_i - delta_sj) - tau_j.
369 subVectors.col(j).array() = zhat.array() / ((delta.array() - delta[sj]) - tau[j]);
370 subVectors.col(j).stableNormalize();
371 }
372 }
373 for (Index k = 0; k < m; ++k) lambdaW[sub[static_cast<std::size_t>(k)]] = lam[k];
374 }
375
376 std::vector<Index> order;
377 order.reserve(static_cast<std::size_t>(n));
378 for (Index i = 0; i < n; ++i) order.push_back(i);
379 std::stable_sort(order.begin(), order.end(), [&lambdaW](Index a, Index b) { return lambdaW[a] < lambdaW[b]; });
380
381 std::vector<Index> subSlot(static_cast<std::size_t>(n), -1);
382 for (Index a = 0; a < m; ++a) subSlot[static_cast<std::size_t>(sub[static_cast<std::size_t>(a)])] = a;
383
384 for (Index t = 0; t < n; ++t) {
385 const Index w = order[static_cast<std::size_t>(t)];
386 const Index outCol = negated ? n - 1 - t : t;
387 m_eivalues[outCol] = negated ? -lambdaW[w] : lambdaW[w];
388 if (!computeVectors) continue;
389
390 VectorType wvec = VectorType::Zero(n);
391 const Index slot = subSlot[static_cast<std::size_t>(w)];
392 if (slot < 0) {
393 wvec[w] = RealScalar(1);
394 } else {
395 for (Index a = 0; a < m; ++a) wvec[sub[static_cast<std::size_t>(a)]] = subVectors(a, slot);
396 }
397 // w <- G*w with G = [[c, s], [-s, c]]: for exactly equal poles this maps the
398 // deflated unit vector e_p to (c, -s) = (z_i, -z_p)/r, the exact eigenvector
399 // of the 2x2 block orthogonal to the weight vector.
400 for (auto it = rotations.rbegin(); it != rotations.rend(); ++it) {
401 const RealScalar wi = wvec[it->i], wj = wvec[it->j];
402 wvec[it->i] = it->c * wi + it->s * wj;
403 wvec[it->j] = -it->s * wi + it->c * wj;
404 }
405 for (Index i = 0; i < n; ++i) m_eivec(pi[static_cast<std::size_t>(i)], outCol) = wvec[i];
406 }
407
408 m_eivalues = m_eivalues.array().ldexp(scaleExp).matrix();
409 if (!m_eivalues.allFinite()) {
410 m_eivalues.setConstant(NumTraits<RealScalar>::quiet_NaN());
411 m_info = InvalidInput;
412 }
413
414 m_vectorsComputed = computeVectors && m_info != InvalidInput;
415 m_isInitialized = true;
416 return *this;
417}
418
419} // namespace Eigen
420
421#endif // EIGEN_STRUCTURED_DPR1_EIGEN_SOLVER_H
Direct O(n^2) eigensolver for real symmetric diagonal-plus-rank-one matrices , via the secular equati...
Definition DPR1EigenSolver.h:73
const VectorType & eigenvalues() const
Definition DPR1EigenSolver.h:98
ComputationInfo info() const
Definition DPR1EigenSolver.h:115
DPR1EigenSolver(const VectorType &d, RealScalar rho, const VectorType &z, int options=ComputeEigenvectors)
Definition DPR1EigenSolver.h:90
const MatrixType & eigenvectors() const
Definition DPR1EigenSolver.h:105
DPR1EigenSolver & compute(const VectorType &d, RealScalar rho, const VectorType &z, int options=ComputeEigenvectors)
Definition DPR1EigenSolver.h:142
Derived & setZero(Index rows, Index cols)
constexpr void resize(Index rows, Index cols)
ComputationInfo
ComputeEigenvectors
Namespace containing all symbols from the Eigen library.
const Eigen::CwiseBinaryOp< Eigen::internal::scalar_zeta_op< typename DerivedX::Scalar >, const DerivedX, const DerivedQ > zeta(const Eigen::ArrayBase< DerivedX > &x, const Eigen::ArrayBase< DerivedQ > &q)
Definition SpecialFunctionsArrayAPI.h:141