Eigen  5.0.1
 
Loading...
Searching...
No Matches
BDCSVDImpl.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// We used the "A Divide-And-Conquer Algorithm for the Bidiagonal SVD"
5// research report written by Ming Gu and Stanley C.Eisenstat
6// The code variable names correspond to the names they used in their
7// report
8//
9// Copyright (C) 2013 Gauthier Brun <brun.gauthier@gmail.com>
10// Copyright (C) 2013 Nicolas Carre <nicolas.carre@ensimag.fr>
11// Copyright (C) 2013 Jean Ceccato <jean.ceccato@ensimag.fr>
12// Copyright (C) 2013 Pierre Zoppitelli <pierre.zoppitelli@ensimag.fr>
13// Copyright (C) 2013 Jitse Niesen <jitse@maths.leeds.ac.uk>
14// Copyright (C) 2014-2017 Gael Guennebaud <gael.guennebaud@inria.fr>
15//
16// Source Code Form is subject to the terms of the Mozilla
17// Public License v. 2.0. If a copy of the MPL was not distributed
18// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
19// SPDX-License-Identifier: MPL-2.0
20
21#ifndef EIGEN_BDCSVD_IMPL_H
22#define EIGEN_BDCSVD_IMPL_H
23
24// IWYU pragma: private
25#include "./InternalHeaderCheck.h"
26
27namespace Eigen {
28
29namespace internal {
30
39template <typename RealScalar_>
40class bdcsvd_impl {
41 public:
42 using RealScalar = RealScalar_;
43 using Literal = typename NumTraits<RealScalar>::Literal;
44 using MatrixXr = Matrix<RealScalar, Dynamic, Dynamic, ColMajor>;
45 using VectorType = Matrix<RealScalar, Dynamic, 1>;
46 using ArrayXr = Array<RealScalar, Dynamic, 1>;
47 using ArrayXi = Array<Index, 1, Dynamic>;
48 using ArrayRef = Ref<ArrayXr>;
49 using ConstArrayRef = Ref<const ArrayXr>;
50 using IndicesRef = Ref<ArrayXi>;
51
52 bdcsvd_impl() : m_algoswap(16), m_compU(false), m_compV(false), m_numIters(0), m_info(Success) {}
53
54 void allocate(Index diagSize, bool compU, bool compV);
55
57 void divide(Index firstCol, Index lastCol, Index firstRowW, Index firstColW, Index shift);
58
60 void splitNegligibleSuperdiagonal(Index n);
61
62 MatrixXr& naiveU() { return m_naiveU; }
63 const MatrixXr& naiveU() const { return m_naiveU; }
64 MatrixXr& naiveV() { return m_naiveV; }
65 const MatrixXr& naiveV() const { return m_naiveV; }
66 MatrixXr& computed() { return m_computed; }
67 const MatrixXr& computed() const { return m_computed; }
68 ComputationInfo info() const { return m_info; }
69 int numIters() const { return m_numIters; }
70 int algoSwap() const { return m_algoswap; }
71 void setAlgoSwap(int s) { m_algoswap = s; }
72
73 private:
74 void computeSVDofM(Index firstCol, Index n, MatrixXr& U, VectorType& singVals, MatrixXr& V);
75 void computeSingVals(const ArrayRef& col0, const ArrayRef& diag, const IndicesRef& perm, VectorType& singVals,
76 ArrayRef shifts, ArrayRef mus, ArrayRef workspace);
77 void perturbCol0(const ArrayRef& col0, const ArrayRef& diag, const IndicesRef& perm, const VectorType& singVals,
78 const ArrayRef& shifts, const ArrayRef& mus, ArrayRef zhat);
79 void computeSingVecs(const ArrayRef& zhat, const ArrayRef& diag, const IndicesRef& perm, const VectorType& singVals,
80 const ArrayRef& shifts, const ArrayRef& mus, MatrixXr& U, MatrixXr& V);
81 void deflation43(Index firstCol, Index shift, Index i, Index size);
82 void deflation44(Index firstColu, Index firstColm, Index firstRowW, Index firstColW, Index i, Index j, Index size);
83 void deflation(Index firstCol, Index lastCol, Index k, Index firstRowW, Index firstColW, Index shift);
84 void structured_update(Block<MatrixXr, Dynamic, Dynamic> A, const MatrixXr& B, Index n1);
85 static EIGEN_STRONG_INLINE RealScalar productOfQuotients(RealScalar firstNumerator, RealScalar firstDenominator,
86 RealScalar secondNumerator, RealScalar secondDenominator);
87 static EIGEN_STRONG_INLINE RealScalar sequentialQuotient(RealScalar numerator, RealScalar firstDenominator,
88 RealScalar secondDenominator);
89 static RealScalar secularEq(RealScalar x, const ConstArrayRef& col0, const ConstArrayRef& diag,
90 const ConstArrayRef& diagShifted, RealScalar shift);
91 template <typename SVDType>
92 void computeBaseCase(SVDType& svd, Index n, Index firstCol, Index firstRowW, Index firstColW, Index shift);
93
94 MatrixXr m_naiveU, m_naiveV;
95 MatrixXr m_computed;
96 ArrayXr m_workspace;
97 ArrayXi m_workspaceI;
98 // Reused base-case JacobiSVDs (one per option set) so that recursive divide()
99 // calls don't reallocate JacobiSVD's internal U/V/sigma buffers each time.
100 JacobiSVD<MatrixXr, ComputeFullU> m_baseSvdU;
101 JacobiSVD<MatrixXr, ComputeFullU | ComputeFullV> m_baseSvdUV;
102 int m_algoswap;
103 bool m_compU, m_compV;
104 int m_numIters;
105 ComputationInfo m_info;
106};
107
108template <typename RealScalar_>
109void bdcsvd_impl<RealScalar_>::allocate(Index diagSize, bool compU, bool compV) {
110 m_compU = compU;
111 m_compV = compV;
112 m_numIters = 0;
113 m_info = Success;
114
115 m_computed = MatrixXr::Zero(diagSize + 1, diagSize);
116
117 if (m_compU)
118 m_naiveU = MatrixXr::Zero(diagSize + 1, diagSize + 1);
119 else
120 m_naiveU = MatrixXr::Zero(2, diagSize + 1);
121
122 if (m_compV) m_naiveV = MatrixXr::Zero(diagSize, diagSize);
123
124 // Vector updates need the three matrix-sized packing buffers used by
125 // structured_update(). Values-only decompositions only need five vectors:
126 // diag, shifts, mus, zhat, and diagShifted.
127 if (m_compU || m_compV)
128 m_workspace.resize((diagSize + 1) * (diagSize + 1) * 3);
129 else
130 m_workspace.resize(5 * diagSize);
131 m_workspaceI.resize(3 * diagSize);
132}
133
134// LAPACK's xBDSDC normalizes the bidiagonal by its largest entry and splits wherever a superdiagonal entry falls
135// below eps, so a run of rounding noise never becomes a sub-problem. Eigen scales the input matrix but has no such
136// split: every threshold in deflation() is formed from the sub-problem's own maximum, so a block whose entries are
137// uniformly tiny looks well scaled from the inside and gets resolved for its own relative accuracy. Zeroing here
138// costs one pass and leaves the perturbation within the eps * ||B|| the SVD already carries.
139template <typename RealScalar_>
140void bdcsvd_impl<RealScalar_>::splitNegligibleSuperdiagonal(Index n) {
141 if (n < 2) return;
142 // xBDSDC scales d and e by DLANST('M', n, d, e), the largest entry of either, and then splits at
143 // 0.9 * DLAMCH('E'). DLAMCH('E') is the unit roundoff, i.e. half of NumTraits::epsilon(), so the
144 // same threshold unscaled is 0.45 * epsilon * ||B||_max.
145 const RealScalar norm = numext::maxi(m_computed.topRows(n).diagonal().cwiseAbs().maxCoeff(),
146 m_computed.topRows(n).template diagonal<-1>().cwiseAbs().maxCoeff());
147 const RealScalar threshold = RealScalar(0.45) * NumTraits<RealScalar>::epsilon() * norm;
148 for (Index i = 0; i + 1 < n; ++i)
149 if (numext::abs(m_computed(i + 1, i)) < threshold) m_computed(i + 1, i) = RealScalar(0);
150}
151
160template <typename RealScalar_>
161void bdcsvd_impl<RealScalar_>::structured_update(Block<MatrixXr, Dynamic, Dynamic> A, const MatrixXr& B, Index n1) {
162 Index n = A.rows();
163 if (n > 100) {
164 // If the matrices are large enough, let's exploit the sparse structure of A by
165 // splitting it in half (wrt n1), and packing the non-zero columns.
166 Index n2 = n - n1;
167 Map<MatrixXr> A1(m_workspace.data(), n1, n);
168 Map<MatrixXr> A2(m_workspace.data() + n1 * n, n2, n);
169 Map<MatrixXr> B1(m_workspace.data() + n * n, n, n);
170 Map<MatrixXr> B2(m_workspace.data() + 2 * n * n, n, n);
171 Index k1 = 0, k2 = 0;
172 for (Index j = 0; j < n; ++j) {
173 if ((A.col(j).head(n1).array() != Literal(0)).any()) {
174 A1.col(k1) = A.col(j).head(n1);
175 B1.row(k1) = B.row(j);
176 ++k1;
177 }
178 if ((A.col(j).tail(n2).array() != Literal(0)).any()) {
179 A2.col(k2) = A.col(j).tail(n2);
180 B2.row(k2) = B.row(j);
181 ++k2;
182 }
183 }
184
185 A.topRows(n1).noalias() = A1.leftCols(k1) * B1.topRows(k1);
186 A.bottomRows(n2).noalias() = A2.leftCols(k2) * B2.topRows(k2);
187 } else {
188 Map<MatrixXr, Aligned> tmp(m_workspace.data(), n, n);
189 tmp.noalias() = A * B;
190 A = tmp;
191 }
192}
193
194template <typename RealScalar_>
195template <typename SVDType>
196void bdcsvd_impl<RealScalar_>::computeBaseCase(SVDType& svd, Index n, Index firstCol, Index firstRowW, Index firstColW,
197 Index shift) {
198 svd.compute(m_computed.block(firstCol, firstCol, n + 1, n));
199 m_info = svd.info();
200 if (m_info != Success && m_info != NoConvergence) return;
201 if (m_compU)
202 m_naiveU.block(firstCol, firstCol, n + 1, n + 1) = svd.matrixU();
203 else {
204 m_naiveU.row(0).segment(firstCol, n + 1) = svd.matrixU().row(0);
205 m_naiveU.row(1).segment(firstCol, n + 1) = svd.matrixU().row(n);
206 }
207 if (m_compV) m_naiveV.block(firstRowW, firstColW, n, n) = svd.matrixV();
208 m_computed.block(firstCol + shift, firstCol + shift, n + 1, n).setZero();
209 m_computed.diagonal().segment(firstCol + shift, n) = svd.singularValues().head(n);
210}
211
212// The divide algorithm is done "in place", we are always working on subsets of the same matrix. The divide methods
213// takes as argument the place of the submatrix we are currently working on.
214
215//@param firstCol : The Index of the first column of the submatrix of m_computed and for m_naiveU;
216//@param lastCol : The Index of the last column of the submatrix of m_computed and for m_naiveU;
217// lastCol + 1 - firstCol is the size of the submatrix.
218//@param firstRowW : The Index of the first row of the matrix W that we are to change. (see the reference paper section
219// 1 for more information on W)
220//@param firstColW : Same as firstRowW with the column.
221//@param shift : Each time one takes the left submatrix, one must add 1 to the shift. Why? Because! We actually want the
222// last column of the U submatrix
223// to become the first column (*coeff) and to shift all the other columns to the right. There are more details on the
224// reference paper.
225template <typename RealScalar_>
226void bdcsvd_impl<RealScalar_>::divide(Index firstCol, Index lastCol, Index firstRowW, Index firstColW, Index shift) {
227 // requires rows = cols + 1;
228 const Index n = lastCol - firstCol + 1;
229 const Index k = n / 2;
230 const RealScalar considerZero = (std::numeric_limits<RealScalar>::min)();
231 RealScalar alphaK;
232 RealScalar betaK;
233 RealScalar r0;
234 RealScalar lambda, phi, c0, s0;
235 // We use the other algorithm which is more efficient for small
236 // matrices.
237 if (n < m_algoswap) {
238 if (m_compV) {
239 computeBaseCase(m_baseSvdUV, n, firstCol, firstRowW, firstColW, shift);
240 } else {
241 computeBaseCase(m_baseSvdU, n, firstCol, firstRowW, firstColW, shift);
242 }
243 return;
244 }
245 // We use the divide and conquer algorithm
246 alphaK = m_computed(firstCol + k, firstCol + k);
247 betaK = m_computed(firstCol + k + 1, firstCol + k);
248 // The divide must be done in that order in order to have good results. Divide change the data inside the submatrices
249 // and the divide of the right submatrice reads one column of the left submatrice. That's why we need to treat the
250 // right submatrix before the left one.
251 divide(k + 1 + firstCol, lastCol, k + 1 + firstRowW, k + 1 + firstColW, shift);
252 if (m_info != Success && m_info != NoConvergence) return;
253 divide(firstCol, k - 1 + firstCol, firstRowW, firstColW + 1, shift + 1);
254 if (m_info != Success && m_info != NoConvergence) return;
255
256 if (m_compU) {
257 lambda = m_naiveU(firstCol + k, firstCol + k);
258 phi = m_naiveU(firstCol + k + 1, lastCol + 1);
259 } else {
260 lambda = m_naiveU(1, firstCol + k);
261 phi = m_naiveU(0, lastCol + 1);
262 }
263 // LAPACK's xLASD2 likewise uses xLAPY2 for this merge coupling. The
264 // scaled hypotenuse avoids destructive underflow when both products are
265 // below sqrt(min()).
266 r0 = numext::hypot(alphaK * lambda, betaK * phi);
267 if (m_compV) m_naiveV(firstRowW + k, firstColW) = Literal(1);
268 if (r0 < considerZero) {
269 c0 = Literal(1);
270 s0 = Literal(0);
271 } else {
272 c0 = alphaK * lambda / r0;
273 s0 = betaK * phi / r0;
274 }
275
276 m_computed(firstCol + shift, firstCol + shift) = r0;
277 if (m_compU) {
278 m_computed.col(firstCol + shift).segment(firstCol + shift + 1, k) =
279 alphaK * m_naiveU.row(firstCol + k).segment(firstCol, k).transpose();
280 m_computed.col(firstCol + shift).segment(firstCol + shift + k + 1, n - k - 1) =
281 betaK * m_naiveU.row(firstCol + k + 1).segment(firstCol + k + 1, n - k - 1).transpose();
282 } else {
283 m_computed.col(firstCol + shift).segment(firstCol + shift + 1, k) =
284 alphaK * m_naiveU.row(1).segment(firstCol, k).transpose();
285 m_computed.col(firstCol + shift).segment(firstCol + shift + k + 1, n - k - 1) =
286 betaK * m_naiveU.row(0).segment(firstCol + k + 1, n - k - 1).transpose();
287 }
288
289 if (m_compU) {
290 Map<VectorType, Aligned> q1(m_workspace.data(), k + 1);
291 q1 = m_naiveU.col(firstCol + k).segment(firstCol, k + 1);
292 // we shift Q1 to the right
293 for (Index i = firstCol + k - 1; i >= firstCol; i--)
294 m_naiveU.col(i + 1).segment(firstCol, k + 1) = m_naiveU.col(i).segment(firstCol, k + 1);
295 // we shift q1 at the left with a factor c0
296 m_naiveU.col(firstCol).segment(firstCol, k + 1) = (q1 * c0);
297 // last column = q1 * - s0
298 m_naiveU.col(lastCol + 1).segment(firstCol, k + 1) = (q1 * (-s0));
299 // first column = q2 * s0
300 m_naiveU.col(firstCol).segment(firstCol + k + 1, n - k) =
301 m_naiveU.col(lastCol + 1).segment(firstCol + k + 1, n - k) * s0;
302 // q2 *= c0
303 m_naiveU.col(lastCol + 1).segment(firstCol + k + 1, n - k) *= c0;
304 } else {
305 RealScalar q1 = m_naiveU(0, firstCol + k);
306 // we shift Q1 to the right
307 for (Index i = firstCol + k - 1; i >= firstCol; i--) m_naiveU(0, i + 1) = m_naiveU(0, i);
308 // we shift q1 at the left with a factor c0
309 m_naiveU(0, firstCol) = (q1 * c0);
310 // last column = q1 * - s0
311 m_naiveU(0, lastCol + 1) = (q1 * (-s0));
312 // first column = q2 * s0
313 m_naiveU(1, firstCol) = m_naiveU(1, lastCol + 1) * s0;
314 // q2 *= c0
315 m_naiveU(1, lastCol + 1) *= c0;
316 m_naiveU.row(1).segment(firstCol + 1, k).setZero();
317 m_naiveU.row(0).segment(firstCol + k + 1, n - k - 1).setZero();
318 }
319
320 // Second part: try to deflate singular values in combined matrix
321 deflation(firstCol, lastCol, k, firstRowW, firstColW, shift);
322
323 // Third part: compute SVD of combined matrix
324 MatrixXr UofSVD, VofSVD;
325 VectorType singVals;
326 computeSVDofM(firstCol + shift, n, UofSVD, singVals, VofSVD);
327
328 if (m_compU)
329 structured_update(m_naiveU.block(firstCol, firstCol, n + 1, n + 1), UofSVD, (n + 2) / 2);
330 else {
331 Map<Matrix<RealScalar, 2, Dynamic>, Aligned> tmp(m_workspace.data(), 2, n + 1);
332 tmp.noalias() = m_naiveU.middleCols(firstCol, n + 1) * UofSVD;
333 m_naiveU.middleCols(firstCol, n + 1) = tmp;
334 }
335
336 if (m_compV) structured_update(m_naiveV.block(firstRowW, firstColW, n, n), VofSVD, (n + 1) / 2);
337
338 // Recursive children leave this block diagonal; this merge only adds its
339 // first column. Clear that column instead of rewriting the full n-by-n block.
340 m_computed.col(firstCol + shift).segment(firstCol + shift, n).setZero();
341 m_computed.diagonal().segment(firstCol + shift, n) = singVals;
342} // end divide
343
344// Compute SVD of m_computed.block(firstCol, firstCol, n + 1, n); this block only has non-zeros in
345// the first column and on the diagonal and has undergone deflation, so diagonal is in increasing
346// order except for possibly the (0,0) entry. The computed SVD is stored U, singVals and V, except
347// that if m_compV is false, then V is not computed. Singular values are sorted in decreasing order.
348template <typename RealScalar_>
349void bdcsvd_impl<RealScalar_>::computeSVDofM(Index firstCol, Index n, MatrixXr& U, VectorType& singVals, MatrixXr& V) {
350 const RealScalar considerZero = (std::numeric_limits<RealScalar>::min)();
351 using std::abs;
352 ArrayRef col0 = m_computed.col(firstCol).segment(firstCol, n);
353 m_workspace.head(n) = m_computed.block(firstCol, firstCol, n, n).diagonal();
354 ArrayRef diag = m_workspace.head(n);
355 diag(0) = Literal(0);
356
357 // Allocate space for singular values and vectors
358 singVals.resize(n);
359 U.resize(n + 1, n + 1);
360 if (m_compV) V.resize(n, n);
361
362 // Many singular values might have been deflated, the zero ones have been moved to the end,
363 // but others are interleaved and we must ignore them at this stage.
364 // To this end, let's compute a permutation skipping them:
365 Index actual_n = n;
366 while (actual_n > 1 && numext::is_exactly_zero(diag(actual_n - 1))) {
367 --actual_n;
368 eigen_internal_assert(numext::is_exactly_zero(col0(actual_n)));
369 }
370 Index m = 0; // size of the deflated problem
371 for (Index k = 0; k < actual_n; ++k)
372 if (abs(col0(k)) > considerZero) m_workspaceI(m++) = k;
373 Map<ArrayXi> perm(m_workspaceI.data(), m);
374
375 Map<ArrayXr> shifts(m_workspace.data() + 1 * n, n);
376 Map<ArrayXr> mus(m_workspace.data() + 2 * n, n);
377 Map<ArrayXr> zhat(m_workspace.data() + 3 * n, n);
378
379 // Compute singVals, shifts, and mus
380 // U is filled by computeSingVecs below; reuse its storage to pack the active secular terms.
381 Map<ArrayXr> secularWorkspace(U.data(), 2 * n);
382 computeSingVals(col0, diag, perm, singVals, shifts, mus, secularWorkspace);
383
384 // Compute zhat
385 perturbCol0(col0, diag, perm, singVals, shifts, mus, zhat);
386
387 computeSingVecs(zhat, diag, perm, singVals, shifts, mus, U, V);
388
389 // Because of deflation, the singular values might not be completely sorted.
390 // Fortunately, reordering them is a O(n) problem
391 for (Index i = 0; i < actual_n - 1; ++i) {
392 if (singVals(i) > singVals(i + 1)) {
393 using std::swap;
394 swap(singVals(i), singVals(i + 1));
395 U.col(i).swap(U.col(i + 1));
396 if (m_compV) V.col(i).swap(V.col(i + 1));
397 }
398 }
399
400 // Reverse order so that singular values in increased order
401 // Because of deflation, the zeros singular-values are already at the end
402 singVals.head(actual_n).reverseInPlace();
403 U.leftCols(actual_n).rowwise().reverseInPlace();
404 if (m_compV) V.leftCols(actual_n).rowwise().reverseInPlace();
405}
406
407template <typename RealScalar_>
408EIGEN_STRONG_INLINE typename bdcsvd_impl<RealScalar_>::RealScalar bdcsvd_impl<RealScalar_>::productOfQuotients(
409 RealScalar firstNumerator, RealScalar firstDenominator, RealScalar secondNumerator, RealScalar secondDenominator) {
410 // Keep the divisions separate: combining their denominators can underflow even when the final product is finite.
411 RealScalar firstQuotient = firstNumerator / firstDenominator;
412 RealScalar secondQuotient = secondNumerator / secondDenominator;
413#if defined(__FAST_MATH__) || EIGEN_COMP_NVHPC
414 // NVHPC does not expose a preprocessor macro for -fast, so retain the barriers in all NVHPC builds.
415 EIGEN_OPTIMIZATION_BARRIER(firstQuotient)
416 EIGEN_OPTIMIZATION_BARRIER(secondQuotient)
417#endif
418 return firstQuotient * secondQuotient;
419}
420
421template <typename RealScalar_>
422EIGEN_STRONG_INLINE typename bdcsvd_impl<RealScalar_>::RealScalar bdcsvd_impl<RealScalar_>::sequentialQuotient(
423 RealScalar numerator, RealScalar firstDenominator, RealScalar secondDenominator) {
424 RealScalar firstQuotient = numerator / firstDenominator;
425#if defined(__FAST_MATH__) || EIGEN_COMP_NVHPC
426 EIGEN_OPTIMIZATION_BARRIER(firstQuotient)
427#endif
428 return firstQuotient / secondDenominator;
429}
430
431template <typename RealScalar_>
432typename bdcsvd_impl<RealScalar_>::RealScalar bdcsvd_impl<RealScalar_>::secularEq(RealScalar mu,
433 const ConstArrayRef& col0,
434 const ConstArrayRef& diag,
435 const ConstArrayRef& diagShifted,
436 RealScalar shift) {
437 RealScalar res = Literal(1);
438 for (Index i = 0; i < col0.size(); ++i) {
439 res += productOfQuotients(col0(i), diagShifted(i) - mu, col0(i), diag(i) + shift + mu);
440 }
441 return res;
442}
443
444template <typename RealScalar_>
445void bdcsvd_impl<RealScalar_>::computeSingVals(const ArrayRef& col0, const ArrayRef& diag, const IndicesRef& perm,
446 VectorType& singVals, ArrayRef shifts, ArrayRef mus,
447 ArrayRef workspace) {
448 // See Ren-Cang Li, "Solving Secular Equations Stably and Efficiently",
449 // LAPACK Working Note 89 (1994), and LAPACK's xLASD4/xLASD5 for the
450 // stability rationale behind pole-relative shifts and safeguarded steps.
451 using std::abs;
452 using std::swap;
453
454 Index n = col0.size();
455 const Index m = perm.size();
456 // perm is strictly increasing, so its last entry identifies a contiguous prefix.
457 const bool contiguous = m == 0 || perm(m - 1) == m - 1;
458 if (!contiguous) {
459 for (Index i = 0; i < m; ++i) {
460 workspace(i) = col0(perm(i));
461 workspace(m + i) = diag(perm(i));
462 }
463 }
464 // Bind the views once for the repeated secularEq evaluations.
465 const ConstArrayRef activeCol0 = Map<const ArrayXr>(contiguous ? col0.data() : workspace.data(), m);
466 const ConstArrayRef activeDiag = Map<const ArrayXr>(contiguous ? diag.data() : workspace.data() + m, m);
467 Index actual_n = n;
468 // Note that here actual_n is computed based on col0(i)==0 instead of diag(i)==0 as above
469 // because 1) we have diag(i)==0 => col0(i)==0 and 2) if col0(i)==0, then diag(i) is already a singular value.
470 while (actual_n > 1 && numext::is_exactly_zero(col0(actual_n - 1))) --actual_n;
471
472 for (Index k = 0; k < n; ++k) {
473 if (numext::is_exactly_zero(col0(k)) || actual_n == 1) {
474 // if col0(k) == 0, then entry is deflated, so singular value is on diagonal
475 // if actual_n==1, then the deflated problem is already diagonalized
476 singVals(k) = k == 0 ? col0(0) : diag(k);
477 mus(k) = Literal(0);
478 shifts(k) = k == 0 ? col0(0) : diag(k);
479 continue;
480 }
481
482 // otherwise, use secular equation to find singular value
483 RealScalar left = diag(k);
484 RealScalar right; // was: = (k != actual_n-1) ? diag(k+1) : (diag(actual_n-1) + col0.matrix().norm());
485 if (k == actual_n - 1)
486 right = (diag(actual_n - 1) + col0.matrix().stableNorm());
487 else {
488 // Skip deflated singular values,
489 // recall that at this stage we assume that z[j]!=0 and all entries for which z[j]==0 have been put aside.
490 // This should be equivalent to using perm[]
491 Index l = k + 1;
492 while (numext::is_exactly_zero(col0(l))) {
493 ++l;
494 eigen_internal_assert(l < actual_n);
495 }
496 right = diag(l);
497 }
498
499 // first decide whether it's closer to the left end or the right end
500 RealScalar mid = left + (right - left) / Literal(2);
501 RealScalar fMid = secularEq(mid, activeCol0, activeDiag, activeDiag, Literal(0));
502 RealScalar shift = (k == actual_n - 1 || fMid > Literal(0)) ? left : right;
503
504 // measure everything relative to shift
505 Map<ArrayXr> diagShifted(m_workspace.data() + 4 * n, m);
506 const ConstArrayRef shiftedRef(diagShifted);
507 diagShifted = activeDiag - shift;
508
509 if (k != actual_n - 1) {
510 // check that after the shift, f(mid) is still negative:
511 RealScalar midShifted = (right - left) / RealScalar(2);
512 // we can test exact equality here, because shift comes from `... ? left : right`
513 if (numext::equal_strict(shift, right)) midShifted = -midShifted;
514 RealScalar fMidShifted = secularEq(midShifted, activeCol0, activeDiag, shiftedRef, shift);
515 if (fMidShifted > 0) {
516 // fMid was erroneous, fix it:
517 shift = fMidShifted > Literal(0) ? left : right;
518 diagShifted = activeDiag - shift;
519 }
520 }
521
522 // initial guess
523 RealScalar muPrev, muCur;
524 // we can test exact equality here, because shift comes from `... ? left : right`
525 if (numext::equal_strict(shift, left)) {
526 muPrev = (right - left) * RealScalar(0.1);
527 if (k == actual_n - 1)
528 muCur = right - left;
529 else
530 muCur = (right - left) * RealScalar(0.5);
531 } else {
532 muPrev = -(right - left) * RealScalar(0.1);
533 muCur = -(right - left) * RealScalar(0.5);
534 }
535
536 RealScalar fPrev = secularEq(muPrev, activeCol0, activeDiag, shiftedRef, shift);
537 RealScalar fCur = secularEq(muCur, activeCol0, activeDiag, shiftedRef, shift);
538 if (abs(fPrev) < abs(fCur)) {
539 swap(fPrev, fCur);
540 swap(muPrev, muCur);
541 }
542
543 // Fit a / mu + b through the two previous iterates. Equal signs permit extrapolation; flat fits and
544 // samples whose reciprocals may overflow stay on bisection. The interval and residual checks below
545 // safeguard each interpolation step.
546 const RealScalar minNormal = (std::numeric_limits<RealScalar>::min)();
547 bool useBisection = fPrev * fCur > Literal(0) && !(abs(fCur - fPrev) > NumTraits<RealScalar>::epsilon() &&
548 abs(fPrev) <= (std::numeric_limits<RealScalar>::max)() &&
549 abs(muPrev) >= minNormal && abs(muCur) >= minNormal);
550 while (!numext::is_exactly_zero(fCur) &&
551 abs(muCur - muPrev) >
552 Literal(8) * NumTraits<RealScalar>::epsilon() * numext::maxi<RealScalar>(abs(muCur), abs(muPrev)) &&
553 abs(fCur - fPrev) > NumTraits<RealScalar>::epsilon() && !useBisection) {
554 ++m_numIters;
555
556 // Find a and b such that the function f(mu) = a / mu + b matches the current and previous samples.
557 RealScalar a = (fCur - fPrev) / (Literal(1) / muCur - Literal(1) / muPrev);
558 RealScalar b = fCur - a / muCur;
559 // And find mu such that f(mu)==0:
560 RealScalar muZero = -a / b;
561 RealScalar fZero = secularEq(muZero, activeCol0, activeDiag, shiftedRef, shift);
562
563 muPrev = muCur;
564 fPrev = fCur;
565 muCur = muZero;
566 fCur = fZero;
567
568 // we can test exact equality here, because shift comes from `... ? left : right`
569 if (numext::equal_strict(shift, left) && (muCur < Literal(0) || muCur > right - left)) useBisection = true;
570 if (numext::equal_strict(shift, right) && (muCur < -(right - left) || muCur > Literal(0))) useBisection = true;
571 if (abs(fCur) > abs(fPrev)) useBisection = true;
572 }
573
574 // fall back on bisection method if rational interpolation did not work
575 if (useBisection) {
576 RealScalar leftShifted, rightShifted;
577 // we can test exact equality here, because shift comes from `... ? left : right`
578 if (numext::equal_strict(shift, left)) {
579 // to avoid overflow, we must have mu > max(real_min, |z(k)|/sqrt(real_max)),
580 // the factor 2 is to be more conservative
581 leftShifted = numext::maxi<RealScalar>(
582 (std::numeric_limits<RealScalar>::min)(),
583 Literal(2) * abs(col0(k)) / numext::sqrt((std::numeric_limits<RealScalar>::max)()));
584
585 // check that we did it right:
586 eigen_internal_assert(
587 (numext::isfinite)(productOfQuotients(col0(k), leftShifted, col0(k), diag(k) + shift + leftShifted)));
588 rightShifted = (k == actual_n - 1)
589 ? right
590 : ((right - left) * RealScalar(0.51)); // theoretically we can take 0.5, but let's be safe
591 } else {
592 leftShifted = -(right - left) * RealScalar(0.51);
593 if (k + 1 < n)
594 rightShifted =
595 -numext::maxi<RealScalar>((std::numeric_limits<RealScalar>::min)(),
596 abs(col0(k + 1)) / numext::sqrt((std::numeric_limits<RealScalar>::max)()));
597 else
598 rightShifted = -(std::numeric_limits<RealScalar>::min)();
599 }
600 RealScalar fLeft = secularEq(leftShifted, activeCol0, activeDiag, shiftedRef, shift);
601 eigen_internal_assert(fLeft < Literal(0));
602
603 if (fLeft < Literal(0)) {
604 while (rightShifted - leftShifted > Literal(2) * NumTraits<RealScalar>::epsilon() *
605 numext::maxi<RealScalar>(abs(leftShifted), abs(rightShifted))) {
606 RealScalar midShifted = (leftShifted + rightShifted) / Literal(2);
607 fMid = secularEq(midShifted, activeCol0, activeDiag, shiftedRef, shift);
608 eigen_internal_assert((numext::isfinite)(fMid));
609
610 if (fLeft * fMid < Literal(0)) {
611 rightShifted = midShifted;
612 } else {
613 leftShifted = midShifted;
614 fLeft = fMid;
615 }
616 }
617 muCur = (leftShifted + rightShifted) / Literal(2);
618 } else {
619 // We have a problem as shifting on the left or right give either a positive or negative value
620 // at the middle of [left,right]...
621 // Instead of aborting or entering an infinite loop,
622 // let's just use the middle as the estimated zero-crossing:
623 muCur = (right - left) * RealScalar(0.5);
624 // we can test exact equality here, because shift comes from `... ? left : right`
625 if (numext::equal_strict(shift, right)) muCur = -muCur;
626 }
627 }
628
629 singVals[k] = shift + muCur;
630 shifts[k] = shift;
631 mus[k] = muCur;
632 }
633}
634
635// zhat is perturbation of col0 for which singular vectors can be computed stably (see Section 3.1)
636template <typename RealScalar_>
637void bdcsvd_impl<RealScalar_>::perturbCol0(const ArrayRef& col0, const ArrayRef& diag, const IndicesRef& perm,
638 const VectorType& singVals, const ArrayRef& shifts, const ArrayRef& mus,
639 ArrayRef zhat) {
640 using std::abs;
641 Index n = col0.size();
642 Index m = perm.size();
643 if (m == 0) {
644 zhat.setZero();
645 return;
646 }
647 Index lastIdx = perm(m - 1);
648 // The offset permits to skip deflated entries while computing zhat
649 for (Index k = 0; k < n; ++k) {
650 if (numext::is_exactly_zero(col0(k))) // deflated
651 zhat(k) = Literal(0);
652 else {
653 // see equation (3.6)
654 RealScalar dk = diag(k);
655 // Materialize the close subtraction before adding the small correction.
656 // Unsafe FP reassociation may otherwise turn `mus + (shift - dk)` into
657 // `(mus + shift) - dk`, losing `mus` when `shift` and `dk` cancel.
658 RealScalar diff = shifts(lastIdx) - dk;
659 EIGEN_OPTIMIZATION_BARRIER(diff)
660 RealScalar prod = (singVals(lastIdx) + dk) * (mus(lastIdx) + diff);
661
662 for (Index l = 0; l < m; ++l) {
663 Index i = perm(l);
664 if (i != k) {
665 // There is no valid predecessor when the first active index is already on the
666 // right of k. Treat this as a numerical issue and zero the product.
667 if (i >= k && l == 0) {
668 m_info = NumericalIssue;
669 prod = Literal(0);
670 break;
671 }
672 Index j = i < k ? i : perm(l - 1);
673 diff = shifts(j) - dk;
674 EIGEN_OPTIMIZATION_BARRIER(diff)
675 prod *= productOfQuotients(singVals(j) + dk, diag(i) + dk, mus(j) + diff, diag(i) - dk);
676 }
677 }
678 // This product is non-negative in exact arithmetic. As in LAPACK's
679 // xLASD8, take abs before sqrt to tolerate a negative rounding residue.
680 RealScalar tmp = numext::sqrt(numext::abs(prod));
681 zhat(k) = col0(k) > Literal(0) ? RealScalar(tmp) : RealScalar(-tmp);
682 }
683 }
684}
685
686// compute singular vectors
687template <typename RealScalar_>
688void bdcsvd_impl<RealScalar_>::computeSingVecs(const ArrayRef& zhat, const ArrayRef& diag, const IndicesRef& perm,
689 const VectorType& singVals, const ArrayRef& shifts, const ArrayRef& mus,
690 MatrixXr& U, MatrixXr& V) {
691 Index n = zhat.size();
692 Index m = perm.size();
693
694 for (Index k = 0; k < n; ++k) {
695 if (numext::is_exactly_zero(zhat(k))) {
696 U.col(k) = VectorType::Unit(n + 1, k);
697 if (m_compV) V.col(k) = VectorType::Unit(n, k);
698 } else {
699 U.col(k).setZero();
700 if (m_compV) V.col(k).setZero();
701 const RealScalar shift = shifts(k);
702 const RealScalar mu = mus(k);
703 const RealScalar singularValue = singVals(k);
704 for (Index l = 0; l < m; ++l) {
705 Index i = perm(l);
706 const RealScalar diagonal = diag(i);
707 const RealScalar z = zhat(i);
708 RealScalar diff = diagonal - shift;
709 EIGEN_OPTIMIZATION_BARRIER(diff)
710 diff -= mu;
711 EIGEN_OPTIMIZATION_BARRIER(diff)
712 const RealScalar sum = diagonal + singularValue;
713 U(i, k) = sequentialQuotient(z, diff, sum);
714 if (m_compV && l > 0) V(i, k) = sequentialQuotient(diagonal * z, diff, sum);
715 }
716 U(n, k) = Literal(0);
717 // LAPACK's xLASD3 normalizes these vectors with xNRM2. Use the scaled
718 // normalization unconditionally: under -ffast-math, compilers may
719 // assume that the overflowing result of norm() is finite and discard
720 // an isfinite-based fallback.
721 U.col(k).stableNormalize();
722
723 if (m_compV) {
724 V(0, k) = Literal(-1);
725 V.col(k).stableNormalize();
726 }
727 }
728 }
729 U.col(n) = VectorType::Unit(n + 1, n);
730}
731
732// page 12_13
733// i >= 1, di almost null and zi non null.
734// We use a rotation to zero out zi applied to the left of M, and set di = 0.
735template <typename RealScalar_>
736void bdcsvd_impl<RealScalar_>::deflation43(Index firstCol, Index shift, Index i, Index size) {
737 Index start = firstCol + shift;
738 RealScalar c = m_computed(start, start);
739 RealScalar s = m_computed(start + i, start);
740 RealScalar r = numext::hypot(c, s);
741 if (numext::is_exactly_zero(r)) {
742 m_computed(start + i, start + i) = Literal(0);
743 return;
744 }
745 m_computed(start, start) = r;
746 m_computed(start + i, start) = Literal(0);
747 m_computed(start + i, start + i) = Literal(0);
748
749 JacobiRotation<RealScalar> J(c / r, -s / r);
750 if (m_compU)
751 m_naiveU.middleRows(firstCol, size + 1).applyOnTheRight(firstCol, firstCol + i, J);
752 else
753 m_naiveU.applyOnTheRight(firstCol, firstCol + i, J);
754} // end deflation 43
755
756// page 13
757// i,j >= 1, i > j, and |di - dj| < epsilon * norm2(M)
758// We apply two rotations to have zi = 0, and dj = di.
759template <typename RealScalar_>
760void bdcsvd_impl<RealScalar_>::deflation44(Index firstColu, Index firstColm, Index firstRowW, Index firstColW, Index i,
761 Index j, Index size) {
762 RealScalar s = m_computed(firstColm + i, firstColm);
763 RealScalar c = m_computed(firstColm + j, firstColm);
764 RealScalar r = numext::hypot(c, s);
765 if (numext::is_exactly_zero(r)) {
766 m_computed(firstColm + j, firstColm + j) = m_computed(firstColm + i, firstColm + i);
767 return;
768 }
769 c /= r;
770 s /= r;
771 m_computed(firstColm + j, firstColm) = r;
772 m_computed(firstColm + j, firstColm + j) = m_computed(firstColm + i, firstColm + i);
773 m_computed(firstColm + i, firstColm) = Literal(0);
774
775 JacobiRotation<RealScalar> J(c, -s);
776 if (m_compU)
777 m_naiveU.middleRows(firstColu, size + 1).applyOnTheRight(firstColu + j, firstColu + i, J);
778 else
779 m_naiveU.applyOnTheRight(firstColu + j, firstColu + i, J);
780 if (m_compV) m_naiveV.middleRows(firstRowW, size).applyOnTheRight(firstColW + j, firstColW + i, J);
781} // end deflation 44
782
783// acts on block from (firstCol+shift, firstCol+shift) to (lastCol+shift, lastCol+shift) [inclusive]
784template <typename RealScalar_>
785void bdcsvd_impl<RealScalar_>::deflation(Index firstCol, Index lastCol, Index k, Index firstRowW, Index firstColW,
786 Index shift) {
787 using std::abs;
788 const Index length = lastCol + 1 - firstCol;
789
790 Block<MatrixXr, Dynamic, 1> col0(m_computed, firstCol + shift, firstCol + shift, length, 1);
791 Diagonal<MatrixXr> fulldiag(m_computed);
792 VectorBlock<Diagonal<MatrixXr>, Dynamic> diag(fulldiag, firstCol + shift, length);
793
794 const RealScalar considerZero = (std::numeric_limits<RealScalar>::min)();
795 RealScalar maxDiag = diag.tail((std::max)(Index(1), length - 1)).cwiseAbs().maxCoeff();
796 RealScalar epsilon_strict = numext::maxi<RealScalar>(considerZero, NumTraits<RealScalar>::epsilon() * maxDiag);
797 RealScalar epsilon_coarse =
798 Literal(8) * NumTraits<RealScalar>::epsilon() * numext::maxi<RealScalar>(col0.cwiseAbs().maxCoeff(), maxDiag);
799
800 // condition 4.1
801 if (diag(0) < epsilon_coarse) {
802 diag(0) = epsilon_coarse;
803 }
804
805 // condition 4.2
806 for (Index i = 1; i < length; ++i)
807 if (abs(col0(i)) < epsilon_strict) {
808 col0(i) = Literal(0);
809 }
810
811 // condition 4.3
812 for (Index i = 1; i < length; i++)
813 if (diag(i) < epsilon_coarse) {
814 deflation43(firstCol, shift, i, length);
815 }
816
817 {
818 // Check for total deflation:
819 // If we have a total deflation, then we have to consider col0(0)==diag(0) as a singular value during sorting.
820 const bool total_deflation = (col0.tail(length - 1).array().abs() < considerZero).all();
821
822 // Sort the diagonal entries, since diag(1:k-1) and diag(k:length) are already sorted, let's do a sorted merge.
823 // First, compute the respective permutation.
824 Index* permutation = m_workspaceI.data();
825 {
826 permutation[0] = 0;
827 Index p = 1;
828
829 // Move deflated diagonal entries at the end.
830 for (Index i = 1; i < length; ++i)
831 if (diag(i) < considerZero) permutation[p++] = i;
832
833 Index i = 1, j = k + 1;
834 for (; p < length; ++p) {
835 if (i > k)
836 permutation[p] = j++;
837 else if (j >= length)
838 permutation[p] = i++;
839 else if (diag(i) < diag(j))
840 permutation[p] = j++;
841 else
842 permutation[p] = i++;
843 }
844 }
845
846 // If we have a total deflation, then we have to insert diag(0) at the right place
847 if (total_deflation) {
848 for (Index i = 1; i < length; ++i) {
849 Index pi = permutation[i];
850 if (diag(pi) < considerZero || diag(0) < diag(pi))
851 permutation[i - 1] = permutation[i];
852 else {
853 permutation[i - 1] = 0;
854 break;
855 }
856 }
857 }
858
859 // Current index of each col, and current column of each index
860 Index* realInd = m_workspaceI.data() + length;
861 Index* realCol = m_workspaceI.data() + 2 * length;
862
863 for (int pos = 0; pos < length; pos++) {
864 realCol[pos] = pos;
865 realInd[pos] = pos;
866 }
867
868 for (Index i = total_deflation ? 0 : 1; i < length; i++) {
869 const Index pi = permutation[length - (total_deflation ? i + 1 : i)];
870 const Index J = realCol[pi];
871
872 using std::swap;
873 // swap diagonal and first column entries:
874 swap(diag(i), diag(J));
875 if (i != 0 && J != 0) swap(col0(i), col0(J));
876
877 // change columns
878 if (m_compU)
879 m_naiveU.col(firstCol + i)
880 .segment(firstCol, length + 1)
881 .swap(m_naiveU.col(firstCol + J).segment(firstCol, length + 1));
882 else
883 m_naiveU.col(firstCol + i).segment(0, 2).swap(m_naiveU.col(firstCol + J).segment(0, 2));
884 if (m_compV)
885 m_naiveV.col(firstColW + i)
886 .segment(firstRowW, length)
887 .swap(m_naiveV.col(firstColW + J).segment(firstRowW, length));
888
889 // update real pos
890 const Index realI = realInd[i];
891 realCol[realI] = J;
892 realCol[pi] = i;
893 realInd[J] = realI;
894 realInd[i] = pi;
895 }
896 }
897
898 // condition 4.4
899 {
900 Index i = length - 1;
901 // Find last non-deflated entry.
902 while (i > 0 && (diag(i) < considerZero || abs(col0(i)) < considerZero)) --i;
903
904 for (; i > 1; --i)
905 if ((diag(i) - diag(i - 1)) < epsilon_coarse) {
906 deflation44(firstCol, firstCol + shift, firstRowW, firstColW, i, i - 1, length);
907 }
908 }
909
910} // end deflation
911
912} // end namespace internal
913
914} // end namespace Eigen
915
916#endif // EIGEN_BDCSVD_IMPL_H
ComputationInfo
Definition Constants.h:455
@ Aligned
Definition Constants.h:243
@ NumericalIssue
Definition Constants.h:459
@ Success
Definition Constants.h:457
@ NoConvergence
Definition Constants.h:461