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