Visual Servoing Platform  version 3.6.1 under development (2024-11-15)
perfColVectorOperations.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  * Benchmark column vector operations.
32  */
33 
38 #include <visp3/core/vpConfig.h>
39 
40 #if defined(VISP_HAVE_CATCH2)
41 
42 #include <numeric>
43 #include <catch_amalgamated.hpp>
44 
45 #include <visp3/core/vpColVector.h>
46 
47 #ifdef ENABLE_VISP_NAMESPACE
48 using namespace VISP_NAMESPACE_NAME;
49 #endif
50 
51 namespace
52 {
53 static bool g_runBenchmark = false;
54 static const std::vector<unsigned int> g_sizes = { 23, 127, 233, 419, 1153, 2749 };
55 
56 double getRandomValues(double min, double max) { return (max - min) * ((double)rand() / (double)RAND_MAX) + min; }
57 
58 vpColVector generateRandomVector(unsigned int rows, double min = -100, double max = 100)
59 {
60  vpColVector v(rows);
61 
62  for (unsigned int i = 0; i < v.getRows(); i++) {
63  v[i] = getRandomValues(min, max);
64  }
65 
66  return v;
67 }
68 
69 double stddev(const std::vector<double> &vec)
70 {
71  double sum = std::accumulate(vec.begin(), vec.end(), 0.0);
72  double mean = sum / vec.size();
73 
74  std::vector<double> diff(vec.size());
75  std::transform(vec.begin(), vec.end(), diff.begin(), [mean](double x) {
76  return x - mean; });
77  double sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
78  return std::sqrt(sq_sum / vec.size());
79 }
80 
81 double computeRegularSum(const vpColVector &v)
82 {
83  double sum = 0.0;
84 
85  for (unsigned int i = 0; i < v.getRows(); i++) {
86  sum += v[i];
87  }
88 
89  return sum;
90 }
91 
92 double computeRegularSumSquare(const vpColVector &v)
93 {
94  double sum_square = 0.0;
95 
96  for (unsigned int i = 0; i < v.getRows(); i++) {
97  sum_square += v[i] * v[i];
98  }
99 
100  return sum_square;
101 }
102 
103 double computeRegularStdev(const vpColVector &v)
104 {
105  double mean_value = computeRegularSum(v) / v.getRows();
106  double sum_squared_diff = 0.0;
107 
108  for (unsigned int i = 0; i < v.size(); i++) {
109  sum_squared_diff += (v[i] - mean_value) * (v[i] - mean_value);
110  }
111 
112  double divisor = (double)v.size();
113 
114  return std::sqrt(sum_squared_diff / divisor);
115 }
116 
117 std::vector<double> computeHadamard(const std::vector<double> &v1, const std::vector<double> &v2)
118 {
119  std::vector<double> result;
120  std::transform(v1.begin(), v1.end(), v2.begin(), std::back_inserter(result), std::multiplies<double>());
121  return result;
122 }
123 
124 bool almostEqual(const vpColVector &v1, const vpColVector &v2, double tol = 1e-9)
125 {
126  if (v1.getRows() != v2.getRows()) {
127  return false;
128  }
129 
130  for (unsigned int i = 0; i < v1.getRows(); i++) {
131  if (!vpMath::equal(v1[i], v2[i], tol)) {
132  return false;
133  }
134  }
135 
136  return true;
137 }
138 } // namespace
139 
140 TEST_CASE("Benchmark vpColVector::sum()", "[benchmark]")
141 {
142  // Sanity checks
143  {
144  const double val = 11;
145  vpColVector v(1, val);
146  CHECK(v.sum() == Catch::Approx(val).epsilon(std::numeric_limits<double>::epsilon()));
147  }
148  {
149  const unsigned int size = 11;
150  std::vector<double> vec(size);
151  vpColVector v(size);
152  for (size_t i = 0; i < 11; i++) {
153  vec[i] = 2. * i;
154  v[static_cast<unsigned int>(i)] = vec[i];
155  }
156  CHECK(v.sum() == Catch::Approx(std::accumulate(vec.begin(), vec.end(), 0.0)).epsilon(std::numeric_limits<double>::epsilon()));
157  }
158 
159  if (g_runBenchmark) {
160  for (auto size : g_sizes) {
161  vpColVector v = generateRandomVector(size);
162  std::vector<double> vec = v.toStdVector();
163 
164  std::ostringstream oss;
165  oss << "Benchmark vpColVector::sum() with size: " << size << " (ViSP)";
166  double vp_sum = 0;
167  BENCHMARK(oss.str().c_str())
168  {
169  vp_sum = v.sum();
170  return vp_sum;
171  };
172 
173  oss.str("");
174  oss << "Benchmark std::accumulate() with size: " << size << " (C++)";
175  double std_sum = 0;
176  BENCHMARK(oss.str().c_str())
177  {
178  std_sum = std::accumulate(vec.begin(), vec.end(), 0.0);
179  return std_sum;
180  };
181  CHECK(vp_sum == Catch::Approx(std_sum));
182 
183  oss.str("");
184  oss << "Benchmark naive sum() with size: " << size;
185  double naive_sum = 0;
186  BENCHMARK(oss.str().c_str())
187  {
188  naive_sum = computeRegularSum(v);
189  return naive_sum;
190  };
191  CHECK(naive_sum == Catch::Approx(std_sum));
192  }
193  }
194 }
195 
196 TEST_CASE("Benchmark vpColVector::sumSquare()", "[benchmark]")
197 {
198  // Sanity checks
199  {
200  const double val = 11;
201  vpColVector v(1, val);
202  CHECK(v.sumSquare() == Catch::Approx(val * val).epsilon(std::numeric_limits<double>::epsilon()));
203  }
204  {
205  const unsigned int size = 11;
206  std::vector<double> vec(size);
207  vpColVector v(size);
208  for (size_t i = 0; i < 11; i++) {
209  vec[i] = 2. * i;
210  v[static_cast<unsigned int>(i)] = vec[i];
211  }
212  CHECK(v.sumSquare() == Catch::Approx(std::inner_product(vec.begin(), vec.end(), vec.begin(), 0.0))
213  .epsilon(std::numeric_limits<double>::epsilon()));
214  }
215 
216  if (g_runBenchmark) {
217  for (auto size : g_sizes) {
218  vpColVector v = generateRandomVector(size);
219  std::vector<double> vec = v.toStdVector();
220 
221  std::ostringstream oss;
222  oss << "Benchmark vpColVector::sumSquare() with size: " << size << " (ViSP)";
223  double vp_sq_sum = 0;
224  BENCHMARK(oss.str().c_str())
225  {
226  vp_sq_sum = v.sumSquare();
227  return vp_sq_sum;
228  };
229 
230  oss.str("");
231  oss << "Benchmark std::inner_product with size: " << size << " (C++)";
232  double std_sq_sum = 0;
233  BENCHMARK(oss.str().c_str())
234  {
235  std_sq_sum = std::inner_product(vec.begin(), vec.end(), vec.begin(), 0.0);
236  return std_sq_sum;
237  };
238  CHECK(vp_sq_sum == Catch::Approx(std_sq_sum));
239 
240  oss.str("");
241  oss << "Benchmark naive sumSquare() with size: " << size;
242  double naive_sq_sum = 0;
243  BENCHMARK(oss.str().c_str())
244  {
245  naive_sq_sum = computeRegularSumSquare(v);
246  return naive_sq_sum;
247  };
248  CHECK(naive_sq_sum == Catch::Approx(std_sq_sum));
249  }
250  }
251 }
252 
253 TEST_CASE("Benchmark vpColVector::stdev()", "[benchmark]")
254 {
255  // Sanity checks
256  {
257  vpColVector v(2);
258  v[0] = 11;
259  v[1] = 16;
260  std::vector<double> vec = v.toStdVector();
261  CHECK(vpColVector::stdev(v) == Catch::Approx(stddev(vec)).epsilon(std::numeric_limits<double>::epsilon()));
262  }
263  {
264  const unsigned int size = 11;
265  std::vector<double> vec(size);
266  vpColVector v(size);
267  for (size_t i = 0; i < 11; i++) {
268  vec[i] = 2. * i;
269  v[static_cast<unsigned int>(i)] = vec[i];
270  }
271  CHECK(vpColVector::stdev(v) == Catch::Approx(stddev(vec)).epsilon(std::numeric_limits<double>::epsilon()));
272  }
273 
274  if (g_runBenchmark) {
275  for (auto size : g_sizes) {
276  vpColVector v = generateRandomVector(size);
277  std::vector<double> vec = v.toStdVector();
278 
279  std::ostringstream oss;
280  oss << "Benchmark vpColVector::stdev() with size: " << size << " (ViSP)";
281  double vp_stddev = 0;
282  BENCHMARK(oss.str().c_str())
283  {
284  vp_stddev = vpColVector::stdev(v);
285  return vp_stddev;
286  };
287 
288  oss.str("");
289  oss << "Benchmark C++ stddev impl with size: " << size << " (C++)";
290  double std_stddev = 0;
291  BENCHMARK(oss.str().c_str())
292  {
293  std_stddev = stddev(vec);
294  return std_stddev;
295  };
296  CHECK(vp_stddev == Catch::Approx(std_stddev));
297 
298  oss.str("");
299  oss << "Benchmark naive stdev() with size: " << size;
300  double naive_stddev = 0;
301  BENCHMARK(oss.str().c_str())
302  {
303  naive_stddev = computeRegularStdev(v);
304  return naive_stddev;
305  };
306  CHECK(naive_stddev == Catch::Approx(std_stddev));
307  }
308  }
309 }
310 
311 TEST_CASE("Benchmark vpColVector::hadamard()", "[benchmark]")
312 {
313  // Sanity checks
314  {
315  vpColVector v1(2), v2(2);
316  v1[0] = 11;
317  v1[1] = 16;
318  v2[0] = 8;
319  v2[1] = 23;
320  vpColVector res1 = v1.hadamard(v2);
321  vpColVector res2(computeHadamard(v1.toStdVector(), v2.toStdVector()));
322  CHECK(almostEqual(res1, res2));
323  }
324  {
325  const unsigned int size = 11;
326  std::vector<double> vec1(size), vec2(size);
327  for (size_t i = 0; i < 11; i++) {
328  vec1[i] = 2. * i;
329  vec2[i] = 3. * i + 5.;
330  }
331  vpColVector v1(vec1), v2(vec2);
332  vpColVector res1 = v1.hadamard(v2);
333  vpColVector res2(computeHadamard(v1.toStdVector(), v2.toStdVector()));
334  CHECK(almostEqual(res1, res2));
335  }
336 
337  if (g_runBenchmark) {
338  for (auto size : g_sizes) {
339  vpColVector v1 = generateRandomVector(size);
340  vpColVector v2 = generateRandomVector(size);
341  std::vector<double> vec1 = v1.toStdVector();
342  std::vector<double> vec2 = v2.toStdVector();
343 
344  std::ostringstream oss;
345  oss << "Benchmark vpColVector::hadamard() with size: " << size << " (ViSP)";
346  vpColVector vp_res;
347  BENCHMARK(oss.str().c_str())
348  {
349  vp_res = v1.hadamard(v2);
350  return vp_res;
351  };
352 
353  oss.str("");
354  oss << "Benchmark C++ element wise multiplication with size: " << size << " (C++)";
355  std::vector<double> std_res;
356  BENCHMARK(oss.str().c_str())
357  {
358  std_res = computeHadamard(vec1, vec2);
359  return std_res;
360  };
361  CHECK(almostEqual(vp_res, vpColVector(std_res)));
362  }
363  }
364 }
365 
366 int main(int argc, char *argv[])
367 {
368  // Set random seed explicitly to avoid confusion
369  // See: https://en.cppreference.com/w/cpp/numeric/random/srand
370  // If rand() is used before any calls to srand(), rand() behaves as if it was seeded with srand(1).
371  srand(1);
372 
373  Catch::Session session;
374  auto cli = session.cli()
375  | Catch::Clara::Opt(g_runBenchmark)["--benchmark"]("run benchmark?");
376 
377  session.cli(cli);
378  session.applyCommandLine(argc, argv);
379 
380  int numFailed = session.run();
381  return numFailed;
382 }
383 #else
384 #include <iostream>
385 
386 int main() { return EXIT_SUCCESS; }
387 #endif
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:349
unsigned int getRows() const
Definition: vpArray2D.h:347
Implementation of column vector and the associated operations.
Definition: vpColVector.h:191
vpColVector hadamard(const vpColVector &v) const
double sumSquare() const
std::vector< double > toStdVector() const
static double stdev(const vpColVector &v, bool useBesselCorrection=false)
double sum() const
static bool equal(double x, double y, double threshold=0.001)
Definition: vpMath.h:459