<T>LAPACK 0.1.1
C++ Template Linear Algebra PACKage
Loading...
Searching...
No Matches
svd_qr.hpp
Go to the documentation of this file.
1
5//
6// Copyright (c) 2021-2023, University of Colorado Denver. All rights reserved.
7//
8// This file is part of <T>LAPACK.
9// <T>LAPACK is free software: you can redistribute it and/or modify it under
10// the terms of the BSD 3-Clause license. See the accompanying LICENSE file.
11
12#ifndef TLAPACK_SVD_QR_HH
13#define TLAPACK_SVD_QR_HH
14
18#include "tlapack/blas/rot.hpp"
19#include "tlapack/blas/swap.hpp"
23
24namespace tlapack {
25
80template <class matrix_t,
81 class d_t,
82 class e_t,
83 enable_if_t<is_same_v<type_t<d_t>, real_type<type_t<d_t>>>, int> = 0,
84 enable_if_t<is_same_v<type_t<e_t>, real_type<type_t<e_t>>>, int> = 0>
86 bool want_u,
87 bool want_vt,
88 d_t& d,
89 e_t& e,
90 matrix_t& U,
91 matrix_t& Vt)
92{
93 using idx_t = size_type<matrix_t>;
95 using T = type_t<matrix_t>;
96 using real_t = real_type<T>;
97
98 // constants
99 const real_t one(1);
100 const real_t zero(0);
101 const idx_t n = size(d);
102 const real_t eps = ulp<real_t>();
103 const real_t unfl = safe_min<real_t>();
104 const real_t tolmul =
105 max(real_t(10.0), min(real_t(100.0), pow(eps, real_t(-0.125))));
106 const real_t tol = tolmul * eps;
107
108 // Quick return
109 if (n == 0) return 0;
110
111 // If the matrix is lower bidiagonal, apply a sequence of rotations
112 // to make it upper bidiagonal.
113 if (uplo == Uplo::Lower) {
114 real_t c, s, r;
115
116 for (idx_t i = 0; i < n - 1; ++i) {
117 lartg(d[i], e[i], c, s, r);
118 d[i] = r;
119 e[i] = s * d[i + 1];
120 d[i + 1] = c * d[i + 1];
121
122 // Update singular vectors if desired
123 if (want_u) {
124 auto u1 = col(U, i);
125 auto u2 = col(U, i + 1);
126 rot(u1, u2, c, s);
127 }
128 }
129 }
130
131 idx_t itmax = 30 * n;
132
133 //
134 // Determine threshold
135 //
136 real_t sminoa = abs(d[0]);
137 if (sminoa != zero) {
138 auto mu = sminoa;
139 for (idx_t i = 1; i < n; ++i) {
140 mu = abs(d[i]) * (mu / (mu + abs(e[i - 1])));
141 sminoa = min(sminoa, mu);
142 if (sminoa == zero) break;
143 }
144 }
145 sminoa = sminoa / sqrt(real_t(n));
146 real_t thresh = max(tol * sminoa, (real_t(n) * unfl));
147
148 // istart and istop determine the active block
149 idx_t istart = 0;
150 idx_t istop = n;
151
152 // Keep track of previous istart and istop to know when to change direction
153 idx_t istart_old = -1;
154 idx_t istop_old = -1;
155
156 // If true, chase bulges from top to bottom
157 // If false chase bulges from bottom to top
158 // This variable is reevaluated for every new subblock
159 bool forwarddirection = true;
160
161 //
162 // Main loop
163 //
164 for (idx_t iter = 0; iter <= itmax; ++iter) {
165 if (iter == itmax) {
166 // The QR algorithm failed to converge, return with error.
167 return istop;
168 }
169
170 if (istop <= 1) {
171 // All singular values have been found, exit and return 0.
172 break;
173 }
174
175 // Find active block
176 auto smax = abs(d[istop - 1]);
177 for (idx_t i = istop - 1; i > istart; --i) {
178 smax = max(smax, abs(d[i - 1]));
179 smax = max(smax, abs(e[i - 1]));
180 if (abs(e[i - 1]) <= thresh) {
181 e[i - 1] = zero;
182 istart = i;
183 break;
184 }
185 }
186
187 // A singular value has split off, reduce istop and start the loop again
188 if (istart == istop - 1) {
189 istop = istop - 1;
190 istart = 0;
191 continue;
192 }
193
194 // A 2x2 block has split off, handle separately
195 if (istart + 1 == istop - 1) {
197 svd22(d[istart], e[istart], d[istart + 1], sigmn, sigmx, csl, snl,
198 csr, snr);
199 d[istart] = sigmx;
200 d[istart + 1] = sigmn;
201 e[istart] = zero;
202
203 // Update singular vectors if desired
204 if (want_u) {
205 auto u1 = col(U, istart);
206 auto u2 = col(U, istart + 1);
207 rot(u1, u2, csl, snl);
208 }
209 if (want_vt) {
210 auto vt1 = row(Vt, istart);
211 auto vt2 = row(Vt, istart + 1);
212 rot(vt1, vt2, csr, snr);
213 }
214
215 istop = istop - 2;
216 istart = 0;
217 continue;
218 }
219
220 if (istart >= istop_old or istop <= istart_old) {
221 forwarddirection = abs(d[istart]) > abs(d[istop - 1]);
222 }
225
226 //
227 // Extra convergence checks
228 //
229
231 if (forwarddirection) {
232 // First apply standard test to bottom of matrix
233 if (abs(e[istop - 2]) <= tol * abs(d[istop - 1])) {
234 e[istop - 2] = zero;
235 istop = istop - 1;
236 continue;
237 }
238 // Now apply fancy convergence criterion using recurrence
239 // relation for minimal singular value estimate
240 auto mu = abs(d[istart]);
241 sminl = mu;
242 bool found_zero = false;
243 for (idx_t i = istart; i + 1 < istop; ++i) {
244 if (abs(e[i]) < tol * mu) {
245 found_zero = true;
246 e[i] = zero;
247 break;
248 }
249 mu = abs(d[i + 1]) * (mu / (mu + abs(e[i])));
250 sminl = min(sminl, mu);
251 }
252 if (found_zero) continue;
253 }
254 else {
255 // First apply standard test to top of matrix
256 if (abs(e[istart]) <= tol * abs(d[istart])) {
257 e[istart] = zero;
258 istart = istart + 1;
259 continue;
260 }
261 // Now apply fancy convergence criterion using recurrence
262 // relation for minimal singular value estimate
263 auto mu = abs(d[istop - 1]);
264 sminl = mu;
265 bool found_zero = false;
266 for (idx_t i2 = istart; i2 + 1 < istop; ++i2) {
267 idx_t i = istop - 2 - (i2 - istart);
268 if (abs(e[i]) < tol * mu) {
269 found_zero = true;
270 e[i] = zero;
271 break;
272 }
273 mu = abs(d[i]) * (mu / (mu + abs(e[i])));
274 sminl = min(sminl, mu);
275 }
276 if (found_zero) continue;
277 }
278
279 // Compute shift. First, test if shifting would ruin relative
280 // accuracy, and if so set the shift to zero.
282 if (real_t(n) * tol * (sminl / smax) <= max(eps, real_t(0.01) * tol)) {
283 shift = zero;
284 }
285 else {
287 if (forwarddirection) {
288 // Compute the shift from 2-by-2 block at end of matrix
289 sstart = abs(d[istart]);
290 singularvalues22(d[istop - 2], e[istop - 2], d[istop - 1],
291 shift, temp);
292 }
293 else {
294 // Compute the shift from 2-by-2 block at start of matrix
295 sstart = abs(d[istop - 1]);
297 temp);
298 }
299
300 // Test if shift negligible, and if so set to zero
301 if (sstart > zero and square(shift / sstart) < eps) shift = zero;
302 }
303
304 if (shift == zero) {
305 // If shift = 0, do simplified QR iteration, this is better for the
306 // relative accuracy of small singular values
307 if (forwarddirection) {
308 real_t r, cs, sn, oldcs, oldsn;
309 cs = one;
310 sn = zero;
311 oldcs = one;
312 oldsn = zero;
313 for (idx_t i = istart; i < istop - 1; ++i) {
314 lartg(d[i] * cs, e[i], cs, sn, r);
315 if (i > istart) e[i - 1] = oldsn * r;
316 lartg(oldcs * r, d[i + 1] * sn, oldcs, oldsn, d[i]);
317
318 // Update singular vectors if desired
319 if (want_u) {
320 auto u1 = col(U, i);
321 auto u2 = col(U, i + 1);
322 rot(u1, u2, oldcs, oldsn);
323 }
324 if (want_vt) {
325 auto vt1 = row(Vt, i);
326 auto vt2 = row(Vt, i + 1);
327 rot(vt1, vt2, cs, sn);
328 }
329 }
330 real_t h = d[istop - 1] * cs;
331 d[istop - 1] = h * oldcs;
332 e[istop - 2] = h * oldsn;
333 }
334 else {
335 real_t r, cs, sn, oldcs, oldsn;
336 cs = one;
337 sn = zero;
338 oldcs = one;
339 oldsn = zero;
340 for (idx_t i = istop - 1; i > istart; --i) {
341 lartg(d[i] * cs, e[i - 1], cs, sn, r);
342 if (i < istop - 1) e[i] = oldsn * r;
343 lartg(oldcs * r, d[i - 1] * sn, oldcs, oldsn, d[i]);
344
345 // Update singular vectors if desired
346 if (want_u) {
347 auto u1 = col(U, i - 1);
348 auto u2 = col(U, i);
349 rot(u1, u2, cs, -sn);
350 }
351 if (want_vt) {
352 auto vt1 = row(Vt, i - 1);
353 auto vt2 = row(Vt, i);
354 rot(vt1, vt2, oldcs, -oldsn);
355 }
356 }
357 real_t h = d[istart] * cs;
358 d[istart] = h * oldcs;
359 e[istart] = h * oldsn;
360 }
361 }
362 else {
363 // Use nonzero shift
364
365 if (forwarddirection) {
366 real_t f = (abs(d[istart]) - shift) *
367 (real_t(sgn(d[istart])) + shift / d[istart]);
368 real_t g = e[istart];
369 for (idx_t i = istart; i < istop - 1; ++i) {
370 real_t r, csl, snl, csr, snr;
371 lartg(f, g, csr, snr, r);
372 if (i > istart) e[i - 1] = r;
373 f = csr * d[i] + snr * e[i];
374 e[i] = csr * e[i] - snr * d[i];
375 g = snr * d[i + 1];
376 d[i + 1] = csr * d[i + 1];
377
378 lartg(f, g, csl, snl, r);
379 d[i] = r;
380 f = csl * e[i] + snl * d[i + 1];
381 d[i + 1] = csl * d[i + 1] - snl * e[i];
382 if (i + 1 < istop - 1) {
383 g = snl * e[i + 1];
384 e[i + 1] = csl * e[i + 1];
385 }
386
387 // Update singular vectors if desired
388 if (want_u) {
389 auto u1 = col(U, i);
390 auto u2 = col(U, i + 1);
391 rot(u1, u2, csl, snl);
392 }
393 if (want_vt) {
394 auto vt1 = row(Vt, i);
395 auto vt2 = row(Vt, i + 1);
396 rot(vt1, vt2, csr, snr);
397 }
398 }
399 e[istop - 2] = f;
400 }
401 else {
402 real_t f = (abs(d[istop - 1]) - shift) *
403 (real_t(sgn(d[istop - 1])) + shift / d[istop - 1]);
404 real_t g = e[istop - 2];
405 for (idx_t i = istop - 1; i > istart; --i) {
406 real_t r, csl, snl, csr, snr;
407 lartg(f, g, csr, snr, r);
408 if (i < istop - 1) e[i] = r;
409 f = csr * d[i] + snr * e[i - 1];
410 e[i - 1] = csr * e[i - 1] - snr * d[i];
411 g = snr * d[i - 1];
412 d[i - 1] = csr * d[i - 1];
413
414 lartg(f, g, csl, snl, r);
415 d[i] = r;
416 f = csl * e[i - 1] + snl * d[i - 1];
417 d[i - 1] = csl * d[i - 1] - snl * e[i - 1];
418 if (i > istart + 1) {
419 g = snl * e[i - 2];
420 e[i - 2] = csl * e[i - 2];
421 }
422
423 // Update singular vectors if desired
424 if (want_u) {
425 auto u1 = col(U, i - 1);
426 auto u2 = col(U, i);
427 rot(u1, u2, csr, -snr);
428 }
429 if (want_vt) {
430 auto vt1 = row(Vt, i - 1);
431 auto vt2 = row(Vt, i);
432 rot(vt1, vt2, csl, -snl);
433 }
434 }
435 e[istart] = f;
436 }
437 }
438 }
439
440 // All singular values converged, so make them positive
441 for (idx_t i = 0; i < n; ++i) {
442 if (d[i] < zero) {
443 d[i] = -d[i];
444 if (want_vt) {
445 auto vt1 = row(Vt, i);
446 scal(-one, vt1);
447 }
448 }
449 }
450
451 // Sort the singular values into decreasing order.
452 for (idx_t i = 0; i < n - 1; ++i) {
453 auto d2 = slice(d, range{i, n});
454 idx_t imax = i + iamax(d2);
455 if (imax != i) {
456 std::swap(d[imax], d[i]);
457
458 if (want_u) {
459 auto u1 = col(U, imax);
460 auto u2 = col(U, i);
462 }
463 if (want_vt) {
464 auto vt1 = row(Vt, imax);
465 auto vt2 = row(Vt, i);
467 }
468 }
469 }
470
471 return 0;
472}
473
474} // namespace tlapack
475
476#endif // TLAPACK_SVD_QR_HH
Uplo
Definition types.hpp:45
constexpr int sgn(const T &val)
Type-safe sgn function.
Definition utils.hpp:109
void svd22(const T &f, const T &g, const T &h, T &ssmin, T &ssmax, T &csl, T &snl, T &csr, T &snr)
Computes the singular value decomposition of a 2-by-2 real triangular matrix.
Definition svd22.hpp:55
void singularvalues22(const T &f, const T &g, const T &h, T &ssmin, T &ssmax)
Computes the singular value decomposition of a 2-by-2 real triangular matrix.
Definition singularvalues22.hpp:40
void rot(vectorX_t &x, vectorY_t &y, const c_type &c, const s_type &s)
Apply plane rotation:
Definition rot.hpp:44
void swap(vectorX_t &x, vectorY_t &y)
Swap vectors, .
Definition swap.hpp:31
size_type< vector_t > iamax(const vector_t &x, const IamaxOpts< abs_f > &opts)
Return .
Definition iamax.hpp:234
void lartg(const T &a, const T &b, real_type< T > &c, T &s, T &r)
Construct plane rotation that eliminates b, such that:
Definition lartg.hpp:38
void scal(const alpha_t &alpha, vector_t &x)
Scale vector by constant, .
Definition scal.hpp:30
int svd_qr(Uplo uplo, bool want_u, bool want_vt, d_t &d, e_t &e, matrix_t &U, matrix_t &Vt)
Computes the singular values and, optionally, the right and/or left singular vectors from the singula...
Definition svd_qr.hpp:85
typename traits::real_type_traits< Types..., int >::type real_type
The common real type of the list of types.
Definition scalar_type_traits.hpp:113