ITK 6.0.0
Insight Toolkit
 
Loading...
Searching...
No Matches
itkMathSVD.h
Go to the documentation of this file.
1/*=========================================================================
2 *
3 * Copyright NumFOCUS
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0.txt
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 *=========================================================================*/
18#ifndef itkMathSVD_h
19#define itkMathSVD_h
20
21#include "itkMacro.h"
22#include "itkMatrix.h"
24#include "vnl/vnl_matrix.h"
25#include "vnl/vnl_matrix_fixed.h"
26#include "vnl/vnl_vector.h"
27#include "vnl/vnl_vector_fixed.h"
28
29#include "itk_eigen.h"
30#include ITK_EIGEN(Dense)
31
32#include <limits>
33
34namespace itk
35{
36namespace Math
37{
38
39namespace detail
40{
41// rcond < 0 selects an automatic relative threshold of n * epsilon.
42template <typename TReal>
43TReal
44ResolveRcond(TReal rcond, unsigned int n)
45{
46 return rcond < TReal{ 0 } ? static_cast<TReal>(n) * std::numeric_limits<TReal>::epsilon() : rcond;
47}
48
49// Moore-Penrose pseudo-inverse V diag(1/w) U^T, with singular values at or below
50// rcond*max(w) treated as zero. Works for vnl_matrix and vnl_matrix_fixed.
51template <typename TMatrix, typename TVector, typename TReal>
52TMatrix
53PseudoInverse(const TMatrix & U, const TVector & W, const TMatrix & V, TReal rcond)
54{
55 const unsigned int k = W.size();
56 // W is sorted descending (Eigen guarantee), so W[0] is the max without an O(k) scan.
57 const TReal tol = ResolveRcond(rcond, k) * W[0];
58 TMatrix scaledV = V;
59 for (unsigned int col = 0; col < k; ++col)
60 {
61 const TReal s = (W[col] > tol) ? TReal{ 1 } / W[col] : TReal{ 0 };
62 for (unsigned int i = 0; i < scaledV.rows(); ++i)
63 {
64 scaledV(i, col) *= s;
65 }
66 }
67 return scaledV * U.transpose();
68}
69
70// Least-squares / minimum-norm solution x = V diag(1/w) U^T b of A x = b.
71template <typename TMatrix, typename TVector, typename TReal>
72TVector
73SolveLinear(const TMatrix & U, const TVector & W, const TMatrix & V, const TVector & b, TReal rcond)
74{
75 const unsigned int n = W.size();
76 const TReal tol = ResolveRcond(rcond, n) * W[0]; // W sorted descending; W[0] == max
77 TVector utb = U.transpose() * b;
78 for (unsigned int k = 0; k < n; ++k)
79 {
80 utb[k] = (W[k] > tol) ? utb[k] / W[k] : TReal{ 0 };
81 }
82 return V * utb;
83}
84
85template <typename TVector, typename TReal>
86unsigned int
87NumericalRank(const TVector & W, TReal rcond)
88{
89 const unsigned int n = W.size();
90 const TReal tol = ResolveRcond(rcond, n) * W[0]; // W sorted descending; W[0] == max
91 unsigned int count = 0;
92 for (unsigned int k = 0; k < n; ++k)
93 {
94 if (W[k] > tol)
95 {
96 ++count;
97 }
98 }
99 return count;
100}
101
102// Reconstruct U diag(w) V^T, treating singular values at or below rcond*max(w) as
103// zero (a truncated, rank-reduced reconstruction of A).
104template <typename TMatrix, typename TVector, typename TReal>
105TMatrix
106Recompose(const TMatrix & U, const TVector & W, const TMatrix & V, TReal rcond)
107{
108 const unsigned int k = W.size();
109 const TReal tol = ResolveRcond(rcond, k) * W[0]; // W sorted descending; W[0] == max
110 TMatrix scaledU = U;
111 for (unsigned int col = 0; col < k; ++col)
112 {
113 const TReal s = (W[col] > tol) ? W[col] : TReal{ 0 };
114 for (unsigned int i = 0; i < scaledU.rows(); ++i)
115 {
116 scaledU(i, col) *= s;
117 }
118 }
119 return scaledU * V.transpose();
120}
121} // namespace detail
122
125template <typename TReal, unsigned int VDim>
127{
128 vnl_matrix_fixed<TReal, VDim, VDim> U{};
129 vnl_vector_fixed<TReal, VDim> W{};
130 vnl_matrix_fixed<TReal, VDim, VDim> V{};
131
133 vnl_matrix_fixed<TReal, VDim, VDim>
134 PseudoInverse(TReal rcond = TReal{ -1 }) const
135 {
136 return detail::PseudoInverse(U, W, V, rcond);
137 }
138
140 vnl_vector_fixed<TReal, VDim>
141 Solve(const vnl_vector_fixed<TReal, VDim> & b, TReal rcond = TReal{ -1 }) const
142 {
143 return detail::SolveLinear(U, W, V, b, rcond);
144 }
145
147 unsigned int
148 Rank(TReal rcond = TReal{ -1 }) const
149 {
150 return detail::NumericalRank(W, rcond);
151 }
152
154 vnl_matrix_fixed<TReal, VDim, VDim>
155 Recompose(TReal rcond = TReal{ -1 }) const
156 {
157 return detail::Recompose(U, W, V, rcond);
158 }
159};
160
164template <typename TReal>
166{
167 vnl_matrix<TReal> U{};
168 vnl_vector<TReal> W{};
169 vnl_matrix<TReal> V{};
170
172 vnl_matrix<TReal>
173 PseudoInverse(TReal rcond = TReal{ -1 }) const
174 {
175 return detail::PseudoInverse(U, W, V, rcond);
176 }
177
179 vnl_vector<TReal>
180 Solve(const vnl_vector<TReal> & b, TReal rcond = TReal{ -1 }) const
181 {
182 return detail::SolveLinear(U, W, V, b, rcond);
183 }
184
186 unsigned int
187 Rank(TReal rcond = TReal{ -1 }) const
188 {
189 return detail::NumericalRank(W, rcond);
190 }
191
193 vnl_matrix<TReal>
194 Recompose(TReal rcond = TReal{ -1 }) const
195 {
196 return detail::Recompose(U, W, V, rcond);
197 }
198};
199
200namespace detail
201{
202// Above this compile-time size the fixed overload delegates to the runtime path
203// (keeps a large VDim off Eigen's stack-allocation limit).
204constexpr unsigned int kFixedSVDMaxDim = 16;
205
206// JacobiSVD+NoQRPreconditioner is fastest for small square inputs; BDCSVD is
207// faster and more accurate for larger n.
208constexpr unsigned int kJacobiMaxDim = 6;
209
210// Runtime-sized square SVD: JacobiSVD + NoQRPreconditioner for small n, BDCSVD for
211// larger n where Jacobi sweeps scale poorly. Throws on a failed decomposition.
212template <typename TReal>
213void
214DynamicSquareSVDEigen(const TReal * inData, unsigned int n, TReal * uData, TReal * wData, TReal * vData)
215{
216 using RowMajor = Eigen::Matrix<TReal, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
217 using ColMajor = Eigen::Matrix<TReal, Eigen::Dynamic, Eigen::Dynamic>;
218 constexpr int full = Eigen::ComputeFullU | Eigen::ComputeFullV;
219
220 const Eigen::Map<const RowMajor> inMap(inData, n, n);
221 Eigen::Map<RowMajor> uMap(uData, n, n);
222 Eigen::Map<RowMajor> vMap(vData, n, n);
223
224 const auto extract = [&](const auto & svd) {
225 if (svd.info() != Eigen::Success)
226 {
227 itkGenericExceptionMacro("itk::Math::SVD failed; input is likely non-finite (NaN/Inf).");
228 }
229 uMap = svd.matrixU();
230 vMap = svd.matrixV();
231 for (unsigned int i = 0; i < n; ++i)
232 {
233 wData[i] = svd.singularValues()[i];
234 }
235 };
236
237 if (n <= kJacobiMaxDim)
238 {
239 extract(Eigen::JacobiSVD < ColMajor, full | Eigen::NoQRPreconditioner > (inMap));
240 }
241 else
242 {
243 extract(Eigen::BDCSVD<ColMajor, full>(inMap));
244 }
245}
246
247// NoQRPreconditioner skips the column-pivot QR (wasted for a square input); large
248// VDim falls back to the runtime path. Throws on a failed/non-finite decomposition.
249template <unsigned int VDim, typename TReal>
250void
251SquareSVDEigen(const TReal * inData, TReal * uData, TReal * wData, TReal * vData)
252{
253 if constexpr (VDim > kFixedSVDMaxDim)
254 {
255 DynamicSquareSVDEigen<TReal>(inData, VDim, uData, wData, vData);
256 }
257 else
258 {
259 // Compile-time sizes ≤ kFixedSVDMaxDim always take JacobiSVD: it stack-allocates,
260 // fully unrolls, and beats BDCSVD's dynamic setup at these sizes (kJacobiMaxDim
261 // gates only the runtime path, where BDCSVD's allocation pays off for larger n).
262 using RowMajor = Eigen::Matrix<TReal, VDim, VDim, Eigen::RowMajor>;
263 using ColMajor = Eigen::Matrix<TReal, VDim, VDim>;
264 constexpr int options = Eigen::ComputeFullU | Eigen::ComputeFullV | Eigen::NoQRPreconditioner;
265
266 const Eigen::Map<const RowMajor> inMap(inData);
267 const Eigen::JacobiSVD<ColMajor, options> svd(inMap);
268 if (svd.info() != Eigen::Success)
269 {
270 itkGenericExceptionMacro("itk::Math::SVD failed; input is likely non-finite (NaN/Inf).");
271 }
272
273 Eigen::Map<RowMajor> uMap(uData);
274 Eigen::Map<RowMajor> vMap(vData);
275 uMap = svd.matrixU();
276 vMap = svd.matrixV();
277 for (unsigned int i = 0; i < VDim; ++i)
278 {
279 wData[i] = svd.singularValues()[i];
280 }
281 }
282}
283
284// Rectangular SVD via BDCSVD with thin U/V (Eigen's general engine for non-square
285// inputs). For an m x n input: U is m x k, V is n x k, W has length k = min(m, n).
286// Throws on a failed decomposition.
287template <typename TReal>
288void
289RectangularSVDEigen(const TReal * inData,
290 unsigned int rows,
291 unsigned int cols,
292 TReal * uData,
293 TReal * wData,
294 TReal * vData)
295{
296 using RowMajor = Eigen::Matrix<TReal, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
297 using ColMajor = Eigen::Matrix<TReal, Eigen::Dynamic, Eigen::Dynamic>;
298 constexpr int thin = Eigen::ComputeThinU | Eigen::ComputeThinV;
299 const unsigned int k = (rows < cols) ? rows : cols;
300
301 const Eigen::Map<const RowMajor> inMap(inData, rows, cols);
302 const Eigen::BDCSVD<ColMajor, thin> svd(inMap);
303 if (svd.info() != Eigen::Success)
304 {
305 itkGenericExceptionMacro("itk::Math::SVD failed; input is likely non-finite (NaN/Inf).");
306 }
307 Eigen::Map<RowMajor> uMap(uData, rows, k);
308 Eigen::Map<RowMajor> vMap(vData, cols, k);
309 uMap = svd.matrixU();
310 vMap = svd.matrixV();
311 for (unsigned int i = 0; i < k; ++i)
312 {
313 wData[i] = svd.singularValues()[i];
314 }
315}
316} // namespace detail
317
348template <typename TReal, unsigned int VDim>
349FixedSquareSVDResult<TReal, VDim>
350SVD(const vnl_matrix_fixed<TReal, VDim, VDim> & A, bool canonicalizeSigns = true)
351{
353 detail::SquareSVDEigen<VDim>(A.data_block(), result.U.data_block(), result.W.data_block(), result.V.data_block());
354 if (canonicalizeSigns)
355 {
357 }
358 return result;
359}
360
362template <typename TReal, unsigned int VDim>
363FixedSquareSVDResult<TReal, VDim>
364SVD(const Matrix<TReal, VDim, VDim> & A, bool canonicalizeSigns = true)
365{
366 return SVD<TReal, VDim>(A.GetVnlMatrix(), canonicalizeSigns);
367}
368
372template <typename TReal>
373SVDResult<TReal>
374SVD(const vnl_matrix<TReal> & A, bool canonicalizeSigns = true)
375{
376 const unsigned int rows = A.rows();
377 const unsigned int cols = A.cols();
378 if (rows == 0 || cols == 0)
379 {
380 itkGenericExceptionMacro("itk::Math::SVD requires a non-empty matrix.");
381 }
382 const unsigned int k = (rows < cols) ? rows : cols;
383 SVDResult<TReal> result;
384 result.U.set_size(rows, k);
385 result.V.set_size(cols, k);
386 result.W.set_size(k);
387 if (rows == cols)
388 {
390 A.data_block(), rows, result.U.data_block(), result.W.data_block(), result.V.data_block());
391 }
392 else
393 {
395 A.data_block(), rows, cols, result.U.data_block(), result.W.data_block(), result.V.data_block());
396 }
397 if (canonicalizeSigns)
398 {
400 }
401 return result;
402}
403
404} // namespace Math
405} // namespace itk
406
407#endif // itkMathSVD_h
A templated class holding a M x N size Matrix.
Definition itkMatrix.h:53
InternalMatrixType & GetVnlMatrix()
Definition itkMatrix.h:214
FixedSquareSVDResult< TReal, VDim > SVD(const vnl_matrix_fixed< TReal, VDim, VDim > &A, bool canonicalizeSigns=true)
Singular value decomposition A = U diag(W) V^T, backed by Eigen.
Definition itkMathSVD.h:350
Cholesky-based linear algebra for symmetric matrices, backed by Eigen.
void DynamicSquareSVDEigen(const TReal *inData, unsigned int n, TReal *uData, TReal *wData, TReal *vData)
Definition itkMathSVD.h:214
TMatrix PseudoInverse(const TMatrix &U, const TVector &W, const TMatrix &V, TReal rcond)
Definition itkMathSVD.h:53
void RectangularSVDEigen(const TReal *inData, unsigned int rows, unsigned int cols, TReal *uData, TReal *wData, TReal *vData)
Definition itkMathSVD.h:289
TReal ResolveRcond(TReal rcond, unsigned int n)
Definition itkMathSVD.h:44
TMatrix Recompose(const TMatrix &U, const TVector &W, const TMatrix &V, TReal rcond)
Definition itkMathSVD.h:106
constexpr unsigned int kFixedSVDMaxDim
Definition itkMathSVD.h:204
constexpr unsigned int kJacobiMaxDim
Definition itkMathSVD.h:208
TVector SolveLinear(const TMatrix &U, const TVector &W, const TMatrix &V, const TVector &b, TReal rcond)
Definition itkMathSVD.h:73
void SquareSVDEigen(const TReal *inData, TReal *uData, TReal *wData, TReal *vData)
Definition itkMathSVD.h:251
unsigned int NumericalRank(const TVector &W, TReal rcond)
Definition itkMathSVD.h:87
void CanonicalizeColumnSignsPaired(TMatrix &u, TMatrix &paired)
The "itk" namespace contains all Insight Segmentation and Registration Toolkit (ITK) classes....
vnl_matrix_fixed< TReal, VDim, VDim > PseudoInverse(TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:134
vnl_vector_fixed< TReal, VDim > Solve(const vnl_vector_fixed< TReal, VDim > &b, TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:141
vnl_matrix_fixed< TReal, VDim, VDim > Recompose(TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:155
vnl_vector_fixed< TReal, VDim > W
Definition itkMathSVD.h:129
vnl_matrix_fixed< TReal, VDim, VDim > U
Definition itkMathSVD.h:128
vnl_matrix_fixed< TReal, VDim, VDim > V
Definition itkMathSVD.h:130
unsigned int Rank(TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:148
vnl_matrix< TReal > Recompose(TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:194
vnl_matrix< TReal > PseudoInverse(TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:173
vnl_matrix< TReal > V
Definition itkMathSVD.h:169
unsigned int Rank(TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:187
vnl_vector< TReal > W
Definition itkMathSVD.h:168
vnl_matrix< TReal > U
Definition itkMathSVD.h:167
vnl_vector< TReal > Solve(const vnl_vector< TReal > &b, TReal rcond=TReal{ -1 }) const
Definition itkMathSVD.h:180