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