blob: 5129b9af51d02ae3872c6244a33f743ca69db041 [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// keir@google.com (Keir Mierle)
31
32#include "ceres/problem.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080033
34#include <memory>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080035
36#include "ceres/autodiff_cost_function.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080037#include "ceres/casts.h"
38#include "ceres/cost_function.h"
39#include "ceres/crs_matrix.h"
40#include "ceres/evaluator_test_utils.h"
41#include "ceres/internal/eigen.h"
42#include "ceres/local_parameterization.h"
43#include "ceres/loss_function.h"
44#include "ceres/map_util.h"
45#include "ceres/parameter_block.h"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080046#include "ceres/problem_impl.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080047#include "ceres/program.h"
48#include "ceres/sized_cost_function.h"
49#include "ceres/sparse_matrix.h"
50#include "ceres/types.h"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080051#include "gmock/gmock.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080052#include "gtest/gtest.h"
53
54namespace ceres {
55namespace internal {
56
57using std::vector;
58
59// The following three classes are for the purposes of defining
60// function signatures. They have dummy Evaluate functions.
61
62// Trivial cost function that accepts a single argument.
63class UnaryCostFunction : public CostFunction {
64 public:
65 UnaryCostFunction(int num_residuals, int32_t parameter_block_size) {
66 set_num_residuals(num_residuals);
67 mutable_parameter_block_sizes()->push_back(parameter_block_size);
68 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080069
Austin Schuh70cc9552019-01-21 19:46:48 -080070 virtual ~UnaryCostFunction() {}
71
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080072 bool Evaluate(double const* const* parameters,
73 double* residuals,
74 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080075 for (int i = 0; i < num_residuals(); ++i) {
76 residuals[i] = 1;
77 }
78 return true;
79 }
80};
81
82// Trivial cost function that accepts two arguments.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080083class BinaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -080084 public:
85 BinaryCostFunction(int num_residuals,
86 int32_t parameter_block1_size,
87 int32_t parameter_block2_size) {
88 set_num_residuals(num_residuals);
89 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
90 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
91 }
92
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080093 bool Evaluate(double const* const* parameters,
94 double* residuals,
95 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080096 for (int i = 0; i < num_residuals(); ++i) {
97 residuals[i] = 2;
98 }
99 return true;
100 }
101};
102
103// Trivial cost function that accepts three arguments.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800104class TernaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -0800105 public:
106 TernaryCostFunction(int num_residuals,
107 int32_t parameter_block1_size,
108 int32_t parameter_block2_size,
109 int32_t parameter_block3_size) {
110 set_num_residuals(num_residuals);
111 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
112 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
113 mutable_parameter_block_sizes()->push_back(parameter_block3_size);
114 }
115
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800116 bool Evaluate(double const* const* parameters,
117 double* residuals,
118 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800119 for (int i = 0; i < num_residuals(); ++i) {
120 residuals[i] = 3;
121 }
122 return true;
123 }
124};
125
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800126TEST(Problem, MoveConstructor) {
127 Problem src;
128 double x;
129 src.AddParameterBlock(&x, 1);
130 Problem dst(std::move(src));
131 EXPECT_TRUE(dst.HasParameterBlock(&x));
132}
133
134TEST(Problem, MoveAssignment) {
135 Problem src;
136 double x;
137 src.AddParameterBlock(&x, 1);
138 Problem dst;
139 dst = std::move(src);
140 EXPECT_TRUE(dst.HasParameterBlock(&x));
141}
142
Austin Schuh70cc9552019-01-21 19:46:48 -0800143TEST(Problem, AddResidualWithNullCostFunctionDies) {
144 double x[3], y[4], z[5];
145
146 Problem problem;
147 problem.AddParameterBlock(x, 3);
148 problem.AddParameterBlock(y, 4);
149 problem.AddParameterBlock(z, 5);
150
151 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(NULL, NULL, x),
152 "cost_function != nullptr");
153}
154
155TEST(Problem, AddResidualWithIncorrectNumberOfParameterBlocksDies) {
156 double x[3], y[4], z[5];
157
158 Problem problem;
159 problem.AddParameterBlock(x, 3);
160 problem.AddParameterBlock(y, 4);
161 problem.AddParameterBlock(z, 5);
162
163 // UnaryCostFunction takes only one parameter, but two are passed.
164 EXPECT_DEATH_IF_SUPPORTED(
165 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x, y),
166 "num_parameter_blocks");
167}
168
169TEST(Problem, AddResidualWithDifferentSizesOnTheSameVariableDies) {
170 double x[3];
171
172 Problem problem;
173 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800174 EXPECT_DEATH_IF_SUPPORTED(
175 problem.AddResidualBlock(
176 new UnaryCostFunction(2, 4 /* 4 != 3 */), NULL, x),
177 "different block sizes");
Austin Schuh70cc9552019-01-21 19:46:48 -0800178}
179
180TEST(Problem, AddResidualWithDuplicateParametersDies) {
181 double x[3], z[5];
182
183 Problem problem;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800184 EXPECT_DEATH_IF_SUPPORTED(
185 problem.AddResidualBlock(new BinaryCostFunction(2, 3, 3), NULL, x, x),
186 "Duplicate parameter blocks");
187 EXPECT_DEATH_IF_SUPPORTED(
188 problem.AddResidualBlock(
189 new TernaryCostFunction(1, 5, 3, 5), NULL, z, x, z),
190 "Duplicate parameter blocks");
Austin Schuh70cc9552019-01-21 19:46:48 -0800191}
192
193TEST(Problem, AddResidualWithIncorrectSizesOfParameterBlockDies) {
194 double x[3], y[4], z[5];
195
196 Problem problem;
197 problem.AddParameterBlock(x, 3);
198 problem.AddParameterBlock(y, 4);
199 problem.AddParameterBlock(z, 5);
200
201 // The cost function expects the size of the second parameter, z, to be 4
202 // instead of 5 as declared above. This is fatal.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800203 EXPECT_DEATH_IF_SUPPORTED(
204 problem.AddResidualBlock(new BinaryCostFunction(2, 3, 4), NULL, x, z),
205 "different block sizes");
Austin Schuh70cc9552019-01-21 19:46:48 -0800206}
207
208TEST(Problem, AddResidualAddsDuplicatedParametersOnlyOnce) {
209 double x[3], y[4], z[5];
210
211 Problem problem;
212 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
213 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
214 problem.AddResidualBlock(new UnaryCostFunction(2, 4), NULL, y);
215 problem.AddResidualBlock(new UnaryCostFunction(2, 5), NULL, z);
216
217 EXPECT_EQ(3, problem.NumParameterBlocks());
218 EXPECT_EQ(12, problem.NumParameters());
219}
220
221TEST(Problem, AddParameterWithDifferentSizesOnTheSameVariableDies) {
222 double x[3], y[4];
223
224 Problem problem;
225 problem.AddParameterBlock(x, 3);
226 problem.AddParameterBlock(y, 4);
227
228 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(x, 4),
229 "different block sizes");
230}
231
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800232static double* IntToPtr(int i) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800233 return reinterpret_cast<double*>(sizeof(double) * i); // NOLINT
234}
235
236TEST(Problem, AddParameterWithAliasedParametersDies) {
237 // Layout is
238 //
239 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
240 // [x] x x x x [y] y y
241 // o==o==o o==o==o o==o
242 // o--o--o o--o--o o--o o--o--o
243 //
244 // Parameter block additions are tested as listed above; expected successful
245 // ones marked with o==o and aliasing ones marked with o--o.
246
247 Problem problem;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800248 problem.AddParameterBlock(IntToPtr(5), 5); // x
Austin Schuh70cc9552019-01-21 19:46:48 -0800249 problem.AddParameterBlock(IntToPtr(13), 3); // y
250
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800251 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 2),
Austin Schuh70cc9552019-01-21 19:46:48 -0800252 "Aliasing detected");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800253 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 3),
Austin Schuh70cc9552019-01-21 19:46:48 -0800254 "Aliasing detected");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800255 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 9),
Austin Schuh70cc9552019-01-21 19:46:48 -0800256 "Aliasing detected");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800257 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(8), 3),
Austin Schuh70cc9552019-01-21 19:46:48 -0800258 "Aliasing detected");
259 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(12), 2),
260 "Aliasing detected");
261 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(14), 3),
262 "Aliasing detected");
263
264 // These ones should work.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800265 problem.AddParameterBlock(IntToPtr(2), 3);
Austin Schuh70cc9552019-01-21 19:46:48 -0800266 problem.AddParameterBlock(IntToPtr(10), 3);
267 problem.AddParameterBlock(IntToPtr(16), 2);
268
269 ASSERT_EQ(5, problem.NumParameterBlocks());
270}
271
272TEST(Problem, AddParameterIgnoresDuplicateCalls) {
273 double x[3], y[4];
274
275 Problem problem;
276 problem.AddParameterBlock(x, 3);
277 problem.AddParameterBlock(y, 4);
278
279 // Creating parameter blocks multiple times is ignored.
280 problem.AddParameterBlock(x, 3);
281 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
282
283 // ... even repeatedly.
284 problem.AddParameterBlock(x, 3);
285 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
286
287 // More parameters are fine.
288 problem.AddParameterBlock(y, 4);
289 problem.AddResidualBlock(new UnaryCostFunction(2, 4), NULL, y);
290
291 EXPECT_EQ(2, problem.NumParameterBlocks());
292 EXPECT_EQ(7, problem.NumParameters());
293}
294
295TEST(Problem, AddingParametersAndResidualsResultsInExpectedProblem) {
296 double x[3], y[4], z[5], w[4];
297
298 Problem problem;
299 problem.AddParameterBlock(x, 3);
300 EXPECT_EQ(1, problem.NumParameterBlocks());
301 EXPECT_EQ(3, problem.NumParameters());
302
303 problem.AddParameterBlock(y, 4);
304 EXPECT_EQ(2, problem.NumParameterBlocks());
305 EXPECT_EQ(7, problem.NumParameters());
306
307 problem.AddParameterBlock(z, 5);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800308 EXPECT_EQ(3, problem.NumParameterBlocks());
Austin Schuh70cc9552019-01-21 19:46:48 -0800309 EXPECT_EQ(12, problem.NumParameters());
310
311 // Add a parameter that has a local parameterization.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800312 w[0] = 1.0;
313 w[1] = 0.0;
314 w[2] = 0.0;
315 w[3] = 0.0;
Austin Schuh70cc9552019-01-21 19:46:48 -0800316 problem.AddParameterBlock(w, 4, new QuaternionParameterization);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800317 EXPECT_EQ(4, problem.NumParameterBlocks());
Austin Schuh70cc9552019-01-21 19:46:48 -0800318 EXPECT_EQ(16, problem.NumParameters());
319
320 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800321 problem.AddResidualBlock(new BinaryCostFunction(6, 5, 4), NULL, z, y);
Austin Schuh70cc9552019-01-21 19:46:48 -0800322 problem.AddResidualBlock(new BinaryCostFunction(3, 3, 5), NULL, x, z);
323 problem.AddResidualBlock(new BinaryCostFunction(7, 5, 3), NULL, z, x);
324 problem.AddResidualBlock(new TernaryCostFunction(1, 5, 3, 4), NULL, z, x, y);
325
326 const int total_residuals = 2 + 6 + 3 + 7 + 1;
327 EXPECT_EQ(problem.NumResidualBlocks(), 5);
328 EXPECT_EQ(problem.NumResiduals(), total_residuals);
329}
330
331class DestructorCountingCostFunction : public SizedCostFunction<3, 4, 5> {
332 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800333 explicit DestructorCountingCostFunction(int* num_destructions)
Austin Schuh70cc9552019-01-21 19:46:48 -0800334 : num_destructions_(num_destructions) {}
335
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800336 virtual ~DestructorCountingCostFunction() { *num_destructions_ += 1; }
Austin Schuh70cc9552019-01-21 19:46:48 -0800337
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800338 bool Evaluate(double const* const* parameters,
339 double* residuals,
340 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800341 return true;
342 }
343
344 private:
345 int* num_destructions_;
346};
347
348TEST(Problem, ReusedCostFunctionsAreOnlyDeletedOnce) {
349 double y[4], z[5];
350 int num_destructions = 0;
351
352 // Add a cost function multiple times and check to make sure that
353 // the destructor on the cost function is only called once.
354 {
355 Problem problem;
356 problem.AddParameterBlock(y, 4);
357 problem.AddParameterBlock(z, 5);
358
359 CostFunction* cost = new DestructorCountingCostFunction(&num_destructions);
360 problem.AddResidualBlock(cost, NULL, y, z);
361 problem.AddResidualBlock(cost, NULL, y, z);
362 problem.AddResidualBlock(cost, NULL, y, z);
363 EXPECT_EQ(3, problem.NumResidualBlocks());
364 }
365
366 // Check that the destructor was called only once.
367 CHECK_EQ(num_destructions, 1);
368}
369
370TEST(Problem, GetCostFunctionForResidualBlock) {
371 double x[3];
372 Problem problem;
373 CostFunction* cost_function = new UnaryCostFunction(2, 3);
374 const ResidualBlockId residual_block =
375 problem.AddResidualBlock(cost_function, NULL, x);
376 EXPECT_EQ(problem.GetCostFunctionForResidualBlock(residual_block),
377 cost_function);
378 EXPECT_TRUE(problem.GetLossFunctionForResidualBlock(residual_block) == NULL);
379}
380
381TEST(Problem, GetLossFunctionForResidualBlock) {
382 double x[3];
383 Problem problem;
384 CostFunction* cost_function = new UnaryCostFunction(2, 3);
385 LossFunction* loss_function = new TrivialLoss();
386 const ResidualBlockId residual_block =
387 problem.AddResidualBlock(cost_function, loss_function, x);
388 EXPECT_EQ(problem.GetCostFunctionForResidualBlock(residual_block),
389 cost_function);
390 EXPECT_EQ(problem.GetLossFunctionForResidualBlock(residual_block),
391 loss_function);
392}
393
394TEST(Problem, CostFunctionsAreDeletedEvenWithRemovals) {
395 double y[4], z[5], w[4];
396 int num_destructions = 0;
397 {
398 Problem problem;
399 problem.AddParameterBlock(y, 4);
400 problem.AddParameterBlock(z, 5);
401
402 CostFunction* cost_yz =
403 new DestructorCountingCostFunction(&num_destructions);
404 CostFunction* cost_wz =
405 new DestructorCountingCostFunction(&num_destructions);
406 ResidualBlock* r_yz = problem.AddResidualBlock(cost_yz, NULL, y, z);
407 ResidualBlock* r_wz = problem.AddResidualBlock(cost_wz, NULL, w, z);
408 EXPECT_EQ(2, problem.NumResidualBlocks());
409
410 problem.RemoveResidualBlock(r_yz);
411 CHECK_EQ(num_destructions, 1);
412 problem.RemoveResidualBlock(r_wz);
413 CHECK_EQ(num_destructions, 2);
414
415 EXPECT_EQ(0, problem.NumResidualBlocks());
416 }
417 CHECK_EQ(num_destructions, 2);
418}
419
420// Make the dynamic problem tests (e.g. for removing residual blocks)
421// parameterized on whether the low-latency mode is enabled or not.
422//
423// This tests against ProblemImpl instead of Problem in order to inspect the
424// state of the resulting Program; this is difficult with only the thin Problem
425// interface.
426struct DynamicProblem : public ::testing::TestWithParam<bool> {
427 DynamicProblem() {
428 Problem::Options options;
429 options.enable_fast_removal = GetParam();
430 problem.reset(new ProblemImpl(options));
431 }
432
433 ParameterBlock* GetParameterBlock(int block) {
434 return problem->program().parameter_blocks()[block];
435 }
436 ResidualBlock* GetResidualBlock(int block) {
437 return problem->program().residual_blocks()[block];
438 }
439
440 bool HasResidualBlock(ResidualBlock* residual_block) {
441 bool have_residual_block = true;
442 if (GetParam()) {
443 have_residual_block &=
444 (problem->residual_block_set().find(residual_block) !=
445 problem->residual_block_set().end());
446 }
447 have_residual_block &=
448 find(problem->program().residual_blocks().begin(),
449 problem->program().residual_blocks().end(),
450 residual_block) != problem->program().residual_blocks().end();
451 return have_residual_block;
452 }
453
454 int NumResidualBlocks() {
455 // Verify that the hash set of residuals is maintained consistently.
456 if (GetParam()) {
457 EXPECT_EQ(problem->residual_block_set().size(),
458 problem->NumResidualBlocks());
459 }
460 return problem->NumResidualBlocks();
461 }
462
463 // The next block of functions until the end are only for testing the
464 // residual block removals.
465 void ExpectParameterBlockContainsResidualBlock(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800466 double* values, ResidualBlock* residual_block) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800467 ParameterBlock* parameter_block =
468 FindOrDie(problem->parameter_map(), values);
469 EXPECT_TRUE(ContainsKey(*(parameter_block->mutable_residual_blocks()),
470 residual_block));
471 }
472
473 void ExpectSize(double* values, int size) {
474 ParameterBlock* parameter_block =
475 FindOrDie(problem->parameter_map(), values);
476 EXPECT_EQ(size, parameter_block->mutable_residual_blocks()->size());
477 }
478
479 // Degenerate case.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800480 void ExpectParameterBlockContains(double* values) { ExpectSize(values, 0); }
Austin Schuh70cc9552019-01-21 19:46:48 -0800481
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800482 void ExpectParameterBlockContains(double* values, ResidualBlock* r1) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800483 ExpectSize(values, 1);
484 ExpectParameterBlockContainsResidualBlock(values, r1);
485 }
486
487 void ExpectParameterBlockContains(double* values,
488 ResidualBlock* r1,
489 ResidualBlock* r2) {
490 ExpectSize(values, 2);
491 ExpectParameterBlockContainsResidualBlock(values, r1);
492 ExpectParameterBlockContainsResidualBlock(values, r2);
493 }
494
495 void ExpectParameterBlockContains(double* values,
496 ResidualBlock* r1,
497 ResidualBlock* r2,
498 ResidualBlock* r3) {
499 ExpectSize(values, 3);
500 ExpectParameterBlockContainsResidualBlock(values, r1);
501 ExpectParameterBlockContainsResidualBlock(values, r2);
502 ExpectParameterBlockContainsResidualBlock(values, r3);
503 }
504
505 void ExpectParameterBlockContains(double* values,
506 ResidualBlock* r1,
507 ResidualBlock* r2,
508 ResidualBlock* r3,
509 ResidualBlock* r4) {
510 ExpectSize(values, 4);
511 ExpectParameterBlockContainsResidualBlock(values, r1);
512 ExpectParameterBlockContainsResidualBlock(values, r2);
513 ExpectParameterBlockContainsResidualBlock(values, r3);
514 ExpectParameterBlockContainsResidualBlock(values, r4);
515 }
516
517 std::unique_ptr<ProblemImpl> problem;
518 double y[4], z[5], w[3];
519};
520
521TEST(Problem, SetParameterBlockConstantWithUnknownPtrDies) {
522 double x[3];
523 double y[2];
524
525 Problem problem;
526 problem.AddParameterBlock(x, 3);
527
528 EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockConstant(y),
529 "Parameter block not found:");
530}
531
532TEST(Problem, SetParameterBlockVariableWithUnknownPtrDies) {
533 double x[3];
534 double y[2];
535
536 Problem problem;
537 problem.AddParameterBlock(x, 3);
538
539 EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockVariable(y),
540 "Parameter block not found:");
541}
542
543TEST(Problem, IsParameterBlockConstant) {
544 double x1[3];
545 double x2[3];
546
547 Problem problem;
548 problem.AddParameterBlock(x1, 3);
549 problem.AddParameterBlock(x2, 3);
550
551 EXPECT_FALSE(problem.IsParameterBlockConstant(x1));
552 EXPECT_FALSE(problem.IsParameterBlockConstant(x2));
553
554 problem.SetParameterBlockConstant(x1);
555 EXPECT_TRUE(problem.IsParameterBlockConstant(x1));
556 EXPECT_FALSE(problem.IsParameterBlockConstant(x2));
557
558 problem.SetParameterBlockConstant(x2);
559 EXPECT_TRUE(problem.IsParameterBlockConstant(x1));
560 EXPECT_TRUE(problem.IsParameterBlockConstant(x2));
561
562 problem.SetParameterBlockVariable(x1);
563 EXPECT_FALSE(problem.IsParameterBlockConstant(x1));
564 EXPECT_TRUE(problem.IsParameterBlockConstant(x2));
565}
566
567TEST(Problem, IsParameterBlockConstantWithUnknownPtrDies) {
568 double x[3];
569 double y[2];
570
571 Problem problem;
572 problem.AddParameterBlock(x, 3);
573
574 EXPECT_DEATH_IF_SUPPORTED(problem.IsParameterBlockConstant(y),
575 "Parameter block not found:");
576}
577
578TEST(Problem, SetLocalParameterizationWithUnknownPtrDies) {
579 double x[3];
580 double y[2];
581
582 Problem problem;
583 problem.AddParameterBlock(x, 3);
584
585 EXPECT_DEATH_IF_SUPPORTED(
586 problem.SetParameterization(y, new IdentityParameterization(3)),
587 "Parameter block not found:");
588}
589
590TEST(Problem, RemoveParameterBlockWithUnknownPtrDies) {
591 double x[3];
592 double y[2];
593
594 Problem problem;
595 problem.AddParameterBlock(x, 3);
596
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800597 EXPECT_DEATH_IF_SUPPORTED(problem.RemoveParameterBlock(y),
598 "Parameter block not found:");
Austin Schuh70cc9552019-01-21 19:46:48 -0800599}
600
601TEST(Problem, GetParameterization) {
602 double x[3];
603 double y[2];
604
605 Problem problem;
606 problem.AddParameterBlock(x, 3);
607 problem.AddParameterBlock(y, 2);
608
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800609 LocalParameterization* parameterization = new IdentityParameterization(3);
Austin Schuh70cc9552019-01-21 19:46:48 -0800610 problem.SetParameterization(x, parameterization);
611 EXPECT_EQ(problem.GetParameterization(x), parameterization);
612 EXPECT_TRUE(problem.GetParameterization(y) == NULL);
613}
614
615TEST(Problem, ParameterBlockQueryTest) {
616 double x[3];
617 double y[4];
618 Problem problem;
619 problem.AddParameterBlock(x, 3);
620 problem.AddParameterBlock(y, 4);
621
622 vector<int> constant_parameters;
623 constant_parameters.push_back(0);
624 problem.SetParameterization(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800625 x, new SubsetParameterization(3, constant_parameters));
Austin Schuh70cc9552019-01-21 19:46:48 -0800626 EXPECT_EQ(problem.ParameterBlockSize(x), 3);
627 EXPECT_EQ(problem.ParameterBlockLocalSize(x), 2);
628 EXPECT_EQ(problem.ParameterBlockLocalSize(y), 4);
629
630 vector<double*> parameter_blocks;
631 problem.GetParameterBlocks(&parameter_blocks);
632 EXPECT_EQ(parameter_blocks.size(), 2);
633 EXPECT_NE(parameter_blocks[0], parameter_blocks[1]);
634 EXPECT_TRUE(parameter_blocks[0] == x || parameter_blocks[0] == y);
635 EXPECT_TRUE(parameter_blocks[1] == x || parameter_blocks[1] == y);
636
637 EXPECT_TRUE(problem.HasParameterBlock(x));
638 problem.RemoveParameterBlock(x);
639 EXPECT_FALSE(problem.HasParameterBlock(x));
640 problem.GetParameterBlocks(&parameter_blocks);
641 EXPECT_EQ(parameter_blocks.size(), 1);
642 EXPECT_TRUE(parameter_blocks[0] == y);
643}
644
645TEST_P(DynamicProblem, RemoveParameterBlockWithNoResiduals) {
646 problem->AddParameterBlock(y, 4);
647 problem->AddParameterBlock(z, 5);
648 problem->AddParameterBlock(w, 3);
649 ASSERT_EQ(3, problem->NumParameterBlocks());
650 ASSERT_EQ(0, NumResidualBlocks());
651 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
652 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
653 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
654
655 // w is at the end, which might break the swapping logic so try adding and
656 // removing it.
657 problem->RemoveParameterBlock(w);
658 ASSERT_EQ(2, problem->NumParameterBlocks());
659 ASSERT_EQ(0, NumResidualBlocks());
660 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
661 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
662 problem->AddParameterBlock(w, 3);
663 ASSERT_EQ(3, problem->NumParameterBlocks());
664 ASSERT_EQ(0, NumResidualBlocks());
665 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
666 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
667 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
668
669 // Now remove z, which is in the middle, and add it back.
670 problem->RemoveParameterBlock(z);
671 ASSERT_EQ(2, problem->NumParameterBlocks());
672 ASSERT_EQ(0, NumResidualBlocks());
673 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
674 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
675 problem->AddParameterBlock(z, 5);
676 ASSERT_EQ(3, problem->NumParameterBlocks());
677 ASSERT_EQ(0, NumResidualBlocks());
678 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
679 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
680 EXPECT_EQ(z, GetParameterBlock(2)->user_state());
681
682 // Now remove everything.
683 // y
684 problem->RemoveParameterBlock(y);
685 ASSERT_EQ(2, problem->NumParameterBlocks());
686 ASSERT_EQ(0, NumResidualBlocks());
687 EXPECT_EQ(z, GetParameterBlock(0)->user_state());
688 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
689
690 // z
691 problem->RemoveParameterBlock(z);
692 ASSERT_EQ(1, problem->NumParameterBlocks());
693 ASSERT_EQ(0, NumResidualBlocks());
694 EXPECT_EQ(w, GetParameterBlock(0)->user_state());
695
696 // w
697 problem->RemoveParameterBlock(w);
698 EXPECT_EQ(0, problem->NumParameterBlocks());
699 EXPECT_EQ(0, NumResidualBlocks());
700}
701
702TEST_P(DynamicProblem, RemoveParameterBlockWithResiduals) {
703 problem->AddParameterBlock(y, 4);
704 problem->AddParameterBlock(z, 5);
705 problem->AddParameterBlock(w, 3);
706 ASSERT_EQ(3, problem->NumParameterBlocks());
707 ASSERT_EQ(0, NumResidualBlocks());
708 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
709 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
710 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
711
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800712 // clang-format off
713
Austin Schuh70cc9552019-01-21 19:46:48 -0800714 // Add all combinations of cost functions.
715 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
716 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
717 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
718 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
719 CostFunction* cost_y = new UnaryCostFunction (1, 4);
720 CostFunction* cost_z = new UnaryCostFunction (1, 5);
721 CostFunction* cost_w = new UnaryCostFunction (1, 3);
722
723 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
724 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
725 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
726 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
727 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
728 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
729 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
730
731 EXPECT_EQ(3, problem->NumParameterBlocks());
732 EXPECT_EQ(7, NumResidualBlocks());
733
734 // Remove w, which should remove r_yzw, r_yw, r_zw, r_w.
735 problem->RemoveParameterBlock(w);
736 ASSERT_EQ(2, problem->NumParameterBlocks());
737 ASSERT_EQ(3, NumResidualBlocks());
738
739 ASSERT_FALSE(HasResidualBlock(r_yzw));
740 ASSERT_TRUE (HasResidualBlock(r_yz ));
741 ASSERT_FALSE(HasResidualBlock(r_yw ));
742 ASSERT_FALSE(HasResidualBlock(r_zw ));
743 ASSERT_TRUE (HasResidualBlock(r_y ));
744 ASSERT_TRUE (HasResidualBlock(r_z ));
745 ASSERT_FALSE(HasResidualBlock(r_w ));
746
747 // Remove z, which will remove almost everything else.
748 problem->RemoveParameterBlock(z);
749 ASSERT_EQ(1, problem->NumParameterBlocks());
750 ASSERT_EQ(1, NumResidualBlocks());
751
752 ASSERT_FALSE(HasResidualBlock(r_yzw));
753 ASSERT_FALSE(HasResidualBlock(r_yz ));
754 ASSERT_FALSE(HasResidualBlock(r_yw ));
755 ASSERT_FALSE(HasResidualBlock(r_zw ));
756 ASSERT_TRUE (HasResidualBlock(r_y ));
757 ASSERT_FALSE(HasResidualBlock(r_z ));
758 ASSERT_FALSE(HasResidualBlock(r_w ));
759
760 // Remove y; all gone.
761 problem->RemoveParameterBlock(y);
762 EXPECT_EQ(0, problem->NumParameterBlocks());
763 EXPECT_EQ(0, NumResidualBlocks());
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800764
765 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800766}
767
768TEST_P(DynamicProblem, RemoveResidualBlock) {
769 problem->AddParameterBlock(y, 4);
770 problem->AddParameterBlock(z, 5);
771 problem->AddParameterBlock(w, 3);
772
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800773 // clang-format off
774
Austin Schuh70cc9552019-01-21 19:46:48 -0800775 // Add all combinations of cost functions.
776 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
777 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
778 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
779 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
780 CostFunction* cost_y = new UnaryCostFunction (1, 4);
781 CostFunction* cost_z = new UnaryCostFunction (1, 5);
782 CostFunction* cost_w = new UnaryCostFunction (1, 3);
783
784 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
785 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
786 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
787 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
788 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
789 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
790 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
791
792 if (GetParam()) {
793 // In this test parameterization, there should be back-pointers from the
794 // parameter blocks to the residual blocks.
795 ExpectParameterBlockContains(y, r_yzw, r_yz, r_yw, r_y);
796 ExpectParameterBlockContains(z, r_yzw, r_yz, r_zw, r_z);
797 ExpectParameterBlockContains(w, r_yzw, r_yw, r_zw, r_w);
798 } else {
799 // Otherwise, nothing.
800 EXPECT_TRUE(GetParameterBlock(0)->mutable_residual_blocks() == NULL);
801 EXPECT_TRUE(GetParameterBlock(1)->mutable_residual_blocks() == NULL);
802 EXPECT_TRUE(GetParameterBlock(2)->mutable_residual_blocks() == NULL);
803 }
804 EXPECT_EQ(3, problem->NumParameterBlocks());
805 EXPECT_EQ(7, NumResidualBlocks());
806
807 // Remove each residual and check the state after each removal.
808
809 // Remove r_yzw.
810 problem->RemoveResidualBlock(r_yzw);
811 ASSERT_EQ(3, problem->NumParameterBlocks());
812 ASSERT_EQ(6, NumResidualBlocks());
813 if (GetParam()) {
814 ExpectParameterBlockContains(y, r_yz, r_yw, r_y);
815 ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
816 ExpectParameterBlockContains(w, r_yw, r_zw, r_w);
817 }
818 ASSERT_TRUE (HasResidualBlock(r_yz ));
819 ASSERT_TRUE (HasResidualBlock(r_yw ));
820 ASSERT_TRUE (HasResidualBlock(r_zw ));
821 ASSERT_TRUE (HasResidualBlock(r_y ));
822 ASSERT_TRUE (HasResidualBlock(r_z ));
823 ASSERT_TRUE (HasResidualBlock(r_w ));
824
825 // Remove r_yw.
826 problem->RemoveResidualBlock(r_yw);
827 ASSERT_EQ(3, problem->NumParameterBlocks());
828 ASSERT_EQ(5, NumResidualBlocks());
829 if (GetParam()) {
830 ExpectParameterBlockContains(y, r_yz, r_y);
831 ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
832 ExpectParameterBlockContains(w, r_zw, r_w);
833 }
834 ASSERT_TRUE (HasResidualBlock(r_yz ));
835 ASSERT_TRUE (HasResidualBlock(r_zw ));
836 ASSERT_TRUE (HasResidualBlock(r_y ));
837 ASSERT_TRUE (HasResidualBlock(r_z ));
838 ASSERT_TRUE (HasResidualBlock(r_w ));
839
840 // Remove r_zw.
841 problem->RemoveResidualBlock(r_zw);
842 ASSERT_EQ(3, problem->NumParameterBlocks());
843 ASSERT_EQ(4, NumResidualBlocks());
844 if (GetParam()) {
845 ExpectParameterBlockContains(y, r_yz, r_y);
846 ExpectParameterBlockContains(z, r_yz, r_z);
847 ExpectParameterBlockContains(w, r_w);
848 }
849 ASSERT_TRUE (HasResidualBlock(r_yz ));
850 ASSERT_TRUE (HasResidualBlock(r_y ));
851 ASSERT_TRUE (HasResidualBlock(r_z ));
852 ASSERT_TRUE (HasResidualBlock(r_w ));
853
854 // Remove r_w.
855 problem->RemoveResidualBlock(r_w);
856 ASSERT_EQ(3, problem->NumParameterBlocks());
857 ASSERT_EQ(3, NumResidualBlocks());
858 if (GetParam()) {
859 ExpectParameterBlockContains(y, r_yz, r_y);
860 ExpectParameterBlockContains(z, r_yz, r_z);
861 ExpectParameterBlockContains(w);
862 }
863 ASSERT_TRUE (HasResidualBlock(r_yz ));
864 ASSERT_TRUE (HasResidualBlock(r_y ));
865 ASSERT_TRUE (HasResidualBlock(r_z ));
866
867 // Remove r_yz.
868 problem->RemoveResidualBlock(r_yz);
869 ASSERT_EQ(3, problem->NumParameterBlocks());
870 ASSERT_EQ(2, NumResidualBlocks());
871 if (GetParam()) {
872 ExpectParameterBlockContains(y, r_y);
873 ExpectParameterBlockContains(z, r_z);
874 ExpectParameterBlockContains(w);
875 }
876 ASSERT_TRUE (HasResidualBlock(r_y ));
877 ASSERT_TRUE (HasResidualBlock(r_z ));
878
879 // Remove the last two.
880 problem->RemoveResidualBlock(r_z);
881 problem->RemoveResidualBlock(r_y);
882 ASSERT_EQ(3, problem->NumParameterBlocks());
883 ASSERT_EQ(0, NumResidualBlocks());
884 if (GetParam()) {
885 ExpectParameterBlockContains(y);
886 ExpectParameterBlockContains(z);
887 ExpectParameterBlockContains(w);
888 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800889
890 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800891}
892
893TEST_P(DynamicProblem, RemoveInvalidResidualBlockDies) {
894 problem->AddParameterBlock(y, 4);
895 problem->AddParameterBlock(z, 5);
896 problem->AddParameterBlock(w, 3);
897
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800898 // clang-format off
899
Austin Schuh70cc9552019-01-21 19:46:48 -0800900 // Add all combinations of cost functions.
901 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
902 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
903 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
904 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
905 CostFunction* cost_y = new UnaryCostFunction (1, 4);
906 CostFunction* cost_z = new UnaryCostFunction (1, 5);
907 CostFunction* cost_w = new UnaryCostFunction (1, 3);
908
909 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
910 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
911 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
912 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
913 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
914 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
915 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
916
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800917 // clang-format on
918
Austin Schuh70cc9552019-01-21 19:46:48 -0800919 // Remove r_yzw.
920 problem->RemoveResidualBlock(r_yzw);
921 ASSERT_EQ(3, problem->NumParameterBlocks());
922 ASSERT_EQ(6, NumResidualBlocks());
923 // Attempt to remove r_yzw again.
924 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yzw), "not found");
925
926 // Attempt to remove a cast pointer never added as a residual.
927 int trash_memory = 1234;
928 ResidualBlock* invalid_residual =
929 reinterpret_cast<ResidualBlock*>(&trash_memory);
930 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(invalid_residual),
931 "not found");
932
933 // Remove a parameter block, which in turn removes the dependent residuals
934 // then attempt to remove them directly.
935 problem->RemoveParameterBlock(z);
936 ASSERT_EQ(2, problem->NumParameterBlocks());
937 ASSERT_EQ(3, NumResidualBlocks());
938 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yz), "not found");
939 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_zw), "not found");
940 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_z), "not found");
941
942 problem->RemoveResidualBlock(r_yw);
943 problem->RemoveResidualBlock(r_w);
944 problem->RemoveResidualBlock(r_y);
945}
946
947// Check that a null-terminated array, a, has the same elements as b.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800948template <typename T>
Austin Schuh70cc9552019-01-21 19:46:48 -0800949void ExpectVectorContainsUnordered(const T* a, const vector<T>& b) {
950 // Compute the size of a.
951 int size = 0;
952 while (a[size]) {
953 ++size;
954 }
955 ASSERT_EQ(size, b.size());
956
957 // Sort a.
958 vector<T> a_sorted(size);
959 copy(a, a + size, a_sorted.begin());
960 sort(a_sorted.begin(), a_sorted.end());
961
962 // Sort b.
963 vector<T> b_sorted(b);
964 sort(b_sorted.begin(), b_sorted.end());
965
966 // Compare.
967 for (int i = 0; i < size; ++i) {
968 EXPECT_EQ(a_sorted[i], b_sorted[i]);
969 }
970}
971
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800972static void ExpectProblemHasResidualBlocks(
973 const ProblemImpl& problem,
974 const ResidualBlockId* expected_residual_blocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800975 vector<ResidualBlockId> residual_blocks;
976 problem.GetResidualBlocks(&residual_blocks);
977 ExpectVectorContainsUnordered(expected_residual_blocks, residual_blocks);
978}
979
980TEST_P(DynamicProblem, GetXXXBlocksForYYYBlock) {
981 problem->AddParameterBlock(y, 4);
982 problem->AddParameterBlock(z, 5);
983 problem->AddParameterBlock(w, 3);
984
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800985 // clang-format off
986
Austin Schuh70cc9552019-01-21 19:46:48 -0800987 // Add all combinations of cost functions.
988 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
989 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
990 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
991 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
992 CostFunction* cost_y = new UnaryCostFunction (1, 4);
993 CostFunction* cost_z = new UnaryCostFunction (1, 5);
994 CostFunction* cost_w = new UnaryCostFunction (1, 3);
995
996 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
997 {
998 ResidualBlockId expected_residuals[] = {r_yzw, 0};
999 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1000 }
1001 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
1002 {
1003 ResidualBlockId expected_residuals[] = {r_yzw, r_yz, 0};
1004 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1005 }
1006 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
1007 {
1008 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, 0};
1009 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1010 }
1011 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
1012 {
1013 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, 0};
1014 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1015 }
1016 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
1017 {
1018 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, r_y, 0};
1019 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1020 }
1021 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
1022 {
1023 ResidualBlock *expected_residuals[] = {
1024 r_yzw, r_yz, r_yw, r_zw, r_y, r_z, 0
1025 };
1026 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1027 }
1028 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
1029 {
1030 ResidualBlock *expected_residuals[] = {
1031 r_yzw, r_yz, r_yw, r_zw, r_y, r_z, r_w, 0
1032 };
1033 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1034 }
1035
1036 vector<double*> parameter_blocks;
1037 vector<ResidualBlockId> residual_blocks;
1038
1039 // Check GetResidualBlocksForParameterBlock() for all parameter blocks.
1040 struct GetResidualBlocksForParameterBlockTestCase {
1041 double* parameter_block;
1042 ResidualBlockId expected_residual_blocks[10];
1043 };
1044 GetResidualBlocksForParameterBlockTestCase get_residual_blocks_cases[] = {
1045 { y, { r_yzw, r_yz, r_yw, r_y, NULL} },
1046 { z, { r_yzw, r_yz, r_zw, r_z, NULL} },
1047 { w, { r_yzw, r_yw, r_zw, r_w, NULL} },
1048 { NULL }
1049 };
1050 for (int i = 0; get_residual_blocks_cases[i].parameter_block; ++i) {
1051 problem->GetResidualBlocksForParameterBlock(
1052 get_residual_blocks_cases[i].parameter_block,
1053 &residual_blocks);
1054 ExpectVectorContainsUnordered(
1055 get_residual_blocks_cases[i].expected_residual_blocks,
1056 residual_blocks);
1057 }
1058
1059 // Check GetParameterBlocksForResidualBlock() for all residual blocks.
1060 struct GetParameterBlocksForResidualBlockTestCase {
1061 ResidualBlockId residual_block;
1062 double* expected_parameter_blocks[10];
1063 };
1064 GetParameterBlocksForResidualBlockTestCase get_parameter_blocks_cases[] = {
1065 { r_yzw, { y, z, w, NULL } },
1066 { r_yz , { y, z, NULL } },
1067 { r_yw , { y, w, NULL } },
1068 { r_zw , { z, w, NULL } },
1069 { r_y , { y, NULL } },
1070 { r_z , { z, NULL } },
1071 { r_w , { w, NULL } },
1072 { NULL }
1073 };
1074 for (int i = 0; get_parameter_blocks_cases[i].residual_block; ++i) {
1075 problem->GetParameterBlocksForResidualBlock(
1076 get_parameter_blocks_cases[i].residual_block,
1077 &parameter_blocks);
1078 ExpectVectorContainsUnordered(
1079 get_parameter_blocks_cases[i].expected_parameter_blocks,
1080 parameter_blocks);
1081 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001082
1083 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001084}
1085
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001086INSTANTIATE_TEST_SUITE_P(OptionsInstantiation,
1087 DynamicProblem,
1088 ::testing::Values(true, false));
Austin Schuh70cc9552019-01-21 19:46:48 -08001089
1090// Test for Problem::Evaluate
1091
1092// r_i = i - (j + 1) * x_ij^2
1093template <int kNumResiduals, int kNumParameterBlocks>
1094class QuadraticCostFunction : public CostFunction {
1095 public:
1096 QuadraticCostFunction() {
1097 CHECK_GT(kNumResiduals, 0);
1098 CHECK_GT(kNumParameterBlocks, 0);
1099 set_num_residuals(kNumResiduals);
1100 for (int i = 0; i < kNumParameterBlocks; ++i) {
1101 mutable_parameter_block_sizes()->push_back(kNumResiduals);
1102 }
1103 }
1104
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001105 bool Evaluate(double const* const* parameters,
1106 double* residuals,
1107 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -08001108 for (int i = 0; i < kNumResiduals; ++i) {
1109 residuals[i] = i;
1110 for (int j = 0; j < kNumParameterBlocks; ++j) {
1111 residuals[i] -= (j + 1.0) * parameters[j][i] * parameters[j][i];
1112 }
1113 }
1114
1115 if (jacobians == NULL) {
1116 return true;
1117 }
1118
1119 for (int j = 0; j < kNumParameterBlocks; ++j) {
1120 if (jacobians[j] != NULL) {
1121 MatrixRef(jacobians[j], kNumResiduals, kNumResiduals) =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001122 (-2.0 * (j + 1.0) * ConstVectorRef(parameters[j], kNumResiduals))
1123 .asDiagonal();
Austin Schuh70cc9552019-01-21 19:46:48 -08001124 }
1125 }
1126
1127 return true;
1128 }
1129};
1130
1131// Convert a CRSMatrix to a dense Eigen matrix.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001132static void CRSToDenseMatrix(const CRSMatrix& input, Matrix* output) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001133 CHECK(output != nullptr);
1134 Matrix& m = *output;
1135 m.resize(input.num_rows, input.num_cols);
1136 m.setZero();
1137 for (int row = 0; row < input.num_rows; ++row) {
1138 for (int j = input.rows[row]; j < input.rows[row + 1]; ++j) {
1139 const int col = input.cols[j];
1140 m(row, col) = input.values[j];
1141 }
1142 }
1143}
1144
1145class ProblemEvaluateTest : public ::testing::Test {
1146 protected:
1147 void SetUp() {
1148 for (int i = 0; i < 6; ++i) {
1149 parameters_[i] = static_cast<double>(i + 1);
1150 }
1151
1152 parameter_blocks_.push_back(parameters_);
1153 parameter_blocks_.push_back(parameters_ + 2);
1154 parameter_blocks_.push_back(parameters_ + 4);
1155
Austin Schuh70cc9552019-01-21 19:46:48 -08001156 CostFunction* cost_function = new QuadraticCostFunction<2, 2>;
1157
1158 // f(x, y)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001159 residual_blocks_.push_back(problem_.AddResidualBlock(
1160 cost_function, NULL, parameters_, parameters_ + 2));
Austin Schuh70cc9552019-01-21 19:46:48 -08001161 // g(y, z)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001162 residual_blocks_.push_back(problem_.AddResidualBlock(
1163 cost_function, NULL, parameters_ + 2, parameters_ + 4));
Austin Schuh70cc9552019-01-21 19:46:48 -08001164 // h(z, x)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001165 residual_blocks_.push_back(problem_.AddResidualBlock(
1166 cost_function, NULL, parameters_ + 4, parameters_));
Austin Schuh70cc9552019-01-21 19:46:48 -08001167 }
1168
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001169 void TearDown() { EXPECT_TRUE(problem_.program().IsValid()); }
Austin Schuh70cc9552019-01-21 19:46:48 -08001170
1171 void EvaluateAndCompare(const Problem::EvaluateOptions& options,
1172 const int expected_num_rows,
1173 const int expected_num_cols,
1174 const double expected_cost,
1175 const double* expected_residuals,
1176 const double* expected_gradient,
1177 const double* expected_jacobian) {
1178 double cost;
1179 vector<double> residuals;
1180 vector<double> gradient;
1181 CRSMatrix jacobian;
1182
1183 EXPECT_TRUE(
1184 problem_.Evaluate(options,
1185 &cost,
1186 expected_residuals != NULL ? &residuals : NULL,
1187 expected_gradient != NULL ? &gradient : NULL,
1188 expected_jacobian != NULL ? &jacobian : NULL));
1189
1190 if (expected_residuals != NULL) {
1191 EXPECT_EQ(residuals.size(), expected_num_rows);
1192 }
1193
1194 if (expected_gradient != NULL) {
1195 EXPECT_EQ(gradient.size(), expected_num_cols);
1196 }
1197
1198 if (expected_jacobian != NULL) {
1199 EXPECT_EQ(jacobian.num_rows, expected_num_rows);
1200 EXPECT_EQ(jacobian.num_cols, expected_num_cols);
1201 }
1202
1203 Matrix dense_jacobian;
1204 if (expected_jacobian != NULL) {
1205 CRSToDenseMatrix(jacobian, &dense_jacobian);
1206 }
1207
1208 CompareEvaluations(expected_num_rows,
1209 expected_num_cols,
1210 expected_cost,
1211 expected_residuals,
1212 expected_gradient,
1213 expected_jacobian,
1214 cost,
1215 residuals.size() > 0 ? &residuals[0] : NULL,
1216 gradient.size() > 0 ? &gradient[0] : NULL,
1217 dense_jacobian.data());
1218 }
1219
1220 void CheckAllEvaluationCombinations(const Problem::EvaluateOptions& options,
1221 const ExpectedEvaluation& expected) {
1222 for (int i = 0; i < 8; ++i) {
1223 EvaluateAndCompare(options,
1224 expected.num_rows,
1225 expected.num_cols,
1226 expected.cost,
1227 (i & 1) ? expected.residuals : NULL,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001228 (i & 2) ? expected.gradient : NULL,
1229 (i & 4) ? expected.jacobian : NULL);
Austin Schuh70cc9552019-01-21 19:46:48 -08001230 }
1231 }
1232
1233 ProblemImpl problem_;
1234 double parameters_[6];
1235 vector<double*> parameter_blocks_;
1236 vector<ResidualBlockId> residual_blocks_;
1237};
1238
Austin Schuh70cc9552019-01-21 19:46:48 -08001239TEST_F(ProblemEvaluateTest, MultipleParameterAndResidualBlocks) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001240 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001241 ExpectedEvaluation expected = {
1242 // Rows/columns
1243 6, 6,
1244 // Cost
1245 7607.0,
1246 // Residuals
1247 { -19.0, -35.0, // f
1248 -59.0, -87.0, // g
1249 -27.0, -43.0 // h
1250 },
1251 // Gradient
1252 { 146.0, 484.0, // x
1253 582.0, 1256.0, // y
1254 1450.0, 2604.0, // z
1255 },
1256 // Jacobian
1257 // x y z
1258 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1259 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1260 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1261 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
1262 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1263 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1264 }
1265 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001266 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001267
1268 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1269}
1270
1271TEST_F(ProblemEvaluateTest, ParameterAndResidualBlocksPassedInOptions) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001272 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001273 ExpectedEvaluation expected = {
1274 // Rows/columns
1275 6, 6,
1276 // Cost
1277 7607.0,
1278 // Residuals
1279 { -19.0, -35.0, // f
1280 -59.0, -87.0, // g
1281 -27.0, -43.0 // h
1282 },
1283 // Gradient
1284 { 146.0, 484.0, // x
1285 582.0, 1256.0, // y
1286 1450.0, 2604.0, // z
1287 },
1288 // Jacobian
1289 // x y z
1290 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1291 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1292 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1293 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
1294 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1295 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1296 }
1297 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001298 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001299
1300 Problem::EvaluateOptions evaluate_options;
1301 evaluate_options.parameter_blocks = parameter_blocks_;
1302 evaluate_options.residual_blocks = residual_blocks_;
1303 CheckAllEvaluationCombinations(evaluate_options, expected);
1304}
1305
1306TEST_F(ProblemEvaluateTest, ReorderedResidualBlocks) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001307 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001308 ExpectedEvaluation expected = {
1309 // Rows/columns
1310 6, 6,
1311 // Cost
1312 7607.0,
1313 // Residuals
1314 { -19.0, -35.0, // f
1315 -27.0, -43.0, // h
1316 -59.0, -87.0 // g
1317 },
1318 // Gradient
1319 { 146.0, 484.0, // x
1320 582.0, 1256.0, // y
1321 1450.0, 2604.0, // z
1322 },
1323 // Jacobian
1324 // x y z
1325 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1326 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1327 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1328 0.0, -8.0, 0.0, 0.0, 0.0, -12.0,
1329 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1330 0.0, 0.0, 0.0, -8.0, 0.0, -24.0
1331 }
1332 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001333 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001334
1335 Problem::EvaluateOptions evaluate_options;
1336 evaluate_options.parameter_blocks = parameter_blocks_;
1337
1338 // f, h, g
1339 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1340 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1341 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1342
1343 CheckAllEvaluationCombinations(evaluate_options, expected);
1344}
1345
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001346TEST_F(ProblemEvaluateTest,
1347 ReorderedResidualBlocksAndReorderedParameterBlocks) {
1348 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001349 ExpectedEvaluation expected = {
1350 // Rows/columns
1351 6, 6,
1352 // Cost
1353 7607.0,
1354 // Residuals
1355 { -19.0, -35.0, // f
1356 -27.0, -43.0, // h
1357 -59.0, -87.0 // g
1358 },
1359 // Gradient
1360 { 1450.0, 2604.0, // z
1361 582.0, 1256.0, // y
1362 146.0, 484.0, // x
1363 },
1364 // Jacobian
1365 // z y x
1366 { /* f(x, y) */ 0.0, 0.0, -12.0, 0.0, -2.0, 0.0,
1367 0.0, 0.0, 0.0, -16.0, 0.0, -4.0,
1368 /* h(z, x) */ -10.0, 0.0, 0.0, 0.0, -4.0, 0.0,
1369 0.0, -12.0, 0.0, 0.0, 0.0, -8.0,
1370 /* g(y, z) */ -20.0, 0.0, -6.0, 0.0, 0.0, 0.0,
1371 0.0, -24.0, 0.0, -8.0, 0.0, 0.0
1372 }
1373 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001374 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001375
1376 Problem::EvaluateOptions evaluate_options;
1377 // z, y, x
1378 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1379 evaluate_options.parameter_blocks.push_back(parameter_blocks_[1]);
1380 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1381
1382 // f, h, g
1383 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1384 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1385 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1386
1387 CheckAllEvaluationCombinations(evaluate_options, expected);
1388}
1389
1390TEST_F(ProblemEvaluateTest, ConstantParameterBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001391 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001392 ExpectedEvaluation expected = {
1393 // Rows/columns
1394 6, 6,
1395 // Cost
1396 7607.0,
1397 // Residuals
1398 { -19.0, -35.0, // f
1399 -59.0, -87.0, // g
1400 -27.0, -43.0 // h
1401 },
1402
1403 // Gradient
1404 { 146.0, 484.0, // x
1405 0.0, 0.0, // y
1406 1450.0, 2604.0, // z
1407 },
1408
1409 // Jacobian
1410 // x y z
1411 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1412 0.0, -4.0, 0.0, 0.0, 0.0, 0.0,
1413 /* g(y, z) */ 0.0, 0.0, 0.0, 0.0, -20.0, 0.0,
1414 0.0, 0.0, 0.0, 0.0, 0.0, -24.0,
1415 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1416 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1417 }
1418 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001419 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001420
1421 problem_.SetParameterBlockConstant(parameters_ + 2);
1422 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1423}
1424
1425TEST_F(ProblemEvaluateTest, ExcludedAResidualBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001426 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001427 ExpectedEvaluation expected = {
1428 // Rows/columns
1429 4, 6,
1430 // Cost
1431 2082.0,
1432 // Residuals
1433 { -19.0, -35.0, // f
1434 -27.0, -43.0 // h
1435 },
1436 // Gradient
1437 { 146.0, 484.0, // x
1438 228.0, 560.0, // y
1439 270.0, 516.0, // z
1440 },
1441 // Jacobian
1442 // x y z
1443 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1444 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1445 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1446 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1447 }
1448 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001449 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001450
1451 Problem::EvaluateOptions evaluate_options;
1452 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1453 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1454
1455 CheckAllEvaluationCombinations(evaluate_options, expected);
1456}
1457
1458TEST_F(ProblemEvaluateTest, ExcludedParameterBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001459 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001460 ExpectedEvaluation expected = {
1461 // Rows/columns
1462 6, 4,
1463 // Cost
1464 7607.0,
1465 // Residuals
1466 { -19.0, -35.0, // f
1467 -59.0, -87.0, // g
1468 -27.0, -43.0 // h
1469 },
1470
1471 // Gradient
1472 { 146.0, 484.0, // x
1473 1450.0, 2604.0, // z
1474 },
1475
1476 // Jacobian
1477 // x z
1478 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
1479 0.0, -4.0, 0.0, 0.0,
1480 /* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
1481 0.0, 0.0, 0.0, -24.0,
1482 /* h(z, x) */ -4.0, 0.0, -10.0, 0.0,
1483 0.0, -8.0, 0.0, -12.0
1484 }
1485 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001486 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001487
1488 Problem::EvaluateOptions evaluate_options;
1489 // x, z
1490 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1491 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1492 evaluate_options.residual_blocks = residual_blocks_;
1493 CheckAllEvaluationCombinations(evaluate_options, expected);
1494}
1495
1496TEST_F(ProblemEvaluateTest, ExcludedParameterBlockAndExcludedResidualBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001497 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001498 ExpectedEvaluation expected = {
1499 // Rows/columns
1500 4, 4,
1501 // Cost
1502 6318.0,
1503 // Residuals
1504 { -19.0, -35.0, // f
1505 -59.0, -87.0, // g
1506 },
1507
1508 // Gradient
1509 { 38.0, 140.0, // x
1510 1180.0, 2088.0, // z
1511 },
1512
1513 // Jacobian
1514 // x z
1515 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
1516 0.0, -4.0, 0.0, 0.0,
1517 /* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
1518 0.0, 0.0, 0.0, -24.0,
1519 }
1520 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001521 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001522
1523 Problem::EvaluateOptions evaluate_options;
1524 // x, z
1525 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1526 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1527 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1528 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1529
1530 CheckAllEvaluationCombinations(evaluate_options, expected);
1531}
1532
1533TEST_F(ProblemEvaluateTest, LocalParameterization) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001534 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001535 ExpectedEvaluation expected = {
1536 // Rows/columns
1537 6, 5,
1538 // Cost
1539 7607.0,
1540 // Residuals
1541 { -19.0, -35.0, // f
1542 -59.0, -87.0, // g
1543 -27.0, -43.0 // h
1544 },
1545 // Gradient
1546 { 146.0, 484.0, // x
1547 1256.0, // y with SubsetParameterization
1548 1450.0, 2604.0, // z
1549 },
1550 // Jacobian
1551 // x y z
1552 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0,
1553 0.0, -4.0, -16.0, 0.0, 0.0,
1554 /* g(y, z) */ 0.0, 0.0, 0.0, -20.0, 0.0,
1555 0.0, 0.0, -8.0, 0.0, -24.0,
1556 /* h(z, x) */ -4.0, 0.0, 0.0, -10.0, 0.0,
1557 0.0, -8.0, 0.0, 0.0, -12.0
1558 }
1559 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001560 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001561
1562 vector<int> constant_parameters;
1563 constant_parameters.push_back(0);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001564 problem_.SetParameterization(
1565 parameters_ + 2, new SubsetParameterization(2, constant_parameters));
Austin Schuh70cc9552019-01-21 19:46:48 -08001566
1567 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1568}
1569
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001570struct IdentityFunctor {
1571 template <typename T>
1572 bool operator()(const T* x, const T* y, T* residuals) const {
1573 residuals[0] = x[0];
1574 residuals[1] = x[1];
1575 residuals[2] = y[0];
1576 residuals[3] = y[1];
1577 residuals[4] = y[2];
1578 return true;
1579 }
1580
1581 static CostFunction* Create() {
1582 return new AutoDiffCostFunction<IdentityFunctor, 5, 2, 3>(
1583 new IdentityFunctor);
1584 }
1585};
1586
1587class ProblemEvaluateResidualBlockTest : public ::testing::Test {
1588 public:
1589 static constexpr bool kApplyLossFunction = true;
1590 static constexpr bool kDoNotApplyLossFunction = false;
1591 static constexpr bool kNewPoint = true;
1592 static constexpr bool kNotNewPoint = false;
1593 static double loss_function_scale_;
1594
1595 protected:
1596 ProblemImpl problem_;
1597 double x_[2] = {1, 2};
1598 double y_[3] = {1, 2, 3};
1599};
1600
1601double ProblemEvaluateResidualBlockTest::loss_function_scale_ = 2.0;
1602
1603TEST_F(ProblemEvaluateResidualBlockTest,
1604 OneResidualBlockNoLossFunctionFullEval) {
1605 ResidualBlockId residual_block_id =
1606 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1607 Vector expected_f(5);
1608 expected_f << 1, 2, 1, 2, 3;
1609 Matrix expected_dfdx = Matrix::Zero(5, 2);
1610 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1611 Matrix expected_dfdy = Matrix::Zero(5, 3);
1612 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1613 double expected_cost = expected_f.squaredNorm() / 2.0;
1614
1615 double actual_cost;
1616 Vector actual_f(5);
1617 Matrix actual_dfdx(5, 2);
1618 Matrix actual_dfdy(5, 3);
1619 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1620 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1621 kApplyLossFunction,
1622 kNewPoint,
1623 &actual_cost,
1624 actual_f.data(),
1625 jacobians));
1626
1627 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1628 0,
1629 std::numeric_limits<double>::epsilon())
1630 << actual_cost;
1631 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1632 0,
1633 std::numeric_limits<double>::epsilon())
1634 << actual_f;
1635 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1636 0,
1637 std::numeric_limits<double>::epsilon())
1638 << actual_dfdx;
1639 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1640 0,
1641 std::numeric_limits<double>::epsilon())
1642 << actual_dfdy;
1643}
1644
1645TEST_F(ProblemEvaluateResidualBlockTest,
1646 OneResidualBlockNoLossFunctionNullEval) {
1647 ResidualBlockId residual_block_id =
1648 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1649 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1650 kApplyLossFunction,
1651 kNewPoint,
1652 nullptr,
1653 nullptr,
1654 nullptr));
1655}
1656
1657TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockNoLossFunctionCost) {
1658 ResidualBlockId residual_block_id =
1659 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1660 Vector expected_f(5);
1661 expected_f << 1, 2, 1, 2, 3;
1662 double expected_cost = expected_f.squaredNorm() / 2.0;
1663
1664 double actual_cost;
1665 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1666 kApplyLossFunction,
1667 kNewPoint,
1668 &actual_cost,
1669 nullptr,
1670 nullptr));
1671
1672 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1673 0,
1674 std::numeric_limits<double>::epsilon())
1675 << actual_cost;
1676}
1677
1678TEST_F(ProblemEvaluateResidualBlockTest,
1679 OneResidualBlockNoLossFunctionCostAndResidual) {
1680 ResidualBlockId residual_block_id =
1681 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1682 Vector expected_f(5);
1683 expected_f << 1, 2, 1, 2, 3;
1684 double expected_cost = expected_f.squaredNorm() / 2.0;
1685
1686 double actual_cost;
1687 Vector actual_f(5);
1688 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1689 kApplyLossFunction,
1690 kNewPoint,
1691 &actual_cost,
1692 actual_f.data(),
1693 nullptr));
1694
1695 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1696 0,
1697 std::numeric_limits<double>::epsilon())
1698 << actual_cost;
1699 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1700 0,
1701 std::numeric_limits<double>::epsilon())
1702 << actual_f;
1703}
1704
1705TEST_F(ProblemEvaluateResidualBlockTest,
1706 OneResidualBlockNoLossFunctionCostResidualAndOneJacobian) {
1707 ResidualBlockId residual_block_id =
1708 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1709 Vector expected_f(5);
1710 expected_f << 1, 2, 1, 2, 3;
1711 Matrix expected_dfdx = Matrix::Zero(5, 2);
1712 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1713 double expected_cost = expected_f.squaredNorm() / 2.0;
1714
1715 double actual_cost;
1716 Vector actual_f(5);
1717 Matrix actual_dfdx(5, 2);
1718 double* jacobians[2] = {actual_dfdx.data(), nullptr};
1719 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1720 kApplyLossFunction,
1721 kNewPoint,
1722 &actual_cost,
1723 actual_f.data(),
1724 jacobians));
1725
1726 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1727 0,
1728 std::numeric_limits<double>::epsilon())
1729 << actual_cost;
1730 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1731 0,
1732 std::numeric_limits<double>::epsilon())
1733 << actual_f;
1734 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1735 0,
1736 std::numeric_limits<double>::epsilon())
1737 << actual_dfdx;
1738}
1739
1740TEST_F(ProblemEvaluateResidualBlockTest,
1741 OneResidualBlockNoLossFunctionResidual) {
1742 ResidualBlockId residual_block_id =
1743 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1744 Vector expected_f(5);
1745 expected_f << 1, 2, 1, 2, 3;
1746 Vector actual_f(5);
1747 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1748 kApplyLossFunction,
1749 kNewPoint,
1750 nullptr,
1751 actual_f.data(),
1752 nullptr));
1753
1754 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1755 0,
1756 std::numeric_limits<double>::epsilon())
1757 << actual_f;
1758}
1759
1760TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockWithLossFunction) {
1761 ResidualBlockId residual_block_id =
1762 problem_.AddResidualBlock(IdentityFunctor::Create(),
1763 new ScaledLoss(nullptr, 2.0, TAKE_OWNERSHIP),
1764 x_,
1765 y_);
1766 Vector expected_f(5);
1767 expected_f << 1, 2, 1, 2, 3;
1768 expected_f *= std::sqrt(loss_function_scale_);
1769 Matrix expected_dfdx = Matrix::Zero(5, 2);
1770 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1771 expected_dfdx *= std::sqrt(loss_function_scale_);
1772 Matrix expected_dfdy = Matrix::Zero(5, 3);
1773 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1774 expected_dfdy *= std::sqrt(loss_function_scale_);
1775 double expected_cost = expected_f.squaredNorm() / 2.0;
1776
1777 double actual_cost;
1778 Vector actual_f(5);
1779 Matrix actual_dfdx(5, 2);
1780 Matrix actual_dfdy(5, 3);
1781 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1782 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1783 kApplyLossFunction,
1784 kNewPoint,
1785 &actual_cost,
1786 actual_f.data(),
1787 jacobians));
1788
1789 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1790 0,
1791 std::numeric_limits<double>::epsilon())
1792 << actual_cost;
1793 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1794 0,
1795 std::numeric_limits<double>::epsilon())
1796 << actual_f;
1797 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1798 0,
1799 std::numeric_limits<double>::epsilon())
1800 << actual_dfdx;
1801 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1802 0,
1803 std::numeric_limits<double>::epsilon())
1804 << actual_dfdy;
1805}
1806
1807TEST_F(ProblemEvaluateResidualBlockTest,
1808 OneResidualBlockWithLossFunctionDisabled) {
1809 ResidualBlockId residual_block_id =
1810 problem_.AddResidualBlock(IdentityFunctor::Create(),
1811 new ScaledLoss(nullptr, 2.0, TAKE_OWNERSHIP),
1812 x_,
1813 y_);
1814 Vector expected_f(5);
1815 expected_f << 1, 2, 1, 2, 3;
1816 Matrix expected_dfdx = Matrix::Zero(5, 2);
1817 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1818 Matrix expected_dfdy = Matrix::Zero(5, 3);
1819 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1820 double expected_cost = expected_f.squaredNorm() / 2.0;
1821
1822 double actual_cost;
1823 Vector actual_f(5);
1824 Matrix actual_dfdx(5, 2);
1825 Matrix actual_dfdy(5, 3);
1826 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1827 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1828 kDoNotApplyLossFunction,
1829 kNewPoint,
1830 &actual_cost,
1831 actual_f.data(),
1832 jacobians));
1833
1834 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1835 0,
1836 std::numeric_limits<double>::epsilon())
1837 << actual_cost;
1838 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1839 0,
1840 std::numeric_limits<double>::epsilon())
1841 << actual_f;
1842 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1843 0,
1844 std::numeric_limits<double>::epsilon())
1845 << actual_dfdx;
1846 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1847 0,
1848 std::numeric_limits<double>::epsilon())
1849 << actual_dfdy;
1850}
1851
1852TEST_F(ProblemEvaluateResidualBlockTest,
1853 OneResidualBlockWithOneLocalParameterization) {
1854 ResidualBlockId residual_block_id =
1855 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1856 problem_.SetParameterization(x_, new SubsetParameterization(2, {1}));
1857
1858 Vector expected_f(5);
1859 expected_f << 1, 2, 1, 2, 3;
1860 Matrix expected_dfdx = Matrix::Zero(5, 1);
1861 expected_dfdx.block(0, 0, 1, 1) = Matrix::Identity(1, 1);
1862 Matrix expected_dfdy = Matrix::Zero(5, 3);
1863 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1864 double expected_cost = expected_f.squaredNorm() / 2.0;
1865
1866 double actual_cost;
1867 Vector actual_f(5);
1868 Matrix actual_dfdx(5, 1);
1869 Matrix actual_dfdy(5, 3);
1870 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1871 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1872 kApplyLossFunction,
1873 kNewPoint,
1874 &actual_cost,
1875 actual_f.data(),
1876 jacobians));
1877
1878 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1879 0,
1880 std::numeric_limits<double>::epsilon())
1881 << actual_cost;
1882 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1883 0,
1884 std::numeric_limits<double>::epsilon())
1885 << actual_f;
1886 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1887 0,
1888 std::numeric_limits<double>::epsilon())
1889 << actual_dfdx;
1890 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1891 0,
1892 std::numeric_limits<double>::epsilon())
1893 << actual_dfdy;
1894}
1895
1896TEST_F(ProblemEvaluateResidualBlockTest,
1897 OneResidualBlockWithTwoLocalParameterizations) {
1898 ResidualBlockId residual_block_id =
1899 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1900 problem_.SetParameterization(x_, new SubsetParameterization(2, {1}));
1901 problem_.SetParameterization(y_, new SubsetParameterization(3, {2}));
1902
1903 Vector expected_f(5);
1904 expected_f << 1, 2, 1, 2, 3;
1905 Matrix expected_dfdx = Matrix::Zero(5, 1);
1906 expected_dfdx.block(0, 0, 1, 1) = Matrix::Identity(1, 1);
1907 Matrix expected_dfdy = Matrix::Zero(5, 2);
1908 expected_dfdy.block(2, 0, 2, 2) = Matrix::Identity(2, 2);
1909 double expected_cost = expected_f.squaredNorm() / 2.0;
1910
1911 double actual_cost;
1912 Vector actual_f(5);
1913 Matrix actual_dfdx(5, 1);
1914 Matrix actual_dfdy(5, 2);
1915 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1916 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1917 kApplyLossFunction,
1918 kNewPoint,
1919 &actual_cost,
1920 actual_f.data(),
1921 jacobians));
1922
1923 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1924 0,
1925 std::numeric_limits<double>::epsilon())
1926 << actual_cost;
1927 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1928 0,
1929 std::numeric_limits<double>::epsilon())
1930 << actual_f;
1931 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1932 0,
1933 std::numeric_limits<double>::epsilon())
1934 << actual_dfdx;
1935 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1936 0,
1937 std::numeric_limits<double>::epsilon())
1938 << actual_dfdy;
1939}
1940
1941TEST_F(ProblemEvaluateResidualBlockTest,
1942 OneResidualBlockWithOneConstantParameterBlock) {
1943 ResidualBlockId residual_block_id =
1944 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1945 problem_.SetParameterBlockConstant(x_);
1946
1947 Vector expected_f(5);
1948 expected_f << 1, 2, 1, 2, 3;
1949 Matrix expected_dfdy = Matrix::Zero(5, 3);
1950 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1951 double expected_cost = expected_f.squaredNorm() / 2.0;
1952
1953 double actual_cost;
1954 Vector actual_f(5);
1955 Matrix actual_dfdx(5, 2);
1956 Matrix actual_dfdy(5, 3);
1957
1958 // Try evaluating both Jacobians, this should fail.
1959 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1960 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
1961 kApplyLossFunction,
1962 kNewPoint,
1963 &actual_cost,
1964 actual_f.data(),
1965 jacobians));
1966
1967 jacobians[0] = nullptr;
1968 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1969 kApplyLossFunction,
1970 kNewPoint,
1971 &actual_cost,
1972 actual_f.data(),
1973 jacobians));
1974
1975 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1976 0,
1977 std::numeric_limits<double>::epsilon())
1978 << actual_cost;
1979 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1980 0,
1981 std::numeric_limits<double>::epsilon())
1982 << actual_f;
1983 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1984 0,
1985 std::numeric_limits<double>::epsilon())
1986 << actual_dfdy;
1987}
1988
1989TEST_F(ProblemEvaluateResidualBlockTest,
1990 OneResidualBlockWithAllConstantParameterBlocks) {
1991 ResidualBlockId residual_block_id =
1992 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1993 problem_.SetParameterBlockConstant(x_);
1994 problem_.SetParameterBlockConstant(y_);
1995
1996 Vector expected_f(5);
1997 expected_f << 1, 2, 1, 2, 3;
1998 double expected_cost = expected_f.squaredNorm() / 2.0;
1999
2000 double actual_cost;
2001 Vector actual_f(5);
2002 Matrix actual_dfdx(5, 2);
2003 Matrix actual_dfdy(5, 3);
2004
2005 // Try evaluating with one or more Jacobians, this should fail.
2006 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2007 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
2008 kApplyLossFunction,
2009 kNewPoint,
2010 &actual_cost,
2011 actual_f.data(),
2012 jacobians));
2013
2014 jacobians[0] = nullptr;
2015 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
2016 kApplyLossFunction,
2017 kNewPoint,
2018 &actual_cost,
2019 actual_f.data(),
2020 jacobians));
2021 jacobians[1] = nullptr;
2022 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
2023 kApplyLossFunction,
2024 kNewPoint,
2025 &actual_cost,
2026 actual_f.data(),
2027 jacobians));
2028
2029 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
2030 0,
2031 std::numeric_limits<double>::epsilon())
2032 << actual_cost;
2033 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
2034 0,
2035 std::numeric_limits<double>::epsilon())
2036 << actual_f;
2037}
2038
2039TEST_F(ProblemEvaluateResidualBlockTest,
2040 OneResidualBlockWithOneParameterBlockConstantAndParameterBlockChanged) {
2041 ResidualBlockId residual_block_id =
2042 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2043 problem_.SetParameterBlockConstant(x_);
2044
2045 x_[0] = 2;
2046 y_[2] = 1;
2047 Vector expected_f(5);
2048 expected_f << 2, 2, 1, 2, 1;
2049 Matrix expected_dfdy = Matrix::Zero(5, 3);
2050 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
2051 double expected_cost = expected_f.squaredNorm() / 2.0;
2052
2053 double actual_cost;
2054 Vector actual_f(5);
2055 Matrix actual_dfdx(5, 2);
2056 Matrix actual_dfdy(5, 3);
2057
2058 // Try evaluating with one or more Jacobians, this should fail.
2059 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2060 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
2061 kApplyLossFunction,
2062 kNewPoint,
2063 &actual_cost,
2064 actual_f.data(),
2065 jacobians));
2066
2067 jacobians[0] = nullptr;
2068 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
2069 kApplyLossFunction,
2070 kNewPoint,
2071 &actual_cost,
2072 actual_f.data(),
2073 jacobians));
2074 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
2075 0,
2076 std::numeric_limits<double>::epsilon())
2077 << actual_cost;
2078 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
2079 0,
2080 std::numeric_limits<double>::epsilon())
2081 << actual_f;
2082 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
2083 0,
2084 std::numeric_limits<double>::epsilon())
2085 << actual_dfdy;
2086}
2087
Austin Schuh70cc9552019-01-21 19:46:48 -08002088TEST(Problem, SetAndGetParameterLowerBound) {
2089 Problem problem;
2090 double x[] = {1.0, 2.0};
2091 problem.AddParameterBlock(x, 2);
2092
2093 EXPECT_EQ(problem.GetParameterLowerBound(x, 0),
2094 -std::numeric_limits<double>::max());
2095 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2096 -std::numeric_limits<double>::max());
2097
2098 problem.SetParameterLowerBound(x, 0, -1.0);
2099 EXPECT_EQ(problem.GetParameterLowerBound(x, 0), -1.0);
2100 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2101 -std::numeric_limits<double>::max());
2102
2103 problem.SetParameterLowerBound(x, 0, -2.0);
2104 EXPECT_EQ(problem.GetParameterLowerBound(x, 0), -2.0);
2105 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2106 -std::numeric_limits<double>::max());
2107
2108 problem.SetParameterLowerBound(x, 0, -std::numeric_limits<double>::max());
2109 EXPECT_EQ(problem.GetParameterLowerBound(x, 0),
2110 -std::numeric_limits<double>::max());
2111 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2112 -std::numeric_limits<double>::max());
2113}
2114
2115TEST(Problem, SetAndGetParameterUpperBound) {
2116 Problem problem;
2117 double x[] = {1.0, 2.0};
2118 problem.AddParameterBlock(x, 2);
2119
2120 EXPECT_EQ(problem.GetParameterUpperBound(x, 0),
2121 std::numeric_limits<double>::max());
2122 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2123 std::numeric_limits<double>::max());
2124
2125 problem.SetParameterUpperBound(x, 0, -1.0);
2126 EXPECT_EQ(problem.GetParameterUpperBound(x, 0), -1.0);
2127 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2128 std::numeric_limits<double>::max());
2129
2130 problem.SetParameterUpperBound(x, 0, -2.0);
2131 EXPECT_EQ(problem.GetParameterUpperBound(x, 0), -2.0);
2132 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2133 std::numeric_limits<double>::max());
2134
2135 problem.SetParameterUpperBound(x, 0, std::numeric_limits<double>::max());
2136 EXPECT_EQ(problem.GetParameterUpperBound(x, 0),
2137 std::numeric_limits<double>::max());
2138 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2139 std::numeric_limits<double>::max());
2140}
2141
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002142TEST(Problem, SetParameterizationTwice) {
2143 Problem problem;
2144 double x[] = {1.0, 2.0, 3.0};
2145 problem.AddParameterBlock(x, 3);
2146 problem.SetParameterization(x, new SubsetParameterization(3, {1}));
2147 EXPECT_EQ(problem.GetParameterization(x)->GlobalSize(), 3);
2148 EXPECT_EQ(problem.GetParameterization(x)->LocalSize(), 2);
2149
2150 problem.SetParameterization(x, new SubsetParameterization(3, {0, 1}));
2151 EXPECT_EQ(problem.GetParameterization(x)->GlobalSize(), 3);
2152 EXPECT_EQ(problem.GetParameterization(x)->LocalSize(), 1);
2153}
2154
2155TEST(Problem, SetParameterizationAndThenClearItWithNull) {
2156 Problem problem;
2157 double x[] = {1.0, 2.0, 3.0};
2158 problem.AddParameterBlock(x, 3);
2159 problem.SetParameterization(x, new SubsetParameterization(3, {1}));
2160 EXPECT_EQ(problem.GetParameterization(x)->GlobalSize(), 3);
2161 EXPECT_EQ(problem.GetParameterization(x)->LocalSize(), 2);
2162
2163 problem.SetParameterization(x, nullptr);
2164 EXPECT_EQ(problem.GetParameterization(x), nullptr);
2165 EXPECT_EQ(problem.ParameterBlockLocalSize(x), 3);
2166 EXPECT_EQ(problem.ParameterBlockSize(x), 3);
2167}
2168
2169TEST(Solver, ZeroSizedLocalParameterizationMeansParameterBlockIsConstant) {
2170 double x = 0.0;
2171 double y = 1.0;
2172 Problem problem;
2173 problem.AddResidualBlock(new BinaryCostFunction(1, 1, 1), nullptr, &x, &y);
2174 problem.SetParameterization(&y, new SubsetParameterization(1, {0}));
2175 EXPECT_TRUE(problem.IsParameterBlockConstant(&y));
2176}
2177
2178class MockEvaluationCallback : public EvaluationCallback {
2179 public:
2180 MOCK_METHOD2(PrepareForEvaluation, void(bool, bool));
2181};
2182
2183TEST(ProblemEvaluate, CallsEvaluationCallbackWithoutJacobian) {
2184 constexpr bool kDoNotComputeJacobians = false;
2185 constexpr bool kNewPoint = true;
2186
2187 MockEvaluationCallback evaluation_callback;
2188 EXPECT_CALL(evaluation_callback,
2189 PrepareForEvaluation(kDoNotComputeJacobians, kNewPoint))
2190 .Times(1);
2191
2192 Problem::Options options;
2193 options.evaluation_callback = &evaluation_callback;
2194 ProblemImpl problem(options);
2195 double x_[2] = {1, 2};
2196 double y_[3] = {1, 2, 3};
2197 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2198
2199 double actual_cost;
2200 EXPECT_TRUE(problem.Evaluate(
2201 Problem::EvaluateOptions(), &actual_cost, nullptr, nullptr, nullptr));
2202}
2203
2204TEST(ProblemEvaluate, CallsEvaluationCallbackWithJacobian) {
2205 constexpr bool kComputeJacobians = true;
2206 constexpr bool kNewPoint = true;
2207
2208 MockEvaluationCallback evaluation_callback;
2209 EXPECT_CALL(evaluation_callback,
2210 PrepareForEvaluation(kComputeJacobians, kNewPoint))
2211 .Times(1);
2212
2213 Problem::Options options;
2214 options.evaluation_callback = &evaluation_callback;
2215 ProblemImpl problem(options);
2216 double x_[2] = {1, 2};
2217 double y_[3] = {1, 2, 3};
2218 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2219
2220 double actual_cost;
2221 ceres::CRSMatrix jacobian;
2222 EXPECT_TRUE(problem.Evaluate(
2223 Problem::EvaluateOptions(), &actual_cost, nullptr, nullptr, &jacobian));
2224}
2225
2226TEST(ProblemEvaluateResidualBlock, NewPointCallsEvaluationCallback) {
2227 constexpr bool kComputeJacobians = true;
2228 constexpr bool kNewPoint = true;
2229
2230 MockEvaluationCallback evaluation_callback;
2231 EXPECT_CALL(evaluation_callback,
2232 PrepareForEvaluation(kComputeJacobians, kNewPoint))
2233 .Times(1);
2234
2235 Problem::Options options;
2236 options.evaluation_callback = &evaluation_callback;
2237 ProblemImpl problem(options);
2238 double x_[2] = {1, 2};
2239 double y_[3] = {1, 2, 3};
2240 ResidualBlockId residual_block_id =
2241 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2242
2243 double actual_cost;
2244 Vector actual_f(5);
2245 Matrix actual_dfdx(5, 2);
2246 Matrix actual_dfdy(5, 3);
2247 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2248 EXPECT_TRUE(problem.EvaluateResidualBlock(
2249 residual_block_id, true, true, &actual_cost, actual_f.data(), jacobians));
2250}
2251
2252TEST(ProblemEvaluateResidualBlock, OldPointCallsEvaluationCallback) {
2253 constexpr bool kComputeJacobians = true;
2254 constexpr bool kOldPoint = false;
2255
2256 MockEvaluationCallback evaluation_callback;
2257 EXPECT_CALL(evaluation_callback,
2258 PrepareForEvaluation(kComputeJacobians, kOldPoint))
2259 .Times(1);
2260
2261 Problem::Options options;
2262 options.evaluation_callback = &evaluation_callback;
2263 ProblemImpl problem(options);
2264 double x_[2] = {1, 2};
2265 double y_[3] = {1, 2, 3};
2266 ResidualBlockId residual_block_id =
2267 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2268
2269 double actual_cost;
2270 Vector actual_f(5);
2271 Matrix actual_dfdx(5, 2);
2272 Matrix actual_dfdy(5, 3);
2273 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2274 EXPECT_TRUE(problem.EvaluateResidualBlock(residual_block_id,
2275 true,
2276 false,
2277 &actual_cost,
2278 actual_f.data(),
2279 jacobians));
2280}
2281
Austin Schuh70cc9552019-01-21 19:46:48 -08002282} // namespace internal
2283} // namespace ceres