blob: 229173fb7e279d0182bb76765d151d8a1b019520 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2015 Google Inc. All rights reserved.
3// http://ceres-solver.org/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors may be
14// used to endorse or promote products derived from this software without
15// specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28//
29// Author: sameeragarwal@google.com (Sameer Agarwal)
30
31#include "ceres/covariance.h"
32
33#include <algorithm>
Austin Schuh70cc9552019-01-21 19:46:48 -080034#include <cmath>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080035#include <cstdint>
Austin Schuh70cc9552019-01-21 19:46:48 -080036#include <map>
37#include <memory>
38#include <utility>
39
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080040#include "ceres/autodiff_cost_function.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080041#include "ceres/compressed_row_sparse_matrix.h"
42#include "ceres/cost_function.h"
43#include "ceres/covariance_impl.h"
44#include "ceres/local_parameterization.h"
45#include "ceres/map_util.h"
46#include "ceres/problem_impl.h"
47#include "gtest/gtest.h"
48
49namespace ceres {
50namespace internal {
51
52using std::make_pair;
53using std::map;
54using std::pair;
55using std::vector;
56
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080057class UnaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -080058 public:
59 UnaryCostFunction(const int num_residuals,
60 const int32_t parameter_block_size,
61 const double* jacobian)
62 : jacobian_(jacobian, jacobian + num_residuals * parameter_block_size) {
63 set_num_residuals(num_residuals);
64 mutable_parameter_block_sizes()->push_back(parameter_block_size);
65 }
66
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080067 bool Evaluate(double const* const* parameters,
68 double* residuals,
69 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080070 for (int i = 0; i < num_residuals(); ++i) {
71 residuals[i] = 1;
72 }
73
74 if (jacobians == NULL) {
75 return true;
76 }
77
78 if (jacobians[0] != NULL) {
79 copy(jacobian_.begin(), jacobian_.end(), jacobians[0]);
80 }
81
82 return true;
83 }
84
85 private:
86 vector<double> jacobian_;
87};
88
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080089class BinaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -080090 public:
91 BinaryCostFunction(const int num_residuals,
92 const int32_t parameter_block1_size,
93 const int32_t parameter_block2_size,
94 const double* jacobian1,
95 const double* jacobian2)
96 : jacobian1_(jacobian1,
97 jacobian1 + num_residuals * parameter_block1_size),
98 jacobian2_(jacobian2,
99 jacobian2 + num_residuals * parameter_block2_size) {
100 set_num_residuals(num_residuals);
101 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
102 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
103 }
104
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800105 bool Evaluate(double const* const* parameters,
106 double* residuals,
107 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800108 for (int i = 0; i < num_residuals(); ++i) {
109 residuals[i] = 2;
110 }
111
112 if (jacobians == NULL) {
113 return true;
114 }
115
116 if (jacobians[0] != NULL) {
117 copy(jacobian1_.begin(), jacobian1_.end(), jacobians[0]);
118 }
119
120 if (jacobians[1] != NULL) {
121 copy(jacobian2_.begin(), jacobian2_.end(), jacobians[1]);
122 }
123
124 return true;
125 }
126
127 private:
128 vector<double> jacobian1_;
129 vector<double> jacobian2_;
130};
131
132// x_plus_delta = delta * x;
133class PolynomialParameterization : public LocalParameterization {
134 public:
135 virtual ~PolynomialParameterization() {}
136
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800137 bool Plus(const double* x,
138 const double* delta,
139 double* x_plus_delta) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800140 x_plus_delta[0] = delta[0] * x[0];
141 x_plus_delta[1] = delta[0] * x[1];
142 return true;
143 }
144
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800145 bool ComputeJacobian(const double* x, double* jacobian) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800146 jacobian[0] = x[0];
147 jacobian[1] = x[1];
148 return true;
149 }
150
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800151 int GlobalSize() const final { return 2; }
152 int LocalSize() const final { return 1; }
Austin Schuh70cc9552019-01-21 19:46:48 -0800153};
154
155TEST(CovarianceImpl, ComputeCovarianceSparsity) {
156 double parameters[10];
157
158 double* block1 = parameters;
159 double* block2 = block1 + 1;
160 double* block3 = block2 + 2;
161 double* block4 = block3 + 3;
162
163 ProblemImpl problem;
164
165 // Add in random order
166 Vector junk_jacobian = Vector::Zero(10);
167 problem.AddResidualBlock(
168 new UnaryCostFunction(1, 1, junk_jacobian.data()), NULL, block1);
169 problem.AddResidualBlock(
170 new UnaryCostFunction(1, 4, junk_jacobian.data()), NULL, block4);
171 problem.AddResidualBlock(
172 new UnaryCostFunction(1, 3, junk_jacobian.data()), NULL, block3);
173 problem.AddResidualBlock(
174 new UnaryCostFunction(1, 2, junk_jacobian.data()), NULL, block2);
175
176 // Sparsity pattern
177 //
178 // Note that the problem structure does not imply this sparsity
179 // pattern since all the residual blocks are unary. But the
180 // ComputeCovarianceSparsity function in its current incarnation
181 // does not pay attention to this fact and only looks at the
182 // parameter block pairs that the user provides.
183 //
184 // X . . . . . X X X X
185 // . X X X X X . . . .
186 // . X X X X X . . . .
187 // . . . X X X . . . .
188 // . . . X X X . . . .
189 // . . . X X X . . . .
190 // . . . . . . X X X X
191 // . . . . . . X X X X
192 // . . . . . . X X X X
193 // . . . . . . X X X X
194
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800195 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800196 int expected_rows[] = {0, 5, 10, 15, 18, 21, 24, 28, 32, 36, 40};
197 int expected_cols[] = {0, 6, 7, 8, 9,
198 1, 2, 3, 4, 5,
199 1, 2, 3, 4, 5,
200 3, 4, 5,
201 3, 4, 5,
202 3, 4, 5,
203 6, 7, 8, 9,
204 6, 7, 8, 9,
205 6, 7, 8, 9,
206 6, 7, 8, 9};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800207 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800208
209 vector<pair<const double*, const double*>> covariance_blocks;
210 covariance_blocks.push_back(make_pair(block1, block1));
211 covariance_blocks.push_back(make_pair(block4, block4));
212 covariance_blocks.push_back(make_pair(block2, block2));
213 covariance_blocks.push_back(make_pair(block3, block3));
214 covariance_blocks.push_back(make_pair(block2, block3));
215 covariance_blocks.push_back(make_pair(block4, block1)); // reversed
216
217 Covariance::Options options;
218 CovarianceImpl covariance_impl(options);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800219 EXPECT_TRUE(
220 covariance_impl.ComputeCovarianceSparsity(covariance_blocks, &problem));
Austin Schuh70cc9552019-01-21 19:46:48 -0800221
222 const CompressedRowSparseMatrix* crsm = covariance_impl.covariance_matrix();
223
224 EXPECT_EQ(crsm->num_rows(), 10);
225 EXPECT_EQ(crsm->num_cols(), 10);
226 EXPECT_EQ(crsm->num_nonzeros(), 40);
227
228 const int* rows = crsm->rows();
229 for (int r = 0; r < crsm->num_rows() + 1; ++r) {
230 EXPECT_EQ(rows[r], expected_rows[r])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800231 << r << " " << rows[r] << " " << expected_rows[r];
Austin Schuh70cc9552019-01-21 19:46:48 -0800232 }
233
234 const int* cols = crsm->cols();
235 for (int c = 0; c < crsm->num_nonzeros(); ++c) {
236 EXPECT_EQ(cols[c], expected_cols[c])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800237 << c << " " << cols[c] << " " << expected_cols[c];
Austin Schuh70cc9552019-01-21 19:46:48 -0800238 }
239}
240
241TEST(CovarianceImpl, ComputeCovarianceSparsityWithConstantParameterBlock) {
242 double parameters[10];
243
244 double* block1 = parameters;
245 double* block2 = block1 + 1;
246 double* block3 = block2 + 2;
247 double* block4 = block3 + 3;
248
249 ProblemImpl problem;
250
251 // Add in random order
252 Vector junk_jacobian = Vector::Zero(10);
253 problem.AddResidualBlock(
254 new UnaryCostFunction(1, 1, junk_jacobian.data()), NULL, block1);
255 problem.AddResidualBlock(
256 new UnaryCostFunction(1, 4, junk_jacobian.data()), NULL, block4);
257 problem.AddResidualBlock(
258 new UnaryCostFunction(1, 3, junk_jacobian.data()), NULL, block3);
259 problem.AddResidualBlock(
260 new UnaryCostFunction(1, 2, junk_jacobian.data()), NULL, block2);
261 problem.SetParameterBlockConstant(block3);
262
263 // Sparsity pattern
264 //
265 // Note that the problem structure does not imply this sparsity
266 // pattern since all the residual blocks are unary. But the
267 // ComputeCovarianceSparsity function in its current incarnation
268 // does not pay attention to this fact and only looks at the
269 // parameter block pairs that the user provides.
270 //
271 // X . . X X X X
272 // . X X . . . .
273 // . X X . . . .
274 // . . . X X X X
275 // . . . X X X X
276 // . . . X X X X
277 // . . . X X X X
278
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800279 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800280 int expected_rows[] = {0, 5, 7, 9, 13, 17, 21, 25};
281 int expected_cols[] = {0, 3, 4, 5, 6,
282 1, 2,
283 1, 2,
284 3, 4, 5, 6,
285 3, 4, 5, 6,
286 3, 4, 5, 6,
287 3, 4, 5, 6};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800288 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800289
290 vector<pair<const double*, const double*>> covariance_blocks;
291 covariance_blocks.push_back(make_pair(block1, block1));
292 covariance_blocks.push_back(make_pair(block4, block4));
293 covariance_blocks.push_back(make_pair(block2, block2));
294 covariance_blocks.push_back(make_pair(block3, block3));
295 covariance_blocks.push_back(make_pair(block2, block3));
296 covariance_blocks.push_back(make_pair(block4, block1)); // reversed
297
298 Covariance::Options options;
299 CovarianceImpl covariance_impl(options);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800300 EXPECT_TRUE(
301 covariance_impl.ComputeCovarianceSparsity(covariance_blocks, &problem));
Austin Schuh70cc9552019-01-21 19:46:48 -0800302
303 const CompressedRowSparseMatrix* crsm = covariance_impl.covariance_matrix();
304
305 EXPECT_EQ(crsm->num_rows(), 7);
306 EXPECT_EQ(crsm->num_cols(), 7);
307 EXPECT_EQ(crsm->num_nonzeros(), 25);
308
309 const int* rows = crsm->rows();
310 for (int r = 0; r < crsm->num_rows() + 1; ++r) {
311 EXPECT_EQ(rows[r], expected_rows[r])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800312 << r << " " << rows[r] << " " << expected_rows[r];
Austin Schuh70cc9552019-01-21 19:46:48 -0800313 }
314
315 const int* cols = crsm->cols();
316 for (int c = 0; c < crsm->num_nonzeros(); ++c) {
317 EXPECT_EQ(cols[c], expected_cols[c])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800318 << c << " " << cols[c] << " " << expected_cols[c];
Austin Schuh70cc9552019-01-21 19:46:48 -0800319 }
320}
321
322TEST(CovarianceImpl, ComputeCovarianceSparsityWithFreeParameterBlock) {
323 double parameters[10];
324
325 double* block1 = parameters;
326 double* block2 = block1 + 1;
327 double* block3 = block2 + 2;
328 double* block4 = block3 + 3;
329
330 ProblemImpl problem;
331
332 // Add in random order
333 Vector junk_jacobian = Vector::Zero(10);
334 problem.AddResidualBlock(
335 new UnaryCostFunction(1, 1, junk_jacobian.data()), NULL, block1);
336 problem.AddResidualBlock(
337 new UnaryCostFunction(1, 4, junk_jacobian.data()), NULL, block4);
338 problem.AddParameterBlock(block3, 3);
339 problem.AddResidualBlock(
340 new UnaryCostFunction(1, 2, junk_jacobian.data()), NULL, block2);
341
342 // Sparsity pattern
343 //
344 // Note that the problem structure does not imply this sparsity
345 // pattern since all the residual blocks are unary. But the
346 // ComputeCovarianceSparsity function in its current incarnation
347 // does not pay attention to this fact and only looks at the
348 // parameter block pairs that the user provides.
349 //
350 // X . . X X X X
351 // . X X . . . .
352 // . X X . . . .
353 // . . . X X X X
354 // . . . X X X X
355 // . . . X X X X
356 // . . . X X X X
357
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800358 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800359 int expected_rows[] = {0, 5, 7, 9, 13, 17, 21, 25};
360 int expected_cols[] = {0, 3, 4, 5, 6,
361 1, 2,
362 1, 2,
363 3, 4, 5, 6,
364 3, 4, 5, 6,
365 3, 4, 5, 6,
366 3, 4, 5, 6};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800367 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800368
369 vector<pair<const double*, const double*>> covariance_blocks;
370 covariance_blocks.push_back(make_pair(block1, block1));
371 covariance_blocks.push_back(make_pair(block4, block4));
372 covariance_blocks.push_back(make_pair(block2, block2));
373 covariance_blocks.push_back(make_pair(block3, block3));
374 covariance_blocks.push_back(make_pair(block2, block3));
375 covariance_blocks.push_back(make_pair(block4, block1)); // reversed
376
377 Covariance::Options options;
378 CovarianceImpl covariance_impl(options);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800379 EXPECT_TRUE(
380 covariance_impl.ComputeCovarianceSparsity(covariance_blocks, &problem));
Austin Schuh70cc9552019-01-21 19:46:48 -0800381
382 const CompressedRowSparseMatrix* crsm = covariance_impl.covariance_matrix();
383
384 EXPECT_EQ(crsm->num_rows(), 7);
385 EXPECT_EQ(crsm->num_cols(), 7);
386 EXPECT_EQ(crsm->num_nonzeros(), 25);
387
388 const int* rows = crsm->rows();
389 for (int r = 0; r < crsm->num_rows() + 1; ++r) {
390 EXPECT_EQ(rows[r], expected_rows[r])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800391 << r << " " << rows[r] << " " << expected_rows[r];
Austin Schuh70cc9552019-01-21 19:46:48 -0800392 }
393
394 const int* cols = crsm->cols();
395 for (int c = 0; c < crsm->num_nonzeros(); ++c) {
396 EXPECT_EQ(cols[c], expected_cols[c])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800397 << c << " " << cols[c] << " " << expected_cols[c];
Austin Schuh70cc9552019-01-21 19:46:48 -0800398 }
399}
400
401class CovarianceTest : public ::testing::Test {
402 protected:
403 typedef map<const double*, pair<int, int>> BoundsMap;
404
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800405 void SetUp() override {
Austin Schuh70cc9552019-01-21 19:46:48 -0800406 double* x = parameters_;
407 double* y = x + 2;
408 double* z = y + 3;
409
410 x[0] = 1;
411 x[1] = 1;
412 y[0] = 2;
413 y[1] = 2;
414 y[2] = 2;
415 z[0] = 3;
416
417 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800418 double jacobian[] = {1.0, 0.0, 0.0, 1.0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800419 problem_.AddResidualBlock(new UnaryCostFunction(2, 2, jacobian), NULL, x);
420 }
421
422 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800423 double jacobian[] = {2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800424 problem_.AddResidualBlock(new UnaryCostFunction(3, 3, jacobian), NULL, y);
425 }
426
427 {
428 double jacobian = 5.0;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800429 problem_.AddResidualBlock(
430 new UnaryCostFunction(1, 1, &jacobian), NULL, z);
Austin Schuh70cc9552019-01-21 19:46:48 -0800431 }
432
433 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800434 double jacobian1[] = {1.0, 2.0, 3.0};
435 double jacobian2[] = {-5.0, -6.0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800436 problem_.AddResidualBlock(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800437 new BinaryCostFunction(1, 3, 2, jacobian1, jacobian2), NULL, y, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800438 }
439
440 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800441 double jacobian1[] = {2.0};
442 double jacobian2[] = {3.0, -2.0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800443 problem_.AddResidualBlock(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800444 new BinaryCostFunction(1, 1, 2, jacobian1, jacobian2), NULL, z, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800445 }
446
447 all_covariance_blocks_.push_back(make_pair(x, x));
448 all_covariance_blocks_.push_back(make_pair(y, y));
449 all_covariance_blocks_.push_back(make_pair(z, z));
450 all_covariance_blocks_.push_back(make_pair(x, y));
451 all_covariance_blocks_.push_back(make_pair(x, z));
452 all_covariance_blocks_.push_back(make_pair(y, z));
453
454 column_bounds_[x] = make_pair(0, 2);
455 column_bounds_[y] = make_pair(2, 5);
456 column_bounds_[z] = make_pair(5, 6);
457 }
458
459 // Computes covariance in ambient space.
460 void ComputeAndCompareCovarianceBlocks(const Covariance::Options& options,
461 const double* expected_covariance) {
462 ComputeAndCompareCovarianceBlocksInTangentOrAmbientSpace(
463 options,
464 true, // ambient
465 expected_covariance);
466 }
467
468 // Computes covariance in tangent space.
469 void ComputeAndCompareCovarianceBlocksInTangentSpace(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800470 const Covariance::Options& options, const double* expected_covariance) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800471 ComputeAndCompareCovarianceBlocksInTangentOrAmbientSpace(
472 options,
473 false, // tangent
474 expected_covariance);
475 }
476
477 void ComputeAndCompareCovarianceBlocksInTangentOrAmbientSpace(
478 const Covariance::Options& options,
479 bool lift_covariance_to_ambient_space,
480 const double* expected_covariance) {
481 // Generate all possible combination of block pairs and check if the
482 // covariance computation is correct.
483 for (int i = 0; i <= 64; ++i) {
484 vector<pair<const double*, const double*>> covariance_blocks;
485 if (i & 1) {
486 covariance_blocks.push_back(all_covariance_blocks_[0]);
487 }
488
489 if (i & 2) {
490 covariance_blocks.push_back(all_covariance_blocks_[1]);
491 }
492
493 if (i & 4) {
494 covariance_blocks.push_back(all_covariance_blocks_[2]);
495 }
496
497 if (i & 8) {
498 covariance_blocks.push_back(all_covariance_blocks_[3]);
499 }
500
501 if (i & 16) {
502 covariance_blocks.push_back(all_covariance_blocks_[4]);
503 }
504
505 if (i & 32) {
506 covariance_blocks.push_back(all_covariance_blocks_[5]);
507 }
508
509 Covariance covariance(options);
510 EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem_));
511
512 for (int i = 0; i < covariance_blocks.size(); ++i) {
513 const double* block1 = covariance_blocks[i].first;
514 const double* block2 = covariance_blocks[i].second;
515 // block1, block2
516 GetCovarianceBlockAndCompare(block1,
517 block2,
518 lift_covariance_to_ambient_space,
519 covariance,
520 expected_covariance);
521 // block2, block1
522 GetCovarianceBlockAndCompare(block2,
523 block1,
524 lift_covariance_to_ambient_space,
525 covariance,
526 expected_covariance);
527 }
528 }
529 }
530
531 void GetCovarianceBlockAndCompare(const double* block1,
532 const double* block2,
533 bool lift_covariance_to_ambient_space,
534 const Covariance& covariance,
535 const double* expected_covariance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800536 const BoundsMap& column_bounds = lift_covariance_to_ambient_space
537 ? column_bounds_
538 : local_column_bounds_;
Austin Schuh70cc9552019-01-21 19:46:48 -0800539 const int row_begin = FindOrDie(column_bounds, block1).first;
540 const int row_end = FindOrDie(column_bounds, block1).second;
541 const int col_begin = FindOrDie(column_bounds, block2).first;
542 const int col_end = FindOrDie(column_bounds, block2).second;
543
544 Matrix actual(row_end - row_begin, col_end - col_begin);
545 if (lift_covariance_to_ambient_space) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800546 EXPECT_TRUE(covariance.GetCovarianceBlock(block1, block2, actual.data()));
Austin Schuh70cc9552019-01-21 19:46:48 -0800547 } else {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800548 EXPECT_TRUE(covariance.GetCovarianceBlockInTangentSpace(
549 block1, block2, actual.data()));
Austin Schuh70cc9552019-01-21 19:46:48 -0800550 }
551
552 int dof = 0; // degrees of freedom = sum of LocalSize()s
553 for (const auto& bound : column_bounds) {
554 dof = std::max(dof, bound.second.second);
555 }
556 ConstMatrixRef expected(expected_covariance, dof, dof);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800557 double diff_norm =
558 (expected.block(
559 row_begin, col_begin, row_end - row_begin, col_end - col_begin) -
560 actual)
561 .norm();
Austin Schuh70cc9552019-01-21 19:46:48 -0800562 diff_norm /= (row_end - row_begin) * (col_end - col_begin);
563
564 const double kTolerance = 1e-5;
565 EXPECT_NEAR(diff_norm, 0.0, kTolerance)
566 << "rows: " << row_begin << " " << row_end << " "
567 << "cols: " << col_begin << " " << col_end << " "
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800568 << "\n\n expected: \n "
569 << expected.block(
570 row_begin, col_begin, row_end - row_begin, col_end - col_begin)
571 << "\n\n actual: \n " << actual << "\n\n full expected: \n"
572 << expected;
Austin Schuh70cc9552019-01-21 19:46:48 -0800573 }
574
575 double parameters_[6];
576 Problem problem_;
577 vector<pair<const double*, const double*>> all_covariance_blocks_;
578 BoundsMap column_bounds_;
579 BoundsMap local_column_bounds_;
580};
581
Austin Schuh70cc9552019-01-21 19:46:48 -0800582TEST_F(CovarianceTest, NormalBehavior) {
583 // J
584 //
585 // 1 0 0 0 0 0
586 // 0 1 0 0 0 0
587 // 0 0 2 0 0 0
588 // 0 0 0 2 0 0
589 // 0 0 0 0 2 0
590 // 0 0 0 0 0 5
591 // -5 -6 1 2 3 0
592 // 3 -2 0 0 0 2
593
594 // J'J
595 //
596 // 35 24 -5 -10 -15 6
597 // 24 41 -6 -12 -18 -4
598 // -5 -6 5 2 3 0
599 // -10 -12 2 8 6 0
600 // -15 -18 3 6 13 0
601 // 6 -4 0 0 0 29
602
603 // inv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800604 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800605 double expected_covariance[] = {
606 7.0747e-02, -8.4923e-03, 1.6821e-02, 3.3643e-02, 5.0464e-02, -1.5809e-02, // NOLINT
607 -8.4923e-03, 8.1352e-02, 2.4758e-02, 4.9517e-02, 7.4275e-02, 1.2978e-02, // NOLINT
608 1.6821e-02, 2.4758e-02, 2.4904e-01, -1.9271e-03, -2.8906e-03, -6.5325e-05, // NOLINT
609 3.3643e-02, 4.9517e-02, -1.9271e-03, 2.4615e-01, -5.7813e-03, -1.3065e-04, // NOLINT
610 5.0464e-02, 7.4275e-02, -2.8906e-03, -5.7813e-03, 2.4133e-01, -1.9598e-04, // NOLINT
611 -1.5809e-02, 1.2978e-02, -6.5325e-05, -1.3065e-04, -1.9598e-04, 3.9544e-02, // NOLINT
612 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800613 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800614
615 Covariance::Options options;
616
617#ifndef CERES_NO_SUITESPARSE
618 options.algorithm_type = SPARSE_QR;
619 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
620 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
621#endif
622
623 options.algorithm_type = DENSE_SVD;
624 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
625
626 options.algorithm_type = SPARSE_QR;
627 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
628 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
629}
630
631#ifdef CERES_USE_OPENMP
632
633TEST_F(CovarianceTest, ThreadedNormalBehavior) {
634 // J
635 //
636 // 1 0 0 0 0 0
637 // 0 1 0 0 0 0
638 // 0 0 2 0 0 0
639 // 0 0 0 2 0 0
640 // 0 0 0 0 2 0
641 // 0 0 0 0 0 5
642 // -5 -6 1 2 3 0
643 // 3 -2 0 0 0 2
644
645 // J'J
646 //
647 // 35 24 -5 -10 -15 6
648 // 24 41 -6 -12 -18 -4
649 // -5 -6 5 2 3 0
650 // -10 -12 2 8 6 0
651 // -15 -18 3 6 13 0
652 // 6 -4 0 0 0 29
653
654 // inv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800655 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800656 double expected_covariance[] = {
657 7.0747e-02, -8.4923e-03, 1.6821e-02, 3.3643e-02, 5.0464e-02, -1.5809e-02, // NOLINT
658 -8.4923e-03, 8.1352e-02, 2.4758e-02, 4.9517e-02, 7.4275e-02, 1.2978e-02, // NOLINT
659 1.6821e-02, 2.4758e-02, 2.4904e-01, -1.9271e-03, -2.8906e-03, -6.5325e-05, // NOLINT
660 3.3643e-02, 4.9517e-02, -1.9271e-03, 2.4615e-01, -5.7813e-03, -1.3065e-04, // NOLINT
661 5.0464e-02, 7.4275e-02, -2.8906e-03, -5.7813e-03, 2.4133e-01, -1.9598e-04, // NOLINT
662 -1.5809e-02, 1.2978e-02, -6.5325e-05, -1.3065e-04, -1.9598e-04, 3.9544e-02, // NOLINT
663 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800664 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800665
666 Covariance::Options options;
667 options.num_threads = 4;
668
669#ifndef CERES_NO_SUITESPARSE
670 options.algorithm_type = SPARSE_QR;
671 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
672 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
673#endif
674
675 options.algorithm_type = DENSE_SVD;
676 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
677
678 options.algorithm_type = SPARSE_QR;
679 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
680 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
681}
682
683#endif // CERES_USE_OPENMP
684
685TEST_F(CovarianceTest, ConstantParameterBlock) {
686 problem_.SetParameterBlockConstant(parameters_);
687
688 // J
689 //
690 // 0 0 0 0 0 0
691 // 0 0 0 0 0 0
692 // 0 0 2 0 0 0
693 // 0 0 0 2 0 0
694 // 0 0 0 0 2 0
695 // 0 0 0 0 0 5
696 // 0 0 1 2 3 0
697 // 0 0 0 0 0 2
698
699 // J'J
700 //
701 // 0 0 0 0 0 0
702 // 0 0 0 0 0 0
703 // 0 0 5 2 3 0
704 // 0 0 2 8 6 0
705 // 0 0 3 6 13 0
706 // 0 0 0 0 0 29
707
708 // pinv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800709 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800710 double expected_covariance[] = {
711 0, 0, 0, 0, 0, 0, // NOLINT
712 0, 0, 0, 0, 0, 0, // NOLINT
713 0, 0, 0.23611, -0.02778, -0.04167, -0.00000, // NOLINT
714 0, 0, -0.02778, 0.19444, -0.08333, -0.00000, // NOLINT
715 0, 0, -0.04167, -0.08333, 0.12500, -0.00000, // NOLINT
716 0, 0, -0.00000, -0.00000, -0.00000, 0.03448 // NOLINT
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800717 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800718 };
719
720 Covariance::Options options;
721
722#ifndef CERES_NO_SUITESPARSE
723 options.algorithm_type = SPARSE_QR;
724 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
725 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
726#endif
727
728 options.algorithm_type = DENSE_SVD;
729 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
730
731 options.algorithm_type = SPARSE_QR;
732 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
733 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
734}
735
736TEST_F(CovarianceTest, LocalParameterization) {
737 double* x = parameters_;
738 double* y = x + 2;
739
740 problem_.SetParameterization(x, new PolynomialParameterization);
741
742 vector<int> subset;
743 subset.push_back(2);
744 problem_.SetParameterization(y, new SubsetParameterization(3, subset));
745
746 // Raw Jacobian: J
747 //
748 // 1 0 0 0 0 0
749 // 0 1 0 0 0 0
750 // 0 0 2 0 0 0
751 // 0 0 0 2 0 0
752 // 0 0 0 0 2 0
753 // 0 0 0 0 0 5
754 // -5 -6 1 2 3 0
755 // 3 -2 0 0 0 2
756
757 // Local to global jacobian: A
758 //
759 // 1 0 0 0
760 // 1 0 0 0
761 // 0 1 0 0
762 // 0 0 1 0
763 // 0 0 0 0
764 // 0 0 0 1
765
766 // A * inv((J*A)'*(J*A)) * A'
767 // Computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800768 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800769 double expected_covariance[] = {
770 0.01766, 0.01766, 0.02158, 0.04316, 0.00000, -0.00122,
771 0.01766, 0.01766, 0.02158, 0.04316, 0.00000, -0.00122,
772 0.02158, 0.02158, 0.24860, -0.00281, 0.00000, -0.00149,
773 0.04316, 0.04316, -0.00281, 0.24439, 0.00000, -0.00298,
774 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000,
775 -0.00122, -0.00122, -0.00149, -0.00298, 0.00000, 0.03457
776 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800777 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800778
779 Covariance::Options options;
780
781#ifndef CERES_NO_SUITESPARSE
782 options.algorithm_type = SPARSE_QR;
783 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
784 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
785#endif
786
787 options.algorithm_type = DENSE_SVD;
788 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
789
790 options.algorithm_type = SPARSE_QR;
791 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
792 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
793}
794
795TEST_F(CovarianceTest, LocalParameterizationInTangentSpace) {
796 double* x = parameters_;
797 double* y = x + 2;
798 double* z = y + 3;
799
800 problem_.SetParameterization(x, new PolynomialParameterization);
801
802 vector<int> subset;
803 subset.push_back(2);
804 problem_.SetParameterization(y, new SubsetParameterization(3, subset));
805
806 local_column_bounds_[x] = make_pair(0, 1);
807 local_column_bounds_[y] = make_pair(1, 3);
808 local_column_bounds_[z] = make_pair(3, 4);
809
810 // Raw Jacobian: J
811 //
812 // 1 0 0 0 0 0
813 // 0 1 0 0 0 0
814 // 0 0 2 0 0 0
815 // 0 0 0 2 0 0
816 // 0 0 0 0 2 0
817 // 0 0 0 0 0 5
818 // -5 -6 1 2 3 0
819 // 3 -2 0 0 0 2
820
821 // Local to global jacobian: A
822 //
823 // 1 0 0 0
824 // 1 0 0 0
825 // 0 1 0 0
826 // 0 0 1 0
827 // 0 0 0 0
828 // 0 0 0 1
829
830 // inv((J*A)'*(J*A))
831 // Computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800832 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800833 double expected_covariance[] = {
834 0.01766, 0.02158, 0.04316, -0.00122,
835 0.02158, 0.24860, -0.00281, -0.00149,
836 0.04316, -0.00281, 0.24439, -0.00298,
837 -0.00122, -0.00149, -0.00298, 0.03457 // NOLINT
838 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800839 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800840
841 Covariance::Options options;
842
843#ifndef CERES_NO_SUITESPARSE
844 options.algorithm_type = SPARSE_QR;
845 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
846
847 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
848#endif
849
850 options.algorithm_type = DENSE_SVD;
851 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
852
853 options.algorithm_type = SPARSE_QR;
854 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
855 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
856}
857
858TEST_F(CovarianceTest, LocalParameterizationInTangentSpaceWithConstantBlocks) {
859 double* x = parameters_;
860 double* y = x + 2;
861 double* z = y + 3;
862
863 problem_.SetParameterization(x, new PolynomialParameterization);
864 problem_.SetParameterBlockConstant(x);
865
866 vector<int> subset;
867 subset.push_back(2);
868 problem_.SetParameterization(y, new SubsetParameterization(3, subset));
869 problem_.SetParameterBlockConstant(y);
870
871 local_column_bounds_[x] = make_pair(0, 1);
872 local_column_bounds_[y] = make_pair(1, 3);
873 local_column_bounds_[z] = make_pair(3, 4);
874
875 // Raw Jacobian: J
876 //
877 // 1 0 0 0 0 0
878 // 0 1 0 0 0 0
879 // 0 0 2 0 0 0
880 // 0 0 0 2 0 0
881 // 0 0 0 0 2 0
882 // 0 0 0 0 0 5
883 // -5 -6 1 2 3 0
884 // 3 -2 0 0 0 2
885
886 // Local to global jacobian: A
887 //
888 // 0 0 0 0
889 // 0 0 0 0
890 // 0 0 0 0
891 // 0 0 0 0
892 // 0 0 0 0
893 // 0 0 0 1
894
895 // pinv((J*A)'*(J*A))
896 // Computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800897 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800898 double expected_covariance[] = {
899 0.0, 0.0, 0.0, 0.0,
900 0.0, 0.0, 0.0, 0.0,
901 0.0, 0.0, 0.0, 0.0,
902 0.0, 0.0, 0.0, 0.034482 // NOLINT
903 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800904 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800905
906 Covariance::Options options;
907
908#ifndef CERES_NO_SUITESPARSE
909 options.algorithm_type = SPARSE_QR;
910 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
911
912 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
913#endif
914
915 options.algorithm_type = DENSE_SVD;
916 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
917
918 options.algorithm_type = SPARSE_QR;
919 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
920 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
921}
922
923TEST_F(CovarianceTest, TruncatedRank) {
924 // J
925 //
926 // 1 0 0 0 0 0
927 // 0 1 0 0 0 0
928 // 0 0 2 0 0 0
929 // 0 0 0 2 0 0
930 // 0 0 0 0 2 0
931 // 0 0 0 0 0 5
932 // -5 -6 1 2 3 0
933 // 3 -2 0 0 0 2
934
935 // J'J
936 //
937 // 35 24 -5 -10 -15 6
938 // 24 41 -6 -12 -18 -4
939 // -5 -6 5 2 3 0
940 // -10 -12 2 8 6 0
941 // -15 -18 3 6 13 0
942 // 6 -4 0 0 0 29
943
944 // 3.4142 is the smallest eigen value of J'J. The following matrix
945 // was obtained by dropping the eigenvector corresponding to this
946 // eigenvalue.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800947 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800948 double expected_covariance[] = {
949 5.4135e-02, -3.5121e-02, 1.7257e-04, 3.4514e-04, 5.1771e-04, -1.6076e-02, // NOLINT
950 -3.5121e-02, 3.8667e-02, -1.9288e-03, -3.8576e-03, -5.7864e-03, 1.2549e-02, // NOLINT
951 1.7257e-04, -1.9288e-03, 2.3235e-01, -3.5297e-02, -5.2946e-02, -3.3329e-04, // NOLINT
952 3.4514e-04, -3.8576e-03, -3.5297e-02, 1.7941e-01, -1.0589e-01, -6.6659e-04, // NOLINT
953 5.1771e-04, -5.7864e-03, -5.2946e-02, -1.0589e-01, 9.1162e-02, -9.9988e-04, // NOLINT
954 -1.6076e-02, 1.2549e-02, -3.3329e-04, -6.6659e-04, -9.9988e-04, 3.9539e-02 // NOLINT
955 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800956 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800957
958 {
959 Covariance::Options options;
960 options.algorithm_type = DENSE_SVD;
961 // Force dropping of the smallest eigenvector.
962 options.null_space_rank = 1;
963 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
964 }
965
966 {
967 Covariance::Options options;
968 options.algorithm_type = DENSE_SVD;
969 // Force dropping of the smallest eigenvector via the ratio but
970 // automatic truncation.
971 options.min_reciprocal_condition_number = 0.044494;
972 options.null_space_rank = -1;
973 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
974 }
975}
976
977TEST_F(CovarianceTest, DenseCovarianceMatrixFromSetOfParameters) {
978 Covariance::Options options;
979 Covariance covariance(options);
980 double* x = parameters_;
981 double* y = x + 2;
982 double* z = y + 3;
983 vector<const double*> parameter_blocks;
984 parameter_blocks.push_back(x);
985 parameter_blocks.push_back(y);
986 parameter_blocks.push_back(z);
987 covariance.Compute(parameter_blocks, &problem_);
988 double expected_covariance[36];
989 covariance.GetCovarianceMatrix(parameter_blocks, expected_covariance);
990
991#ifndef CERES_NO_SUITESPARSE
992 options.algorithm_type = SPARSE_QR;
993 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
994 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
995#endif
996
997 options.algorithm_type = DENSE_SVD;
998 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
999
1000 options.algorithm_type = SPARSE_QR;
1001 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
1002 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1003}
1004
1005TEST_F(CovarianceTest, DenseCovarianceMatrixFromSetOfParametersThreaded) {
1006 Covariance::Options options;
1007 options.num_threads = 4;
1008 Covariance covariance(options);
1009 double* x = parameters_;
1010 double* y = x + 2;
1011 double* z = y + 3;
1012 vector<const double*> parameter_blocks;
1013 parameter_blocks.push_back(x);
1014 parameter_blocks.push_back(y);
1015 parameter_blocks.push_back(z);
1016 covariance.Compute(parameter_blocks, &problem_);
1017 double expected_covariance[36];
1018 covariance.GetCovarianceMatrix(parameter_blocks, expected_covariance);
1019
1020#ifndef CERES_NO_SUITESPARSE
1021 options.algorithm_type = SPARSE_QR;
1022 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
1023 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1024#endif
1025
1026 options.algorithm_type = DENSE_SVD;
1027 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1028
1029 options.algorithm_type = SPARSE_QR;
1030 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
1031 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1032}
1033
1034TEST_F(CovarianceTest, DenseCovarianceMatrixFromSetOfParametersInTangentSpace) {
1035 Covariance::Options options;
1036 Covariance covariance(options);
1037 double* x = parameters_;
1038 double* y = x + 2;
1039 double* z = y + 3;
1040
1041 problem_.SetParameterization(x, new PolynomialParameterization);
1042
1043 vector<int> subset;
1044 subset.push_back(2);
1045 problem_.SetParameterization(y, new SubsetParameterization(3, subset));
1046
1047 local_column_bounds_[x] = make_pair(0, 1);
1048 local_column_bounds_[y] = make_pair(1, 3);
1049 local_column_bounds_[z] = make_pair(3, 4);
1050
1051 vector<const double*> parameter_blocks;
1052 parameter_blocks.push_back(x);
1053 parameter_blocks.push_back(y);
1054 parameter_blocks.push_back(z);
1055 covariance.Compute(parameter_blocks, &problem_);
1056 double expected_covariance[16];
1057 covariance.GetCovarianceMatrixInTangentSpace(parameter_blocks,
1058 expected_covariance);
1059
1060#ifndef CERES_NO_SUITESPARSE
1061 options.algorithm_type = SPARSE_QR;
1062 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
1063
1064 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
1065#endif
1066
1067 options.algorithm_type = DENSE_SVD;
1068 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
1069
1070 options.algorithm_type = SPARSE_QR;
1071 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
1072 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
1073}
1074
1075TEST_F(CovarianceTest, ComputeCovarianceFailure) {
1076 Covariance::Options options;
1077 Covariance covariance(options);
1078 double* x = parameters_;
1079 double* y = x + 2;
1080 vector<const double*> parameter_blocks;
1081 parameter_blocks.push_back(x);
1082 parameter_blocks.push_back(x);
1083 parameter_blocks.push_back(y);
1084 parameter_blocks.push_back(y);
1085 EXPECT_DEATH_IF_SUPPORTED(covariance.Compute(parameter_blocks, &problem_),
1086 "Covariance::Compute called with duplicate blocks "
1087 "at indices \\(0, 1\\) and \\(2, 3\\)");
1088 vector<pair<const double*, const double*>> covariance_blocks;
1089 covariance_blocks.push_back(make_pair(x, x));
1090 covariance_blocks.push_back(make_pair(x, x));
1091 covariance_blocks.push_back(make_pair(y, y));
1092 covariance_blocks.push_back(make_pair(y, y));
1093 EXPECT_DEATH_IF_SUPPORTED(covariance.Compute(covariance_blocks, &problem_),
1094 "Covariance::Compute called with duplicate blocks "
1095 "at indices \\(0, 1\\) and \\(2, 3\\)");
1096}
1097
1098class RankDeficientCovarianceTest : public CovarianceTest {
1099 protected:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001100 void SetUp() final {
Austin Schuh70cc9552019-01-21 19:46:48 -08001101 double* x = parameters_;
1102 double* y = x + 2;
1103 double* z = y + 3;
1104
1105 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001106 double jacobian[] = {1.0, 0.0, 0.0, 1.0};
Austin Schuh70cc9552019-01-21 19:46:48 -08001107 problem_.AddResidualBlock(new UnaryCostFunction(2, 2, jacobian), NULL, x);
1108 }
1109
1110 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001111 double jacobian[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
Austin Schuh70cc9552019-01-21 19:46:48 -08001112 problem_.AddResidualBlock(new UnaryCostFunction(3, 3, jacobian), NULL, y);
1113 }
1114
1115 {
1116 double jacobian = 5.0;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001117 problem_.AddResidualBlock(
1118 new UnaryCostFunction(1, 1, &jacobian), NULL, z);
Austin Schuh70cc9552019-01-21 19:46:48 -08001119 }
1120
1121 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001122 double jacobian1[] = {0.0, 0.0, 0.0};
1123 double jacobian2[] = {-5.0, -6.0};
Austin Schuh70cc9552019-01-21 19:46:48 -08001124 problem_.AddResidualBlock(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001125 new BinaryCostFunction(1, 3, 2, jacobian1, jacobian2), NULL, y, x);
Austin Schuh70cc9552019-01-21 19:46:48 -08001126 }
1127
1128 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001129 double jacobian1[] = {2.0};
1130 double jacobian2[] = {3.0, -2.0};
Austin Schuh70cc9552019-01-21 19:46:48 -08001131 problem_.AddResidualBlock(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001132 new BinaryCostFunction(1, 1, 2, jacobian1, jacobian2), NULL, z, x);
Austin Schuh70cc9552019-01-21 19:46:48 -08001133 }
1134
1135 all_covariance_blocks_.push_back(make_pair(x, x));
1136 all_covariance_blocks_.push_back(make_pair(y, y));
1137 all_covariance_blocks_.push_back(make_pair(z, z));
1138 all_covariance_blocks_.push_back(make_pair(x, y));
1139 all_covariance_blocks_.push_back(make_pair(x, z));
1140 all_covariance_blocks_.push_back(make_pair(y, z));
1141
1142 column_bounds_[x] = make_pair(0, 2);
1143 column_bounds_[y] = make_pair(2, 5);
1144 column_bounds_[z] = make_pair(5, 6);
1145 }
1146};
1147
1148TEST_F(RankDeficientCovarianceTest, AutomaticTruncation) {
1149 // J
1150 //
1151 // 1 0 0 0 0 0
1152 // 0 1 0 0 0 0
1153 // 0 0 0 0 0 0
1154 // 0 0 0 0 0 0
1155 // 0 0 0 0 0 0
1156 // 0 0 0 0 0 5
1157 // -5 -6 0 0 0 0
1158 // 3 -2 0 0 0 2
1159
1160 // J'J
1161 //
1162 // 35 24 0 0 0 6
1163 // 24 41 0 0 0 -4
1164 // 0 0 0 0 0 0
1165 // 0 0 0 0 0 0
1166 // 0 0 0 0 0 0
1167 // 6 -4 0 0 0 29
1168
1169 // pinv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001170 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001171 double expected_covariance[] = {
1172 0.053998, -0.033145, 0.000000, 0.000000, 0.000000, -0.015744,
1173 -0.033145, 0.045067, 0.000000, 0.000000, 0.000000, 0.013074,
1174 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
1175 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
1176 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
1177 -0.015744, 0.013074, 0.000000, 0.000000, 0.000000, 0.039543
1178 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001179 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001180
1181 Covariance::Options options;
1182 options.algorithm_type = DENSE_SVD;
1183 options.null_space_rank = -1;
1184 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1185}
1186
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001187struct LinearCostFunction {
1188 template <typename T>
1189 bool operator()(const T* x, const T* y, T* residual) const {
1190 residual[0] = T(10.0) - *x;
1191 residual[1] = T(5.0) - *y;
1192 return true;
1193 }
1194 static CostFunction* Create() {
1195 return new AutoDiffCostFunction<LinearCostFunction, 2, 1, 1>(
1196 new LinearCostFunction);
1197 }
1198};
1199
1200TEST(Covariance, ZeroSizedLocalParameterizationGetCovariance) {
1201 double x = 0.0;
1202 double y = 1.0;
1203 Problem problem;
1204 problem.AddResidualBlock(LinearCostFunction::Create(), nullptr, &x, &y);
1205 problem.SetParameterization(&y, new SubsetParameterization(1, {0}));
1206 // J = [-1 0]
1207 // [ 0 0]
1208 Covariance::Options options;
1209 options.algorithm_type = DENSE_SVD;
1210 Covariance covariance(options);
1211 vector<pair<const double*, const double*>> covariance_blocks;
1212 covariance_blocks.push_back(std::make_pair(&x, &x));
1213 covariance_blocks.push_back(std::make_pair(&x, &y));
1214 covariance_blocks.push_back(std::make_pair(&y, &x));
1215 covariance_blocks.push_back(std::make_pair(&y, &y));
1216 EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem));
1217
1218 double value = -1;
1219 covariance.GetCovarianceBlock(&x, &x, &value);
1220 EXPECT_NEAR(value, 1.0, std::numeric_limits<double>::epsilon());
1221
1222 value = -1;
1223 covariance.GetCovarianceBlock(&x, &y, &value);
1224 EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
1225
1226 value = -1;
1227 covariance.GetCovarianceBlock(&y, &x, &value);
1228 EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
1229
1230 value = -1;
1231 covariance.GetCovarianceBlock(&y, &y, &value);
1232 EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
1233}
1234
1235TEST(Covariance, ZeroSizedLocalParameterizationGetCovarianceInTangentSpace) {
1236 double x = 0.0;
1237 double y = 1.0;
1238 Problem problem;
1239 problem.AddResidualBlock(LinearCostFunction::Create(), nullptr, &x, &y);
1240 problem.SetParameterization(&y, new SubsetParameterization(1, {0}));
1241 // J = [-1 0]
1242 // [ 0 0]
1243 Covariance::Options options;
1244 options.algorithm_type = DENSE_SVD;
1245 Covariance covariance(options);
1246 vector<pair<const double*, const double*>> covariance_blocks;
1247 covariance_blocks.push_back(std::make_pair(&x, &x));
1248 covariance_blocks.push_back(std::make_pair(&x, &y));
1249 covariance_blocks.push_back(std::make_pair(&y, &x));
1250 covariance_blocks.push_back(std::make_pair(&y, &y));
1251 EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem));
1252
1253 double value = -1;
1254 covariance.GetCovarianceBlockInTangentSpace(&x, &x, &value);
1255 EXPECT_NEAR(value, 1.0, std::numeric_limits<double>::epsilon());
1256
1257 value = -1;
1258 // The following three calls, should not touch this value, since the
1259 // tangent space is of size zero
1260 covariance.GetCovarianceBlockInTangentSpace(&x, &y, &value);
1261 EXPECT_EQ(value, -1);
1262 covariance.GetCovarianceBlockInTangentSpace(&y, &x, &value);
1263 EXPECT_EQ(value, -1);
1264 covariance.GetCovarianceBlockInTangentSpace(&y, &y, &value);
1265 EXPECT_EQ(value, -1);
1266}
1267
Austin Schuh70cc9552019-01-21 19:46:48 -08001268class LargeScaleCovarianceTest : public ::testing::Test {
1269 protected:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001270 void SetUp() final {
Austin Schuh70cc9552019-01-21 19:46:48 -08001271 num_parameter_blocks_ = 2000;
1272 parameter_block_size_ = 5;
1273 parameters_.reset(
1274 new double[parameter_block_size_ * num_parameter_blocks_]);
1275
1276 Matrix jacobian(parameter_block_size_, parameter_block_size_);
1277 for (int i = 0; i < num_parameter_blocks_; ++i) {
1278 jacobian.setIdentity();
1279 jacobian *= (i + 1);
1280
1281 double* block_i = parameters_.get() + i * parameter_block_size_;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001282 problem_.AddResidualBlock(
1283 new UnaryCostFunction(
1284 parameter_block_size_, parameter_block_size_, jacobian.data()),
1285 NULL,
1286 block_i);
Austin Schuh70cc9552019-01-21 19:46:48 -08001287 for (int j = i; j < num_parameter_blocks_; ++j) {
1288 double* block_j = parameters_.get() + j * parameter_block_size_;
1289 all_covariance_blocks_.push_back(make_pair(block_i, block_j));
1290 }
1291 }
1292 }
1293
1294 void ComputeAndCompare(
1295 CovarianceAlgorithmType algorithm_type,
1296 SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
1297 int num_threads) {
1298 Covariance::Options options;
1299 options.algorithm_type = algorithm_type;
1300 options.sparse_linear_algebra_library_type =
1301 sparse_linear_algebra_library_type;
1302 options.num_threads = num_threads;
1303 Covariance covariance(options);
1304 EXPECT_TRUE(covariance.Compute(all_covariance_blocks_, &problem_));
1305
1306 Matrix expected(parameter_block_size_, parameter_block_size_);
1307 Matrix actual(parameter_block_size_, parameter_block_size_);
1308 const double kTolerance = 1e-16;
1309
1310 for (int i = 0; i < num_parameter_blocks_; ++i) {
1311 expected.setIdentity();
1312 expected /= (i + 1.0) * (i + 1.0);
1313
1314 double* block_i = parameters_.get() + i * parameter_block_size_;
1315 covariance.GetCovarianceBlock(block_i, block_i, actual.data());
1316 EXPECT_NEAR((expected - actual).norm(), 0.0, kTolerance)
1317 << "block: " << i << ", " << i << "\n"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001318 << "expected: \n"
1319 << expected << "\n"
1320 << "actual: \n"
1321 << actual;
Austin Schuh70cc9552019-01-21 19:46:48 -08001322
1323 expected.setZero();
1324 for (int j = i + 1; j < num_parameter_blocks_; ++j) {
1325 double* block_j = parameters_.get() + j * parameter_block_size_;
1326 covariance.GetCovarianceBlock(block_i, block_j, actual.data());
1327 EXPECT_NEAR((expected - actual).norm(), 0.0, kTolerance)
1328 << "block: " << i << ", " << j << "\n"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001329 << "expected: \n"
1330 << expected << "\n"
1331 << "actual: \n"
1332 << actual;
Austin Schuh70cc9552019-01-21 19:46:48 -08001333 }
1334 }
1335 }
1336
1337 std::unique_ptr<double[]> parameters_;
1338 int parameter_block_size_;
1339 int num_parameter_blocks_;
1340
1341 Problem problem_;
1342 vector<pair<const double*, const double*>> all_covariance_blocks_;
1343};
1344
1345#if !defined(CERES_NO_SUITESPARSE) && defined(CERES_USE_OPENMP)
1346
1347TEST_F(LargeScaleCovarianceTest, Parallel) {
1348 ComputeAndCompare(SPARSE_QR, SUITE_SPARSE, 4);
1349}
1350
1351#endif // !defined(CERES_NO_SUITESPARSE) && defined(CERES_USE_OPENMP)
1352
1353} // namespace internal
1354} // namespace ceres