blob: db082ec82c43bc1b8709f24c1cc203f1c2fa954b [file] [log] [blame]
Angus Kong0ae28bd2013-02-13 14:56:04 -08001// Ceres Solver - A fast non-linear least squares minimizer
Carlos Hernandez79397c22014-08-07 17:51:38 -07002// Copyright 2013 Google Inc. All rights reserved.
Angus Kong0ae28bd2013-02-13 14:56:04 -08003// http://code.google.com/p/ceres-solver/
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"
Sascha Haeberling1d2624a2013-07-23 19:00:21 -070033#include "ceres/problem_impl.h"
Angus Kong0ae28bd2013-02-13 14:56:04 -080034
Sascha Haeberling1d2624a2013-07-23 19:00:21 -070035#include "ceres/casts.h"
Angus Kong0ae28bd2013-02-13 14:56:04 -080036#include "ceres/cost_function.h"
Sascha Haeberling1d2624a2013-07-23 19:00:21 -070037#include "ceres/crs_matrix.h"
38#include "ceres/evaluator_test_utils.cc"
39#include "ceres/internal/eigen.h"
Angus Kong0ae28bd2013-02-13 14:56:04 -080040#include "ceres/internal/scoped_ptr.h"
Sascha Haeberling1d2624a2013-07-23 19:00:21 -070041#include "ceres/local_parameterization.h"
42#include "ceres/map_util.h"
43#include "ceres/parameter_block.h"
44#include "ceres/program.h"
45#include "ceres/sized_cost_function.h"
46#include "ceres/sparse_matrix.h"
47#include "ceres/types.h"
48#include "gtest/gtest.h"
Angus Kong0ae28bd2013-02-13 14:56:04 -080049
50namespace ceres {
51namespace internal {
52
53// The following three classes are for the purposes of defining
54// function signatures. They have dummy Evaluate functions.
55
56// Trivial cost function that accepts a single argument.
57class UnaryCostFunction : public CostFunction {
58 public:
Carlos Hernandez79397c22014-08-07 17:51:38 -070059 UnaryCostFunction(int num_residuals, int32 parameter_block_size) {
Angus Kong0ae28bd2013-02-13 14:56:04 -080060 set_num_residuals(num_residuals);
61 mutable_parameter_block_sizes()->push_back(parameter_block_size);
62 }
63 virtual ~UnaryCostFunction() {}
64
65 virtual bool Evaluate(double const* const* parameters,
66 double* residuals,
67 double** jacobians) const {
68 for (int i = 0; i < num_residuals(); ++i) {
69 residuals[i] = 1;
70 }
71 return true;
72 }
73};
74
75// Trivial cost function that accepts two arguments.
76class BinaryCostFunction: public CostFunction {
77 public:
78 BinaryCostFunction(int num_residuals,
Carlos Hernandez79397c22014-08-07 17:51:38 -070079 int32 parameter_block1_size,
80 int32 parameter_block2_size) {
Angus Kong0ae28bd2013-02-13 14:56:04 -080081 set_num_residuals(num_residuals);
82 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
83 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
84 }
85
86 virtual bool Evaluate(double const* const* parameters,
87 double* residuals,
88 double** jacobians) const {
89 for (int i = 0; i < num_residuals(); ++i) {
90 residuals[i] = 2;
91 }
92 return true;
93 }
94};
95
96// Trivial cost function that accepts three arguments.
97class TernaryCostFunction: public CostFunction {
98 public:
99 TernaryCostFunction(int num_residuals,
Carlos Hernandez79397c22014-08-07 17:51:38 -0700100 int32 parameter_block1_size,
101 int32 parameter_block2_size,
102 int32 parameter_block3_size) {
Angus Kong0ae28bd2013-02-13 14:56:04 -0800103 set_num_residuals(num_residuals);
104 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
105 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
106 mutable_parameter_block_sizes()->push_back(parameter_block3_size);
107 }
108
109 virtual bool Evaluate(double const* const* parameters,
110 double* residuals,
111 double** jacobians) const {
112 for (int i = 0; i < num_residuals(); ++i) {
113 residuals[i] = 3;
114 }
115 return true;
116 }
117};
118
119TEST(Problem, AddResidualWithNullCostFunctionDies) {
120 double x[3], y[4], z[5];
121
122 Problem problem;
123 problem.AddParameterBlock(x, 3);
124 problem.AddParameterBlock(y, 4);
125 problem.AddParameterBlock(z, 5);
126
127 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(NULL, NULL, x),
128 "'cost_function' Must be non NULL");
129}
130
131TEST(Problem, AddResidualWithIncorrectNumberOfParameterBlocksDies) {
132 double x[3], y[4], z[5];
133
134 Problem problem;
135 problem.AddParameterBlock(x, 3);
136 problem.AddParameterBlock(y, 4);
137 problem.AddParameterBlock(z, 5);
138
139 // UnaryCostFunction takes only one parameter, but two are passed.
140 EXPECT_DEATH_IF_SUPPORTED(
141 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x, y),
Carlos Hernandez79397c22014-08-07 17:51:38 -0700142 "parameter_blocks.size");
Angus Kong0ae28bd2013-02-13 14:56:04 -0800143}
144
145TEST(Problem, AddResidualWithDifferentSizesOnTheSameVariableDies) {
146 double x[3];
147
148 Problem problem;
149 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
150 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
151 new UnaryCostFunction(
152 2, 4 /* 4 != 3 */), NULL, x),
153 "different block sizes");
154}
155
156TEST(Problem, AddResidualWithDuplicateParametersDies) {
157 double x[3], z[5];
158
159 Problem problem;
160 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
161 new BinaryCostFunction(2, 3, 3), NULL, x, x),
162 "Duplicate parameter blocks");
163 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
164 new TernaryCostFunction(1, 5, 3, 5),
165 NULL, z, x, z),
166 "Duplicate parameter blocks");
167}
168
169TEST(Problem, AddResidualWithIncorrectSizesOfParameterBlockDies) {
170 double x[3], y[4], z[5];
171
172 Problem problem;
173 problem.AddParameterBlock(x, 3);
174 problem.AddParameterBlock(y, 4);
175 problem.AddParameterBlock(z, 5);
176
177 // The cost function expects the size of the second parameter, z, to be 4
178 // instead of 5 as declared above. This is fatal.
179 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
180 new BinaryCostFunction(2, 3, 4), NULL, x, z),
181 "different block sizes");
182}
183
184TEST(Problem, AddResidualAddsDuplicatedParametersOnlyOnce) {
185 double x[3], y[4], z[5];
186
187 Problem problem;
188 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
189 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
190 problem.AddResidualBlock(new UnaryCostFunction(2, 4), NULL, y);
191 problem.AddResidualBlock(new UnaryCostFunction(2, 5), NULL, z);
192
193 EXPECT_EQ(3, problem.NumParameterBlocks());
194 EXPECT_EQ(12, problem.NumParameters());
195}
196
197TEST(Problem, AddParameterWithDifferentSizesOnTheSameVariableDies) {
198 double x[3], y[4];
199
200 Problem problem;
201 problem.AddParameterBlock(x, 3);
202 problem.AddParameterBlock(y, 4);
203
204 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(x, 4),
205 "different block sizes");
206}
207
208static double *IntToPtr(int i) {
209 return reinterpret_cast<double*>(sizeof(double) * i); // NOLINT
210}
211
212TEST(Problem, AddParameterWithAliasedParametersDies) {
213 // Layout is
214 //
215 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
216 // [x] x x x x [y] y y
217 // o==o==o o==o==o o==o
218 // o--o--o o--o--o o--o o--o--o
219 //
220 // Parameter block additions are tested as listed above; expected successful
221 // ones marked with o==o and aliasing ones marked with o--o.
222
223 Problem problem;
224 problem.AddParameterBlock(IntToPtr(5), 5); // x
225 problem.AddParameterBlock(IntToPtr(13), 3); // y
226
227 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 4), 2),
228 "Aliasing detected");
229 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 4), 3),
230 "Aliasing detected");
231 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 4), 9),
232 "Aliasing detected");
233 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 8), 3),
234 "Aliasing detected");
235 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(12), 2),
236 "Aliasing detected");
237 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(14), 3),
238 "Aliasing detected");
239
240 // These ones should work.
241 problem.AddParameterBlock(IntToPtr( 2), 3);
242 problem.AddParameterBlock(IntToPtr(10), 3);
243 problem.AddParameterBlock(IntToPtr(16), 2);
244
245 ASSERT_EQ(5, problem.NumParameterBlocks());
246}
247
248TEST(Problem, AddParameterIgnoresDuplicateCalls) {
249 double x[3], y[4];
250
251 Problem problem;
252 problem.AddParameterBlock(x, 3);
253 problem.AddParameterBlock(y, 4);
254
255 // Creating parameter blocks multiple times is ignored.
256 problem.AddParameterBlock(x, 3);
257 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
258
259 // ... even repeatedly.
260 problem.AddParameterBlock(x, 3);
261 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
262
263 // More parameters are fine.
264 problem.AddParameterBlock(y, 4);
265 problem.AddResidualBlock(new UnaryCostFunction(2, 4), NULL, y);
266
267 EXPECT_EQ(2, problem.NumParameterBlocks());
268 EXPECT_EQ(7, problem.NumParameters());
269}
270
271TEST(Problem, AddingParametersAndResidualsResultsInExpectedProblem) {
272 double x[3], y[4], z[5], w[4];
273
274 Problem problem;
275 problem.AddParameterBlock(x, 3);
276 EXPECT_EQ(1, problem.NumParameterBlocks());
277 EXPECT_EQ(3, problem.NumParameters());
278
279 problem.AddParameterBlock(y, 4);
280 EXPECT_EQ(2, problem.NumParameterBlocks());
281 EXPECT_EQ(7, problem.NumParameters());
282
283 problem.AddParameterBlock(z, 5);
284 EXPECT_EQ(3, problem.NumParameterBlocks());
285 EXPECT_EQ(12, problem.NumParameters());
286
287 // Add a parameter that has a local parameterization.
288 w[0] = 1.0; w[1] = 0.0; w[2] = 0.0; w[3] = 0.0;
289 problem.AddParameterBlock(w, 4, new QuaternionParameterization);
290 EXPECT_EQ(4, problem.NumParameterBlocks());
291 EXPECT_EQ(16, problem.NumParameters());
292
293 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
294 problem.AddResidualBlock(new BinaryCostFunction(6, 5, 4) , NULL, z, y);
295 problem.AddResidualBlock(new BinaryCostFunction(3, 3, 5), NULL, x, z);
296 problem.AddResidualBlock(new BinaryCostFunction(7, 5, 3), NULL, z, x);
297 problem.AddResidualBlock(new TernaryCostFunction(1, 5, 3, 4), NULL, z, x, y);
298
299 const int total_residuals = 2 + 6 + 3 + 7 + 1;
300 EXPECT_EQ(problem.NumResidualBlocks(), 5);
301 EXPECT_EQ(problem.NumResiduals(), total_residuals);
302}
303
304class DestructorCountingCostFunction : public SizedCostFunction<3, 4, 5> {
305 public:
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700306 explicit DestructorCountingCostFunction(int *num_destructions)
307 : num_destructions_(num_destructions) {}
Angus Kong0ae28bd2013-02-13 14:56:04 -0800308
309 virtual ~DestructorCountingCostFunction() {
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700310 *num_destructions_ += 1;
Angus Kong0ae28bd2013-02-13 14:56:04 -0800311 }
312
313 virtual bool Evaluate(double const* const* parameters,
314 double* residuals,
315 double** jacobians) const {
316 return true;
317 }
318
319 private:
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700320 int* num_destructions_;
Angus Kong0ae28bd2013-02-13 14:56:04 -0800321};
322
323TEST(Problem, ReusedCostFunctionsAreOnlyDeletedOnce) {
324 double y[4], z[5];
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700325 int num_destructions = 0;
Angus Kong0ae28bd2013-02-13 14:56:04 -0800326
327 // Add a cost function multiple times and check to make sure that
328 // the destructor on the cost function is only called once.
329 {
330 Problem problem;
331 problem.AddParameterBlock(y, 4);
332 problem.AddParameterBlock(z, 5);
333
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700334 CostFunction* cost = new DestructorCountingCostFunction(&num_destructions);
Angus Kong0ae28bd2013-02-13 14:56:04 -0800335 problem.AddResidualBlock(cost, NULL, y, z);
336 problem.AddResidualBlock(cost, NULL, y, z);
337 problem.AddResidualBlock(cost, NULL, y, z);
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700338 EXPECT_EQ(3, problem.NumResidualBlocks());
Angus Kong0ae28bd2013-02-13 14:56:04 -0800339 }
340
341 // Check that the destructor was called only once.
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700342 CHECK_EQ(num_destructions, 1);
343}
344
345TEST(Problem, CostFunctionsAreDeletedEvenWithRemovals) {
346 double y[4], z[5], w[4];
347 int num_destructions = 0;
348 {
349 Problem problem;
350 problem.AddParameterBlock(y, 4);
351 problem.AddParameterBlock(z, 5);
352
353 CostFunction* cost_yz =
354 new DestructorCountingCostFunction(&num_destructions);
355 CostFunction* cost_wz =
356 new DestructorCountingCostFunction(&num_destructions);
357 ResidualBlock* r_yz = problem.AddResidualBlock(cost_yz, NULL, y, z);
358 ResidualBlock* r_wz = problem.AddResidualBlock(cost_wz, NULL, w, z);
359 EXPECT_EQ(2, problem.NumResidualBlocks());
360
361 // In the current implementation, the destructor shouldn't get run yet.
362 problem.RemoveResidualBlock(r_yz);
363 CHECK_EQ(num_destructions, 0);
364 problem.RemoveResidualBlock(r_wz);
365 CHECK_EQ(num_destructions, 0);
366
367 EXPECT_EQ(0, problem.NumResidualBlocks());
368 }
369 CHECK_EQ(num_destructions, 2);
370}
371
372// Make the dynamic problem tests (e.g. for removing residual blocks)
373// parameterized on whether the low-latency mode is enabled or not.
374//
375// This tests against ProblemImpl instead of Problem in order to inspect the
376// state of the resulting Program; this is difficult with only the thin Problem
377// interface.
378struct DynamicProblem : public ::testing::TestWithParam<bool> {
379 DynamicProblem() {
380 Problem::Options options;
Carlos Hernandez79397c22014-08-07 17:51:38 -0700381 options.enable_fast_removal = GetParam();
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700382 problem.reset(new ProblemImpl(options));
383 }
384
385 ParameterBlock* GetParameterBlock(int block) {
386 return problem->program().parameter_blocks()[block];
387 }
388 ResidualBlock* GetResidualBlock(int block) {
389 return problem->program().residual_blocks()[block];
390 }
391
392 bool HasResidualBlock(ResidualBlock* residual_block) {
Carlos Hernandez79397c22014-08-07 17:51:38 -0700393 bool have_residual_block = true;
394 if (GetParam()) {
395 have_residual_block &=
396 (problem->residual_block_set().find(residual_block) !=
397 problem->residual_block_set().end());
398 }
399 have_residual_block &=
400 find(problem->program().residual_blocks().begin(),
401 problem->program().residual_blocks().end(),
402 residual_block) != problem->program().residual_blocks().end();
403 return have_residual_block;
404 }
405
406 int NumResidualBlocks() {
407 // Verify that the hash set of residuals is maintained consistently.
408 if (GetParam()) {
409 EXPECT_EQ(problem->residual_block_set().size(),
410 problem->NumResidualBlocks());
411 }
412 return problem->NumResidualBlocks();
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700413 }
414
415 // The next block of functions until the end are only for testing the
416 // residual block removals.
417 void ExpectParameterBlockContainsResidualBlock(
418 double* values,
419 ResidualBlock* residual_block) {
420 ParameterBlock* parameter_block =
421 FindOrDie(problem->parameter_map(), values);
422 EXPECT_TRUE(ContainsKey(*(parameter_block->mutable_residual_blocks()),
423 residual_block));
424 }
425
426 void ExpectSize(double* values, int size) {
427 ParameterBlock* parameter_block =
428 FindOrDie(problem->parameter_map(), values);
429 EXPECT_EQ(size, parameter_block->mutable_residual_blocks()->size());
430 }
431
432 // Degenerate case.
433 void ExpectParameterBlockContains(double* values) {
434 ExpectSize(values, 0);
435 }
436
437 void ExpectParameterBlockContains(double* values,
438 ResidualBlock* r1) {
439 ExpectSize(values, 1);
440 ExpectParameterBlockContainsResidualBlock(values, r1);
441 }
442
443 void ExpectParameterBlockContains(double* values,
444 ResidualBlock* r1,
445 ResidualBlock* r2) {
446 ExpectSize(values, 2);
447 ExpectParameterBlockContainsResidualBlock(values, r1);
448 ExpectParameterBlockContainsResidualBlock(values, r2);
449 }
450
451 void ExpectParameterBlockContains(double* values,
452 ResidualBlock* r1,
453 ResidualBlock* r2,
454 ResidualBlock* r3) {
455 ExpectSize(values, 3);
456 ExpectParameterBlockContainsResidualBlock(values, r1);
457 ExpectParameterBlockContainsResidualBlock(values, r2);
458 ExpectParameterBlockContainsResidualBlock(values, r3);
459 }
460
461 void ExpectParameterBlockContains(double* values,
462 ResidualBlock* r1,
463 ResidualBlock* r2,
464 ResidualBlock* r3,
465 ResidualBlock* r4) {
466 ExpectSize(values, 4);
467 ExpectParameterBlockContainsResidualBlock(values, r1);
468 ExpectParameterBlockContainsResidualBlock(values, r2);
469 ExpectParameterBlockContainsResidualBlock(values, r3);
470 ExpectParameterBlockContainsResidualBlock(values, r4);
471 }
472
473 scoped_ptr<ProblemImpl> problem;
474 double y[4], z[5], w[3];
475};
476
477TEST(Problem, SetParameterBlockConstantWithUnknownPtrDies) {
478 double x[3];
479 double y[2];
480
481 Problem problem;
482 problem.AddParameterBlock(x, 3);
483
484 EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockConstant(y),
485 "Parameter block not found:");
486}
487
488TEST(Problem, SetParameterBlockVariableWithUnknownPtrDies) {
489 double x[3];
490 double y[2];
491
492 Problem problem;
493 problem.AddParameterBlock(x, 3);
494
495 EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockVariable(y),
496 "Parameter block not found:");
497}
498
499TEST(Problem, SetLocalParameterizationWithUnknownPtrDies) {
500 double x[3];
501 double y[2];
502
503 Problem problem;
504 problem.AddParameterBlock(x, 3);
505
506 EXPECT_DEATH_IF_SUPPORTED(
507 problem.SetParameterization(y, new IdentityParameterization(3)),
508 "Parameter block not found:");
509}
510
511TEST(Problem, RemoveParameterBlockWithUnknownPtrDies) {
512 double x[3];
513 double y[2];
514
515 Problem problem;
516 problem.AddParameterBlock(x, 3);
517
518 EXPECT_DEATH_IF_SUPPORTED(
519 problem.RemoveParameterBlock(y), "Parameter block not found:");
520}
521
Carlos Hernandez79397c22014-08-07 17:51:38 -0700522TEST(Problem, GetParameterization) {
523 double x[3];
524 double y[2];
525
526 Problem problem;
527 problem.AddParameterBlock(x, 3);
528 problem.AddParameterBlock(y, 2);
529
530 LocalParameterization* parameterization = new IdentityParameterization(3);
531 problem.SetParameterization(x, parameterization);
532 EXPECT_EQ(problem.GetParameterization(x), parameterization);
533 EXPECT_TRUE(problem.GetParameterization(y) == NULL);
534}
535
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700536TEST(Problem, ParameterBlockQueryTest) {
537 double x[3];
538 double y[4];
539 Problem problem;
540 problem.AddParameterBlock(x, 3);
541 problem.AddParameterBlock(y, 4);
542
543 vector<int> constant_parameters;
544 constant_parameters.push_back(0);
545 problem.SetParameterization(
546 x,
547 new SubsetParameterization(3, constant_parameters));
548 EXPECT_EQ(problem.ParameterBlockSize(x), 3);
549 EXPECT_EQ(problem.ParameterBlockLocalSize(x), 2);
550 EXPECT_EQ(problem.ParameterBlockLocalSize(y), 4);
551
552 vector<double*> parameter_blocks;
553 problem.GetParameterBlocks(&parameter_blocks);
554 EXPECT_EQ(parameter_blocks.size(), 2);
555 EXPECT_NE(parameter_blocks[0], parameter_blocks[1]);
556 EXPECT_TRUE(parameter_blocks[0] == x || parameter_blocks[0] == y);
557 EXPECT_TRUE(parameter_blocks[1] == x || parameter_blocks[1] == y);
558
Carlos Hernandez79397c22014-08-07 17:51:38 -0700559 EXPECT_TRUE(problem.HasParameterBlock(x));
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700560 problem.RemoveParameterBlock(x);
Carlos Hernandez79397c22014-08-07 17:51:38 -0700561 EXPECT_FALSE(problem.HasParameterBlock(x));
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700562 problem.GetParameterBlocks(&parameter_blocks);
563 EXPECT_EQ(parameter_blocks.size(), 1);
564 EXPECT_TRUE(parameter_blocks[0] == y);
565}
566
567TEST_P(DynamicProblem, RemoveParameterBlockWithNoResiduals) {
568 problem->AddParameterBlock(y, 4);
569 problem->AddParameterBlock(z, 5);
570 problem->AddParameterBlock(w, 3);
571 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700572 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700573 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
574 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
575 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
576
577 // w is at the end, which might break the swapping logic so try adding and
578 // removing it.
579 problem->RemoveParameterBlock(w);
580 ASSERT_EQ(2, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700581 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700582 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
583 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
584 problem->AddParameterBlock(w, 3);
585 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700586 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700587 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
588 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
589 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
590
591 // Now remove z, which is in the middle, and add it back.
592 problem->RemoveParameterBlock(z);
593 ASSERT_EQ(2, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700594 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700595 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
596 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
597 problem->AddParameterBlock(z, 5);
598 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700599 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700600 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
601 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
602 EXPECT_EQ(z, GetParameterBlock(2)->user_state());
603
604 // Now remove everything.
605 // y
606 problem->RemoveParameterBlock(y);
607 ASSERT_EQ(2, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700608 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700609 EXPECT_EQ(z, GetParameterBlock(0)->user_state());
610 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
611
612 // z
613 problem->RemoveParameterBlock(z);
614 ASSERT_EQ(1, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700615 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700616 EXPECT_EQ(w, GetParameterBlock(0)->user_state());
617
618 // w
619 problem->RemoveParameterBlock(w);
620 EXPECT_EQ(0, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700621 EXPECT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700622}
623
624TEST_P(DynamicProblem, RemoveParameterBlockWithResiduals) {
625 problem->AddParameterBlock(y, 4);
626 problem->AddParameterBlock(z, 5);
627 problem->AddParameterBlock(w, 3);
628 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700629 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700630 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
631 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
632 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
633
634 // Add all combinations of cost functions.
635 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
636 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
637 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
638 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
639 CostFunction* cost_y = new UnaryCostFunction (1, 4);
640 CostFunction* cost_z = new UnaryCostFunction (1, 5);
641 CostFunction* cost_w = new UnaryCostFunction (1, 3);
642
643 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
644 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
645 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
646 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
647 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
648 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
649 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
650
651 EXPECT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700652 EXPECT_EQ(7, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700653
654 // Remove w, which should remove r_yzw, r_yw, r_zw, r_w.
655 problem->RemoveParameterBlock(w);
656 ASSERT_EQ(2, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700657 ASSERT_EQ(3, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700658
659 ASSERT_FALSE(HasResidualBlock(r_yzw));
660 ASSERT_TRUE (HasResidualBlock(r_yz ));
661 ASSERT_FALSE(HasResidualBlock(r_yw ));
662 ASSERT_FALSE(HasResidualBlock(r_zw ));
663 ASSERT_TRUE (HasResidualBlock(r_y ));
664 ASSERT_TRUE (HasResidualBlock(r_z ));
665 ASSERT_FALSE(HasResidualBlock(r_w ));
666
667 // Remove z, which will remove almost everything else.
668 problem->RemoveParameterBlock(z);
669 ASSERT_EQ(1, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700670 ASSERT_EQ(1, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700671
672 ASSERT_FALSE(HasResidualBlock(r_yzw));
673 ASSERT_FALSE(HasResidualBlock(r_yz ));
674 ASSERT_FALSE(HasResidualBlock(r_yw ));
675 ASSERT_FALSE(HasResidualBlock(r_zw ));
676 ASSERT_TRUE (HasResidualBlock(r_y ));
677 ASSERT_FALSE(HasResidualBlock(r_z ));
678 ASSERT_FALSE(HasResidualBlock(r_w ));
679
680 // Remove y; all gone.
681 problem->RemoveParameterBlock(y);
682 EXPECT_EQ(0, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700683 EXPECT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700684}
685
686TEST_P(DynamicProblem, RemoveResidualBlock) {
687 problem->AddParameterBlock(y, 4);
688 problem->AddParameterBlock(z, 5);
689 problem->AddParameterBlock(w, 3);
690
691 // Add all combinations of cost functions.
692 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
693 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
694 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
695 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
696 CostFunction* cost_y = new UnaryCostFunction (1, 4);
697 CostFunction* cost_z = new UnaryCostFunction (1, 5);
698 CostFunction* cost_w = new UnaryCostFunction (1, 3);
699
700 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
701 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
702 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
703 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
704 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
705 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
706 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
707
708 if (GetParam()) {
709 // In this test parameterization, there should be back-pointers from the
710 // parameter blocks to the residual blocks.
711 ExpectParameterBlockContains(y, r_yzw, r_yz, r_yw, r_y);
712 ExpectParameterBlockContains(z, r_yzw, r_yz, r_zw, r_z);
713 ExpectParameterBlockContains(w, r_yzw, r_yw, r_zw, r_w);
714 } else {
715 // Otherwise, nothing.
716 EXPECT_TRUE(GetParameterBlock(0)->mutable_residual_blocks() == NULL);
717 EXPECT_TRUE(GetParameterBlock(1)->mutable_residual_blocks() == NULL);
718 EXPECT_TRUE(GetParameterBlock(2)->mutable_residual_blocks() == NULL);
719 }
720 EXPECT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700721 EXPECT_EQ(7, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700722
723 // Remove each residual and check the state after each removal.
724
725 // Remove r_yzw.
726 problem->RemoveResidualBlock(r_yzw);
727 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700728 ASSERT_EQ(6, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700729 if (GetParam()) {
730 ExpectParameterBlockContains(y, r_yz, r_yw, r_y);
731 ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
732 ExpectParameterBlockContains(w, r_yw, r_zw, r_w);
733 }
734 ASSERT_TRUE (HasResidualBlock(r_yz ));
735 ASSERT_TRUE (HasResidualBlock(r_yw ));
736 ASSERT_TRUE (HasResidualBlock(r_zw ));
737 ASSERT_TRUE (HasResidualBlock(r_y ));
738 ASSERT_TRUE (HasResidualBlock(r_z ));
739 ASSERT_TRUE (HasResidualBlock(r_w ));
740
741 // Remove r_yw.
742 problem->RemoveResidualBlock(r_yw);
743 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700744 ASSERT_EQ(5, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700745 if (GetParam()) {
746 ExpectParameterBlockContains(y, r_yz, r_y);
747 ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
748 ExpectParameterBlockContains(w, r_zw, r_w);
749 }
750 ASSERT_TRUE (HasResidualBlock(r_yz ));
751 ASSERT_TRUE (HasResidualBlock(r_zw ));
752 ASSERT_TRUE (HasResidualBlock(r_y ));
753 ASSERT_TRUE (HasResidualBlock(r_z ));
754 ASSERT_TRUE (HasResidualBlock(r_w ));
755
756 // Remove r_zw.
757 problem->RemoveResidualBlock(r_zw);
758 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700759 ASSERT_EQ(4, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700760 if (GetParam()) {
761 ExpectParameterBlockContains(y, r_yz, r_y);
762 ExpectParameterBlockContains(z, r_yz, r_z);
763 ExpectParameterBlockContains(w, r_w);
764 }
765 ASSERT_TRUE (HasResidualBlock(r_yz ));
766 ASSERT_TRUE (HasResidualBlock(r_y ));
767 ASSERT_TRUE (HasResidualBlock(r_z ));
768 ASSERT_TRUE (HasResidualBlock(r_w ));
769
770 // Remove r_w.
771 problem->RemoveResidualBlock(r_w);
772 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700773 ASSERT_EQ(3, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700774 if (GetParam()) {
775 ExpectParameterBlockContains(y, r_yz, r_y);
776 ExpectParameterBlockContains(z, r_yz, r_z);
777 ExpectParameterBlockContains(w);
778 }
779 ASSERT_TRUE (HasResidualBlock(r_yz ));
780 ASSERT_TRUE (HasResidualBlock(r_y ));
781 ASSERT_TRUE (HasResidualBlock(r_z ));
782
783 // Remove r_yz.
784 problem->RemoveResidualBlock(r_yz);
785 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700786 ASSERT_EQ(2, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700787 if (GetParam()) {
788 ExpectParameterBlockContains(y, r_y);
789 ExpectParameterBlockContains(z, r_z);
790 ExpectParameterBlockContains(w);
791 }
792 ASSERT_TRUE (HasResidualBlock(r_y ));
793 ASSERT_TRUE (HasResidualBlock(r_z ));
794
795 // Remove the last two.
796 problem->RemoveResidualBlock(r_z);
797 problem->RemoveResidualBlock(r_y);
798 ASSERT_EQ(3, problem->NumParameterBlocks());
Carlos Hernandez79397c22014-08-07 17:51:38 -0700799 ASSERT_EQ(0, NumResidualBlocks());
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700800 if (GetParam()) {
801 ExpectParameterBlockContains(y);
802 ExpectParameterBlockContains(z);
803 ExpectParameterBlockContains(w);
804 }
805}
806
Carlos Hernandez79397c22014-08-07 17:51:38 -0700807TEST_P(DynamicProblem, RemoveInvalidResidualBlockDies) {
808 problem->AddParameterBlock(y, 4);
809 problem->AddParameterBlock(z, 5);
810 problem->AddParameterBlock(w, 3);
811
812 // Add all combinations of cost functions.
813 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
814 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
815 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
816 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
817 CostFunction* cost_y = new UnaryCostFunction (1, 4);
818 CostFunction* cost_z = new UnaryCostFunction (1, 5);
819 CostFunction* cost_w = new UnaryCostFunction (1, 3);
820
821 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
822 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
823 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
824 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
825 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
826 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
827 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
828
829 // Remove r_yzw.
830 problem->RemoveResidualBlock(r_yzw);
831 ASSERT_EQ(3, problem->NumParameterBlocks());
832 ASSERT_EQ(6, NumResidualBlocks());
833 // Attempt to remove r_yzw again.
834 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yzw), "not found");
835
836 // Attempt to remove a cast pointer never added as a residual.
837 int trash_memory = 1234;
838 ResidualBlock* invalid_residual =
839 reinterpret_cast<ResidualBlock*>(&trash_memory);
840 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(invalid_residual),
841 "not found");
842
843 // Remove a parameter block, which in turn removes the dependent residuals
844 // then attempt to remove them directly.
845 problem->RemoveParameterBlock(z);
846 ASSERT_EQ(2, problem->NumParameterBlocks());
847 ASSERT_EQ(3, NumResidualBlocks());
848 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yz), "not found");
849 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_zw), "not found");
850 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_z), "not found");
851
852 problem->RemoveResidualBlock(r_yw);
853 problem->RemoveResidualBlock(r_w);
854 problem->RemoveResidualBlock(r_y);
855}
856
857// Check that a null-terminated array, a, has the same elements as b.
858template<typename T>
859void ExpectVectorContainsUnordered(const T* a, const vector<T>& b) {
860 // Compute the size of a.
861 int size = 0;
862 while (a[size]) {
863 ++size;
864 }
865 ASSERT_EQ(size, b.size());
866
867 // Sort a.
868 vector<T> a_sorted(size);
869 copy(a, a + size, a_sorted.begin());
870 sort(a_sorted.begin(), a_sorted.end());
871
872 // Sort b.
873 vector<T> b_sorted(b);
874 sort(b_sorted.begin(), b_sorted.end());
875
876 // Compare.
877 for (int i = 0; i < size; ++i) {
878 EXPECT_EQ(a_sorted[i], b_sorted[i]);
879 }
880}
881
882void ExpectProblemHasResidualBlocks(
883 const ProblemImpl &problem,
884 const ResidualBlockId *expected_residual_blocks) {
885 vector<ResidualBlockId> residual_blocks;
886 problem.GetResidualBlocks(&residual_blocks);
887 ExpectVectorContainsUnordered(expected_residual_blocks, residual_blocks);
888}
889
890TEST_P(DynamicProblem, GetXXXBlocksForYYYBlock) {
891 problem->AddParameterBlock(y, 4);
892 problem->AddParameterBlock(z, 5);
893 problem->AddParameterBlock(w, 3);
894
895 // Add all combinations of cost functions.
896 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
897 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
898 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
899 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
900 CostFunction* cost_y = new UnaryCostFunction (1, 4);
901 CostFunction* cost_z = new UnaryCostFunction (1, 5);
902 CostFunction* cost_w = new UnaryCostFunction (1, 3);
903
904 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, NULL, y, z, w);
905 {
906 ResidualBlockId expected_residuals[] = {r_yzw, 0};
907 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
908 }
909 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
910 {
911 ResidualBlockId expected_residuals[] = {r_yzw, r_yz, 0};
912 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
913 }
914 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
915 {
916 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, 0};
917 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
918 }
919 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
920 {
921 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, 0};
922 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
923 }
924 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
925 {
926 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, r_y, 0};
927 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
928 }
929 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
930 {
931 ResidualBlock *expected_residuals[] = {
932 r_yzw, r_yz, r_yw, r_zw, r_y, r_z, 0
933 };
934 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
935 }
936 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
937 {
938 ResidualBlock *expected_residuals[] = {
939 r_yzw, r_yz, r_yw, r_zw, r_y, r_z, r_w, 0
940 };
941 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
942 }
943
944 vector<double*> parameter_blocks;
945 vector<ResidualBlockId> residual_blocks;
946
947 // Check GetResidualBlocksForParameterBlock() for all parameter blocks.
948 struct GetResidualBlocksForParameterBlockTestCase {
949 double* parameter_block;
950 ResidualBlockId expected_residual_blocks[10];
951 };
952 GetResidualBlocksForParameterBlockTestCase get_residual_blocks_cases[] = {
953 { y, { r_yzw, r_yz, r_yw, r_y, NULL} },
954 { z, { r_yzw, r_yz, r_zw, r_z, NULL} },
955 { w, { r_yzw, r_yw, r_zw, r_w, NULL} },
956 { NULL }
957 };
958 for (int i = 0; get_residual_blocks_cases[i].parameter_block; ++i) {
959 problem->GetResidualBlocksForParameterBlock(
960 get_residual_blocks_cases[i].parameter_block,
961 &residual_blocks);
962 ExpectVectorContainsUnordered(
963 get_residual_blocks_cases[i].expected_residual_blocks,
964 residual_blocks);
965 }
966
967 // Check GetParameterBlocksForResidualBlock() for all residual blocks.
968 struct GetParameterBlocksForResidualBlockTestCase {
969 ResidualBlockId residual_block;
970 double* expected_parameter_blocks[10];
971 };
972 GetParameterBlocksForResidualBlockTestCase get_parameter_blocks_cases[] = {
973 { r_yzw, { y, z, w, NULL } },
974 { r_yz , { y, z, NULL } },
975 { r_yw , { y, w, NULL } },
976 { r_zw , { z, w, NULL } },
977 { r_y , { y, NULL } },
978 { r_z , { z, NULL } },
979 { r_w , { w, NULL } },
980 { NULL }
981 };
982 for (int i = 0; get_parameter_blocks_cases[i].residual_block; ++i) {
983 problem->GetParameterBlocksForResidualBlock(
984 get_parameter_blocks_cases[i].residual_block,
985 &parameter_blocks);
986 ExpectVectorContainsUnordered(
987 get_parameter_blocks_cases[i].expected_parameter_blocks,
988 parameter_blocks);
989 }
990}
991
Sascha Haeberling1d2624a2013-07-23 19:00:21 -0700992INSTANTIATE_TEST_CASE_P(OptionsInstantiation,
993 DynamicProblem,
994 ::testing::Values(true, false));
995
996// Test for Problem::Evaluate
997
998// r_i = i - (j + 1) * x_ij^2
999template <int kNumResiduals, int kNumParameterBlocks>
1000class QuadraticCostFunction : public CostFunction {
1001 public:
1002 QuadraticCostFunction() {
1003 CHECK_GT(kNumResiduals, 0);
1004 CHECK_GT(kNumParameterBlocks, 0);
1005 set_num_residuals(kNumResiduals);
1006 for (int i = 0; i < kNumParameterBlocks; ++i) {
1007 mutable_parameter_block_sizes()->push_back(kNumResiduals);
1008 }
1009 }
1010
1011 virtual bool Evaluate(double const* const* parameters,
1012 double* residuals,
1013 double** jacobians) const {
1014 for (int i = 0; i < kNumResiduals; ++i) {
1015 residuals[i] = i;
1016 for (int j = 0; j < kNumParameterBlocks; ++j) {
1017 residuals[i] -= (j + 1.0) * parameters[j][i] * parameters[j][i];
1018 }
1019 }
1020
1021 if (jacobians == NULL) {
1022 return true;
1023 }
1024
1025 for (int j = 0; j < kNumParameterBlocks; ++j) {
1026 if (jacobians[j] != NULL) {
1027 MatrixRef(jacobians[j], kNumResiduals, kNumResiduals) =
1028 (-2.0 * (j + 1.0) *
1029 ConstVectorRef(parameters[j], kNumResiduals)).asDiagonal();
1030 }
1031 }
1032
1033 return true;
1034 }
1035};
1036
1037// Convert a CRSMatrix to a dense Eigen matrix.
1038void CRSToDenseMatrix(const CRSMatrix& input, Matrix* output) {
1039 Matrix& m = *CHECK_NOTNULL(output);
1040 m.resize(input.num_rows, input.num_cols);
1041 m.setZero();
1042 for (int row = 0; row < input.num_rows; ++row) {
1043 for (int j = input.rows[row]; j < input.rows[row + 1]; ++j) {
1044 const int col = input.cols[j];
1045 m(row, col) = input.values[j];
1046 }
1047 }
1048}
1049
1050class ProblemEvaluateTest : public ::testing::Test {
1051 protected:
1052 void SetUp() {
1053 for (int i = 0; i < 6; ++i) {
1054 parameters_[i] = static_cast<double>(i + 1);
1055 }
1056
1057 parameter_blocks_.push_back(parameters_);
1058 parameter_blocks_.push_back(parameters_ + 2);
1059 parameter_blocks_.push_back(parameters_ + 4);
1060
1061
1062 CostFunction* cost_function = new QuadraticCostFunction<2, 2>;
1063
1064 // f(x, y)
1065 residual_blocks_.push_back(
1066 problem_.AddResidualBlock(cost_function,
1067 NULL,
1068 parameters_,
1069 parameters_ + 2));
1070 // g(y, z)
1071 residual_blocks_.push_back(
1072 problem_.AddResidualBlock(cost_function,
1073 NULL, parameters_ + 2,
1074 parameters_ + 4));
1075 // h(z, x)
1076 residual_blocks_.push_back(
1077 problem_.AddResidualBlock(cost_function,
1078 NULL,
1079 parameters_ + 4,
1080 parameters_));
1081 }
1082
Carlos Hernandez79397c22014-08-07 17:51:38 -07001083 void TearDown() {
1084 EXPECT_TRUE(problem_.program().IsValid());
1085 }
Sascha Haeberling1d2624a2013-07-23 19:00:21 -07001086
1087 void EvaluateAndCompare(const Problem::EvaluateOptions& options,
1088 const int expected_num_rows,
1089 const int expected_num_cols,
1090 const double expected_cost,
1091 const double* expected_residuals,
1092 const double* expected_gradient,
1093 const double* expected_jacobian) {
1094 double cost;
1095 vector<double> residuals;
1096 vector<double> gradient;
1097 CRSMatrix jacobian;
1098
1099 EXPECT_TRUE(
1100 problem_.Evaluate(options,
1101 &cost,
1102 expected_residuals != NULL ? &residuals : NULL,
1103 expected_gradient != NULL ? &gradient : NULL,
1104 expected_jacobian != NULL ? &jacobian : NULL));
1105
1106 if (expected_residuals != NULL) {
1107 EXPECT_EQ(residuals.size(), expected_num_rows);
1108 }
1109
1110 if (expected_gradient != NULL) {
1111 EXPECT_EQ(gradient.size(), expected_num_cols);
1112 }
1113
1114 if (expected_jacobian != NULL) {
1115 EXPECT_EQ(jacobian.num_rows, expected_num_rows);
1116 EXPECT_EQ(jacobian.num_cols, expected_num_cols);
1117 }
1118
1119 Matrix dense_jacobian;
1120 if (expected_jacobian != NULL) {
1121 CRSToDenseMatrix(jacobian, &dense_jacobian);
1122 }
1123
1124 CompareEvaluations(expected_num_rows,
1125 expected_num_cols,
1126 expected_cost,
1127 expected_residuals,
1128 expected_gradient,
1129 expected_jacobian,
1130 cost,
1131 residuals.size() > 0 ? &residuals[0] : NULL,
1132 gradient.size() > 0 ? &gradient[0] : NULL,
1133 dense_jacobian.data());
1134 }
1135
1136 void CheckAllEvaluationCombinations(const Problem::EvaluateOptions& options,
1137 const ExpectedEvaluation& expected) {
1138 for (int i = 0; i < 8; ++i) {
1139 EvaluateAndCompare(options,
1140 expected.num_rows,
1141 expected.num_cols,
1142 expected.cost,
1143 (i & 1) ? expected.residuals : NULL,
1144 (i & 2) ? expected.gradient : NULL,
1145 (i & 4) ? expected.jacobian : NULL);
1146 }
1147 }
1148
1149 ProblemImpl problem_;
1150 double parameters_[6];
1151 vector<double*> parameter_blocks_;
1152 vector<ResidualBlockId> residual_blocks_;
1153};
1154
1155
1156TEST_F(ProblemEvaluateTest, MultipleParameterAndResidualBlocks) {
1157 ExpectedEvaluation expected = {
1158 // Rows/columns
1159 6, 6,
1160 // Cost
1161 7607.0,
1162 // Residuals
1163 { -19.0, -35.0, // f
1164 -59.0, -87.0, // g
1165 -27.0, -43.0 // h
1166 },
1167 // Gradient
1168 { 146.0, 484.0, // x
1169 582.0, 1256.0, // y
1170 1450.0, 2604.0, // z
1171 },
1172 // Jacobian
1173 // x y z
1174 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1175 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1176 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1177 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
1178 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1179 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1180 }
1181 };
1182
1183 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1184}
1185
1186TEST_F(ProblemEvaluateTest, ParameterAndResidualBlocksPassedInOptions) {
1187 ExpectedEvaluation expected = {
1188 // Rows/columns
1189 6, 6,
1190 // Cost
1191 7607.0,
1192 // Residuals
1193 { -19.0, -35.0, // f
1194 -59.0, -87.0, // g
1195 -27.0, -43.0 // h
1196 },
1197 // Gradient
1198 { 146.0, 484.0, // x
1199 582.0, 1256.0, // y
1200 1450.0, 2604.0, // z
1201 },
1202 // Jacobian
1203 // x y z
1204 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1205 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1206 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1207 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
1208 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1209 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1210 }
1211 };
1212
1213 Problem::EvaluateOptions evaluate_options;
1214 evaluate_options.parameter_blocks = parameter_blocks_;
1215 evaluate_options.residual_blocks = residual_blocks_;
1216 CheckAllEvaluationCombinations(evaluate_options, expected);
1217}
1218
1219TEST_F(ProblemEvaluateTest, ReorderedResidualBlocks) {
1220 ExpectedEvaluation expected = {
1221 // Rows/columns
1222 6, 6,
1223 // Cost
1224 7607.0,
1225 // Residuals
1226 { -19.0, -35.0, // f
1227 -27.0, -43.0, // h
1228 -59.0, -87.0 // g
1229 },
1230 // Gradient
1231 { 146.0, 484.0, // x
1232 582.0, 1256.0, // y
1233 1450.0, 2604.0, // z
1234 },
1235 // Jacobian
1236 // x y z
1237 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1238 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1239 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1240 0.0, -8.0, 0.0, 0.0, 0.0, -12.0,
1241 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1242 0.0, 0.0, 0.0, -8.0, 0.0, -24.0
1243 }
1244 };
1245
1246 Problem::EvaluateOptions evaluate_options;
1247 evaluate_options.parameter_blocks = parameter_blocks_;
1248
1249 // f, h, g
1250 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1251 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1252 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1253
1254 CheckAllEvaluationCombinations(evaluate_options, expected);
1255}
1256
1257TEST_F(ProblemEvaluateTest, ReorderedResidualBlocksAndReorderedParameterBlocks) {
1258 ExpectedEvaluation expected = {
1259 // Rows/columns
1260 6, 6,
1261 // Cost
1262 7607.0,
1263 // Residuals
1264 { -19.0, -35.0, // f
1265 -27.0, -43.0, // h
1266 -59.0, -87.0 // g
1267 },
1268 // Gradient
1269 { 1450.0, 2604.0, // z
1270 582.0, 1256.0, // y
1271 146.0, 484.0, // x
1272 },
1273 // Jacobian
1274 // z y x
1275 { /* f(x, y) */ 0.0, 0.0, -12.0, 0.0, -2.0, 0.0,
1276 0.0, 0.0, 0.0, -16.0, 0.0, -4.0,
1277 /* h(z, x) */ -10.0, 0.0, 0.0, 0.0, -4.0, 0.0,
1278 0.0, -12.0, 0.0, 0.0, 0.0, -8.0,
1279 /* g(y, z) */ -20.0, 0.0, -6.0, 0.0, 0.0, 0.0,
1280 0.0, -24.0, 0.0, -8.0, 0.0, 0.0
1281 }
1282 };
1283
1284 Problem::EvaluateOptions evaluate_options;
1285 // z, y, x
1286 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1287 evaluate_options.parameter_blocks.push_back(parameter_blocks_[1]);
1288 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1289
1290 // f, h, g
1291 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1292 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1293 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1294
1295 CheckAllEvaluationCombinations(evaluate_options, expected);
1296}
1297
1298TEST_F(ProblemEvaluateTest, ConstantParameterBlock) {
1299 ExpectedEvaluation expected = {
1300 // Rows/columns
1301 6, 6,
1302 // Cost
1303 7607.0,
1304 // Residuals
1305 { -19.0, -35.0, // f
1306 -59.0, -87.0, // g
1307 -27.0, -43.0 // h
1308 },
1309
1310 // Gradient
1311 { 146.0, 484.0, // x
1312 0.0, 0.0, // y
1313 1450.0, 2604.0, // z
1314 },
1315
1316 // Jacobian
1317 // x y z
1318 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1319 0.0, -4.0, 0.0, 0.0, 0.0, 0.0,
1320 /* g(y, z) */ 0.0, 0.0, 0.0, 0.0, -20.0, 0.0,
1321 0.0, 0.0, 0.0, 0.0, 0.0, -24.0,
1322 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1323 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1324 }
1325 };
1326
1327 problem_.SetParameterBlockConstant(parameters_ + 2);
1328 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1329}
1330
1331TEST_F(ProblemEvaluateTest, ExcludedAResidualBlock) {
1332 ExpectedEvaluation expected = {
1333 // Rows/columns
1334 4, 6,
1335 // Cost
1336 2082.0,
1337 // Residuals
1338 { -19.0, -35.0, // f
1339 -27.0, -43.0 // h
1340 },
1341 // Gradient
1342 { 146.0, 484.0, // x
1343 228.0, 560.0, // y
1344 270.0, 516.0, // z
1345 },
1346 // Jacobian
1347 // x y z
1348 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1349 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1350 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1351 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1352 }
1353 };
1354
1355 Problem::EvaluateOptions evaluate_options;
1356 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1357 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1358
1359 CheckAllEvaluationCombinations(evaluate_options, expected);
1360}
1361
1362TEST_F(ProblemEvaluateTest, ExcludedParameterBlock) {
1363 ExpectedEvaluation expected = {
1364 // Rows/columns
1365 6, 4,
1366 // Cost
1367 7607.0,
1368 // Residuals
1369 { -19.0, -35.0, // f
1370 -59.0, -87.0, // g
1371 -27.0, -43.0 // h
1372 },
1373
1374 // Gradient
1375 { 146.0, 484.0, // x
1376 1450.0, 2604.0, // z
1377 },
1378
1379 // Jacobian
1380 // x z
1381 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
1382 0.0, -4.0, 0.0, 0.0,
1383 /* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
1384 0.0, 0.0, 0.0, -24.0,
1385 /* h(z, x) */ -4.0, 0.0, -10.0, 0.0,
1386 0.0, -8.0, 0.0, -12.0
1387 }
1388 };
1389
1390 Problem::EvaluateOptions evaluate_options;
1391 // x, z
1392 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1393 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1394 evaluate_options.residual_blocks = residual_blocks_;
1395 CheckAllEvaluationCombinations(evaluate_options, expected);
1396}
1397
1398TEST_F(ProblemEvaluateTest, ExcludedParameterBlockAndExcludedResidualBlock) {
1399 ExpectedEvaluation expected = {
1400 // Rows/columns
1401 4, 4,
1402 // Cost
1403 6318.0,
1404 // Residuals
1405 { -19.0, -35.0, // f
1406 -59.0, -87.0, // g
1407 },
1408
1409 // Gradient
1410 { 38.0, 140.0, // x
1411 1180.0, 2088.0, // z
1412 },
1413
1414 // Jacobian
1415 // x z
1416 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
1417 0.0, -4.0, 0.0, 0.0,
1418 /* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
1419 0.0, 0.0, 0.0, -24.0,
1420 }
1421 };
1422
1423 Problem::EvaluateOptions evaluate_options;
1424 // x, z
1425 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1426 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1427 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1428 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1429
1430 CheckAllEvaluationCombinations(evaluate_options, expected);
1431}
1432
1433TEST_F(ProblemEvaluateTest, LocalParameterization) {
1434 ExpectedEvaluation expected = {
1435 // Rows/columns
1436 6, 5,
1437 // Cost
1438 7607.0,
1439 // Residuals
1440 { -19.0, -35.0, // f
1441 -59.0, -87.0, // g
1442 -27.0, -43.0 // h
1443 },
1444 // Gradient
1445 { 146.0, 484.0, // x
1446 1256.0, // y with SubsetParameterization
1447 1450.0, 2604.0, // z
1448 },
1449 // Jacobian
1450 // x y z
1451 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0,
1452 0.0, -4.0, -16.0, 0.0, 0.0,
1453 /* g(y, z) */ 0.0, 0.0, 0.0, -20.0, 0.0,
1454 0.0, 0.0, -8.0, 0.0, -24.0,
1455 /* h(z, x) */ -4.0, 0.0, 0.0, -10.0, 0.0,
1456 0.0, -8.0, 0.0, 0.0, -12.0
1457 }
1458 };
1459
1460 vector<int> constant_parameters;
1461 constant_parameters.push_back(0);
1462 problem_.SetParameterization(parameters_ + 2,
1463 new SubsetParameterization(2,
1464 constant_parameters));
1465
1466 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
Angus Kong0ae28bd2013-02-13 14:56:04 -08001467}
1468
1469} // namespace internal
1470} // namespace ceres