blob: 40d9b0e078883d4565103ece739f7c4ec57715a5 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
Austin Schuh3de38b02024-06-25 18:25:10 -07002// Copyright 2023 Google Inc. All rights reserved.
Austin Schuh70cc9552019-01-21 19:46:48 -08003// 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>
Austin Schuh3de38b02024-06-25 18:25:10 -070039#include <vector>
Austin Schuh70cc9552019-01-21 19:46:48 -080040
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080041#include "ceres/autodiff_cost_function.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080042#include "ceres/compressed_row_sparse_matrix.h"
43#include "ceres/cost_function.h"
44#include "ceres/covariance_impl.h"
Austin Schuh3de38b02024-06-25 18:25:10 -070045#include "ceres/internal/config.h"
46#include "ceres/manifold.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080047#include "ceres/map_util.h"
48#include "ceres/problem_impl.h"
49#include "gtest/gtest.h"
50
51namespace ceres {
52namespace internal {
53
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080054class UnaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -080055 public:
56 UnaryCostFunction(const int num_residuals,
57 const int32_t parameter_block_size,
58 const double* jacobian)
59 : jacobian_(jacobian, jacobian + num_residuals * parameter_block_size) {
60 set_num_residuals(num_residuals);
61 mutable_parameter_block_sizes()->push_back(parameter_block_size);
62 }
63
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080064 bool Evaluate(double const* const* parameters,
65 double* residuals,
66 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080067 for (int i = 0; i < num_residuals(); ++i) {
68 residuals[i] = 1;
69 }
70
Austin Schuh3de38b02024-06-25 18:25:10 -070071 if (jacobians == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -080072 return true;
73 }
74
Austin Schuh3de38b02024-06-25 18:25:10 -070075 if (jacobians[0] != nullptr) {
76 std::copy(jacobian_.begin(), jacobian_.end(), jacobians[0]);
Austin Schuh70cc9552019-01-21 19:46:48 -080077 }
78
79 return true;
80 }
81
82 private:
Austin Schuh3de38b02024-06-25 18:25:10 -070083 std::vector<double> jacobian_;
Austin Schuh70cc9552019-01-21 19:46:48 -080084};
85
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080086class BinaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -080087 public:
88 BinaryCostFunction(const int num_residuals,
89 const int32_t parameter_block1_size,
90 const int32_t parameter_block2_size,
91 const double* jacobian1,
92 const double* jacobian2)
93 : jacobian1_(jacobian1,
94 jacobian1 + num_residuals * parameter_block1_size),
95 jacobian2_(jacobian2,
96 jacobian2 + num_residuals * parameter_block2_size) {
97 set_num_residuals(num_residuals);
98 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
99 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
100 }
101
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800102 bool Evaluate(double const* const* parameters,
103 double* residuals,
104 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800105 for (int i = 0; i < num_residuals(); ++i) {
106 residuals[i] = 2;
107 }
108
Austin Schuh3de38b02024-06-25 18:25:10 -0700109 if (jacobians == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800110 return true;
111 }
112
Austin Schuh3de38b02024-06-25 18:25:10 -0700113 if (jacobians[0] != nullptr) {
114 std::copy(jacobian1_.begin(), jacobian1_.end(), jacobians[0]);
Austin Schuh70cc9552019-01-21 19:46:48 -0800115 }
116
Austin Schuh3de38b02024-06-25 18:25:10 -0700117 if (jacobians[1] != nullptr) {
118 std::copy(jacobian2_.begin(), jacobian2_.end(), jacobians[1]);
Austin Schuh70cc9552019-01-21 19:46:48 -0800119 }
120
121 return true;
122 }
123
124 private:
Austin Schuh3de38b02024-06-25 18:25:10 -0700125 std::vector<double> jacobian1_;
126 std::vector<double> jacobian2_;
Austin Schuh70cc9552019-01-21 19:46:48 -0800127};
128
129TEST(CovarianceImpl, ComputeCovarianceSparsity) {
130 double parameters[10];
131
132 double* block1 = parameters;
133 double* block2 = block1 + 1;
134 double* block3 = block2 + 2;
135 double* block4 = block3 + 3;
136
137 ProblemImpl problem;
138
139 // Add in random order
140 Vector junk_jacobian = Vector::Zero(10);
141 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700142 new UnaryCostFunction(1, 1, junk_jacobian.data()), nullptr, block1);
Austin Schuh70cc9552019-01-21 19:46:48 -0800143 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700144 new UnaryCostFunction(1, 4, junk_jacobian.data()), nullptr, block4);
Austin Schuh70cc9552019-01-21 19:46:48 -0800145 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700146 new UnaryCostFunction(1, 3, junk_jacobian.data()), nullptr, block3);
Austin Schuh70cc9552019-01-21 19:46:48 -0800147 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700148 new UnaryCostFunction(1, 2, junk_jacobian.data()), nullptr, block2);
Austin Schuh70cc9552019-01-21 19:46:48 -0800149
150 // Sparsity pattern
151 //
152 // Note that the problem structure does not imply this sparsity
153 // pattern since all the residual blocks are unary. But the
154 // ComputeCovarianceSparsity function in its current incarnation
155 // does not pay attention to this fact and only looks at the
156 // parameter block pairs that the user provides.
157 //
158 // X . . . . . X X X X
159 // . X X X X X . . . .
160 // . X X X X X . . . .
161 // . . . X X X . . . .
162 // . . . X X X . . . .
163 // . . . X X X . . . .
164 // . . . . . . X X X X
165 // . . . . . . X X X X
166 // . . . . . . X X X X
167 // . . . . . . X X X X
168
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800169 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800170 int expected_rows[] = {0, 5, 10, 15, 18, 21, 24, 28, 32, 36, 40};
171 int expected_cols[] = {0, 6, 7, 8, 9,
172 1, 2, 3, 4, 5,
173 1, 2, 3, 4, 5,
174 3, 4, 5,
175 3, 4, 5,
176 3, 4, 5,
177 6, 7, 8, 9,
178 6, 7, 8, 9,
179 6, 7, 8, 9,
180 6, 7, 8, 9};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800181 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800182
Austin Schuh3de38b02024-06-25 18:25:10 -0700183 std::vector<std::pair<const double*, const double*>> covariance_blocks;
184 covariance_blocks.emplace_back(block1, block1);
185 covariance_blocks.emplace_back(block4, block4);
186 covariance_blocks.emplace_back(block2, block2);
187 covariance_blocks.emplace_back(block3, block3);
188 covariance_blocks.emplace_back(block2, block3);
189 covariance_blocks.emplace_back(block4, block1); // reversed
Austin Schuh70cc9552019-01-21 19:46:48 -0800190
191 Covariance::Options options;
192 CovarianceImpl covariance_impl(options);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800193 EXPECT_TRUE(
194 covariance_impl.ComputeCovarianceSparsity(covariance_blocks, &problem));
Austin Schuh70cc9552019-01-21 19:46:48 -0800195
196 const CompressedRowSparseMatrix* crsm = covariance_impl.covariance_matrix();
197
198 EXPECT_EQ(crsm->num_rows(), 10);
199 EXPECT_EQ(crsm->num_cols(), 10);
200 EXPECT_EQ(crsm->num_nonzeros(), 40);
201
202 const int* rows = crsm->rows();
203 for (int r = 0; r < crsm->num_rows() + 1; ++r) {
204 EXPECT_EQ(rows[r], expected_rows[r])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800205 << r << " " << rows[r] << " " << expected_rows[r];
Austin Schuh70cc9552019-01-21 19:46:48 -0800206 }
207
208 const int* cols = crsm->cols();
209 for (int c = 0; c < crsm->num_nonzeros(); ++c) {
210 EXPECT_EQ(cols[c], expected_cols[c])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800211 << c << " " << cols[c] << " " << expected_cols[c];
Austin Schuh70cc9552019-01-21 19:46:48 -0800212 }
213}
214
215TEST(CovarianceImpl, ComputeCovarianceSparsityWithConstantParameterBlock) {
216 double parameters[10];
217
218 double* block1 = parameters;
219 double* block2 = block1 + 1;
220 double* block3 = block2 + 2;
221 double* block4 = block3 + 3;
222
223 ProblemImpl problem;
224
225 // Add in random order
226 Vector junk_jacobian = Vector::Zero(10);
227 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700228 new UnaryCostFunction(1, 1, junk_jacobian.data()), nullptr, block1);
Austin Schuh70cc9552019-01-21 19:46:48 -0800229 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700230 new UnaryCostFunction(1, 4, junk_jacobian.data()), nullptr, block4);
Austin Schuh70cc9552019-01-21 19:46:48 -0800231 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700232 new UnaryCostFunction(1, 3, junk_jacobian.data()), nullptr, block3);
Austin Schuh70cc9552019-01-21 19:46:48 -0800233 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700234 new UnaryCostFunction(1, 2, junk_jacobian.data()), nullptr, block2);
Austin Schuh70cc9552019-01-21 19:46:48 -0800235 problem.SetParameterBlockConstant(block3);
236
237 // Sparsity pattern
238 //
239 // Note that the problem structure does not imply this sparsity
240 // pattern since all the residual blocks are unary. But the
241 // ComputeCovarianceSparsity function in its current incarnation
242 // does not pay attention to this fact and only looks at the
243 // parameter block pairs that the user provides.
244 //
245 // X . . X X X X
246 // . X X . . . .
247 // . X X . . . .
248 // . . . X X X X
249 // . . . X X X X
250 // . . . X X X X
251 // . . . X X X X
252
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800253 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800254 int expected_rows[] = {0, 5, 7, 9, 13, 17, 21, 25};
255 int expected_cols[] = {0, 3, 4, 5, 6,
256 1, 2,
257 1, 2,
258 3, 4, 5, 6,
259 3, 4, 5, 6,
260 3, 4, 5, 6,
261 3, 4, 5, 6};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800262 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800263
Austin Schuh3de38b02024-06-25 18:25:10 -0700264 std::vector<std::pair<const double*, const double*>> covariance_blocks;
265 covariance_blocks.emplace_back(block1, block1);
266 covariance_blocks.emplace_back(block4, block4);
267 covariance_blocks.emplace_back(block2, block2);
268 covariance_blocks.emplace_back(block3, block3);
269 covariance_blocks.emplace_back(block2, block3);
270 covariance_blocks.emplace_back(block4, block1); // reversed
Austin Schuh70cc9552019-01-21 19:46:48 -0800271
272 Covariance::Options options;
273 CovarianceImpl covariance_impl(options);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800274 EXPECT_TRUE(
275 covariance_impl.ComputeCovarianceSparsity(covariance_blocks, &problem));
Austin Schuh70cc9552019-01-21 19:46:48 -0800276
277 const CompressedRowSparseMatrix* crsm = covariance_impl.covariance_matrix();
278
279 EXPECT_EQ(crsm->num_rows(), 7);
280 EXPECT_EQ(crsm->num_cols(), 7);
281 EXPECT_EQ(crsm->num_nonzeros(), 25);
282
283 const int* rows = crsm->rows();
284 for (int r = 0; r < crsm->num_rows() + 1; ++r) {
285 EXPECT_EQ(rows[r], expected_rows[r])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800286 << r << " " << rows[r] << " " << expected_rows[r];
Austin Schuh70cc9552019-01-21 19:46:48 -0800287 }
288
289 const int* cols = crsm->cols();
290 for (int c = 0; c < crsm->num_nonzeros(); ++c) {
291 EXPECT_EQ(cols[c], expected_cols[c])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800292 << c << " " << cols[c] << " " << expected_cols[c];
Austin Schuh70cc9552019-01-21 19:46:48 -0800293 }
294}
295
296TEST(CovarianceImpl, ComputeCovarianceSparsityWithFreeParameterBlock) {
297 double parameters[10];
298
299 double* block1 = parameters;
300 double* block2 = block1 + 1;
301 double* block3 = block2 + 2;
302 double* block4 = block3 + 3;
303
304 ProblemImpl problem;
305
306 // Add in random order
307 Vector junk_jacobian = Vector::Zero(10);
308 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700309 new UnaryCostFunction(1, 1, junk_jacobian.data()), nullptr, block1);
Austin Schuh70cc9552019-01-21 19:46:48 -0800310 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700311 new UnaryCostFunction(1, 4, junk_jacobian.data()), nullptr, block4);
Austin Schuh70cc9552019-01-21 19:46:48 -0800312 problem.AddParameterBlock(block3, 3);
313 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700314 new UnaryCostFunction(1, 2, junk_jacobian.data()), nullptr, block2);
Austin Schuh70cc9552019-01-21 19:46:48 -0800315
316 // Sparsity pattern
317 //
318 // Note that the problem structure does not imply this sparsity
319 // pattern since all the residual blocks are unary. But the
320 // ComputeCovarianceSparsity function in its current incarnation
321 // does not pay attention to this fact and only looks at the
322 // parameter block pairs that the user provides.
323 //
324 // X . . X X X X
325 // . X X . . . .
326 // . X X . . . .
327 // . . . X X X X
328 // . . . X X X X
329 // . . . X X X X
330 // . . . X X X X
331
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800332 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800333 int expected_rows[] = {0, 5, 7, 9, 13, 17, 21, 25};
334 int expected_cols[] = {0, 3, 4, 5, 6,
335 1, 2,
336 1, 2,
337 3, 4, 5, 6,
338 3, 4, 5, 6,
339 3, 4, 5, 6,
340 3, 4, 5, 6};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800341 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800342
Austin Schuh3de38b02024-06-25 18:25:10 -0700343 std::vector<std::pair<const double*, const double*>> covariance_blocks;
344 covariance_blocks.emplace_back(block1, block1);
345 covariance_blocks.emplace_back(block4, block4);
346 covariance_blocks.emplace_back(block2, block2);
347 covariance_blocks.emplace_back(block3, block3);
348 covariance_blocks.emplace_back(block2, block3);
349 covariance_blocks.emplace_back(block4, block1); // reversed
Austin Schuh70cc9552019-01-21 19:46:48 -0800350
351 Covariance::Options options;
352 CovarianceImpl covariance_impl(options);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800353 EXPECT_TRUE(
354 covariance_impl.ComputeCovarianceSparsity(covariance_blocks, &problem));
Austin Schuh70cc9552019-01-21 19:46:48 -0800355
356 const CompressedRowSparseMatrix* crsm = covariance_impl.covariance_matrix();
357
358 EXPECT_EQ(crsm->num_rows(), 7);
359 EXPECT_EQ(crsm->num_cols(), 7);
360 EXPECT_EQ(crsm->num_nonzeros(), 25);
361
362 const int* rows = crsm->rows();
363 for (int r = 0; r < crsm->num_rows() + 1; ++r) {
364 EXPECT_EQ(rows[r], expected_rows[r])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800365 << r << " " << rows[r] << " " << expected_rows[r];
Austin Schuh70cc9552019-01-21 19:46:48 -0800366 }
367
368 const int* cols = crsm->cols();
369 for (int c = 0; c < crsm->num_nonzeros(); ++c) {
370 EXPECT_EQ(cols[c], expected_cols[c])
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800371 << c << " " << cols[c] << " " << expected_cols[c];
Austin Schuh70cc9552019-01-21 19:46:48 -0800372 }
373}
374
Austin Schuh3de38b02024-06-25 18:25:10 -0700375// x_plus_delta = delta * x;
376class PolynomialManifold : public Manifold {
377 public:
378 bool Plus(const double* x,
379 const double* delta,
380 double* x_plus_delta) const final {
381 x_plus_delta[0] = delta[0] * x[0];
382 x_plus_delta[1] = delta[0] * x[1];
383 return true;
384 }
385
386 bool Minus(const double* y, const double* x, double* y_minus_x) const final {
387 LOG(FATAL) << "Should not be called";
388 return true;
389 }
390
391 bool PlusJacobian(const double* x, double* jacobian) const final {
392 jacobian[0] = x[0];
393 jacobian[1] = x[1];
394 return true;
395 }
396
397 bool MinusJacobian(const double* x, double* jacobian) const final {
398 LOG(FATAL) << "Should not be called";
399 return true;
400 }
401
402 int AmbientSize() const final { return 2; }
403 int TangentSize() const final { return 1; }
404};
405
Austin Schuh70cc9552019-01-21 19:46:48 -0800406class CovarianceTest : public ::testing::Test {
407 protected:
Austin Schuh3de38b02024-06-25 18:25:10 -0700408 // TODO(sameeragarwal): Investigate if this should be an ordered or an
409 // unordered map.
410 using BoundsMap = std::map<const double*, std::pair<int, int>>;
Austin Schuh70cc9552019-01-21 19:46:48 -0800411
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800412 void SetUp() override {
Austin Schuh70cc9552019-01-21 19:46:48 -0800413 double* x = parameters_;
414 double* y = x + 2;
415 double* z = y + 3;
416
417 x[0] = 1;
418 x[1] = 1;
419 y[0] = 2;
420 y[1] = 2;
421 y[2] = 2;
422 z[0] = 3;
423
424 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800425 double jacobian[] = {1.0, 0.0, 0.0, 1.0};
Austin Schuh3de38b02024-06-25 18:25:10 -0700426 problem_.AddResidualBlock(
427 new UnaryCostFunction(2, 2, jacobian), nullptr, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800428 }
429
430 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800431 double jacobian[] = {2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0};
Austin Schuh3de38b02024-06-25 18:25:10 -0700432 problem_.AddResidualBlock(
433 new UnaryCostFunction(3, 3, jacobian), nullptr, y);
Austin Schuh70cc9552019-01-21 19:46:48 -0800434 }
435
436 {
437 double jacobian = 5.0;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800438 problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700439 new UnaryCostFunction(1, 1, &jacobian), nullptr, z);
Austin Schuh70cc9552019-01-21 19:46:48 -0800440 }
441
442 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800443 double jacobian1[] = {1.0, 2.0, 3.0};
444 double jacobian2[] = {-5.0, -6.0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800445 problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700446 new BinaryCostFunction(1, 3, 2, jacobian1, jacobian2), nullptr, y, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800447 }
448
449 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800450 double jacobian1[] = {2.0};
451 double jacobian2[] = {3.0, -2.0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800452 problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700453 new BinaryCostFunction(1, 1, 2, jacobian1, jacobian2), nullptr, z, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800454 }
455
Austin Schuh3de38b02024-06-25 18:25:10 -0700456 all_covariance_blocks_.emplace_back(x, x);
457 all_covariance_blocks_.emplace_back(y, y);
458 all_covariance_blocks_.emplace_back(z, z);
459 all_covariance_blocks_.emplace_back(x, y);
460 all_covariance_blocks_.emplace_back(x, z);
461 all_covariance_blocks_.emplace_back(y, z);
Austin Schuh70cc9552019-01-21 19:46:48 -0800462
Austin Schuh3de38b02024-06-25 18:25:10 -0700463 column_bounds_[x] = std::make_pair(0, 2);
464 column_bounds_[y] = std::make_pair(2, 5);
465 column_bounds_[z] = std::make_pair(5, 6);
Austin Schuh70cc9552019-01-21 19:46:48 -0800466 }
467
468 // Computes covariance in ambient space.
469 void ComputeAndCompareCovarianceBlocks(const Covariance::Options& options,
470 const double* expected_covariance) {
471 ComputeAndCompareCovarianceBlocksInTangentOrAmbientSpace(
472 options,
473 true, // ambient
474 expected_covariance);
475 }
476
477 // Computes covariance in tangent space.
478 void ComputeAndCompareCovarianceBlocksInTangentSpace(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800479 const Covariance::Options& options, const double* expected_covariance) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800480 ComputeAndCompareCovarianceBlocksInTangentOrAmbientSpace(
481 options,
482 false, // tangent
483 expected_covariance);
484 }
485
486 void ComputeAndCompareCovarianceBlocksInTangentOrAmbientSpace(
487 const Covariance::Options& options,
488 bool lift_covariance_to_ambient_space,
489 const double* expected_covariance) {
490 // Generate all possible combination of block pairs and check if the
491 // covariance computation is correct.
492 for (int i = 0; i <= 64; ++i) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700493 std::vector<std::pair<const double*, const double*>> covariance_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800494 if (i & 1) {
495 covariance_blocks.push_back(all_covariance_blocks_[0]);
496 }
497
498 if (i & 2) {
499 covariance_blocks.push_back(all_covariance_blocks_[1]);
500 }
501
502 if (i & 4) {
503 covariance_blocks.push_back(all_covariance_blocks_[2]);
504 }
505
506 if (i & 8) {
507 covariance_blocks.push_back(all_covariance_blocks_[3]);
508 }
509
510 if (i & 16) {
511 covariance_blocks.push_back(all_covariance_blocks_[4]);
512 }
513
514 if (i & 32) {
515 covariance_blocks.push_back(all_covariance_blocks_[5]);
516 }
517
518 Covariance covariance(options);
519 EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem_));
520
Austin Schuh3de38b02024-06-25 18:25:10 -0700521 for (auto& covariance_block : covariance_blocks) {
522 const double* block1 = covariance_block.first;
523 const double* block2 = covariance_block.second;
Austin Schuh70cc9552019-01-21 19:46:48 -0800524 // block1, block2
525 GetCovarianceBlockAndCompare(block1,
526 block2,
527 lift_covariance_to_ambient_space,
528 covariance,
529 expected_covariance);
530 // block2, block1
531 GetCovarianceBlockAndCompare(block2,
532 block1,
533 lift_covariance_to_ambient_space,
534 covariance,
535 expected_covariance);
536 }
537 }
538 }
539
540 void GetCovarianceBlockAndCompare(const double* block1,
541 const double* block2,
542 bool lift_covariance_to_ambient_space,
543 const Covariance& covariance,
544 const double* expected_covariance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800545 const BoundsMap& column_bounds = lift_covariance_to_ambient_space
546 ? column_bounds_
547 : local_column_bounds_;
Austin Schuh70cc9552019-01-21 19:46:48 -0800548 const int row_begin = FindOrDie(column_bounds, block1).first;
549 const int row_end = FindOrDie(column_bounds, block1).second;
550 const int col_begin = FindOrDie(column_bounds, block2).first;
551 const int col_end = FindOrDie(column_bounds, block2).second;
552
553 Matrix actual(row_end - row_begin, col_end - col_begin);
554 if (lift_covariance_to_ambient_space) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800555 EXPECT_TRUE(covariance.GetCovarianceBlock(block1, block2, actual.data()));
Austin Schuh70cc9552019-01-21 19:46:48 -0800556 } else {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800557 EXPECT_TRUE(covariance.GetCovarianceBlockInTangentSpace(
558 block1, block2, actual.data()));
Austin Schuh70cc9552019-01-21 19:46:48 -0800559 }
560
561 int dof = 0; // degrees of freedom = sum of LocalSize()s
562 for (const auto& bound : column_bounds) {
563 dof = std::max(dof, bound.second.second);
564 }
565 ConstMatrixRef expected(expected_covariance, dof, dof);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800566 double diff_norm =
567 (expected.block(
568 row_begin, col_begin, row_end - row_begin, col_end - col_begin) -
569 actual)
570 .norm();
Austin Schuh70cc9552019-01-21 19:46:48 -0800571 diff_norm /= (row_end - row_begin) * (col_end - col_begin);
572
573 const double kTolerance = 1e-5;
574 EXPECT_NEAR(diff_norm, 0.0, kTolerance)
575 << "rows: " << row_begin << " " << row_end << " "
576 << "cols: " << col_begin << " " << col_end << " "
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800577 << "\n\n expected: \n "
578 << expected.block(
579 row_begin, col_begin, row_end - row_begin, col_end - col_begin)
580 << "\n\n actual: \n " << actual << "\n\n full expected: \n"
581 << expected;
Austin Schuh70cc9552019-01-21 19:46:48 -0800582 }
583
584 double parameters_[6];
585 Problem problem_;
Austin Schuh3de38b02024-06-25 18:25:10 -0700586 std::vector<std::pair<const double*, const double*>> all_covariance_blocks_;
Austin Schuh70cc9552019-01-21 19:46:48 -0800587 BoundsMap column_bounds_;
588 BoundsMap local_column_bounds_;
589};
590
Austin Schuh70cc9552019-01-21 19:46:48 -0800591TEST_F(CovarianceTest, NormalBehavior) {
592 // J
593 //
594 // 1 0 0 0 0 0
595 // 0 1 0 0 0 0
596 // 0 0 2 0 0 0
597 // 0 0 0 2 0 0
598 // 0 0 0 0 2 0
599 // 0 0 0 0 0 5
600 // -5 -6 1 2 3 0
601 // 3 -2 0 0 0 2
602
603 // J'J
604 //
605 // 35 24 -5 -10 -15 6
606 // 24 41 -6 -12 -18 -4
607 // -5 -6 5 2 3 0
608 // -10 -12 2 8 6 0
609 // -15 -18 3 6 13 0
610 // 6 -4 0 0 0 29
611
612 // inv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800613 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800614 double expected_covariance[] = {
615 7.0747e-02, -8.4923e-03, 1.6821e-02, 3.3643e-02, 5.0464e-02, -1.5809e-02, // NOLINT
616 -8.4923e-03, 8.1352e-02, 2.4758e-02, 4.9517e-02, 7.4275e-02, 1.2978e-02, // NOLINT
617 1.6821e-02, 2.4758e-02, 2.4904e-01, -1.9271e-03, -2.8906e-03, -6.5325e-05, // NOLINT
618 3.3643e-02, 4.9517e-02, -1.9271e-03, 2.4615e-01, -5.7813e-03, -1.3065e-04, // NOLINT
619 5.0464e-02, 7.4275e-02, -2.8906e-03, -5.7813e-03, 2.4133e-01, -1.9598e-04, // NOLINT
620 -1.5809e-02, 1.2978e-02, -6.5325e-05, -1.3065e-04, -1.9598e-04, 3.9544e-02, // NOLINT
621 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800622 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800623
624 Covariance::Options options;
625
626#ifndef CERES_NO_SUITESPARSE
627 options.algorithm_type = SPARSE_QR;
628 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
629 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
630#endif
631
632 options.algorithm_type = DENSE_SVD;
633 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
634
635 options.algorithm_type = SPARSE_QR;
636 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
637 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
638}
639
Austin Schuh70cc9552019-01-21 19:46:48 -0800640TEST_F(CovarianceTest, ThreadedNormalBehavior) {
641 // J
642 //
643 // 1 0 0 0 0 0
644 // 0 1 0 0 0 0
645 // 0 0 2 0 0 0
646 // 0 0 0 2 0 0
647 // 0 0 0 0 2 0
648 // 0 0 0 0 0 5
649 // -5 -6 1 2 3 0
650 // 3 -2 0 0 0 2
651
652 // J'J
653 //
654 // 35 24 -5 -10 -15 6
655 // 24 41 -6 -12 -18 -4
656 // -5 -6 5 2 3 0
657 // -10 -12 2 8 6 0
658 // -15 -18 3 6 13 0
659 // 6 -4 0 0 0 29
660
661 // inv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800662 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800663 double expected_covariance[] = {
664 7.0747e-02, -8.4923e-03, 1.6821e-02, 3.3643e-02, 5.0464e-02, -1.5809e-02, // NOLINT
665 -8.4923e-03, 8.1352e-02, 2.4758e-02, 4.9517e-02, 7.4275e-02, 1.2978e-02, // NOLINT
666 1.6821e-02, 2.4758e-02, 2.4904e-01, -1.9271e-03, -2.8906e-03, -6.5325e-05, // NOLINT
667 3.3643e-02, 4.9517e-02, -1.9271e-03, 2.4615e-01, -5.7813e-03, -1.3065e-04, // NOLINT
668 5.0464e-02, 7.4275e-02, -2.8906e-03, -5.7813e-03, 2.4133e-01, -1.9598e-04, // NOLINT
669 -1.5809e-02, 1.2978e-02, -6.5325e-05, -1.3065e-04, -1.9598e-04, 3.9544e-02, // NOLINT
670 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800671 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800672
673 Covariance::Options options;
674 options.num_threads = 4;
675
676#ifndef CERES_NO_SUITESPARSE
677 options.algorithm_type = SPARSE_QR;
678 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
679 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
680#endif
681
682 options.algorithm_type = DENSE_SVD;
683 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
684
685 options.algorithm_type = SPARSE_QR;
686 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
687 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
688}
689
Austin Schuh70cc9552019-01-21 19:46:48 -0800690TEST_F(CovarianceTest, ConstantParameterBlock) {
691 problem_.SetParameterBlockConstant(parameters_);
692
693 // J
694 //
695 // 0 0 0 0 0 0
696 // 0 0 0 0 0 0
697 // 0 0 2 0 0 0
698 // 0 0 0 2 0 0
699 // 0 0 0 0 2 0
700 // 0 0 0 0 0 5
701 // 0 0 1 2 3 0
702 // 0 0 0 0 0 2
703
704 // J'J
705 //
706 // 0 0 0 0 0 0
707 // 0 0 0 0 0 0
708 // 0 0 5 2 3 0
709 // 0 0 2 8 6 0
710 // 0 0 3 6 13 0
711 // 0 0 0 0 0 29
712
713 // pinv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800714 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800715 double expected_covariance[] = {
716 0, 0, 0, 0, 0, 0, // NOLINT
717 0, 0, 0, 0, 0, 0, // NOLINT
718 0, 0, 0.23611, -0.02778, -0.04167, -0.00000, // NOLINT
719 0, 0, -0.02778, 0.19444, -0.08333, -0.00000, // NOLINT
720 0, 0, -0.04167, -0.08333, 0.12500, -0.00000, // NOLINT
721 0, 0, -0.00000, -0.00000, -0.00000, 0.03448 // NOLINT
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800722 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800723 };
724
725 Covariance::Options options;
726
727#ifndef CERES_NO_SUITESPARSE
728 options.algorithm_type = SPARSE_QR;
729 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
730 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
731#endif
732
733 options.algorithm_type = DENSE_SVD;
734 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
735
736 options.algorithm_type = SPARSE_QR;
737 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
738 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
739}
740
Austin Schuh3de38b02024-06-25 18:25:10 -0700741TEST_F(CovarianceTest, Manifold) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800742 double* x = parameters_;
743 double* y = x + 2;
744
Austin Schuh3de38b02024-06-25 18:25:10 -0700745 problem_.SetManifold(x, new PolynomialManifold);
Austin Schuh70cc9552019-01-21 19:46:48 -0800746
Austin Schuh3de38b02024-06-25 18:25:10 -0700747 std::vector<int> subset;
Austin Schuh70cc9552019-01-21 19:46:48 -0800748 subset.push_back(2);
Austin Schuh3de38b02024-06-25 18:25:10 -0700749 problem_.SetManifold(y, new SubsetManifold(3, subset));
Austin Schuh70cc9552019-01-21 19:46:48 -0800750
751 // Raw Jacobian: J
752 //
753 // 1 0 0 0 0 0
754 // 0 1 0 0 0 0
755 // 0 0 2 0 0 0
756 // 0 0 0 2 0 0
757 // 0 0 0 0 2 0
758 // 0 0 0 0 0 5
759 // -5 -6 1 2 3 0
760 // 3 -2 0 0 0 2
761
762 // Local to global jacobian: A
763 //
764 // 1 0 0 0
765 // 1 0 0 0
766 // 0 1 0 0
767 // 0 0 1 0
768 // 0 0 0 0
769 // 0 0 0 1
770
771 // A * inv((J*A)'*(J*A)) * A'
772 // Computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800773 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800774 double expected_covariance[] = {
775 0.01766, 0.01766, 0.02158, 0.04316, 0.00000, -0.00122,
776 0.01766, 0.01766, 0.02158, 0.04316, 0.00000, -0.00122,
777 0.02158, 0.02158, 0.24860, -0.00281, 0.00000, -0.00149,
778 0.04316, 0.04316, -0.00281, 0.24439, 0.00000, -0.00298,
779 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000,
780 -0.00122, -0.00122, -0.00149, -0.00298, 0.00000, 0.03457
781 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800782 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800783
784 Covariance::Options options;
785
786#ifndef CERES_NO_SUITESPARSE
787 options.algorithm_type = SPARSE_QR;
788 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
789 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
790#endif
791
792 options.algorithm_type = DENSE_SVD;
793 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
794
795 options.algorithm_type = SPARSE_QR;
796 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
797 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
798}
799
Austin Schuh3de38b02024-06-25 18:25:10 -0700800TEST_F(CovarianceTest, ManifoldInTangentSpace) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800801 double* x = parameters_;
802 double* y = x + 2;
803 double* z = y + 3;
804
Austin Schuh3de38b02024-06-25 18:25:10 -0700805 problem_.SetManifold(x, new PolynomialManifold);
Austin Schuh70cc9552019-01-21 19:46:48 -0800806
Austin Schuh3de38b02024-06-25 18:25:10 -0700807 std::vector<int> subset;
Austin Schuh70cc9552019-01-21 19:46:48 -0800808 subset.push_back(2);
Austin Schuh3de38b02024-06-25 18:25:10 -0700809 problem_.SetManifold(y, new SubsetManifold(3, subset));
Austin Schuh70cc9552019-01-21 19:46:48 -0800810
Austin Schuh3de38b02024-06-25 18:25:10 -0700811 local_column_bounds_[x] = std::make_pair(0, 1);
812 local_column_bounds_[y] = std::make_pair(1, 3);
813 local_column_bounds_[z] = std::make_pair(3, 4);
Austin Schuh70cc9552019-01-21 19:46:48 -0800814
815 // Raw Jacobian: J
816 //
817 // 1 0 0 0 0 0
818 // 0 1 0 0 0 0
819 // 0 0 2 0 0 0
820 // 0 0 0 2 0 0
821 // 0 0 0 0 2 0
822 // 0 0 0 0 0 5
823 // -5 -6 1 2 3 0
824 // 3 -2 0 0 0 2
825
826 // Local to global jacobian: A
827 //
828 // 1 0 0 0
829 // 1 0 0 0
830 // 0 1 0 0
831 // 0 0 1 0
832 // 0 0 0 0
833 // 0 0 0 1
834
835 // inv((J*A)'*(J*A))
836 // Computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800837 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800838 double expected_covariance[] = {
839 0.01766, 0.02158, 0.04316, -0.00122,
840 0.02158, 0.24860, -0.00281, -0.00149,
841 0.04316, -0.00281, 0.24439, -0.00298,
842 -0.00122, -0.00149, -0.00298, 0.03457 // NOLINT
843 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800844 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800845
846 Covariance::Options options;
847
848#ifndef CERES_NO_SUITESPARSE
849 options.algorithm_type = SPARSE_QR;
850 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
851
852 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
853#endif
854
855 options.algorithm_type = DENSE_SVD;
856 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
857
858 options.algorithm_type = SPARSE_QR;
859 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
860 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
861}
862
Austin Schuh3de38b02024-06-25 18:25:10 -0700863TEST_F(CovarianceTest, ManifoldInTangentSpaceWithConstantBlocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800864 double* x = parameters_;
865 double* y = x + 2;
866 double* z = y + 3;
867
Austin Schuh3de38b02024-06-25 18:25:10 -0700868 problem_.SetManifold(x, new PolynomialManifold);
Austin Schuh70cc9552019-01-21 19:46:48 -0800869 problem_.SetParameterBlockConstant(x);
870
Austin Schuh3de38b02024-06-25 18:25:10 -0700871 std::vector<int> subset;
Austin Schuh70cc9552019-01-21 19:46:48 -0800872 subset.push_back(2);
Austin Schuh3de38b02024-06-25 18:25:10 -0700873 problem_.SetManifold(y, new SubsetManifold(3, subset));
Austin Schuh70cc9552019-01-21 19:46:48 -0800874 problem_.SetParameterBlockConstant(y);
875
Austin Schuh3de38b02024-06-25 18:25:10 -0700876 local_column_bounds_[x] = std::make_pair(0, 1);
877 local_column_bounds_[y] = std::make_pair(1, 3);
878 local_column_bounds_[z] = std::make_pair(3, 4);
Austin Schuh70cc9552019-01-21 19:46:48 -0800879
880 // Raw Jacobian: J
881 //
882 // 1 0 0 0 0 0
883 // 0 1 0 0 0 0
884 // 0 0 2 0 0 0
885 // 0 0 0 2 0 0
886 // 0 0 0 0 2 0
887 // 0 0 0 0 0 5
888 // -5 -6 1 2 3 0
889 // 3 -2 0 0 0 2
890
891 // Local to global jacobian: A
892 //
893 // 0 0 0 0
894 // 0 0 0 0
895 // 0 0 0 0
896 // 0 0 0 0
897 // 0 0 0 0
898 // 0 0 0 1
899
900 // pinv((J*A)'*(J*A))
901 // Computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800902 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800903 double expected_covariance[] = {
904 0.0, 0.0, 0.0, 0.0,
905 0.0, 0.0, 0.0, 0.0,
906 0.0, 0.0, 0.0, 0.0,
907 0.0, 0.0, 0.0, 0.034482 // NOLINT
908 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800909 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800910
911 Covariance::Options options;
912
913#ifndef CERES_NO_SUITESPARSE
914 options.algorithm_type = SPARSE_QR;
915 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
916
917 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
918#endif
919
920 options.algorithm_type = DENSE_SVD;
921 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
922
923 options.algorithm_type = SPARSE_QR;
924 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
925 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
926}
927
928TEST_F(CovarianceTest, TruncatedRank) {
929 // J
930 //
931 // 1 0 0 0 0 0
932 // 0 1 0 0 0 0
933 // 0 0 2 0 0 0
934 // 0 0 0 2 0 0
935 // 0 0 0 0 2 0
936 // 0 0 0 0 0 5
937 // -5 -6 1 2 3 0
938 // 3 -2 0 0 0 2
939
940 // J'J
941 //
942 // 35 24 -5 -10 -15 6
943 // 24 41 -6 -12 -18 -4
944 // -5 -6 5 2 3 0
945 // -10 -12 2 8 6 0
946 // -15 -18 3 6 13 0
947 // 6 -4 0 0 0 29
948
Austin Schuh3de38b02024-06-25 18:25:10 -0700949 // 3.4142 is the smallest eigenvalue of J'J. The following matrix
Austin Schuh70cc9552019-01-21 19:46:48 -0800950 // was obtained by dropping the eigenvector corresponding to this
951 // eigenvalue.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800952 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800953 double expected_covariance[] = {
954 5.4135e-02, -3.5121e-02, 1.7257e-04, 3.4514e-04, 5.1771e-04, -1.6076e-02, // NOLINT
955 -3.5121e-02, 3.8667e-02, -1.9288e-03, -3.8576e-03, -5.7864e-03, 1.2549e-02, // NOLINT
956 1.7257e-04, -1.9288e-03, 2.3235e-01, -3.5297e-02, -5.2946e-02, -3.3329e-04, // NOLINT
957 3.4514e-04, -3.8576e-03, -3.5297e-02, 1.7941e-01, -1.0589e-01, -6.6659e-04, // NOLINT
958 5.1771e-04, -5.7864e-03, -5.2946e-02, -1.0589e-01, 9.1162e-02, -9.9988e-04, // NOLINT
959 -1.6076e-02, 1.2549e-02, -3.3329e-04, -6.6659e-04, -9.9988e-04, 3.9539e-02 // NOLINT
960 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800961 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800962
963 {
964 Covariance::Options options;
965 options.algorithm_type = DENSE_SVD;
966 // Force dropping of the smallest eigenvector.
967 options.null_space_rank = 1;
968 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
969 }
970
971 {
972 Covariance::Options options;
973 options.algorithm_type = DENSE_SVD;
974 // Force dropping of the smallest eigenvector via the ratio but
975 // automatic truncation.
976 options.min_reciprocal_condition_number = 0.044494;
977 options.null_space_rank = -1;
978 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
979 }
980}
981
982TEST_F(CovarianceTest, DenseCovarianceMatrixFromSetOfParameters) {
983 Covariance::Options options;
984 Covariance covariance(options);
985 double* x = parameters_;
986 double* y = x + 2;
987 double* z = y + 3;
Austin Schuh3de38b02024-06-25 18:25:10 -0700988 std::vector<const double*> parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800989 parameter_blocks.push_back(x);
990 parameter_blocks.push_back(y);
991 parameter_blocks.push_back(z);
992 covariance.Compute(parameter_blocks, &problem_);
993 double expected_covariance[36];
994 covariance.GetCovarianceMatrix(parameter_blocks, expected_covariance);
995
996#ifndef CERES_NO_SUITESPARSE
997 options.algorithm_type = SPARSE_QR;
998 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
999 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1000#endif
1001
1002 options.algorithm_type = DENSE_SVD;
1003 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1004
1005 options.algorithm_type = SPARSE_QR;
1006 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
1007 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1008}
1009
1010TEST_F(CovarianceTest, DenseCovarianceMatrixFromSetOfParametersThreaded) {
1011 Covariance::Options options;
1012 options.num_threads = 4;
1013 Covariance covariance(options);
1014 double* x = parameters_;
1015 double* y = x + 2;
1016 double* z = y + 3;
Austin Schuh3de38b02024-06-25 18:25:10 -07001017 std::vector<const double*> parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -08001018 parameter_blocks.push_back(x);
1019 parameter_blocks.push_back(y);
1020 parameter_blocks.push_back(z);
1021 covariance.Compute(parameter_blocks, &problem_);
1022 double expected_covariance[36];
1023 covariance.GetCovarianceMatrix(parameter_blocks, expected_covariance);
1024
1025#ifndef CERES_NO_SUITESPARSE
1026 options.algorithm_type = SPARSE_QR;
1027 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
1028 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1029#endif
1030
1031 options.algorithm_type = DENSE_SVD;
1032 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1033
1034 options.algorithm_type = SPARSE_QR;
1035 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
1036 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1037}
1038
1039TEST_F(CovarianceTest, DenseCovarianceMatrixFromSetOfParametersInTangentSpace) {
1040 Covariance::Options options;
1041 Covariance covariance(options);
1042 double* x = parameters_;
1043 double* y = x + 2;
1044 double* z = y + 3;
1045
Austin Schuh3de38b02024-06-25 18:25:10 -07001046 problem_.SetManifold(x, new PolynomialManifold);
Austin Schuh70cc9552019-01-21 19:46:48 -08001047
Austin Schuh3de38b02024-06-25 18:25:10 -07001048 std::vector<int> subset;
Austin Schuh70cc9552019-01-21 19:46:48 -08001049 subset.push_back(2);
Austin Schuh3de38b02024-06-25 18:25:10 -07001050 problem_.SetManifold(y, new SubsetManifold(3, subset));
Austin Schuh70cc9552019-01-21 19:46:48 -08001051
Austin Schuh3de38b02024-06-25 18:25:10 -07001052 local_column_bounds_[x] = std::make_pair(0, 1);
1053 local_column_bounds_[y] = std::make_pair(1, 3);
1054 local_column_bounds_[z] = std::make_pair(3, 4);
Austin Schuh70cc9552019-01-21 19:46:48 -08001055
Austin Schuh3de38b02024-06-25 18:25:10 -07001056 std::vector<const double*> parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -08001057 parameter_blocks.push_back(x);
1058 parameter_blocks.push_back(y);
1059 parameter_blocks.push_back(z);
1060 covariance.Compute(parameter_blocks, &problem_);
1061 double expected_covariance[16];
1062 covariance.GetCovarianceMatrixInTangentSpace(parameter_blocks,
1063 expected_covariance);
1064
1065#ifndef CERES_NO_SUITESPARSE
1066 options.algorithm_type = SPARSE_QR;
1067 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
1068
1069 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
1070#endif
1071
1072 options.algorithm_type = DENSE_SVD;
1073 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
1074
1075 options.algorithm_type = SPARSE_QR;
1076 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
1077 ComputeAndCompareCovarianceBlocksInTangentSpace(options, expected_covariance);
1078}
1079
1080TEST_F(CovarianceTest, ComputeCovarianceFailure) {
1081 Covariance::Options options;
1082 Covariance covariance(options);
1083 double* x = parameters_;
1084 double* y = x + 2;
Austin Schuh3de38b02024-06-25 18:25:10 -07001085 std::vector<const double*> parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -08001086 parameter_blocks.push_back(x);
1087 parameter_blocks.push_back(x);
1088 parameter_blocks.push_back(y);
1089 parameter_blocks.push_back(y);
1090 EXPECT_DEATH_IF_SUPPORTED(covariance.Compute(parameter_blocks, &problem_),
1091 "Covariance::Compute called with duplicate blocks "
1092 "at indices \\(0, 1\\) and \\(2, 3\\)");
Austin Schuh3de38b02024-06-25 18:25:10 -07001093 std::vector<std::pair<const double*, const double*>> covariance_blocks;
1094 covariance_blocks.emplace_back(x, x);
1095 covariance_blocks.emplace_back(x, x);
1096 covariance_blocks.emplace_back(y, y);
1097 covariance_blocks.emplace_back(y, y);
Austin Schuh70cc9552019-01-21 19:46:48 -08001098 EXPECT_DEATH_IF_SUPPORTED(covariance.Compute(covariance_blocks, &problem_),
1099 "Covariance::Compute called with duplicate blocks "
1100 "at indices \\(0, 1\\) and \\(2, 3\\)");
1101}
1102
1103class RankDeficientCovarianceTest : public CovarianceTest {
1104 protected:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001105 void SetUp() final {
Austin Schuh70cc9552019-01-21 19:46:48 -08001106 double* x = parameters_;
1107 double* y = x + 2;
1108 double* z = y + 3;
1109
1110 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001111 double jacobian[] = {1.0, 0.0, 0.0, 1.0};
Austin Schuh3de38b02024-06-25 18:25:10 -07001112 problem_.AddResidualBlock(
1113 new UnaryCostFunction(2, 2, jacobian), nullptr, x);
Austin Schuh70cc9552019-01-21 19:46:48 -08001114 }
1115
1116 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001117 double jacobian[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
Austin Schuh3de38b02024-06-25 18:25:10 -07001118 problem_.AddResidualBlock(
1119 new UnaryCostFunction(3, 3, jacobian), nullptr, y);
Austin Schuh70cc9552019-01-21 19:46:48 -08001120 }
1121
1122 {
1123 double jacobian = 5.0;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001124 problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -07001125 new UnaryCostFunction(1, 1, &jacobian), nullptr, z);
Austin Schuh70cc9552019-01-21 19:46:48 -08001126 }
1127
1128 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001129 double jacobian1[] = {0.0, 0.0, 0.0};
1130 double jacobian2[] = {-5.0, -6.0};
Austin Schuh70cc9552019-01-21 19:46:48 -08001131 problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -07001132 new BinaryCostFunction(1, 3, 2, jacobian1, jacobian2), nullptr, y, x);
Austin Schuh70cc9552019-01-21 19:46:48 -08001133 }
1134
1135 {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001136 double jacobian1[] = {2.0};
1137 double jacobian2[] = {3.0, -2.0};
Austin Schuh70cc9552019-01-21 19:46:48 -08001138 problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -07001139 new BinaryCostFunction(1, 1, 2, jacobian1, jacobian2), nullptr, z, x);
Austin Schuh70cc9552019-01-21 19:46:48 -08001140 }
1141
Austin Schuh3de38b02024-06-25 18:25:10 -07001142 all_covariance_blocks_.emplace_back(x, x);
1143 all_covariance_blocks_.emplace_back(y, y);
1144 all_covariance_blocks_.emplace_back(z, z);
1145 all_covariance_blocks_.emplace_back(x, y);
1146 all_covariance_blocks_.emplace_back(x, z);
1147 all_covariance_blocks_.emplace_back(y, z);
Austin Schuh70cc9552019-01-21 19:46:48 -08001148
Austin Schuh3de38b02024-06-25 18:25:10 -07001149 column_bounds_[x] = std::make_pair(0, 2);
1150 column_bounds_[y] = std::make_pair(2, 5);
1151 column_bounds_[z] = std::make_pair(5, 6);
Austin Schuh70cc9552019-01-21 19:46:48 -08001152 }
1153};
1154
1155TEST_F(RankDeficientCovarianceTest, AutomaticTruncation) {
1156 // J
1157 //
1158 // 1 0 0 0 0 0
1159 // 0 1 0 0 0 0
1160 // 0 0 0 0 0 0
1161 // 0 0 0 0 0 0
1162 // 0 0 0 0 0 0
1163 // 0 0 0 0 0 5
1164 // -5 -6 0 0 0 0
1165 // 3 -2 0 0 0 2
1166
1167 // J'J
1168 //
1169 // 35 24 0 0 0 6
1170 // 24 41 0 0 0 -4
1171 // 0 0 0 0 0 0
1172 // 0 0 0 0 0 0
1173 // 0 0 0 0 0 0
1174 // 6 -4 0 0 0 29
1175
1176 // pinv(J'J) computed using octave.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001177 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001178 double expected_covariance[] = {
1179 0.053998, -0.033145, 0.000000, 0.000000, 0.000000, -0.015744,
1180 -0.033145, 0.045067, 0.000000, 0.000000, 0.000000, 0.013074,
1181 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
1182 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
1183 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
1184 -0.015744, 0.013074, 0.000000, 0.000000, 0.000000, 0.039543
1185 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001186 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001187
1188 Covariance::Options options;
1189 options.algorithm_type = DENSE_SVD;
1190 options.null_space_rank = -1;
1191 ComputeAndCompareCovarianceBlocks(options, expected_covariance);
1192}
1193
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001194struct LinearCostFunction {
1195 template <typename T>
1196 bool operator()(const T* x, const T* y, T* residual) const {
1197 residual[0] = T(10.0) - *x;
1198 residual[1] = T(5.0) - *y;
1199 return true;
1200 }
1201 static CostFunction* Create() {
1202 return new AutoDiffCostFunction<LinearCostFunction, 2, 1, 1>(
1203 new LinearCostFunction);
1204 }
1205};
1206
Austin Schuh3de38b02024-06-25 18:25:10 -07001207TEST(Covariance, ZeroSizedManifoldGetCovariance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001208 double x = 0.0;
1209 double y = 1.0;
1210 Problem problem;
1211 problem.AddResidualBlock(LinearCostFunction::Create(), nullptr, &x, &y);
Austin Schuh3de38b02024-06-25 18:25:10 -07001212 problem.SetManifold(&y, new SubsetManifold(1, {0}));
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001213 // J = [-1 0]
1214 // [ 0 0]
1215 Covariance::Options options;
1216 options.algorithm_type = DENSE_SVD;
1217 Covariance covariance(options);
Austin Schuh3de38b02024-06-25 18:25:10 -07001218 std::vector<std::pair<const double*, const double*>> covariance_blocks;
1219 covariance_blocks.emplace_back(&x, &x);
1220 covariance_blocks.emplace_back(&x, &y);
1221 covariance_blocks.emplace_back(&y, &x);
1222 covariance_blocks.emplace_back(&y, &y);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001223 EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem));
1224
1225 double value = -1;
1226 covariance.GetCovarianceBlock(&x, &x, &value);
1227 EXPECT_NEAR(value, 1.0, std::numeric_limits<double>::epsilon());
1228
1229 value = -1;
1230 covariance.GetCovarianceBlock(&x, &y, &value);
1231 EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
1232
1233 value = -1;
1234 covariance.GetCovarianceBlock(&y, &x, &value);
1235 EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
1236
1237 value = -1;
1238 covariance.GetCovarianceBlock(&y, &y, &value);
1239 EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
1240}
1241
Austin Schuh3de38b02024-06-25 18:25:10 -07001242TEST(Covariance, ZeroSizedManifoldGetCovarianceInTangentSpace) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001243 double x = 0.0;
1244 double y = 1.0;
1245 Problem problem;
1246 problem.AddResidualBlock(LinearCostFunction::Create(), nullptr, &x, &y);
Austin Schuh3de38b02024-06-25 18:25:10 -07001247 problem.SetManifold(&y, new SubsetManifold(1, {0}));
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001248 // J = [-1 0]
1249 // [ 0 0]
1250 Covariance::Options options;
1251 options.algorithm_type = DENSE_SVD;
1252 Covariance covariance(options);
Austin Schuh3de38b02024-06-25 18:25:10 -07001253 std::vector<std::pair<const double*, const double*>> covariance_blocks;
1254 covariance_blocks.emplace_back(&x, &x);
1255 covariance_blocks.emplace_back(&x, &y);
1256 covariance_blocks.emplace_back(&y, &x);
1257 covariance_blocks.emplace_back(&y, &y);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001258 EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem));
1259
1260 double value = -1;
1261 covariance.GetCovarianceBlockInTangentSpace(&x, &x, &value);
1262 EXPECT_NEAR(value, 1.0, std::numeric_limits<double>::epsilon());
1263
1264 value = -1;
1265 // The following three calls, should not touch this value, since the
1266 // tangent space is of size zero
1267 covariance.GetCovarianceBlockInTangentSpace(&x, &y, &value);
1268 EXPECT_EQ(value, -1);
1269 covariance.GetCovarianceBlockInTangentSpace(&y, &x, &value);
1270 EXPECT_EQ(value, -1);
1271 covariance.GetCovarianceBlockInTangentSpace(&y, &y, &value);
1272 EXPECT_EQ(value, -1);
1273}
1274
Austin Schuh70cc9552019-01-21 19:46:48 -08001275class LargeScaleCovarianceTest : public ::testing::Test {
1276 protected:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001277 void SetUp() final {
Austin Schuh70cc9552019-01-21 19:46:48 -08001278 num_parameter_blocks_ = 2000;
1279 parameter_block_size_ = 5;
Austin Schuh3de38b02024-06-25 18:25:10 -07001280 parameters_ = std::make_unique<double[]>(parameter_block_size_ *
1281 num_parameter_blocks_);
Austin Schuh70cc9552019-01-21 19:46:48 -08001282
1283 Matrix jacobian(parameter_block_size_, parameter_block_size_);
1284 for (int i = 0; i < num_parameter_blocks_; ++i) {
1285 jacobian.setIdentity();
1286 jacobian *= (i + 1);
1287
1288 double* block_i = parameters_.get() + i * parameter_block_size_;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001289 problem_.AddResidualBlock(
1290 new UnaryCostFunction(
1291 parameter_block_size_, parameter_block_size_, jacobian.data()),
Austin Schuh3de38b02024-06-25 18:25:10 -07001292 nullptr,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001293 block_i);
Austin Schuh70cc9552019-01-21 19:46:48 -08001294 for (int j = i; j < num_parameter_blocks_; ++j) {
1295 double* block_j = parameters_.get() + j * parameter_block_size_;
Austin Schuh3de38b02024-06-25 18:25:10 -07001296 all_covariance_blocks_.emplace_back(block_i, block_j);
Austin Schuh70cc9552019-01-21 19:46:48 -08001297 }
1298 }
1299 }
1300
1301 void ComputeAndCompare(
1302 CovarianceAlgorithmType algorithm_type,
1303 SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
1304 int num_threads) {
1305 Covariance::Options options;
1306 options.algorithm_type = algorithm_type;
1307 options.sparse_linear_algebra_library_type =
1308 sparse_linear_algebra_library_type;
1309 options.num_threads = num_threads;
1310 Covariance covariance(options);
1311 EXPECT_TRUE(covariance.Compute(all_covariance_blocks_, &problem_));
1312
1313 Matrix expected(parameter_block_size_, parameter_block_size_);
1314 Matrix actual(parameter_block_size_, parameter_block_size_);
1315 const double kTolerance = 1e-16;
1316
1317 for (int i = 0; i < num_parameter_blocks_; ++i) {
1318 expected.setIdentity();
1319 expected /= (i + 1.0) * (i + 1.0);
1320
1321 double* block_i = parameters_.get() + i * parameter_block_size_;
1322 covariance.GetCovarianceBlock(block_i, block_i, actual.data());
1323 EXPECT_NEAR((expected - actual).norm(), 0.0, kTolerance)
1324 << "block: " << i << ", " << i << "\n"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001325 << "expected: \n"
1326 << expected << "\n"
1327 << "actual: \n"
1328 << actual;
Austin Schuh70cc9552019-01-21 19:46:48 -08001329
1330 expected.setZero();
1331 for (int j = i + 1; j < num_parameter_blocks_; ++j) {
1332 double* block_j = parameters_.get() + j * parameter_block_size_;
1333 covariance.GetCovarianceBlock(block_i, block_j, actual.data());
1334 EXPECT_NEAR((expected - actual).norm(), 0.0, kTolerance)
1335 << "block: " << i << ", " << j << "\n"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001336 << "expected: \n"
1337 << expected << "\n"
1338 << "actual: \n"
1339 << actual;
Austin Schuh70cc9552019-01-21 19:46:48 -08001340 }
1341 }
1342 }
1343
1344 std::unique_ptr<double[]> parameters_;
1345 int parameter_block_size_;
1346 int num_parameter_blocks_;
1347
1348 Problem problem_;
Austin Schuh3de38b02024-06-25 18:25:10 -07001349 std::vector<std::pair<const double*, const double*>> all_covariance_blocks_;
Austin Schuh70cc9552019-01-21 19:46:48 -08001350};
1351
Austin Schuh3de38b02024-06-25 18:25:10 -07001352#if !defined(CERES_NO_SUITESPARSE)
Austin Schuh70cc9552019-01-21 19:46:48 -08001353
1354TEST_F(LargeScaleCovarianceTest, Parallel) {
1355 ComputeAndCompare(SPARSE_QR, SUITE_SPARSE, 4);
1356}
1357
Austin Schuh3de38b02024-06-25 18:25:10 -07001358#endif // !defined(CERES_NO_SUITESPARSE)
Austin Schuh70cc9552019-01-21 19:46:48 -08001359
1360} // namespace internal
1361} // namespace ceres