Eigen  5.0.1
 
Loading...
Searching...
No Matches
JacobiSVD.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2009-2010 Benoit Jacob <jacob.benoit.1@gmail.com>
5// Copyright (C) 2013-2014 Gael Guennebaud <gael.guennebaud@inria.fr>
6//
7// This Source Code Form is subject to the terms of the Mozilla
8// Public License v. 2.0. If a copy of the MPL was not distributed
9// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
10// SPDX-License-Identifier: MPL-2.0
11
12#ifndef EIGEN_JACOBISVD_H
13#define EIGEN_JACOBISVD_H
14
15// IWYU pragma: private
16#include "./InternalHeaderCheck.h"
17
18namespace Eigen {
19
20namespace internal {
21
22// forward declaration (needed by ICC)
23// the empty body is required by MSVC
24template <typename MatrixType, int Options, bool IsComplex = NumTraits<typename MatrixType::Scalar>::IsComplex>
25struct svd_precondition_2x2_block_to_be_real {};
26
27/*** QR preconditioners (R-SVD)
28 ***
29 *** Their role is to reduce the problem of computing the SVD to the case of a square matrix.
30 *** This approach, known as R-SVD, is an optimization for rectangular-enough matrices, and is a requirement for
31 *** JacobiSVD which by itself is only able to work on square matrices.
32 ***/
33
34enum { PreconditionIfMoreColsThanRows, PreconditionIfMoreRowsThanCols };
35
36// If option PreconditionSquareMatrix is set, square matrices take the PreconditionIfMoreRowsThanCols path: A = Q R P^*.
37constexpr bool svd_precondition_more_rows(int options, Index rows, Index cols) {
38 return rows > cols || (should_svd_precondition_square_matrix(options) && rows == cols);
39}
40
41template <typename MatrixType, int QRPreconditioner, int Case, bool PreconditionSquare>
42struct qr_preconditioner_should_do_anything
43 : bool_constant<!((QRPreconditioner == NoQRPreconditioner) ||
44 (Case == PreconditionIfMoreColsThanRows && MatrixType::RowsAtCompileTime != Dynamic &&
45 MatrixType::ColsAtCompileTime != Dynamic &&
46 MatrixType::ColsAtCompileTime <= MatrixType::RowsAtCompileTime) ||
47 (Case == PreconditionIfMoreRowsThanCols && MatrixType::RowsAtCompileTime != Dynamic &&
48 MatrixType::ColsAtCompileTime != Dynamic &&
49 (PreconditionSquare ? MatrixType::RowsAtCompileTime < MatrixType::ColsAtCompileTime
50 : MatrixType::RowsAtCompileTime <= MatrixType::ColsAtCompileTime)))> {};
51
52template <typename MatrixType, int Options, int QRPreconditioner, int Case,
53 bool DoAnything = qr_preconditioner_should_do_anything<MatrixType, QRPreconditioner, Case,
54 should_svd_precondition_square_matrix(Options)>::value>
55struct qr_preconditioner_impl {};
56
57template <typename MatrixType, int Options, int QRPreconditioner, int Case>
58class qr_preconditioner_impl<MatrixType, Options, QRPreconditioner, Case, false> {
59 public:
60 void allocate(const JacobiSVD<MatrixType, Options>&) {}
61 template <typename Xpr>
62 bool run(JacobiSVD<MatrixType, Options>&, const Xpr&) {
63 return false;
64 }
65};
66
67/*** preconditioner using FullPivHouseholderQR ***/
68
69template <typename MatrixType, int Options>
70class qr_preconditioner_impl<MatrixType, Options, FullPivHouseholderQRPreconditioner, PreconditionIfMoreRowsThanCols,
71 true> {
72 public:
73 using Scalar = typename MatrixType::Scalar;
74 using SVDType = JacobiSVD<MatrixType, Options>;
75
76 enum { WorkspaceSize = MatrixType::RowsAtCompileTime, MaxWorkspaceSize = MatrixType::MaxRowsAtCompileTime };
77
78 using WorkspaceType = Matrix<Scalar, 1, WorkspaceSize, RowMajor, 1, MaxWorkspaceSize>;
79
80 void allocate(const SVDType& svd) {
81 if (svd.rows() != m_qr.rows() || svd.cols() != m_qr.cols()) {
82 internal::destroy_at(&m_qr);
83 internal::construct_at(&m_qr, svd.rows(), svd.cols());
84 }
85 if (svd.m_computeFullU) m_workspace.resize(svd.rows());
86 }
87 template <typename Xpr>
88 bool run(SVDType& svd, const Xpr& matrix) {
89 if (svd_precondition_more_rows(Options, matrix.rows(), matrix.cols())) {
90 m_qr.compute(matrix);
91 svd.m_workMatrix = m_qr.matrixQR().block(0, 0, matrix.cols(), matrix.cols()).template triangularView<Upper>();
92 if (svd.m_computeFullU) m_qr.matrixQ().evalTo(svd.m_matrixU, m_workspace);
93 if (svd.computeV()) svd.m_matrixV = m_qr.colsPermutation();
94 return true;
95 }
96 return false;
97 }
98
99 private:
100 using QRType = FullPivHouseholderQR<MatrixType>;
101 QRType m_qr;
102 WorkspaceType m_workspace;
103};
104
105template <typename MatrixType, int Options>
106class qr_preconditioner_impl<MatrixType, Options, FullPivHouseholderQRPreconditioner, PreconditionIfMoreColsThanRows,
107 true> {
108 public:
109 using Scalar = typename MatrixType::Scalar;
110 using SVDType = JacobiSVD<MatrixType, Options>;
111
112 enum {
113 RowsAtCompileTime = MatrixType::RowsAtCompileTime,
114 ColsAtCompileTime = MatrixType::ColsAtCompileTime,
115 MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
116 MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime,
117 MatrixOptions = traits<MatrixType>::Options
118 };
119
120 using TransposeTypeWithSameStorageOrder =
121 typename internal::make_proper_matrix_type<Scalar, ColsAtCompileTime, RowsAtCompileTime, MatrixOptions,
122 MaxColsAtCompileTime, MaxRowsAtCompileTime>::type;
123
124 void allocate(const SVDType& svd) {
125 if (svd.cols() != m_qr.rows() || svd.rows() != m_qr.cols()) {
126 internal::destroy_at(&m_qr);
127 internal::construct_at(&m_qr, svd.cols(), svd.rows());
128 }
129 if (svd.m_computeFullV) m_workspace.resize(svd.cols());
130 }
131 template <typename Xpr>
132 bool run(SVDType& svd, const Xpr& matrix) {
133 if (matrix.cols() > matrix.rows()) {
134 m_qr.compute(matrix.adjoint());
135 svd.m_workMatrix =
136 m_qr.matrixQR().block(0, 0, matrix.rows(), matrix.rows()).template triangularView<Upper>().adjoint();
137 if (svd.m_computeFullV) m_qr.matrixQ().evalTo(svd.m_matrixV, m_workspace);
138 if (svd.computeU()) svd.m_matrixU = m_qr.colsPermutation();
139 return true;
140 } else
141 return false;
142 }
143
144 private:
145 using QRType = FullPivHouseholderQR<TransposeTypeWithSameStorageOrder>;
146 QRType m_qr;
147 typename plain_row_type<MatrixType>::type m_workspace;
148};
149
150/*** preconditioner using ColPivHouseholderQR ***/
151
152template <typename MatrixType, int Options>
153class qr_preconditioner_impl<MatrixType, Options, ColPivHouseholderQRPreconditioner, PreconditionIfMoreRowsThanCols,
154 true> {
155 public:
156 using Scalar = typename MatrixType::Scalar;
157 using SVDType = JacobiSVD<MatrixType, Options>;
158
159 enum {
160 WorkspaceSize = internal::traits<SVDType>::MatrixUColsAtCompileTime,
161 MaxWorkspaceSize = internal::traits<SVDType>::MatrixUMaxColsAtCompileTime
162 };
163
164 using WorkspaceType = Matrix<Scalar, 1, WorkspaceSize, RowMajor, 1, MaxWorkspaceSize>;
165
166 void allocate(const SVDType& svd) {
167 if (svd.rows() != m_qr.rows() || svd.cols() != m_qr.cols()) {
168 internal::destroy_at(&m_qr);
169 internal::construct_at(&m_qr, svd.rows(), svd.cols());
170 }
171 if (svd.m_computeFullU)
172 m_workspace.resize(svd.rows());
173 else if (svd.m_computeThinU)
174 m_workspace.resize(svd.cols());
175 }
176 template <typename Xpr>
177 bool run(SVDType& svd, const Xpr& matrix) {
178 if (svd_precondition_more_rows(Options, matrix.rows(), matrix.cols())) {
179 m_qr.compute(matrix);
180 svd.m_workMatrix = m_qr.matrixQR().block(0, 0, matrix.cols(), matrix.cols()).template triangularView<Upper>();
181 if (svd.m_computeFullU)
182 m_qr.householderQ().evalTo(svd.m_matrixU, m_workspace);
183 else if (svd.m_computeThinU) {
184 svd.m_matrixU.setIdentity(matrix.rows(), matrix.cols());
185 m_qr.householderQ().applyThisOnTheLeft(svd.m_matrixU, m_workspace);
186 }
187 if (svd.computeV()) svd.m_matrixV = m_qr.colsPermutation();
188 return true;
189 }
190 return false;
191 }
192
193 private:
194 using QRType = ColPivHouseholderQR<MatrixType>;
195 QRType m_qr;
196 WorkspaceType m_workspace;
197};
198
199template <typename MatrixType, int Options>
200class qr_preconditioner_impl<MatrixType, Options, ColPivHouseholderQRPreconditioner, PreconditionIfMoreColsThanRows,
201 true> {
202 public:
203 using Scalar = typename MatrixType::Scalar;
204 using SVDType = JacobiSVD<MatrixType, Options>;
205
206 enum {
207 RowsAtCompileTime = MatrixType::RowsAtCompileTime,
208 ColsAtCompileTime = MatrixType::ColsAtCompileTime,
209 MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
210 MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime,
211 MatrixOptions = internal::traits<MatrixType>::Options,
212 WorkspaceSize = internal::traits<SVDType>::MatrixVColsAtCompileTime,
213 MaxWorkspaceSize = internal::traits<SVDType>::MatrixVMaxColsAtCompileTime
214 };
215
216 using WorkspaceType = Matrix<Scalar, WorkspaceSize, 1, ColMajor, MaxWorkspaceSize, 1>;
217
218 using TransposeTypeWithSameStorageOrder =
219 typename internal::make_proper_matrix_type<Scalar, ColsAtCompileTime, RowsAtCompileTime, MatrixOptions,
220 MaxColsAtCompileTime, MaxRowsAtCompileTime>::type;
221
222 void allocate(const SVDType& svd) {
223 if (svd.cols() != m_qr.rows() || svd.rows() != m_qr.cols()) {
224 internal::destroy_at(&m_qr);
225 internal::construct_at(&m_qr, svd.cols(), svd.rows());
226 }
227 if (svd.m_computeFullV)
228 m_workspace.resize(svd.cols());
229 else if (svd.m_computeThinV)
230 m_workspace.resize(svd.rows());
231 }
232 template <typename Xpr>
233 bool run(SVDType& svd, const Xpr& matrix) {
234 if (matrix.cols() > matrix.rows()) {
235 m_qr.compute(matrix.adjoint());
236
237 svd.m_workMatrix =
238 m_qr.matrixQR().block(0, 0, matrix.rows(), matrix.rows()).template triangularView<Upper>().adjoint();
239 if (svd.m_computeFullV)
240 m_qr.householderQ().evalTo(svd.m_matrixV, m_workspace);
241 else if (svd.m_computeThinV) {
242 svd.m_matrixV.setIdentity(matrix.cols(), matrix.rows());
243 m_qr.householderQ().applyThisOnTheLeft(svd.m_matrixV, m_workspace);
244 }
245 if (svd.computeU()) svd.m_matrixU = m_qr.colsPermutation();
246 return true;
247 } else
248 return false;
249 }
250
251 private:
252 using QRType = ColPivHouseholderQR<TransposeTypeWithSameStorageOrder>;
253 QRType m_qr;
254 WorkspaceType m_workspace;
255};
256
257/*** preconditioner using HouseholderQR ***/
258
259template <typename MatrixType, int Options>
260class qr_preconditioner_impl<MatrixType, Options, HouseholderQRPreconditioner, PreconditionIfMoreRowsThanCols, true> {
261 public:
262 using Scalar = typename MatrixType::Scalar;
263 using SVDType = JacobiSVD<MatrixType, Options>;
264
265 enum {
266 WorkspaceSize = internal::traits<SVDType>::MatrixUColsAtCompileTime,
267 MaxWorkspaceSize = internal::traits<SVDType>::MatrixUMaxColsAtCompileTime
268 };
269
270 using WorkspaceType = Matrix<Scalar, 1, WorkspaceSize, RowMajor, 1, MaxWorkspaceSize>;
271
272 void allocate(const SVDType& svd) {
273 if (svd.rows() != m_qr.rows() || svd.cols() != m_qr.cols()) {
274 internal::destroy_at(&m_qr);
275 internal::construct_at(&m_qr, svd.rows(), svd.cols());
276 }
277 if (svd.m_computeFullU)
278 m_workspace.resize(svd.rows());
279 else if (svd.m_computeThinU)
280 m_workspace.resize(svd.cols());
281 }
282 template <typename Xpr>
283 bool run(SVDType& svd, const Xpr& matrix) {
284 if (svd_precondition_more_rows(Options, matrix.rows(), matrix.cols())) {
285 m_qr.compute(matrix);
286 svd.m_workMatrix = m_qr.matrixQR().block(0, 0, matrix.cols(), matrix.cols()).template triangularView<Upper>();
287 if (svd.m_computeFullU)
288 m_qr.householderQ().evalTo(svd.m_matrixU, m_workspace);
289 else if (svd.m_computeThinU) {
290 svd.m_matrixU.setIdentity(matrix.rows(), matrix.cols());
291 m_qr.householderQ().applyThisOnTheLeft(svd.m_matrixU, m_workspace);
292 }
293 if (svd.computeV()) svd.m_matrixV.setIdentity(matrix.cols(), matrix.cols());
294 return true;
295 }
296 return false;
297 }
298
299 private:
300 using QRType = HouseholderQR<MatrixType>;
301 QRType m_qr;
302 WorkspaceType m_workspace;
303};
304
305template <typename MatrixType, int Options>
306class qr_preconditioner_impl<MatrixType, Options, HouseholderQRPreconditioner, PreconditionIfMoreColsThanRows, true> {
307 public:
308 using Scalar = typename MatrixType::Scalar;
309 using SVDType = JacobiSVD<MatrixType, Options>;
310
311 enum {
312 RowsAtCompileTime = MatrixType::RowsAtCompileTime,
313 ColsAtCompileTime = MatrixType::ColsAtCompileTime,
314 MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
315 MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime,
316 MatrixOptions = internal::traits<MatrixType>::Options,
317 WorkspaceSize = internal::traits<SVDType>::MatrixVColsAtCompileTime,
318 MaxWorkspaceSize = internal::traits<SVDType>::MatrixVMaxColsAtCompileTime
319 };
320
321 using WorkspaceType = Matrix<Scalar, WorkspaceSize, 1, ColMajor, MaxWorkspaceSize, 1>;
322
323 using TransposeTypeWithSameStorageOrder =
324 typename internal::make_proper_matrix_type<Scalar, ColsAtCompileTime, RowsAtCompileTime, MatrixOptions,
325 MaxColsAtCompileTime, MaxRowsAtCompileTime>::type;
326
327 void allocate(const SVDType& svd) {
328 if (svd.cols() != m_qr.rows() || svd.rows() != m_qr.cols()) {
329 internal::destroy_at(&m_qr);
330 internal::construct_at(&m_qr, svd.cols(), svd.rows());
331 }
332 if (svd.m_computeFullV)
333 m_workspace.resize(svd.cols());
334 else if (svd.m_computeThinV)
335 m_workspace.resize(svd.rows());
336 }
337
338 template <typename Xpr>
339 bool run(SVDType& svd, const Xpr& matrix) {
340 if (matrix.cols() > matrix.rows()) {
341 m_qr.compute(matrix.adjoint());
342
343 svd.m_workMatrix =
344 m_qr.matrixQR().block(0, 0, matrix.rows(), matrix.rows()).template triangularView<Upper>().adjoint();
345 if (svd.m_computeFullV)
346 m_qr.householderQ().evalTo(svd.m_matrixV, m_workspace);
347 else if (svd.m_computeThinV) {
348 svd.m_matrixV.setIdentity(matrix.cols(), matrix.rows());
349 m_qr.householderQ().applyThisOnTheLeft(svd.m_matrixV, m_workspace);
350 }
351 if (svd.computeU()) svd.m_matrixU.setIdentity(matrix.rows(), matrix.rows());
352 return true;
353 } else
354 return false;
355 }
356
357 private:
359 QRType m_qr;
360 WorkspaceType m_workspace;
361};
362
363/*** 2x2 SVD implementation
364 ***
365 *** JacobiSVD consists in performing a series of 2x2 SVD subproblems
366 ***/
367
368template <typename MatrixType, int Options>
369struct svd_precondition_2x2_block_to_be_real<MatrixType, Options, false> {
370 using SVD = JacobiSVD<MatrixType, Options>;
371 using RealScalar = typename MatrixType::RealScalar;
372 static bool run(typename SVD::WorkMatrixType&, SVD&, Index, Index, RealScalar&) { return true; }
373};
374
375template <typename MatrixType, int Options>
376struct svd_precondition_2x2_block_to_be_real<MatrixType, Options, true> {
377 using SVD = JacobiSVD<MatrixType, Options>;
378 using Scalar = typename MatrixType::Scalar;
379 using RealScalar = typename MatrixType::RealScalar;
380 static bool run(typename SVD::WorkMatrixType& work_matrix, SVD& svd, Index p, Index q, RealScalar& maxDiagEntry) {
381 using numext::abs;
382 using numext::sqrt;
383 Scalar z;
384 JacobiRotation<Scalar> rot;
385 RealScalar n = sqrt(numext::abs2(work_matrix.coeff(p, p)) + numext::abs2(work_matrix.coeff(q, p)));
386
387 const RealScalar considerAsZero = (std::numeric_limits<RealScalar>::min)();
388 const RealScalar precision = NumTraits<Scalar>::epsilon();
389
390 if (numext::is_exactly_zero(n)) {
391 // make sure first column is zero
392 work_matrix.coeffRef(p, p) = work_matrix.coeffRef(q, p) = Scalar(0);
393
394 if (abs(numext::imag(work_matrix.coeff(p, q))) > considerAsZero) {
395 // work_matrix.coeff(p,q) can be zero if work_matrix.coeff(q,p) is not zero but small enough to underflow when
396 // computing n
397 z = abs(work_matrix.coeff(p, q)) / work_matrix.coeff(p, q);
398 work_matrix.row(p) *= z;
399 if (svd.computeU()) svd.m_matrixU.col(p) *= conj(z);
400 }
401 if (abs(numext::imag(work_matrix.coeff(q, q))) > considerAsZero) {
402 z = abs(work_matrix.coeff(q, q)) / work_matrix.coeff(q, q);
403 work_matrix.row(q) *= z;
404 if (svd.computeU()) svd.m_matrixU.col(q) *= conj(z);
405 }
406 // otherwise the second row is already zero, so we have nothing to do.
407 } else {
408 rot.c() = conj(work_matrix.coeff(p, p)) / n;
409 rot.s() = work_matrix.coeff(q, p) / n;
410 work_matrix.applyOnTheLeft(p, q, rot);
411 if (svd.computeU()) svd.m_matrixU.applyOnTheRight(p, q, rot.adjoint());
412 if (abs(numext::imag(work_matrix.coeff(p, q))) > considerAsZero) {
413 z = abs(work_matrix.coeff(p, q)) / work_matrix.coeff(p, q);
414 work_matrix.col(q) *= z;
415 if (svd.computeV()) svd.m_matrixV.col(q) *= z;
416 }
417 if (abs(numext::imag(work_matrix.coeff(q, q))) > considerAsZero) {
418 z = abs(work_matrix.coeff(q, q)) / work_matrix.coeff(q, q);
419 work_matrix.row(q) *= z;
420 if (svd.computeU()) svd.m_matrixU.col(q) *= conj(z);
421 }
422 }
423
424 // update largest diagonal entry
425 maxDiagEntry = numext::maxi<RealScalar>(
426 maxDiagEntry, numext::maxi<RealScalar>(abs(work_matrix.coeff(p, p)), abs(work_matrix.coeff(q, q))));
427 // and check whether the 2x2 block is already diagonal
428 RealScalar threshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
429 return abs(work_matrix.coeff(p, q)) > threshold || abs(work_matrix.coeff(q, p)) > threshold;
430 }
431};
432
433template <typename WorkMatrixType, typename MatrixUType, typename MatrixVType>
434EIGEN_DONT_INLINE bool jacobi_svd_nonblocking_sweep(WorkMatrixType& work_matrix, MatrixUType& matrix_u,
435 MatrixVType& matrix_v, bool compute_u, bool compute_v,
436 typename WorkMatrixType::RealScalar considerAsZero,
437 typename WorkMatrixType::RealScalar precision,
438 typename WorkMatrixType::RealScalar& maxDiagEntry) {
439 using numext::abs;
440 using numext::sqrt;
441 using Scalar = typename WorkMatrixType::Scalar;
442 using RealScalar = typename WorkMatrixType::RealScalar;
443 const Index n = work_matrix.rows();
444 bool notFinished = false;
445
446 EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) {
447 // Complex non-blocking sweep: condition each 2x2 block to be real before diagonalizing.
448 for (Index p = 1; p < n; ++p) {
449 for (Index q = 0; q < p; ++q) {
450 RealScalar threshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
451 if (abs(work_matrix.coeff(p, q)) > threshold || abs(work_matrix.coeff(q, p)) > threshold) {
452 notFinished = true;
453 Scalar z;
454 bool doRealSvd = true;
455 RealScalar nn = sqrt(numext::abs2(work_matrix.coeff(p, p)) + numext::abs2(work_matrix.coeff(q, p)));
456
457 if (numext::is_exactly_zero(nn)) {
458 // Make sure first column is zero.
459 work_matrix.coeffRef(p, p) = work_matrix.coeffRef(q, p) = Scalar(0);
460
461 if (abs(numext::imag(work_matrix.coeff(p, q))) > considerAsZero) {
462 // work_matrix.coeff(p, q) can be zero if work_matrix.coeff(q, p) is not zero but small enough to
463 // underflow when computing nn.
464 z = abs(work_matrix.coeff(p, q)) / work_matrix.coeff(p, q);
465 work_matrix.row(p) *= z;
466 if (compute_u) matrix_u.col(p) *= numext::conj(z);
467 }
468 if (abs(numext::imag(work_matrix.coeff(q, q))) > considerAsZero) {
469 z = abs(work_matrix.coeff(q, q)) / work_matrix.coeff(q, q);
470 work_matrix.row(q) *= z;
471 if (compute_u) matrix_u.col(q) *= numext::conj(z);
472 }
473 } else {
474 JacobiRotation<Scalar> rot;
475 rot.c() = numext::conj(work_matrix.coeff(p, p)) / nn;
476 rot.s() = work_matrix.coeff(q, p) / nn;
477 work_matrix.applyOnTheLeft(p, q, rot);
478 if (compute_u) matrix_u.applyOnTheRight(p, q, rot.adjoint());
479 if (abs(numext::imag(work_matrix.coeff(p, q))) > considerAsZero) {
480 z = abs(work_matrix.coeff(p, q)) / work_matrix.coeff(p, q);
481 work_matrix.col(q) *= z;
482 if (compute_v) matrix_v.col(q) *= z;
483 }
484 if (abs(numext::imag(work_matrix.coeff(q, q))) > considerAsZero) {
485 z = abs(work_matrix.coeff(q, q)) / work_matrix.coeff(q, q);
486 work_matrix.row(q) *= z;
487 if (compute_u) matrix_u.col(q) *= numext::conj(z);
488 }
489 }
490
491 maxDiagEntry = numext::maxi<RealScalar>(
492 maxDiagEntry, numext::maxi<RealScalar>(abs(work_matrix.coeff(p, p)), abs(work_matrix.coeff(q, q))));
493 threshold = numext::maxi<RealScalar>(considerAsZero, NumTraits<Scalar>::epsilon() * maxDiagEntry);
494 doRealSvd = abs(work_matrix.coeff(p, q)) > threshold || abs(work_matrix.coeff(q, p)) > threshold;
495
496 if (doRealSvd) {
497 JacobiRotation<RealScalar> j_left, j_right;
498 internal::real_2x2_jacobi_svd(work_matrix, p, q, &j_left, &j_right);
499 work_matrix.applyOnTheLeft(p, q, j_left);
500 if (compute_u) matrix_u.applyOnTheRight(p, q, j_left.transpose());
501 work_matrix.applyOnTheRight(p, q, j_right);
502 if (compute_v) matrix_v.applyOnTheRight(p, q, j_right);
503 maxDiagEntry = numext::maxi<RealScalar>(
504 maxDiagEntry, numext::maxi<RealScalar>(abs(work_matrix.coeff(p, p)), abs(work_matrix.coeff(q, q))));
505 }
506 }
507 }
508 }
509 } else {
510 // Real non-blocking sweep: diagonalize each 2x2 block directly.
511 RealScalar threshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
512 for (Index p = 1; p < n; ++p) {
513 for (Index q = 0; q < p; ++q) {
514 if (abs(work_matrix.coeff(p, q)) > threshold || abs(work_matrix.coeff(q, p)) > threshold) {
515 notFinished = true;
516 JacobiRotation<RealScalar> j_left, j_right;
517 internal::real_2x2_jacobi_svd(work_matrix, p, q, &j_left, &j_right);
518 work_matrix.applyOnTheLeft(p, q, j_left);
519 if (compute_u) matrix_u.applyOnTheRight(p, q, j_left.transpose());
520 work_matrix.applyOnTheRight(p, q, j_right);
521 if (compute_v) matrix_v.applyOnTheRight(p, q, j_right);
522 maxDiagEntry = numext::maxi<RealScalar>(
523 maxDiagEntry, numext::maxi<RealScalar>(abs(work_matrix.coeff(p, p)), abs(work_matrix.coeff(q, q))));
524 threshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
525 }
526 }
527 }
528 }
529
530 return notFinished;
531}
532
533template <typename MatrixType_, int Options>
534struct traits<JacobiSVD<MatrixType_, Options>> : svd_traits<MatrixType_, Options> {
535 using MatrixType = MatrixType_;
536};
537
538} // end namespace internal
539
612template <typename MatrixType_, int Options_>
613class JacobiSVD : public SVDBase<JacobiSVD<MatrixType_, Options_>> {
614 using Base = SVDBase<JacobiSVD>;
615
616 public:
617 using MatrixType = MatrixType_;
618 using Scalar = typename Base::Scalar;
619 using RealScalar = typename Base::RealScalar;
620 enum : int {
621 Options = Options_,
622 QRPreconditioner = internal::get_qr_preconditioner(Options),
623 RowsAtCompileTime = Base::RowsAtCompileTime,
624 ColsAtCompileTime = Base::ColsAtCompileTime,
625 DiagSizeAtCompileTime = Base::DiagSizeAtCompileTime,
626 MaxRowsAtCompileTime = Base::MaxRowsAtCompileTime,
627 MaxColsAtCompileTime = Base::MaxColsAtCompileTime,
628 MaxDiagSizeAtCompileTime = Base::MaxDiagSizeAtCompileTime,
629 MatrixOptions = Base::MatrixOptions
630 };
631
632 using MatrixUType = typename Base::MatrixUType;
633 using MatrixVType = typename Base::MatrixVType;
634 using SingularValuesType = typename Base::SingularValuesType;
635 using WorkMatrixType = Matrix<Scalar, DiagSizeAtCompileTime, DiagSizeAtCompileTime, MatrixOptions,
636 MaxDiagSizeAtCompileTime, MaxDiagSizeAtCompileTime>;
637
644
652 JacobiSVD(Index rows, Index cols) { allocate(rows, cols, internal::get_computation_options(Options)); }
653
670 EIGEN_DEPRECATED_WITH_REASON("Options should be specified using the class template parameter.")
671 JacobiSVD(Index rows, Index cols, unsigned int computationOptions) {
672 internal::check_svd_options_assertions<MatrixType, Options>(computationOptions, rows, cols);
673 allocate(rows, cols, computationOptions);
674 }
675
681 template <typename Derived>
682 explicit JacobiSVD(const MatrixBase<Derived>& matrix) {
683 compute_impl(matrix, internal::get_computation_options(Options));
684 }
685
686 template <typename Derived>
687 explicit JacobiSVD(const TriangularBase<Derived>& matrix) {
688 compute_impl(matrix, internal::get_computation_options(Options));
689 }
690
703 // EIGEN_DEPRECATED // TODO(cantonios): re-enable after fixing a few 3p libraries that error on deprecation warnings.
704 template <typename Derived>
705 JacobiSVD(const MatrixBase<Derived>& matrix, unsigned int computationOptions) {
706 internal::check_svd_options_assertions<MatrixType, Options>(computationOptions, matrix.rows(), matrix.cols());
707 compute_impl(matrix, computationOptions);
708 }
709
715 template <typename Derived>
717 return compute_impl(matrix, m_computationOptions);
718 }
719
720 template <typename Derived>
721 JacobiSVD& compute(const TriangularBase<Derived>& matrix) {
722 return compute_impl(matrix, m_computationOptions);
723 }
724
734 template <typename Derived>
735 EIGEN_DEPRECATED_WITH_REASON("Options should be specified using the class template parameter.")
736 JacobiSVD& compute(const MatrixBase<Derived>& matrix, unsigned int computationOptions) {
737 internal::check_svd_options_assertions<MatrixType, Options>(m_computationOptions, matrix.rows(), matrix.cols());
738 return compute_impl(matrix, computationOptions);
739 }
740
741 using Base::cols;
742 using Base::computeU;
743 using Base::computeV;
744 using Base::diagSize;
745 using Base::rank;
746 using Base::rows;
747
748 void allocate(Index rows_, Index cols_, unsigned int computationOptions) {
749 if (Base::allocate(rows_, cols_, computationOptions)) return;
750 eigen_assert(
751 !((m_computeThinU || m_computeThinV) && int(QRPreconditioner) == int(FullPivHouseholderQRPreconditioner)) &&
752 "JacobiSVD: can't compute thin U or thin V with the FullPivHouseholderQR preconditioner. "
753 "Use the ColPivHouseholderQR preconditioner instead.");
754
755 m_workMatrix.resize(diagSize(), diagSize());
756 if (cols() > rows()) m_qr_precond_morecols.allocate(*this);
757 if (internal::svd_precondition_more_rows(Options, rows(), cols())) m_qr_precond_morerows.allocate(*this);
758 }
759
760 private:
761 template <typename Derived>
762 JacobiSVD& compute_impl(const TriangularBase<Derived>& matrix, unsigned int computationOptions);
763 template <typename Derived>
764 JacobiSVD& compute_impl(const MatrixBase<Derived>& matrix, unsigned int computationOptions);
765
766 // Blocked sweep for the Jacobi SVD (works for both real and complex scalars).
767 // Extracted into a separate EIGEN_DONT_INLINE method to prevent the blocking
768 // code from interfering with the compiler's optimization of the non-blocking
769 // scalar sweep.
770 EIGEN_DONT_INLINE bool blocked_sweep(RealScalar considerAsZero, RealScalar precision, RealScalar& maxDiagEntry);
771
772 protected:
773 using Base::m_computationOptions;
774 using Base::m_computeFullU;
775 using Base::m_computeFullV;
776 using Base::m_computeThinU;
777 using Base::m_computeThinV;
778 using Base::m_info;
779 using Base::m_isAllocated;
780 using Base::m_isInitialized;
781 using Base::m_matrixU;
782 using Base::m_matrixV;
783 using Base::m_nonzeroSingularValues;
784 using Base::m_prescribedThreshold;
785 using Base::m_singularValues;
786 using Base::m_usePrescribedThreshold;
787 using Base::ShouldComputeThinU;
788 using Base::ShouldComputeThinV;
789
790 EIGEN_STATIC_ASSERT(!(ShouldComputeThinU && int(QRPreconditioner) == int(FullPivHouseholderQRPreconditioner)) &&
791 !(ShouldComputeThinV && int(QRPreconditioner) == int(FullPivHouseholderQRPreconditioner)),
792 "JacobiSVD: can't compute thin U or thin V with the FullPivHouseholderQR preconditioner. "
793 "Use the ColPivHouseholderQR preconditioner instead.")
794 EIGEN_STATIC_ASSERT(!(internal::should_svd_precondition_square_matrix(Options) &&
795 int(QRPreconditioner) == int(NoQRPreconditioner)),
796 "JacobiSVD: PreconditionSquareMatrix requires a QR preconditioner other than NoQRPreconditioner.")
797
798 template <typename MatrixType__, int Options__, bool IsComplex_>
799 friend struct internal::svd_precondition_2x2_block_to_be_real;
800 template <typename MatrixType__, int Options__, int QRPreconditioner_, int Case_, bool DoAnything_>
801 friend struct internal::qr_preconditioner_impl;
802
803 internal::qr_preconditioner_impl<MatrixType, Options, QRPreconditioner, internal::PreconditionIfMoreColsThanRows>
804 m_qr_precond_morecols;
805 internal::qr_preconditioner_impl<MatrixType, Options, QRPreconditioner, internal::PreconditionIfMoreRowsThanCols>
806 m_qr_precond_morerows;
807 WorkMatrixType m_workMatrix;
808
809 // Blocking parameters for the Jacobi SVD sweep.
810#ifdef EIGEN_JACOBI_SVD_BLOCK_SIZE
811 static constexpr Index kDefaultBlockSize = EIGEN_JACOBI_SVD_BLOCK_SIZE;
812#else
813 static constexpr Index kDefaultBlockSize = 32;
814#endif
815
816 // Use the lower of the default block size and static maximum matrix dimensions.
817 static constexpr Index kBlockSize = internal::min_size_prefer_fixed(kDefaultBlockSize, MaxDiagSizeAtCompileTime);
818};
819
820template <typename MatrixType, int Options>
821template <typename Derived>
822JacobiSVD<MatrixType, Options>& JacobiSVD<MatrixType, Options>::compute_impl(const TriangularBase<Derived>& matrix,
823 unsigned int computationOptions) {
824 return compute_impl(matrix.toDenseMatrix(), computationOptions);
825}
826
827template <typename MatrixType, int Options>
828template <typename Derived>
829JacobiSVD<MatrixType, Options>& JacobiSVD<MatrixType, Options>::compute_impl(const MatrixBase<Derived>& matrix,
830 unsigned int computationOptions) {
831 EIGEN_STATIC_ASSERT_SAME_MATRIX_SIZE(Derived, MatrixType);
832 EIGEN_STATIC_ASSERT((std::is_same<typename Derived::Scalar, typename MatrixType::Scalar>::value),
833 Input matrix must have the same Scalar type as the JacobiSVD object.);
834
835 using numext::abs;
836
837 allocate(matrix.rows(), matrix.cols(), computationOptions);
838
839 // currently we stop when we reach precision 2*epsilon as the last bit of precision can require an unreasonable number
840 // of iterations, only worsening the precision of U and V as we accumulate more rotations
841 const RealScalar precision = RealScalar(2) * NumTraits<Scalar>::epsilon();
842
843 // limit for denormal numbers to be considered zero in order to avoid infinite loops (see bug 286)
844 const RealScalar considerAsZero = (std::numeric_limits<RealScalar>::min)();
845
846 // Scaling factor to reduce over/under-flows. A SIMD unit that flushes subnormal inputs reads an all-subnormal matrix
847 // as zero; recover its maximum from the representation so that the scaling still brings it into the normal range.
848 const RealScalar maxCoeff = matrix.size() == 0
849 ? RealScalar(0)
850 : internal::safe_scaling<RealScalar>::recover_flushed_max_coeff(
851 matrix.derived(), matrix.cwiseAbs().template maxCoeff<PropagateNaN>());
852 if (!(numext::isfinite)(maxCoeff)) {
853 m_isInitialized = true;
854 m_info = InvalidInput;
855 m_nonzeroSingularValues = 0;
856 m_singularValues.setZero();
857 return *this;
858 }
859 internal::safe_scaling_factors<RealScalar> factors;
860
861 /*** step 1. The R-SVD step: we use a QR decomposition to reduce to the case of a square matrix */
862
863 if (rows() != cols() || internal::should_svd_precondition_square_matrix(Options)) {
864 factors =
865 internal::safe_scaling<RealScalar>::with_scaled(matrix.derived(), maxCoeff, [&](const auto& scaledMatrix) {
866 m_qr_precond_morecols.run(*this, scaledMatrix);
867 m_qr_precond_morerows.run(*this, scaledMatrix);
868 });
869 } else {
870 factors = internal::safe_scaling<RealScalar>::scale_to(
871 m_workMatrix,
872 matrix.template topLeftCorner<DiagSizeAtCompileTime, DiagSizeAtCompileTime>(diagSize(), diagSize()), maxCoeff);
873 if (m_computeFullU) m_matrixU.setIdentity(rows(), rows());
874 if (m_computeThinU) m_matrixU.setIdentity(rows(), diagSize());
875 if (m_computeFullV) m_matrixV.setIdentity(cols(), cols());
876 if (m_computeThinV) m_matrixV.setIdentity(cols(), diagSize());
877 }
878
879 /*** step 2. The main Jacobi SVD iteration. ***/
880 RealScalar maxDiagEntry = diagSize() == 0 ? RealScalar(0) : m_workMatrix.cwiseAbs().diagonal().maxCoeff();
881
882 bool finished = false;
883 while (!finished) {
884 finished = true;
885
886 EIGEN_IF_CONSTEXPR (MaxDiagSizeAtCompileTime == Dynamic || MaxDiagSizeAtCompileTime > kBlockSize) {
887 // Sweep with optional blocking for large matrices.
888 // Use blocking when the matrix is large enough that individual left rotations
889 // (strided row operations on column-major data) cause significant cache misses.
890 // The threshold is derived from the L2 cache size: blocking becomes worthwhile
891 // when n exceeds sqrt(L2 / 4). We divide by sizeof(float) rather than sizeof(RealScalar)
892 // because the cache miss pattern depends on the number of columns accessed (one cache
893 // line per column), not the scalar size. This also makes the threshold appropriately
894 // more conservative for larger types where GEMM overhead is higher.
895 const Index n = diagSize();
896#ifdef EIGEN_JACOBI_SVD_BLOCKING_THRESHOLD
897 const Index blockingThreshold = EIGEN_JACOBI_SVD_BLOCKING_THRESHOLD;
898#else
899 const Index blockingThreshold =
900 static_cast<Index>(numext::sqrt(static_cast<double>(l2CacheSize() / sizeof(float))));
901#endif
902
903 if (n >= blockingThreshold) {
904 // The blocked sweep is in a separate EIGEN_DONT_INLINE method to prevent
905 // the blocking code from interfering with the compiler's optimization of
906 // the non-blocking scalar sweep below.
907 finished = !blocked_sweep(considerAsZero, precision, maxDiagEntry);
908 continue;
909 }
910 }
911 finished = !internal::jacobi_svd_nonblocking_sweep(m_workMatrix, m_matrixU, m_matrixV, computeU(), computeV(),
912 considerAsZero, precision, maxDiagEntry);
913 }
914
915 /*** step 3. The work matrix is now diagonal, so ensure it's positive so its diagonal entries are the singular values
916 * ***/
917
918 for (Index i = 0; i < diagSize(); ++i) {
919 // For a complex matrix, some diagonal coefficients might not have been
920 // treated by svd_precondition_2x2_block_to_be_real, and the imaginary part
921 // of some diagonal entry might not be null.
922 bool diagonal_has_imaginary_part = false;
923 EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) {
924 diagonal_has_imaginary_part = abs(numext::imag(m_workMatrix.coeff(i, i))) > considerAsZero;
925 }
926 if (diagonal_has_imaginary_part) {
927 RealScalar a = abs(m_workMatrix.coeff(i, i));
928 m_singularValues.coeffRef(i) = abs(a);
929 if (computeU()) m_matrixU.col(i) *= m_workMatrix.coeff(i, i) / a;
930 } else {
931 // m_workMatrix.coeff(i,i) is already real. Its magnitude and sign are read from the representation for float
932 // and double: a subnormal entry compares as zero under DAZ, and an abs() that widens flushes it under FTZ.
933 RealScalar a = numext::real(m_workMatrix.coeff(i, i));
934 m_singularValues.coeffRef(i) = internal::abs_preserving_subnormals(a);
935 if (computeU() && internal::is_negative_preserving_subnormals(a)) m_matrixU.col(i) = -m_matrixU.col(i);
936 }
937 }
938
939 /*** step 4. Sort singular values in descending order and compute the number of nonzero singular values ***/
940
941 // Sort in the scaled frame, where the largest values are normal. A tail whose maximum reads zero or subnormal
942 // holds zeros and subnormals, which FTZ/DAZ hardware compares as zero: order it from the representation.
943 for (Index i = 0; i < diagSize(); i++) {
944 Index pos;
945 RealScalar maxRemainingSingularValue = m_singularValues.tail(diagSize() - i).maxCoeff(&pos);
946 if (internal::is_zero_or_subnormal_magnitude(maxRemainingSingularValue)) {
947 pos = internal::safe_scaling<RealScalar>::recover_flushed_max_coeff_index(m_singularValues.tail(diagSize() - i),
948 pos);
949 if (numext::is_exactly_zero_no_flush(m_singularValues.coeff(i + pos))) break;
950 }
951 if (pos) {
952 pos += i;
953 std::swap(m_singularValues.coeffRef(i), m_singularValues.coeffRef(pos));
954 if (computeU()) m_matrixU.col(pos).swap(m_matrixU.col(i));
955 if (computeV()) m_matrixV.col(pos).swap(m_matrixV.col(i));
956 }
957 }
958 // Unscaling with maxCoeff keeps singular values that land in the subnormal range under FTZ; the count then reads
959 // them from the representation.
960 internal::safe_scaling<RealScalar>::unscale_in_place(m_singularValues, maxCoeff, factors);
961 m_nonzeroSingularValues = diagSize();
962 for (Index i = 0; i < diagSize(); i++) {
963 if (numext::is_exactly_zero_no_flush(m_singularValues.coeff(i))) {
964 m_nonzeroSingularValues = i;
965 break;
966 }
967 }
968
969 m_isInitialized = true;
970 return *this;
971}
972
973// Blocked Jacobi SVD sweep for both real and complex scalar types. For large n,
974// applying left rotations (row operations on column-major data) causes cache
975// misses due to strided access. To mitigate this, we accumulate kBlockSize left
976// rotations into a small dense matrix and apply them via a single GEMM to the
977// contiguous row block q..q+kBlockSize-1 and the (possibly distant) row p.
978// Right rotations and column scalings act on columns (contiguous in column-major)
979// and are applied individually.
980//
981// For complex types, the 2x2 preconditioning (making the block real) involves
982// complex left rotations and row scalings, which are also accumulated into the
983// block matrix. Column scalings from preconditioning are applied directly.
984//
985// The accumulated rotation matrix has lower-triangular structure in its top-left
986// kBlockSize x kBlockSize corner, which we exploit with triangularView.
987//
988// Returns true if any off-diagonal element exceeded the threshold (i.e. sweep
989// is not yet converged).
990template <typename MatrixType, int Options>
991EIGEN_DONT_INLINE bool JacobiSVD<MatrixType, Options>::blocked_sweep(RealScalar considerAsZero, RealScalar precision,
992 RealScalar& maxDiagEntry) {
993 using numext::abs;
994 using numext::sqrt;
995 const Index n = diagSize();
996 RealScalar threshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
997 bool notFinished = false;
998 static constexpr Index kBlockBufferSize = (kBlockSize + 1) * (kBlockSize + 1);
999 ei_declare_aligned_stack_constructed_variable(Scalar, blockBufferPtr, kBlockBufferSize, 0);
1000 Map<Matrix<Scalar, kBlockSize + 1, kBlockSize + 1, MatrixOptions>, AlignedMax> blockBuffer(
1001 blockBufferPtr, kBlockSize + 1, kBlockSize + 1);
1002
1003 ei_declare_aligned_stack_constructed_variable(Scalar, accumPtr, kBlockBufferSize, 0);
1004 Map<Matrix<Scalar, kBlockSize + 1, kBlockSize + 1, MatrixOptions>, AlignedMax> accum(accumPtr, kBlockSize + 1,
1005 kBlockSize + 1);
1006 Matrix<Scalar, 1, Dynamic, RowMajor, 1, MaxDiagSizeAtCompileTime> Mp_save;
1007
1008 for (Index p = 1; p < n; ++p) {
1009 Index q = 0;
1010
1011 // Blocked loop: process kBlockSize pairs (p,q+qq) for qq=0..kBlockSize-1.
1012 // We extract the relevant (kBlockSize+1) x (kBlockSize+1) submatrix of W
1013 // into a small buffer, compute all rotations on the buffer, accumulate the
1014 // left transformations into `accum`, and apply them in one GEMM at the end.
1015 for (; q + kBlockSize <= p; q += kBlockSize) {
1016 // Buffer = [ W(q:q+k, q:q+k) W(q:q+k, p) ]
1017 // [ W(p, q:q+k) W(p, p) ]
1018 blockBuffer.template topLeftCorner<kBlockSize, kBlockSize>() =
1019 m_workMatrix.template block<kBlockSize, kBlockSize>(q, q);
1020 blockBuffer.col(kBlockSize).template head<kBlockSize>() = m_workMatrix.col(p).template segment<kBlockSize>(q);
1021 blockBuffer.row(kBlockSize).template head<kBlockSize>() = m_workMatrix.row(p).template segment<kBlockSize>(q);
1022 blockBuffer(kBlockSize, kBlockSize) = m_workMatrix(p, p);
1023
1024 // Accumulator for left transformations: W <- accum * W.
1025 // After processing qq pairs, accum's top-left kBlockSize x kBlockSize
1026 // block is lower-triangular (each rotation only mixes row qq with row
1027 // kBlockSize, so rows 0..qq-1 are unchanged).
1028 accum.setIdentity(kBlockSize + 1, kBlockSize + 1);
1029 bool blockDirty = false;
1030
1031 for (Index qq = 0; qq < kBlockSize; ++qq) {
1032 if (abs(blockBuffer.coeff(kBlockSize, qq)) > threshold || abs(blockBuffer.coeff(qq, kBlockSize)) > threshold) {
1033 notFinished = true;
1034 blockDirty = true;
1035
1036 // Complex preconditioning: transform the 2x2 block
1037 // [w_pp w_pq] = [buffer(kBlockSize, kBlockSize) buffer(kBlockSize, qq)]
1038 // [w_qp w_qq] [buffer(qq, kBlockSize) buffer(qq, qq) ]
1039 // to have real entries via unitary row/column operations, so
1040 // real_2x2_jacobi_svd can be applied.
1041 //
1042 // Left operations (complex rotation, row scaling by e^{i*theta}) are
1043 // accumulated into `accum` for deferred GEMM application.
1044 // Right operations (column scaling) are applied directly since column
1045 // ops are contiguous in column-major layout.
1046 bool doRealSvd = true;
1047 EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) {
1048 Scalar z;
1049 // nn = ||(w_pp, w_qp)||_2, the norm of the first column of the 2x2 block.
1050 RealScalar nn = sqrt(numext::abs2(blockBuffer.coeff(kBlockSize, kBlockSize)) +
1051 numext::abs2(blockBuffer.coeff(qq, kBlockSize)));
1052
1053 if (numext::is_exactly_zero(nn)) {
1054 // First column is zero => block is already upper triangular.
1055 blockBuffer.coeffRef(kBlockSize, kBlockSize) = Scalar(0);
1056 blockBuffer.coeffRef(qq, kBlockSize) = Scalar(0);
1057
1058 // Scale rows by z = e^{-i*arg(w)} to make remaining entries real.
1059 if (abs(numext::imag(blockBuffer.coeff(kBlockSize, qq))) > considerAsZero) {
1060 z = abs(blockBuffer.coeff(kBlockSize, qq)) / blockBuffer.coeff(kBlockSize, qq);
1061 blockBuffer.row(kBlockSize) *= z;
1062 accum.row(kBlockSize) *= z; // accumulate left op
1063 if (computeU()) m_matrixU.col(p) *= numext::conj(z);
1064 }
1065 if (abs(numext::imag(blockBuffer.coeff(qq, qq))) > considerAsZero) {
1066 z = abs(blockBuffer.coeff(qq, qq)) / blockBuffer.coeff(qq, qq);
1067 blockBuffer.row(qq) *= z;
1068 accum.row(qq) *= z; // accumulate left op
1069 if (computeU()) m_matrixU.col(q + qq) *= numext::conj(z);
1070 }
1071 } else {
1072 // Apply complex Givens rotation to zero out w_qp:
1073 // [c s] [w_pp] [nn] conj(w_pp) w_qp
1074 // [-s c] [w_qp] = [0 ] c = ----------, s = ------
1075 // nn nn
1076 JacobiRotation<Scalar> rot;
1077 rot.c() = numext::conj(blockBuffer.coeff(kBlockSize, kBlockSize)) / nn;
1078 rot.s() = blockBuffer.coeff(qq, kBlockSize) / nn;
1079 blockBuffer.applyOnTheLeft(kBlockSize, qq, rot);
1080 accum.applyOnTheLeft(kBlockSize, qq, rot); // accumulate left op
1081 if (computeU()) m_matrixU.applyOnTheRight(p, q + qq, rot.adjoint());
1082
1083 // Scale column qq by z = e^{-i*arg(w_pq)} to make w_pq real.
1084 if (abs(numext::imag(blockBuffer.coeff(kBlockSize, qq))) > considerAsZero) {
1085 z = abs(blockBuffer.coeff(kBlockSize, qq)) / blockBuffer.coeff(kBlockSize, qq);
1086 blockBuffer.col(qq) *= z;
1087 m_workMatrix.col(q + qq) *= z; // right op: apply directly
1088 if (computeV()) m_matrixV.col(q + qq) *= z;
1089 }
1090 // Scale row qq by z = e^{-i*arg(w_qq)} to make w_qq real.
1091 if (abs(numext::imag(blockBuffer.coeff(qq, qq))) > considerAsZero) {
1092 z = abs(blockBuffer.coeff(qq, qq)) / blockBuffer.coeff(qq, qq);
1093 blockBuffer.row(qq) *= z;
1094 accum.row(qq) *= z; // accumulate left op
1095 if (computeU()) m_matrixU.col(q + qq) *= numext::conj(z);
1096 }
1097 }
1098 // Update maxDiagEntry from preconditioning.
1099 maxDiagEntry = numext::maxi<RealScalar>(
1100 maxDiagEntry, numext::maxi<RealScalar>(abs(blockBuffer.coeff(kBlockSize, kBlockSize)),
1101 abs(blockBuffer.coeff(qq, qq))));
1102 // Check if 2x2 block still needs diagonalizing.
1103 RealScalar precondThreshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
1104 doRealSvd = abs(blockBuffer.coeff(kBlockSize, qq)) > precondThreshold ||
1105 abs(blockBuffer.coeff(qq, kBlockSize)) > precondThreshold;
1106 }
1107
1108 if (doRealSvd) {
1109 // Compute real 2x2 SVD: buffer_2x2 = j_left * diag * j_right^T.
1110 JacobiRotation<RealScalar> j_left, j_right;
1111 internal::real_2x2_jacobi_svd(blockBuffer, kBlockSize, qq, &j_left, &j_right);
1112 blockBuffer.applyOnTheLeft(kBlockSize, qq, j_left);
1113 blockBuffer.applyOnTheRight(kBlockSize, qq, j_right);
1114
1115 // Accumulate left rotation for deferred GEMM.
1116 accum.applyOnTheLeft(kBlockSize, qq, j_left);
1117
1118 // Right rotation is a column op (contiguous): apply directly.
1119 m_workMatrix.applyOnTheRight(p, q + qq, j_right);
1120 if (computeU()) m_matrixU.applyOnTheRight(p, q + qq, j_left.transpose());
1121 if (computeV()) m_matrixV.applyOnTheRight(p, q + qq, j_right);
1122
1123 maxDiagEntry = numext::maxi<RealScalar>(
1124 maxDiagEntry, numext::maxi<RealScalar>(abs(blockBuffer.coeff(kBlockSize, kBlockSize)),
1125 abs(blockBuffer.coeff(qq, qq))));
1126 }
1127 threshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
1128 }
1129 }
1130
1131 // Apply accumulated left rotations: W <- accum * W, via GEMM.
1132 // When p == q + kBlockSize, all kBlockSize+1 rows are contiguous.
1133 // Otherwise, rows q..q+k-1 and row p are non-adjacent; we split:
1134 // [Mq] [L11 l12] [Mq]
1135 // [Mp] <- [l21 l22] [Mp]
1136 // L11 is lower-triangular (exploited via triangularView).
1137 if (blockDirty) {
1138 if (p == q + kBlockSize) {
1139 m_workMatrix.template middleRows<kBlockSize + 1>(q) =
1140 accum * m_workMatrix.template middleRows<kBlockSize + 1>(q);
1141 } else {
1142 const auto L11 = accum.template topLeftCorner<kBlockSize, kBlockSize>();
1143 const auto l12 = accum.col(kBlockSize).template head<kBlockSize>();
1144 const auto l21 = accum.row(kBlockSize).template head<kBlockSize>();
1145 const Scalar l22 = accum(kBlockSize, kBlockSize);
1146 auto Mq = m_workMatrix.template middleRows<kBlockSize>(q);
1147 auto Mp = m_workMatrix.row(p);
1148 Mp_save = Mp;
1149 Mp.noalias() = l21 * Mq + l22 * Mp_save;
1150 Mq = L11.template triangularView<Lower>() * Mq + l12 * Mp_save;
1151 }
1152 }
1153 }
1154
1155 // Scalar loop for remaining pairs after blocked processing.
1156 for (; q < p; ++q) {
1157 if (abs(m_workMatrix.coeff(p, q)) > threshold || abs(m_workMatrix.coeff(q, p)) > threshold) {
1158 notFinished = true;
1159
1160 bool doRealSvd = true;
1161 EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) {
1162 doRealSvd = internal::svd_precondition_2x2_block_to_be_real<MatrixType, Options>::run(m_workMatrix, *this, p,
1163 q, maxDiagEntry);
1164 }
1165
1166 if (doRealSvd) {
1167 JacobiRotation<RealScalar> j_left, j_right;
1168 internal::real_2x2_jacobi_svd(m_workMatrix, p, q, &j_left, &j_right);
1169 m_workMatrix.applyOnTheLeft(p, q, j_left);
1170 if (computeU()) m_matrixU.applyOnTheRight(p, q, j_left.transpose());
1171 m_workMatrix.applyOnTheRight(p, q, j_right);
1172 if (computeV()) m_matrixV.applyOnTheRight(p, q, j_right);
1173 maxDiagEntry = numext::maxi<RealScalar>(
1174 maxDiagEntry, numext::maxi<RealScalar>(abs(m_workMatrix.coeff(p, p)), abs(m_workMatrix.coeff(q, q))));
1175 }
1176 threshold = numext::maxi<RealScalar>(considerAsZero, precision * maxDiagEntry);
1177 }
1178 }
1179 }
1180
1181 return notFinished;
1182}
1183
1191template <typename Derived>
1192template <int Options>
1193JacobiSVD<typename MatrixBase<Derived>::PlainObject, Options> MatrixBase<Derived>::jacobiSvd() const {
1194 return JacobiSVD<PlainObject, Options>(*this);
1195}
1196
1197template <typename Derived>
1198template <int Options>
1199JacobiSVD<typename MatrixBase<Derived>::PlainObject, Options> MatrixBase<Derived>::jacobiSvd(
1200 unsigned int computationOptions) const {
1201 return JacobiSVD<PlainObject, Options>(*this, computationOptions);
1202}
1203
1204} // end namespace Eigen
1205
1206#endif // EIGEN_JACOBISVD_H
Householder QR decomposition of a matrix.
Definition HouseholderQR.h:77
Two-sided Jacobi SVD decomposition of a rectangular matrix.
Definition JacobiSVD.h:613
JacobiSVD()
Default Constructor.
Definition JacobiSVD.h:643
JacobiSVD & compute(const MatrixBase< Derived > &matrix)
Method performing the decomposition of given matrix. Computes Thin/Full unitaries U/V if specified us...
Definition JacobiSVD.h:716
JacobiSVD(Index rows, Index cols)
Default Constructor with memory preallocation.
Definition JacobiSVD.h:652
JacobiSVD(const MatrixBase< Derived > &matrix)
Constructor performing the decomposition of given matrix, using the custom options specified with the...
Definition JacobiSVD.h:682
JacobiSVD(const MatrixBase< Derived > &matrix, unsigned int computationOptions)
Constructor performing the decomposition of given matrix using specified options for computing unitar...
Definition JacobiSVD.h:705
Base class for all dense matrices, vectors, and expressions.
Definition MatrixBase.h:53
The matrix class, also used for vectors and row-vectors.
Definition Matrix.h:188
Base class for triangular part in a matrix.
Definition TriangularMatrix.h:68
@ FullPivHouseholderQRPreconditioner
Definition Constants.h:432
@ PropagateNaN
Definition Constants.h:343
@ InvalidInput
Definition Constants.h:464