1 // Ceres Solver - A fast non-linear least squares minimizer
2 // Copyright 2015 Google Inc. All rights reserved.
3 // http://ceres-solver.org/
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are met:
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.
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.
29 // Author: keir@google.com (Keir Mierle)
31 // Tests shared across evaluators. The tests try all combinations of linear
32 // solver and num_eliminate_blocks (for schur-based solvers).
34 #include "ceres/evaluator.h"
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/internal/scoped_ptr.h"
42 #include "ceres/local_parameterization.h"
43 #include "ceres/problem_impl.h"
44 #include "ceres/program.h"
45 #include "ceres/sized_cost_function.h"
46 #include "ceres/sparse_matrix.h"
47 #include "ceres/stringprintf.h"
48 #include "ceres/types.h"
49 #include "gtest/gtest.h"
57 // TODO(keir): Consider pushing this into a common test utils file.
58 template<int kFactor, int kNumResiduals,
59 int N0 = 0, int N1 = 0, int N2 = 0, bool kSucceeds = true>
60 class ParameterIgnoringCostFunction
61 : public SizedCostFunction<kNumResiduals, N0, N1, N2> {
62 typedef SizedCostFunction<kNumResiduals, N0, N1, N2> Base;
64 virtual bool Evaluate(double const* const* parameters,
66 double** jacobians) const {
67 for (int i = 0; i < Base::num_residuals(); ++i) {
71 for (int k = 0; k < Base::parameter_block_sizes().size(); ++k) {
72 // The jacobians here are full sized, but they are transformed in the
73 // evaluator into the "local" jacobian. In the tests, the "subset
74 // constant" parameterization is used, which should pick out columns
75 // from these jacobians. Put values in the jacobian that make this
76 // obvious; in particular, make the jacobians like this:
79 // 1 2 3 4 ... .* kFactor
82 // where the multiplication by kFactor makes it easier to distinguish
83 // between Jacobians of different residuals for the same parameter.
84 if (jacobians[k] != NULL) {
85 MatrixRef jacobian(jacobians[k],
86 Base::num_residuals(),
87 Base::parameter_block_sizes()[k]);
88 for (int j = 0; j < Base::parameter_block_sizes()[k]; ++j) {
89 jacobian.col(j).setConstant(kFactor * (j + 1));
98 struct EvaluatorTestOptions {
99 EvaluatorTestOptions(LinearSolverType linear_solver_type,
100 int num_eliminate_blocks,
101 bool dynamic_sparsity = false)
102 : linear_solver_type(linear_solver_type),
103 num_eliminate_blocks(num_eliminate_blocks),
104 dynamic_sparsity(dynamic_sparsity) {}
106 LinearSolverType linear_solver_type;
107 int num_eliminate_blocks;
108 bool dynamic_sparsity;
112 : public ::testing::TestWithParam<EvaluatorTestOptions> {
113 Evaluator* CreateEvaluator(Program* program) {
114 // This program is straight from the ProblemImpl, and so has no index/offset
115 // yet; compute it here as required by the evalutor implementations.
116 program->SetParameterOffsetsAndIndex();
120 StringAppendF(&report, "Creating evaluator with type: %d",
121 GetParam().linear_solver_type);
122 if (GetParam().linear_solver_type == SPARSE_NORMAL_CHOLESKY) {
123 StringAppendF(&report, ", dynamic_sparsity: %d",
124 GetParam().dynamic_sparsity);
126 StringAppendF(&report, " and num_eliminate_blocks: %d",
127 GetParam().num_eliminate_blocks);
130 Evaluator::Options options;
131 options.linear_solver_type = GetParam().linear_solver_type;
132 options.num_eliminate_blocks = GetParam().num_eliminate_blocks;
133 options.dynamic_sparsity = GetParam().dynamic_sparsity;
135 return Evaluator::Create(options, program, &error);
138 void EvaluateAndCompare(ProblemImpl *problem,
139 int expected_num_rows,
140 int expected_num_cols,
141 double expected_cost,
142 const double* expected_residuals,
143 const double* expected_gradient,
144 const double* expected_jacobian) {
145 scoped_ptr<Evaluator> evaluator(
146 CreateEvaluator(problem->mutable_program()));
147 int num_residuals = expected_num_rows;
148 int num_parameters = expected_num_cols;
152 Vector residuals(num_residuals);
153 residuals.setConstant(-2000);
155 Vector gradient(num_parameters);
156 gradient.setConstant(-3000);
158 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
160 ASSERT_EQ(expected_num_rows, evaluator->NumResiduals());
161 ASSERT_EQ(expected_num_cols, evaluator->NumEffectiveParameters());
162 ASSERT_EQ(expected_num_rows, jacobian->num_rows());
163 ASSERT_EQ(expected_num_cols, jacobian->num_cols());
165 vector<double> state(evaluator->NumParameters());
167 ASSERT_TRUE(evaluator->Evaluate(
170 expected_residuals != NULL ? &residuals[0] : NULL,
171 expected_gradient != NULL ? &gradient[0] : NULL,
172 expected_jacobian != NULL ? jacobian.get() : NULL));
174 Matrix actual_jacobian;
175 if (expected_jacobian != NULL) {
176 jacobian->ToDenseMatrix(&actual_jacobian);
179 CompareEvaluations(expected_num_rows,
188 actual_jacobian.data());
191 // Try all combinations of parameters for the evaluator.
192 void CheckAllEvaluationCombinations(const ExpectedEvaluation &expected) {
193 for (int i = 0; i < 8; ++i) {
194 EvaluateAndCompare(&problem,
198 (i & 1) ? expected.residuals : NULL,
199 (i & 2) ? expected.gradient : NULL,
200 (i & 4) ? expected.jacobian : NULL);
204 // The values are ignored completely by the cost function.
212 void SetSparseMatrixConstant(SparseMatrix* sparse_matrix, double value) {
213 VectorRef(sparse_matrix->mutable_values(),
214 sparse_matrix->num_nonzeros()).setConstant(value);
217 TEST_P(EvaluatorTest, SingleResidualProblem) {
218 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
222 ExpectedEvaluation expected = {
231 6.0, 12.0, 18.0, // y
232 6.0, 12.0, 18.0, 24.0, // z
236 { 1, 2, 1, 2, 3, 1, 2, 3, 4,
237 1, 2, 1, 2, 3, 1, 2, 3, 4,
238 1, 2, 1, 2, 3, 1, 2, 3, 4
241 CheckAllEvaluationCombinations(expected);
244 TEST_P(EvaluatorTest, SingleResidualProblemWithPermutedParameters) {
245 // Add the parameters in explicit order to force the ordering in the program.
246 problem.AddParameterBlock(x, 2);
247 problem.AddParameterBlock(y, 3);
248 problem.AddParameterBlock(z, 4);
250 // Then use a cost function which is similar to the others, but swap around
251 // the ordering of the parameters to the cost function. This shouldn't affect
252 // the jacobian evaluation, but requires explicit handling in the evaluators.
253 // At one point the compressed row evaluator had a bug that went undetected
254 // for a long time, since by chance most users added parameters to the problem
255 // in the same order that they occurred as parameters to a cost function.
256 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 4, 3, 2>,
260 ExpectedEvaluation expected = {
269 6.0, 12.0, 18.0, // y
270 6.0, 12.0, 18.0, 24.0, // z
274 { 1, 2, 1, 2, 3, 1, 2, 3, 4,
275 1, 2, 1, 2, 3, 1, 2, 3, 4,
276 1, 2, 1, 2, 3, 1, 2, 3, 4
279 CheckAllEvaluationCombinations(expected);
282 TEST_P(EvaluatorTest, SingleResidualProblemWithNuisanceParameters) {
283 // These parameters are not used.
289 // Add the parameters in a mixed order so the Jacobian is "checkered" with the
290 // values from the other parameters.
291 problem.AddParameterBlock(a, 2);
292 problem.AddParameterBlock(x, 2);
293 problem.AddParameterBlock(b, 1);
294 problem.AddParameterBlock(y, 3);
295 problem.AddParameterBlock(c, 1);
296 problem.AddParameterBlock(z, 4);
297 problem.AddParameterBlock(d, 3);
299 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
303 ExpectedEvaluation expected = {
314 6.0, 12.0, 18.0, // y
316 6.0, 12.0, 18.0, 24.0, // z
321 { 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
322 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
323 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0
326 CheckAllEvaluationCombinations(expected);
329 TEST_P(EvaluatorTest, MultipleResidualProblem) {
330 // Add the parameters in explicit order to force the ordering in the program.
331 problem.AddParameterBlock(x, 2);
332 problem.AddParameterBlock(y, 3);
333 problem.AddParameterBlock(z, 4);
336 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
341 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
346 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
350 ExpectedEvaluation expected = {
355 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
359 1.0, 2.0, 3.0, 4.0 // h
363 33.0, 66.0, 99.0, // y
364 42.0, 84.0, 126.0, 168.0 // z
368 { /* f(x, y) */ 1, 2, 1, 2, 3, 0, 0, 0, 0,
369 1, 2, 1, 2, 3, 0, 0, 0, 0,
371 /* g(x, z) */ 2, 4, 0, 0, 0, 2, 4, 6, 8,
372 2, 4, 0, 0, 0, 2, 4, 6, 8,
373 2, 4, 0, 0, 0, 2, 4, 6, 8,
375 /* h(y, z) */ 0, 0, 3, 6, 9, 3, 6, 9, 12,
376 0, 0, 3, 6, 9, 3, 6, 9, 12,
377 0, 0, 3, 6, 9, 3, 6, 9, 12,
378 0, 0, 3, 6, 9, 3, 6, 9, 12
381 CheckAllEvaluationCombinations(expected);
384 TEST_P(EvaluatorTest, MultipleResidualsWithLocalParameterizations) {
385 // Add the parameters in explicit order to force the ordering in the program.
386 problem.AddParameterBlock(x, 2);
388 // Fix y's first dimension.
390 y_fixed.push_back(0);
391 problem.AddParameterBlock(y, 3, new SubsetParameterization(3, y_fixed));
393 // Fix z's second dimension.
395 z_fixed.push_back(1);
396 problem.AddParameterBlock(z, 4, new SubsetParameterization(4, z_fixed));
399 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
404 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
409 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
413 ExpectedEvaluation expected = {
418 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
422 1.0, 2.0, 3.0, 4.0 // h
427 42.0, 126.0, 168.0 // z
431 { /* f(x, y) */ 1, 2, 2, 3, 0, 0, 0,
434 /* g(x, z) */ 2, 4, 0, 0, 2, 6, 8,
438 /* h(y, z) */ 0, 0, 6, 9, 3, 9, 12,
439 0, 0, 6, 9, 3, 9, 12,
440 0, 0, 6, 9, 3, 9, 12,
444 CheckAllEvaluationCombinations(expected);
447 TEST_P(EvaluatorTest, MultipleResidualProblemWithSomeConstantParameters) {
448 // The values are ignored completely by the cost function.
453 // Add the parameters in explicit order to force the ordering in the program.
454 problem.AddParameterBlock(x, 2);
455 problem.AddParameterBlock(y, 3);
456 problem.AddParameterBlock(z, 4);
459 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
464 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
469 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
473 // For this test, "z" is constant.
474 problem.SetParameterBlockConstant(z);
476 // Create the reduced program which is missing the fixed "z" variable.
477 // Normally, the preprocessing of the program that happens in solver_impl
478 // takes care of this, but we don't want to invoke the solver here.
479 Program reduced_program;
480 vector<ParameterBlock*>* parameter_blocks =
481 problem.mutable_program()->mutable_parameter_blocks();
483 // "z" is the last parameter; save it for later and pop it off temporarily.
484 // Note that "z" will still get read during evaluation, so it cannot be
485 // deleted at this point.
486 ParameterBlock* parameter_block_z = parameter_blocks->back();
487 parameter_blocks->pop_back();
489 ExpectedEvaluation expected = {
494 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
498 1.0, 2.0, 3.0, 4.0 // h
502 33.0, 66.0, 99.0, // y
506 { /* f(x, y) */ 1, 2, 1, 2, 3,
509 /* g(x, z) */ 2, 4, 0, 0, 0,
513 /* h(y, z) */ 0, 0, 3, 6, 9,
519 CheckAllEvaluationCombinations(expected);
521 // Restore parameter block z, so it will get freed in a consistent way.
522 parameter_blocks->push_back(parameter_block_z);
525 TEST_P(EvaluatorTest, EvaluatorAbortsForResidualsThatFailToEvaluate) {
526 // Switch the return value to failure.
527 problem.AddResidualBlock(
528 new ParameterIgnoringCostFunction<20, 3, 2, 3, 4, false>, NULL, x, y, z);
530 // The values are ignored.
533 scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
534 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
536 EXPECT_FALSE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
539 // In the pairs, the first argument is the linear solver type, and the second
540 // argument is num_eliminate_blocks. Changing the num_eliminate_blocks only
541 // makes sense for the schur-based solvers.
543 // Try all values of num_eliminate_blocks that make sense given that in the
544 // tests a maximum of 4 parameter blocks are present.
545 INSTANTIATE_TEST_CASE_P(
549 EvaluatorTestOptions(DENSE_QR, 0),
550 EvaluatorTestOptions(DENSE_SCHUR, 0),
551 EvaluatorTestOptions(DENSE_SCHUR, 1),
552 EvaluatorTestOptions(DENSE_SCHUR, 2),
553 EvaluatorTestOptions(DENSE_SCHUR, 3),
554 EvaluatorTestOptions(DENSE_SCHUR, 4),
555 EvaluatorTestOptions(SPARSE_SCHUR, 0),
556 EvaluatorTestOptions(SPARSE_SCHUR, 1),
557 EvaluatorTestOptions(SPARSE_SCHUR, 2),
558 EvaluatorTestOptions(SPARSE_SCHUR, 3),
559 EvaluatorTestOptions(SPARSE_SCHUR, 4),
560 EvaluatorTestOptions(ITERATIVE_SCHUR, 0),
561 EvaluatorTestOptions(ITERATIVE_SCHUR, 1),
562 EvaluatorTestOptions(ITERATIVE_SCHUR, 2),
563 EvaluatorTestOptions(ITERATIVE_SCHUR, 3),
564 EvaluatorTestOptions(ITERATIVE_SCHUR, 4),
565 EvaluatorTestOptions(SPARSE_NORMAL_CHOLESKY, 0, false),
566 EvaluatorTestOptions(SPARSE_NORMAL_CHOLESKY, 0, true)));
568 // Simple cost function used to check if the evaluator is sensitive to
570 class ParameterSensitiveCostFunction : public SizedCostFunction<2, 2> {
572 virtual bool Evaluate(double const* const* parameters,
574 double** jacobians) const {
575 double x1 = parameters[0][0];
576 double x2 = parameters[0][1];
577 residuals[0] = x1 * x1;
578 residuals[1] = x2 * x2;
580 if (jacobians != NULL) {
581 double* jacobian = jacobians[0];
582 if (jacobian != NULL) {
583 jacobian[0] = 2.0 * x1;
586 jacobian[3] = 2.0 * x2;
593 TEST(Evaluator, EvaluatorRespectsParameterChanges) {
600 problem.AddResidualBlock(new ParameterSensitiveCostFunction(), NULL, x);
601 Program* program = problem.mutable_program();
602 program->SetParameterOffsetsAndIndex();
604 Evaluator::Options options;
605 options.linear_solver_type = DENSE_QR;
606 options.num_eliminate_blocks = 0;
608 scoped_ptr<Evaluator> evaluator(Evaluator::Create(options, program, &error));
609 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
611 ASSERT_EQ(2, jacobian->num_rows());
612 ASSERT_EQ(2, jacobian->num_cols());
618 // The original state of a residual block comes from the user's
619 // state. So the original state is 1.0, 1.0, and the only way we get
620 // the 2.0, 3.0 results in the following tests is if it respects the
621 // values in the state vector.
623 // Cost only; no residuals and no jacobian.
626 ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
627 EXPECT_EQ(48.5, cost);
630 // Cost and residuals, no jacobian.
633 double residuals[2] = { -2, -2 };
634 ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL, NULL));
635 EXPECT_EQ(48.5, cost);
636 EXPECT_EQ(4, residuals[0]);
637 EXPECT_EQ(9, residuals[1]);
640 // Cost, residuals, and jacobian.
643 double residuals[2] = { -2, -2};
644 SetSparseMatrixConstant(jacobian.get(), -1);
645 ASSERT_TRUE(evaluator->Evaluate(state,
650 EXPECT_EQ(48.5, cost);
651 EXPECT_EQ(4, residuals[0]);
652 EXPECT_EQ(9, residuals[1]);
653 Matrix actual_jacobian;
654 jacobian->ToDenseMatrix(&actual_jacobian);
656 Matrix expected_jacobian(2, 2);
661 EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
662 << "Actual:\n" << actual_jacobian
663 << "\nExpected:\n" << expected_jacobian;
667 } // namespace internal