LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
svd.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_SVD_H
6#define LINE_UTIL_SVD_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * Singular value decomposition WITH the singular vectors, and the
12 * Moore-Penrose pseudo-inverse built from it.
13 *
14 * util/eig.h already exposes the singular VALUES (dgesvd with jobu = jobvt =
15 * 'N'), which is all the rank and conditioning tests need. This header adds
16 * the factors themselves, for the one place in the port that needs them: the
17 * pinv fallback of ldqbd_R, taken when a level's local block is singular.
18 *
19 * DOUBLE ONLY, for the same reason eig.h is: singular values of a rational
20 * matrix are algebraic, not rational, so there is no exact instantiation to
21 * offer, and LAPACK has no multiprecision path. Callers templated on T must
22 * convert and state the precision loss at the call site. Configured without
23 * LAPACK the entry points throw UnsupportedError naming the dependency rather
24 * than substituting anything.
25 *
26 * The Fortran symbol dgesvd_ is declared by eig.h under the same LAPACK guard
27 * and is reused from there, so there is exactly one declaration of it in the
28 * tree.
29 */
30
31#include <cstddef>
32#include <vector>
33
34#include "line/util/eig.h"
35#include "line/util/error.h"
36#include "line/util/matrix.h"
37
38namespace line {
39
40/** A = U diag(s) Vt, with U (m x m), s of length min(m,n) and Vt (n x n). */
41struct SvdFactors {
43 std::vector<double> s;
45};
46
47/** Full SVD of a real matrix, singular values in descending order. */
49#ifndef LINE_MP_HAVE_LAPACK
50 (void)A;
51 throw UnsupportedError(
52 "svd_full requires LAPACK: reconfigure with -DLINE_MP_USE_LAPACK=ON and liblapack "
53 "available");
54#else
55 const std::size_t m = A.rows(), n = A.cols();
56 if (m == 0 || n == 0) throw InputError("svd_full: empty matrix");
57
58 // LAPACK is column-major, the port is row-major, so transpose on the way in.
59 std::vector<double> a(m * n);
60 for (std::size_t i = 0; i < m; ++i)
61 for (std::size_t j = 0; j < n; ++j) a[j * m + i] = A(i, j);
62
63 const int mi = static_cast<int>(m), nj = static_cast<int>(n);
64 const std::size_t k = m < n ? m : n;
65 std::vector<double> s(k), u(m * m), vt(n * n);
66 int info = 0, lwork = -1;
67 double wopt = 0.0;
68 dgesvd_("A", "A", &mi, &nj, a.data(), &mi, s.data(), u.data(), &mi, vt.data(), &nj, &wopt,
69 &lwork, &info);
70 if (info != 0) throw NumericError("svd_full: LAPACK workspace query failed");
71 lwork = static_cast<int>(wopt);
72 std::vector<double> work(static_cast<std::size_t>(lwork));
73 dgesvd_("A", "A", &mi, &nj, a.data(), &mi, s.data(), u.data(), &mi, vt.data(), &nj, work.data(),
74 &lwork, &info);
75 if (info != 0) throw NumericError("svd_full: LAPACK dgesvd failed to converge");
76
77 SvdFactors out;
78 out.s = s;
79 out.U = Matrix<double>(m, m);
80 for (std::size_t i = 0; i < m; ++i)
81 for (std::size_t j = 0; j < m; ++j) out.U(i, j) = u[j * m + i];
82 out.Vt = Matrix<double>(n, n);
83 for (std::size_t i = 0; i < n; ++i)
84 for (std::size_t j = 0; j < n; ++j) out.Vt(i, j) = vt[j * n + i];
85 return out;
86#endif
87}
88
89/**
90 * Moore-Penrose pseudo-inverse, A^+ = V diag(1/s_i) U^T over the singular
91 * values above max(m,n) eps sigma_1, which is MATLAB's default pinv tolerance.
92 */
94 const SvdFactors f = svd_full(A);
95 const std::size_t m = A.rows(), n = A.cols();
96 const std::size_t k = f.s.size();
97 const std::size_t dim = m > n ? m : n;
98 const double tol = static_cast<double>(dim) * 2.220446049250313e-16 * (k ? f.s[0] : 0.0);
99 Matrix<double> X(n, m, 0.0);
100 for (std::size_t r = 0; r < k; ++r) {
101 if (!(f.s[r] > tol)) continue;
102 const double inv = 1.0 / f.s[r];
103 // X += inv * V(:,r) * U(:,r)^T, with V(:,r) the r-th ROW of Vt.
104 for (std::size_t i = 0; i < n; ++i) {
105 const double vi = f.Vt(r, i);
106 if (vi == 0.0) continue;
107 for (std::size_t j = 0; j < m; ++j) X(i, j) += inv * vi * f.U(j, r);
108 }
109 }
110 return X;
111}
112
113} // namespace line
114
115#endif // LINE_UTIL_SVD_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:45
UnsupportedError(const std::string &what)
Definition error.h:51
Eigenvalues and singular values, backed by LAPACK.
The exception types the port throws.
Dense matrix and non-owning view.
Matrix< double > pinv(const Matrix< double > &A)
Moore-Penrose pseudo-inverse, A^+ = V diag(1/s_i) U^T over the singular values above max(m,...
Definition svd.h:93
SvdFactors svd_full(const Matrix< double > &A)
Full SVD of a real matrix, singular values in descending order.
Definition svd.h:48
A = U diag(s) Vt, with U (m x m), s of length min(m,n) and Vt (n x n).
Definition svd.h:41
Matrix< double > Vt
Definition svd.h:44
Matrix< double > U
Definition svd.h:42
std::vector< double > s
Definition svd.h:43