vnl_svd_fixed.hxx
Go to the documentation of this file.
1 // This is core/vnl/algo/vnl_svd_fixed.hxx
2 #ifndef vnl_svd_fixed_hxx_
3 #define vnl_svd_fixed_hxx_
4 //:
5 // \file
6 
7 #include <cstdlib>
8 #include <complex>
9 #include <iostream>
10 #include <algorithm>
11 #include "vnl_svd_fixed.h"
12 
13 #include <cassert>
14 #ifdef _MSC_VER
15 # include <vcl_msvc_warnings.h>
16 #endif
17 
18 #include <vnl/vnl_math.h>
20 #include <vnl/algo/vnl_netlib.h> // dsvdc_()
21 
22 // use C++ overloading to call the right linpack routine from the template code :
23 #define macro(p, T) \
24 inline void vnl_linpack_svdc_fixed(vnl_netlib_svd_proto(T)) \
25 { v3p_netlib_##p##svdc_(vnl_netlib_svd_params); }
26 macro(s, float);
27 macro(d, double);
28 macro(c, std::complex<float>);
29 macro(z, std::complex<double>);
30 #undef macro
31 
32 //--------------------------------------------------------------------------------
33 
34 static bool vnl_svd_fixed_test_heavily = false;
35 #include <vnl/vnl_matlab_print.h>
36 
37 template <class T, unsigned int R, unsigned int C>
39 {
40  {
41  const long n=R, p=C;
42  const unsigned mm = std::min(R+1u,C);
43 
44  // Copy source matrix into fortran storage
45  // SVD is slow, don't worry about the cost of this transpose.
47 
48  // Make workspace vectors.
49  vnl_vector_fixed<T, C> work(T(0));
50  vnl_vector_fixed<T, R*C> uspace(T(0));
51  vnl_vector_fixed<T, C*C> vspace(T(0));
52  vnl_vector_fixed<T, (R+1<C?R+1:C)> wspace(T(0)); // complex fortran routine actually _wants_ complex W!
53  vnl_vector_fixed<T, C> espace(T(0));
54 
55  // Call Linpack SVD
56  long info = 0;
57  constexpr long job = 21; // min(n,p) svs in U, n svs in V (i.e. economy size)
58  vnl_linpack_svdc_fixed((T*)X, &n, &n, &p,
59  wspace.data_block(),
60  espace.data_block(),
61  uspace.data_block(), &n,
62  vspace.data_block(), &p,
63  work.data_block(),
64  &job, &info);
65 
66  // Error return?
67  if (info != 0)
68  {
69  // If info is non-zero, it contains the number of singular values
70  // for this the SVD algorithm failed to converge. The condition is
71  // not bogus. Even if the returned singular values are sensible,
72  // the singular vectors can be utterly wrong.
73 
74  // It is possible the failure was due to NaNs or infinities in the
75  // matrix. Check for that now.
76  M.assert_finite();
77 
78  // If we get here it might be because
79  // 1. The scalar type has such
80  // extreme precision that too few iterations were performed to
81  // converge to within machine precision (that is the svdc criterion).
82  // One solution to that is to increase the maximum number of
83  // iterations in the netlib code.
84  //
85  // 2. The LINPACK dsvdc_ code expects correct IEEE rounding behaviour,
86  // which some platforms (notably x86 processors)
87  // have trouble doing. For example, gcc can output
88  // code in -O2 and static-linked code that causes this problem.
89  // One solution to this is to persuade gcc to output slightly different code
90  // by adding and -fPIC option to the command line for v3p/netlib/dsvdc.c. If
91  // that doesn't work try adding -ffloat-store, which should fix the problem
92  // at the expense of being significantly slower for big problems. Note that
93  // if this is the cause, core/vnl/tests/test_svd should have failed.
94  //
95  // You may be able to diagnose the problem here by printing a warning message.
96  std::cerr << __FILE__ ": suspicious return value (" << info << ") from SVDC\n"
97  << __FILE__ ": M is " << M.rows() << 'x' << M.cols() << std::endl;
98 
100  valid_ = false;
101  }
102  else
103  valid_ = true;
104 
105  // Copy fortran outputs into our storage
106  {
107  const T *d = uspace.data_block();
108  for (long j = 0; j < p; ++j)
109  for (long i = 0; i < n; ++i)
110  U_(i,j) = *d++;
111  }
112 
113  for (unsigned j = 0; j < mm; ++j)
114  W_(j, j) = std::abs(wspace(j)); // we get rid of complexness here.
115 
116  for (unsigned j = mm; j < C; ++j)
117  W_(j, j) = 0;
118 
119  {
120  const T *d = vspace.data_block();
121  for (unsigned j = 0; j < C; ++j)
122  for (unsigned i = 0; i < C; ++i)
123  V_(i,j) = *d++;
124  }
125  }
126 
127  if (vnl_svd_fixed_test_heavily)
128  {
129  // Test that recomposed matrix == M
130  typedef typename vnl_numeric_traits<T>::abs_t abs_t;
131  abs_t recomposition_residual = std::abs((recompose() - M).fro_norm());
132  abs_t n = std::abs(M.fro_norm());
133  abs_t thresh = abs_t(R) * abs_t(vnl_math::eps) * n;
134  if (recomposition_residual > thresh)
135  {
136  std::cerr << "vnl_svd_fixed<T>::vnl_svd_fixed<T>() -- Warning, recomposition_residual = "
137  << recomposition_residual << std::endl
138  << "fro_norm(M) = " << n << std::endl
139  << "eps*fro_norm(M) = " << thresh << std::endl
140  << "Press return to continue\n";
141  char x;
142  std::cin.get(&x, 1, '\n');
143  }
144  }
145 
146  if (zero_out_tol >= 0)
147  // Zero out small sv's and update rank count.
148  zero_out_absolute(double(+zero_out_tol));
149  else
150  // negative tolerance implies relative to max elt.
151  zero_out_relative(double(-zero_out_tol));
152 }
153 
154 template <class T, unsigned int R, unsigned int C>
155 std::ostream& operator<<(std::ostream& s, const vnl_svd_fixed<T,R,C>& svd)
156 {
157  s << "vnl_svd_fixed<T,R,C>:\n"
158  << "U = [\n" << svd.U() << "]\n"
159  << "W = " << svd.W() << '\n'
160  << "V = [\n" << svd.V() << "]\n"
161  << "rank = " << svd.rank() << std::endl;
162  return s;
163 }
164 
165 //-----------------------------------------------------------------------------
166 // Chunky bits.
167 
168 //: find weights below threshold tol, zero them out, and update W_ and Winverse_
169 template <class T, unsigned int R, unsigned int C>
171 {
172  last_tol_ = tol;
173  rank_ = C;
174  for (unsigned k = 0; k < C; k++)
175  {
176  singval_t& weight = W_(k, k);
177  if (std::abs(weight) <= tol)
178  {
179  Winverse_(k,k) = 0;
180  weight = 0;
181  --rank_;
182  }
183  else
184  {
185  Winverse_(k,k) = singval_t(1.0)/weight;
186  }
187  }
188 }
189 
190 //: find weights below tol*max(w) and zero them out
191 template <class T, unsigned int R, unsigned int C>
192 void vnl_svd_fixed<T,R,C>::zero_out_relative(double tol) // sqrt(machine epsilon)
193 {
194  zero_out_absolute(tol * std::abs(sigma_max()));
195 }
196 
197 static bool wf=false;
198 inline bool warned_f() { if (wf) return true; else { wf=true; return false; } }
199 
200 //: Calculate determinant as product of diagonals in W.
201 template <class T, unsigned int R, unsigned int C>
203 {
204  if (!warned_f() && R != C)
205  std::cerr << __FILE__ ": called determinant_magnitude() on SVD of non-square matrix\n"
206  << "(This warning is displayed only once)\n";
207  singval_t product = W_(0, 0);
208  for (unsigned long k = 1; k < C; k++)
209  product *= W_(k, k);
210 
211  return product;
212 }
213 
214 template <class T, unsigned int R, unsigned int C>
216 {
217  return std::abs(sigma_max());
218 }
219 
220 //: Recompose SVD to U*W*V'
221 template <class T, unsigned int R, unsigned int C>
223 {
224  if (rnk > rank_) rnk=rank_;
225  vnl_diag_matrix_fixed<T,C> Wmatr(W_);
226  for (unsigned int i=rnk;i<C;++i)
227  Wmatr(i,i)=0;
228 
229  return U_*Wmatr*V_.conjugate_transpose();
230 }
231 
232 
233 //: Calculate pseudo-inverse.
234 template <class T, unsigned int R, unsigned int C>
236 {
237  if (rnk > rank_) rnk=rank_;
238  vnl_diag_matrix_fixed<T,C> W_inverse(Winverse_);
239  for (unsigned int i=rnk;i<C;++i)
240  W_inverse(i,i)=0;
241 
242  return V_ * W_inverse * U_.conjugate_transpose();
243 }
244 
245 
246 //: Calculate (pseudo-)inverse of transpose.
247 template <class T, unsigned int R, unsigned int C>
249 {
250  if (rnk > rank_) rnk=rank_;
251  vnl_diag_matrix_fixed<T,C> W_inverse(Winverse_);
252  for (unsigned int i=rnk;i<C;++i)
253  W_inverse(i,i)=0;
254 
255  return U_ * W_inverse * V_.conjugate_transpose();
256 }
257 
258 
259 //: Solve the matrix equation M X = B, returning X
260 template <class T, unsigned int R, unsigned int C>
262 {
263  vnl_matrix<T> x; // solution matrix
264  if (U_.rows() < U_.columns()) { // augment y with extra rows of
265  vnl_matrix<T> yy(U_.rows(), B.columns(), T(0)); // zeros, so that it matches
266  yy.update(B); // cols of u.transpose. ???
267  x = U_.conjugate_transpose() * yy;
268  }
269  else
270  x = U_.conjugate_transpose() * B;
271  for (unsigned long i = 0; i < x.rows(); ++i) { // multiply with diagonal 1/W
272  T weight = W_(i, i);
273  if (weight != T(0)) // vnl_numeric_traits<T>::zero
274  weight = T(1) / weight;
275  for (unsigned long j = 0; j < x.columns(); ++j)
276  x(i, j) *= weight;
277  }
278  x = V_ * x; // premultiply with v.
279  return x;
280 }
281 
282 //: Solve the matrix-vector system M x = y, returning x.
283 template <class T, unsigned int R, unsigned int C>
285 {
286  vnl_vector_fixed<T, C> x; // Solution matrix.
287  x = U_.conjugate_transpose() * y;
288 
289  for (unsigned i = 0; i < C; i++) { // multiply with diagonal 1/W
290  T weight = W_(i, i), zero_(0);
291  if (weight != zero_)
292  x[i] /= weight;
293  else
294  x[i] = zero_;
295  }
296  return V_ * x; // premultiply with v.
297 }
298 
299 template <class T, unsigned int R, unsigned int C> // FIXME. this should implement the above, not the other way round.
300 void vnl_svd_fixed<T,R,C>::solve(T const *y, T *x) const
301 {
302  solve(vnl_vector_fixed<T, R>(y)).copy_out(x);
303 }
304 
305 //: Solve the matrix-vector system M x = y.
306 // Assume that the singular values W have been preinverted by the caller.
307 template <class T, unsigned int R, unsigned int C>
309 {
310  vnl_vector_fixed<T, C> x; // solution matrix
311  x = U_.conjugate_transpose() * y;
312  for (unsigned i = 0; i < C; i++) // multiply with diagonal W, assumed inverted
313  x[i] *= W_(i, i);
314 
315  *x_out = V_ * x; // premultiply with v.
316 }
317 
318 //-----------------------------------------------------------------------------
319 //: Return N s.t. M * N = 0
320 template <class T, unsigned int R, unsigned int C>
322 {
323  int k = rank();
324  if (k == C)
325  std::cerr << "vnl_svd_fixed<T>::nullspace() -- Matrix is full rank." << last_tol_ << std::endl;
326  return nullspace(C-k);
327 }
328 
329 //-----------------------------------------------------------------------------
330 //: Return N s.t. M * N = 0
331 template <class T, unsigned int R, unsigned int C>
332 vnl_matrix<T> vnl_svd_fixed<T,R,C>::nullspace(int required_nullspace_dimension) const
333 {
334  return V_.extract(V_.rows(), required_nullspace_dimension, 0, C - required_nullspace_dimension);
335 }
336 
337 //-----------------------------------------------------------------------------
338 //: Return N s.t. M' * N = 0
339 template <class T, unsigned int R, unsigned int C>
341 {
342  int k = rank();
343  if (k == C)
344  std::cerr << "vnl_svd_fixed<T>::left_nullspace() -- Matrix is full rank." << last_tol_ << std::endl;
345  return U_.extract(U_.rows(), C-k, 0, k);
346 }
347 
348 //:
349 // \todo Implementation to be done yet; currently returns left_nullspace(). - PVr.
350 template <class T, unsigned int R, unsigned int C>
351 vnl_matrix<T> vnl_svd_fixed<T,R,C>::left_nullspace(int /*required_nullspace_dimension*/) const
352 {
353  return left_nullspace();
354 }
355 
356 
357 //-----------------------------------------------------------------------------
358 //: Return the rightmost column of V.
359 // Does not check to see whether or not the matrix actually was rank-deficient -
360 // the caller is assumed to have examined W and decided that to his or her satisfaction.
361 template <class T, unsigned int R, unsigned int C>
363 {
365  for (unsigned i = 0; i < C; ++i)
366  ret(i) = V_(i, C-1);
367  return ret;
368 }
369 
370 //-----------------------------------------------------------------------------
371 //: Return the rightmost column of U.
372 // Does not check to see whether or not the matrix actually was rank-deficient.
373 template <class T, unsigned int R, unsigned int C>
375 {
377  const unsigned col = std::min(R, C) - 1;
378  for (unsigned i = 0; i < R; ++i)
379  ret(i) = U_(i, col);
380  return ret;
381 }
382 
383 //--------------------------------------------------------------------------------
384 
385 #undef VNL_SVD_FIXED_INSTANTIATE
386 #define VNL_SVD_FIXED_INSTANTIATE(T , R , C ) \
387 template class VNL_ALGO_EXPORT vnl_svd_fixed<T, R, C >; \
388 template VNL_ALGO_EXPORT std::ostream& operator<<(std::ostream &, vnl_svd_fixed<T, R, C > const &)
389 
390 #endif // vnl_svd_fixed_hxx_
singval_t norm() const
Print matrices and vectors in nice MATLAB format.
vnl_numeric_traits< T >::abs_t singval_t
The singular values of a matrix of complex<T> are of type T, not complex<T>.
Definition: vnl_svd_fixed.h:40
unsigned int cols() const
Return the number of columns.
vnl_matrix< T > solve(vnl_matrix< T > const &B) const
Solve the matrix equation M X = B, returning X.
VNL_EXPORT std::ostream & vnl_matlab_print(std::ostream &, vnl_diag_matrix< T > const &, char const *variable_name=nullptr, vnl_matlab_print_format=vnl_matlab_print_format_default)
print a vnl_diagonal_matrix<T>.
vnl_matrix< T > conjugate_transpose() const
Return conjugate transpose.
Definition: vnl_matrix.hxx:694
singval_t determinant_magnitude() const
Calculate determinant as product of diagonals in W.
abs_t fro_norm() const
Return Frobenius norm of matrix (sqrt of sum of squares of its elements).
vnl_matrix_fixed< T, C, R > pinverse(unsigned int rank=~0u) const
pseudo-inverse (for non-square matrix) of desired rank.
void assert_finite() const
abort if matrix contains any INFs or NANs.
Namespace with standard math functions.
vnl_matrix_fixed< T, R, C > & U()
Return the matrix U.
Definition: vnl_svd_fixed.h:76
void solve_preinverted(vnl_vector_fixed< T, R > const &rhs, vnl_vector_fixed< T, C > *out) const
Solve the matrix-vector system M x = y.
unsigned int rows() const
Return the number of rows.
vnl_matrix_fixed< T, R, C > recompose(unsigned int rank=~0u) const
Recompose SVD to U*W*V', using desired rank.
vnl_matrix_fixed< T, C, C > & V()
Return the matrix V.
Definition: vnl_svd_fixed.h:97
std::ostream & operator<<(std::ostream &s, vnl_decnum const &r)
decimal output.
Definition: vnl_decnum.h:393
#define macro(p, T)
Convert row-stored matrix to column-stored.
vnl_vector_fixed< T, C > nullvector() const
Return the rightmost column of V.
vnl_diag_matrix_fixed< singval_t, C > & W()
Get at DiagMatrix (q.v.) of singular values, sorted from largest to smallest.
Definition: vnl_svd_fixed.h:85
void zero_out_relative(double tol=1e-8)
find weights below tol*max(w) and zero them out.
vnl_matrix< T > left_nullspace() const
Return N such that M' * N = 0.
unsigned int rank() const
Definition: vnl_svd_fixed.h:68
An ordinary mathematical matrix.
Definition: vnl_adjugate.h:22
vnl_matrix_fixed< T, R, C > tinverse(unsigned int rank=~0u) const
Calculate inverse of transpose, using desired rank.
Declare in a central place the list of symbols from netlib.
Convert row-stored matrix to column-stored.
T const * data_block() const
Access the contiguous block storing the elements in the vector.
Fixed length stack-stored, space-efficient vector.
Definition: vnl_fwd.h:22
vnl_bignum abs(vnl_bignum const &x)
Definition: vnl_bignum.h:432
unsigned int rows() const
Return the number of rows.
Definition: vnl_matrix.h:179
stores a diagonal matrix as a single vector.
void zero_out_absolute(double tol=1e-8)
find weights below threshold tol, zero them out, and update W_ and Winverse_.
bool warned_f()
vnl_decnum min(vnl_decnum const &x, vnl_decnum const &y)
Definition: vnl_decnum.h:413
Holds the singular value decomposition of a vnl_matrix_fixed.
Definition: vnl_svd_fixed.h:36
vnl_vector_fixed< T, R > left_nullvector() const
Return the rightmost column of U.
Holds the singular value decomposition of a vnl_matrix_fixed.
vnl_matrix< T > nullspace() const
Return N such that M * N = 0.
vnl_matrix< T > & update(vnl_matrix< T > const &, unsigned top=0, unsigned left=0)
Set values of this matrix to those of M, starting at [top,left].
Definition: vnl_matrix.hxx:707
vnl_svd_fixed(vnl_matrix_fixed< T, R, C > const &M, double zero_out_tol=0.0)
Construct a vnl_svd_fixed<T> object from matrix .
unsigned int columns() const
Return the number of columns.
Definition: vnl_matrix.h:187