Visual Servoing Platform  version 3.6.1 under development (2024-04-23)
testSvd.cpp
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See https://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Test various svd decompositions.
33  */
34 
40 #include <algorithm>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <vector>
44 
45 #include <visp3/core/vpColVector.h>
46 #include <visp3/core/vpMatrix.h>
47 #include <visp3/core/vpTime.h>
48 #include <visp3/io/vpParseArgv.h>
49 
50 // List of allowed command line options
51 #define GETOPTARGS "cdn:i:pf:R:C:vh"
52 
61 void usage(const char *name, const char *badparam)
62 {
63  fprintf(stdout, "\n\
64 Test matrix inversions\n\
65 using LU, QR and Cholesky methods as well as Pseudo-inverse.\n\
66 Outputs a comparison of these methods.\n\
67 \n\
68 SYNOPSIS\n\
69  %s [-n <number of matrices>] [-f <plot filename>]\n\
70  [-R <number of rows>] [-C <number of columns>]\n\
71  [-i <number of iterations>] [-p] [-h]\n",
72  name);
73 
74  fprintf(stdout, "\n\
75 OPTIONS: Default\n\
76  -n <number of matrices> \n\
77  Number of matrices inverted during each test loop.\n\
78 \n\
79  -i <number of iterations> \n\
80  Number of iterations of the test.\n\
81 \n\
82  -f <plot filename> \n\
83  Set output path for plot output.\n\
84  The plot logs the times of \n\
85  the different inversion methods: \n\
86  QR,LU,Cholesky and Pseudo-inverse.\n\
87 \n\
88  -R <number of rows>\n\
89  Number of rows of the automatically generated matrices \n\
90  we test on.\n\
91 \n\
92  -C <number of columns>\n\
93  Number of colums of the automatically generated matrices \n\
94  we test on.\n\
95 \n\
96  -p \n\
97  Plot into filename in the gnuplot format. \n\
98  If this option is used, tests results will be logged \n\
99  into a filename specified with -f.\n\
100 \n\
101  -h\n\
102  Print the help.\n\n");
103 
104  if (badparam) {
105  fprintf(stderr, "ERROR: \n");
106  fprintf(stderr, "\nBad parameter [%s]\n", badparam);
107  }
108 }
109 
117 bool getOptions(int argc, const char **argv, unsigned int &nb_matrices, unsigned int &nb_iterations,
118  bool &use_plot_file, std::string &plotfile, unsigned int &nbrows, unsigned int &nbcols, bool &verbose)
119 {
120  const char *optarg_;
121  int c;
122  while ((c = vpParseArgv::parse(argc, argv, GETOPTARGS, &optarg_)) > 1) {
123 
124  switch (c) {
125  case 'h':
126  usage(argv[0], nullptr);
127  return false;
128  break;
129  case 'n':
130  nb_matrices = (unsigned int)atoi(optarg_);
131  break;
132  case 'i':
133  nb_iterations = (unsigned int)atoi(optarg_);
134  break;
135  case 'f':
136  plotfile = optarg_;
137  use_plot_file = true;
138  break;
139  case 'p':
140  use_plot_file = true;
141  break;
142  case 'R':
143  nbrows = (unsigned int)atoi(optarg_);
144  break;
145  case 'C':
146  nbcols = (unsigned int)atoi(optarg_);
147  break;
148  case 'v':
149  verbose = true;
150  break;
151  // add default options -c -d
152  case 'c':
153  break;
154  case 'd':
155  break;
156  default:
157  usage(argv[0], optarg_);
158  return false;
159  break;
160  }
161  }
162 
163  if ((c == 1) || (c == -1)) {
164  // standalone param or error
165  usage(argv[0], nullptr);
166  std::cerr << "ERROR: " << std::endl;
167  std::cerr << " Bad argument " << optarg_ << std::endl << std::endl;
168  return false;
169  }
170 
171  return true;
172 }
173 
174 vpMatrix make_random_matrix(unsigned int nbrows, unsigned int nbcols)
175 {
176  vpMatrix A;
177  A.resize(nbrows, nbcols);
178 
179  for (unsigned int i = 0; i < A.getRows(); i++) {
180  for (unsigned int j = 0; j < A.getCols(); j++) {
181  A[i][j] = static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
182  }
183  }
184 
185  return A;
186 }
187 
188 vpMatrix make_random_symmetric_matrix(unsigned int nbrows)
189 {
190  vpMatrix A;
191  A.resize(nbrows, nbrows);
192 
193  for (unsigned int i = 0; i < A.getRows(); i++) {
194  for (unsigned int j = i; j < A.getCols(); j++) {
195  A[i][j] = static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
196  if (i != j) {
197  A[j][i] = A[i][j];
198  }
199  }
200  }
201 
202  return A;
203 }
204 
205 void create_bench_random_matrix(unsigned int nb_matrices, unsigned int nb_rows, unsigned int nb_cols, bool verbose,
206  std::vector<vpMatrix> &bench)
207 {
208  if (verbose)
209  std::cout << "Create a bench of " << nb_matrices << " " << nb_rows << " by " << nb_cols << " matrices" << std::endl;
210  bench.clear();
211  for (unsigned int i = 0; i < nb_matrices; i++) {
212  vpMatrix M;
213  //#if defined(VISP_HAVE_EIGEN3) || defined(VISP_HAVE_LAPACK) ||
214  //(VISP_HAVE_OPENCV_VERSION >= 0x020101)
215  // double det = 0.;
216  // // don't put singular matrices in the benchmark
217  // for(M = make_random_matrix(nb_rows, nb_cols);
218  // std::fabs(det=M.AtA().det())<.01; M = make_random_matrix(nb_rows,
219  // nb_cols)) {
220  // if(verbose) {
221  // std::cout << " Generated random matrix AtA=" << std::endl <<
222  // M.AtA() << std::endl; std::cout << " Generated random matrix
223  // not invertible: det=" << det << ". Retrying..." << std::endl;
224  // }
225  // }
226  //#else
227  M = make_random_matrix(nb_rows, nb_cols);
228  //#endif
229  bench.push_back(M);
230  }
231 }
232 
233 void create_bench_random_symmetric_matrix(unsigned int nb_matrices, unsigned int nb_rows, bool verbose,
234  std::vector<vpMatrix> &bench)
235 {
236  if (verbose)
237  std::cout << "Create a bench of " << nb_matrices << " " << nb_rows << " by " << nb_rows << " symmetric matrices" << std::endl;
238  bench.clear();
239  for (unsigned int i = 0; i < nb_matrices; i++) {
240  vpMatrix M;
241  //#if defined(VISP_HAVE_EIGEN3) || defined(VISP_HAVE_LAPACK) ||
242  //(VISP_HAVE_OPENCV_VERSION >= 0x020101) || defined(VISP_HAVE_GSL)
243  // double det = 0.;
244  // // don't put singular matrices in the benchmark
245  // for(M = make_random_matrix(nb_rows, nb_cols);
246  // std::fabs(det=M.AtA().det())<.01; M = make_random_matrix(nb_rows,
247  // nb_cols)) {
248  // if(verbose) {
249  // std::cout << " Generated random matrix AtA=" << std::endl <<
250  // M.AtA() << std::endl; std::cout << " Generated random matrix
251  // not invertible: det=" << det << ". Retrying..." << std::endl;
252  // }
253  // }
254  //#else
255  M = make_random_symmetric_matrix(nb_rows);
256  //#endif
257  bench.push_back(M);
258  }
259 }
260 
261 int test_svd(std::vector<vpMatrix> M, std::vector<vpMatrix> U, std::vector<vpColVector> s, std::vector<vpMatrix> V, double &error)
262 {
263  for (unsigned int i = 0; i < M.size(); i++) {
264  vpMatrix S;
265  S.diag(s[i]);
266  vpMatrix U_S_Vt = U[i] * S * V[i].t();
267  vpMatrix D = M[i] - U_S_Vt;
268  error = D.frobeniusNorm();
269  if (error > 1e-6) {
270  std::cout << "SVD decomposition failed. Error: " << error << std::endl;
271  return EXIT_FAILURE;
272  }
273  }
274  return EXIT_SUCCESS;
275 }
276 
277 int test_eigen_values(std::vector<vpMatrix> M, std::vector<vpColVector> e, std::vector<vpMatrix> V,
278  std::vector<vpColVector> e2)
279 {
280  for (unsigned int i = 0; i < M.size(); i++) {
281  vpColVector error_e = e[i] - e2[i];
282  if (error_e.frobeniusNorm() > 1e-6) {
283  std::cout << "Eigen values differ" << std::endl;
284  return EXIT_FAILURE;
285  }
286  vpMatrix D;
287  D.diag(e[i]);
288  vpMatrix MV_VD = M[i] * V[i] - V[i] * D;
289  if (MV_VD.frobeniusNorm() > 1e-6) {
290  std::cout << "Eigen values/vector decomposition failed" << std::endl;
291  return EXIT_FAILURE;
292  }
293  }
294  return EXIT_SUCCESS;
295 }
296 
297 #if defined(VISP_HAVE_EIGEN3)
298 int test_svd_eigen3(bool verbose, const std::vector<vpMatrix> &bench, double &time, double &error)
299 {
300  if (verbose)
301  std::cout << "Test SVD using Eigen3 3rd party" << std::endl;
302  // Compute inverse
303  if (verbose)
304  std::cout << " SVD on a " << bench[0].getRows() << "x" << bench[0].getCols() << " matrix" << std::endl;
305 
306  std::vector<vpMatrix> U = bench;
307  std::vector<vpMatrix> V(bench.size());
308  std::vector<vpColVector> s(bench.size());
309 
310  double t = vpTime::measureTimeMs();
311  for (unsigned int i = 0; i < bench.size(); i++) {
312  U[i].svdEigen3(s[i], V[i]);
313  }
314 
315  time = vpTime::measureTimeMs() - t;
316 
317  return test_svd(bench, U, s, V, error);
318 }
319 #endif
320 
321 #if defined(VISP_HAVE_LAPACK)
322 int test_svd_lapack(bool verbose, const std::vector<vpMatrix> &bench, double &time, double &error)
323 {
324  if (verbose)
325  std::cout << "Test SVD using Lapack 3rd party" << std::endl;
326  // Compute inverse
327  if (verbose)
328  std::cout << " SVD on a " << bench[0].getRows() << "x" << bench[0].getCols() << " matrix" << std::endl;
329 
330  std::vector<vpMatrix> U = bench;
331  std::vector<vpMatrix> V(bench.size());
332  std::vector<vpColVector> s(bench.size());
333 
334  double t = vpTime::measureTimeMs();
335  for (unsigned int i = 0; i < bench.size(); i++) {
336  U[i].svdLapack(s[i], V[i]);
337  }
338  time = vpTime::measureTimeMs() - t;
339 
340  return test_svd(bench, U, s, V, error);
341 }
342 
343 int test_eigen_values_lapack(bool verbose, const std::vector<vpMatrix> &bench, double &time)
344 {
345  if (verbose)
346  std::cout << "Test eigenValues() using Lapack 3rd party" << std::endl;
347 
348  std::vector<vpColVector> e(bench.size());
349  std::vector<vpColVector> e2(bench.size());
350  std::vector<vpMatrix> V(bench.size());
351 
352  for (unsigned int i = 0; i < bench.size(); i++) {
353  e2[i] = bench[i].eigenValues();
354  }
355 
356  // Compute the eigenvalues and eigenvectors
357  double t = vpTime::measureTimeMs();
358  for (unsigned int i = 0; i < bench.size(); i++) {
359  bench[i].eigenValues(e[i], V[i]);
360  }
361  time = vpTime::measureTimeMs() - t;
362 
363  return test_eigen_values(bench, e, V, e2);
364 }
365 #endif
366 
367 #if defined(VISP_HAVE_OPENCV)
368 int test_svd_opencv(bool verbose, const std::vector<vpMatrix> &bench, double &time, double &error)
369 {
370  if (verbose)
371  std::cout << "Test SVD using OpenCV 3rd party" << std::endl;
372  // Compute inverse
373  if (verbose)
374  std::cout << " SVD on a " << bench[0].getRows() << "x" << bench[0].getCols() << " matrix" << std::endl;
375 
376  std::vector<vpMatrix> U = bench;
377  std::vector<vpMatrix> V(bench.size());
378  std::vector<vpColVector> s(bench.size());
379 
380  double t = vpTime::measureTimeMs();
381  for (unsigned int i = 0; i < bench.size(); i++) {
382  U[i].svdOpenCV(s[i], V[i]);
383  }
384  time = vpTime::measureTimeMs() - t;
385 
386  return test_svd(bench, U, s, V, error);
387 }
388 #endif
389 
390 void save_time(const std::string &method, bool verbose, bool use_plot_file, std::ofstream &of, double time, double error)
391 {
392  if (use_plot_file)
393  of << time << "\t";
394  if (verbose || !use_plot_file) {
395  std::cout << method << "took " << time << "s, error = " << error << std::endl;
396  }
397 }
398 
399 bool testAllSvds(const std::string &test_name, unsigned nb_matrices, unsigned nb_iterations,
400  unsigned nb_rows, unsigned nb_cols,
401  bool doEigenValues, bool verbose, bool use_plot_file, std::ofstream &of)
402 {
403  int ret = EXIT_SUCCESS;
404  int ret_test = 0;
405  for (unsigned int iter = 0; iter < nb_iterations; iter++) {
406  std::cout << "\n-> Iteration: " << iter << std::endl;
407  std::vector<vpMatrix> bench_random_matrices;
408  create_bench_random_matrix(nb_matrices, nb_rows, nb_cols, verbose, bench_random_matrices);
409  std::vector<vpMatrix> bench_random_symmetric_matrices;
410  create_bench_random_symmetric_matrix(nb_matrices, nb_rows, verbose, bench_random_symmetric_matrices);
411 
412  if (use_plot_file)
413  of << test_name << iter << "\t";
414  double time;
415  double error;
416 
417 #if defined(VISP_HAVE_LAPACK)
418  std::cout << "\n-- Test SVD using lapack" << std::endl;
419  ret_test = test_svd_lapack(verbose, bench_random_matrices, time, error);
420  ret += ret_test;
421  std::cout << test_name << ": SVD (Lapack) " << (ret_test ? "failed" : "succeed") << std::endl;
422  save_time("SVD (Lapack): ", verbose, use_plot_file, of, time, error);
423 #endif
424 
425 #if defined(VISP_HAVE_EIGEN3)
426  std::cout << "\n-- Test SVD using eigen" << std::endl;
427  ret_test = test_svd_eigen3(verbose, bench_random_matrices, time, error);
428  ret += ret_test;
429  std::cout << test_name << ": SVD (Eigen) " << (ret_test ? "failed" : "succeed") << std::endl;
430  save_time("SVD (Eigen3): ", verbose, use_plot_file, of, time, error);
431 #endif
432 
433 #if defined(VISP_HAVE_OPENCV)
434  std::cout << "\n-- Test SVD using OpenCV" << std::endl;
435  ret_test = test_svd_opencv(verbose, bench_random_matrices, time, error);
436  ret += ret_test;
437  std::cout << test_name << ": SVD (OpenCV) " << (ret_test ? "failed" : "succeed") << std::endl;
438  save_time("SVD (OpenCV): ", verbose, use_plot_file, of, time, error);
439 #endif
440 
441 #if defined(VISP_HAVE_LAPACK)
442  if (doEigenValues) {
443  std::cout << "\n-- Test Eigen Values using lapack" << std::endl;
444  ret_test = test_eigen_values_lapack(verbose, bench_random_symmetric_matrices, time);
445  ret += ret_test;
446  std::cout << "Eigen values (Lapack) " << (ret_test ? "failed" : "succeed") << std::endl;
447  error = 0.0;
448  save_time("Eigen values (Lapack): ", verbose, use_plot_file, of, time, error);
449  }
450 #endif
451  std::cout << "Result after iteration " << iter << ": " << (ret ? "failed" : "succeed") << std::endl;
452  if (use_plot_file)
453  of << std::endl;
454  }
455  return (ret == EXIT_SUCCESS);
456 }
457 
458 int main(int argc, const char *argv[])
459 {
460  try {
461 #if defined(VISP_HAVE_EIGEN3) || defined(VISP_HAVE_LAPACK) || defined(VISP_HAVE_OPENCV)
462  unsigned int nb_matrices = 100;
463  unsigned int nb_iterations = 10;
464  unsigned int nb_rows = 6;
465  unsigned int nb_cols = 6;
466  bool verbose = false;
467  std::string plotfile("plot-svd.csv");
468  bool use_plot_file = false;
469  std::ofstream of;
470 
471  // Read the command line options
472  if (getOptions(argc, argv, nb_matrices, nb_iterations, use_plot_file, plotfile, nb_rows, nb_cols, verbose) ==
473  false) {
474  return EXIT_FAILURE;
475  }
476 
477  if (use_plot_file) {
478  of.open(plotfile.c_str());
479  of << "iter"
480  << "\t";
481 
482 #if defined(VISP_HAVE_LAPACK)
483  of << "\"SVD Lapack\""
484  << "\t";
485 #endif
486 #if defined(VISP_HAVE_EIGEN3)
487  of << "\"SVD Eigen3\""
488  << "\t";
489 #endif
490 #if defined(VISP_HAVE_OPENCV)
491  of << "\"SVD OpenCV\""
492  << "\t";
493 #endif
494  of << std::endl;
495  }
496  bool success = true;
497  std::string test_case;
498  test_case = "Test case: Square matrices";
499  std::cout << "\n== " << test_case << ": " << nb_rows << " x " << nb_cols << " ==" << std::endl;
500  bool defaultSuccess = testAllSvds(test_case, nb_matrices, nb_iterations, nb_rows, nb_cols,
501  true, verbose, use_plot_file, of);
502  std::cout << "=> " << test_case << ": " << (defaultSuccess ? "succeed" : "failed") << std::endl;
503 
504  test_case = "Test case: More rows than columns";
505  std::cout << "\n== " << test_case << ": " << nb_cols * 2 << " x " << nb_cols << " ==" << std::endl;
506  bool rowsSuccess = testAllSvds(test_case, nb_matrices, nb_iterations, nb_cols * 2, nb_cols,
507  false, verbose, use_plot_file, of);
508  std::cout << "=> " << test_case << ": " << (rowsSuccess ? "succeed" : "failed") << std::endl;
509 
510  test_case = "Test case: More columns than rows";
511  std::cout << "\n== " << test_case << ": " << nb_rows << " x " << nb_rows * 2 << " ==" << std::endl;
512  bool colsSuccess = testAllSvds(test_case, nb_matrices, nb_iterations, nb_rows, nb_rows * 2,
513  false, verbose, use_plot_file, of);
514  std::cout << "=> " << test_case << ": " << (colsSuccess ? "succeed" : "failed") << std::endl;
515 
516  std::cout << "\nResume:" << std::endl;
517  std::cout << "- Square matrices (" << nb_rows << "x" << nb_cols << "): " << (defaultSuccess ? "succeed" : "failed") << std::endl;
518 
519  std::cout << "- More rows case (" << nb_cols * 2 << "x" << nb_cols << "): " << (rowsSuccess ? "succeed" : "failed") << std::endl;
520 
521  std::cout << "- More columns case (" << nb_rows << "x" << nb_rows * 2 << "): " << (colsSuccess ? "succeed" : "failed") << std::endl;
522 
523  success = defaultSuccess && rowsSuccess && colsSuccess;
524 
525  if (use_plot_file) {
526  of.close();
527  std::cout << "Result saved in " << plotfile << std::endl;
528  }
529 
530  if (success) {
531  std::cout << "Test succeed" << std::endl;
532  }
533  else {
534  std::cout << "Test failed" << std::endl;
535  }
536 
537  return success ? EXIT_SUCCESS : EXIT_FAILURE;
538 #else
539  (void)argc;
540  (void)argv;
541  std::cout << "Test does nothing since you dont't have Lapack, Eigen3 or OpenCV 3rd party" << std::endl;
542  return EXIT_SUCCESS;
543 #endif
544  }
545  catch (const vpException &e) {
546  std::cout << "Catch an exception: " << e.getStringMessage() << std::endl;
547  return EXIT_FAILURE;
548  }
549 }
unsigned int getCols() const
Definition: vpArray2D.h:327
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
Definition: vpArray2D.h:352
unsigned int getRows() const
Definition: vpArray2D.h:337
Implementation of column vector and the associated operations.
Definition: vpColVector.h:163
double frobeniusNorm() const
error that can be emitted by ViSP classes.
Definition: vpException.h:59
const std::string & getStringMessage() const
Definition: vpException.cpp:66
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:146
void diag(const double &val=1.0)
Definition: vpMatrix.cpp:910
vpMatrix t() const
Definition: vpMatrix.cpp:465
double frobeniusNorm() const
Definition: vpMatrix.cpp:6514
static bool parse(int *argcPtr, const char **argv, vpArgvInfo *argTable, int flags)
Definition: vpParseArgv.cpp:69
VISP_EXPORT double measureTimeMs()