blob: a4e7b2540deff9763177fcfcd4f71bb278e7afff [file] [log] [blame]
Keir Mierle8ebb0732012-04-30 23:09:08 -07001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2010, 2011, 2012 Google Inc. All rights reserved.
3// 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: keir@google.com (Keir Mierle)
30//
31// Tests shared across evaluators. The tests try all combinations of linear
32// solver and num_eliminate_blocks (for schur-based solvers).
33
34#include "ceres/evaluator.h"
35
Keir Mierle8ebb0732012-04-30 23:09:08 -070036#include "ceres/casts.h"
Sameer Agarwal4997cbc2012-07-02 12:44:34 -070037#include "ceres/cost_function.h"
38#include "ceres/crs_matrix.h"
39#include "ceres/internal/eigen.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070040#include "ceres/internal/scoped_ptr.h"
41#include "ceres/local_parameterization.h"
Sameer Agarwal4997cbc2012-07-02 12:44:34 -070042#include "ceres/problem_impl.h"
43#include "ceres/program.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070044#include "ceres/sized_cost_function.h"
Sameer Agarwal4997cbc2012-07-02 12:44:34 -070045#include "ceres/sparse_matrix.h"
46#include "ceres/types.h"
47#include "gtest/gtest.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070048
49namespace ceres {
50namespace internal {
51
52// TODO(keir): Consider pushing this into a common test utils file.
53template<int kFactor, int kNumResiduals,
54 int N0 = 0, int N1 = 0, int N2 = 0, bool kSucceeds = true>
55class ParameterIgnoringCostFunction
56 : public SizedCostFunction<kNumResiduals, N0, N1, N2> {
57 typedef SizedCostFunction<kNumResiduals, N0, N1, N2> Base;
58 public:
59 virtual bool Evaluate(double const* const* parameters,
60 double* residuals,
61 double** jacobians) const {
62 for (int i = 0; i < Base::num_residuals(); ++i) {
63 residuals[i] = i + 1;
64 }
65 if (jacobians) {
66 for (int k = 0; k < Base::parameter_block_sizes().size(); ++k) {
67 // The jacobians here are full sized, but they are transformed in the
68 // evaluator into the "local" jacobian. In the tests, the "subset
69 // constant" parameterization is used, which should pick out columns
70 // from these jacobians. Put values in the jacobian that make this
71 // obvious; in particular, make the jacobians like this:
72 //
73 // 1 2 3 4 ...
74 // 1 2 3 4 ... .* kFactor
75 // 1 2 3 4 ...
76 //
77 // where the multiplication by kFactor makes it easier to distinguish
78 // between Jacobians of different residuals for the same parameter.
79 if (jacobians[k] != NULL) {
80 MatrixRef jacobian(jacobians[k],
81 Base::num_residuals(),
82 Base::parameter_block_sizes()[k]);
83 for (int j = 0; j < Base::parameter_block_sizes()[k]; ++j) {
84 jacobian.col(j).setConstant(kFactor * (j + 1));
85 }
86 }
87 }
88 }
89 return kSucceeds;
90 }
91};
92
Keir Mierlef44907f2012-07-06 13:52:32 -070093struct ExpectedEvaluation {
94 int num_rows;
95 int num_cols;
96 double cost;
97 const double residuals[50];
98 const double gradient[50];
99 const double jacobian[200];
100};
101
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700102void CompareEvaluations(int expected_num_rows,
103 int expected_num_cols,
104 double expected_cost,
105 const double* expected_residuals,
106 const double* expected_gradient,
107 const double* expected_jacobian,
108 const double actual_cost,
109 const double* actual_residuals,
110 const double* actual_gradient,
111 const double* actual_jacobian) {
112 EXPECT_EQ(expected_cost, actual_cost);
113
114 if (expected_residuals != NULL) {
115 ConstVectorRef expected_residuals_vector(expected_residuals,
116 expected_num_rows);
117 ConstVectorRef actual_residuals_vector(actual_residuals,
118 expected_num_rows);
119 EXPECT_TRUE((actual_residuals_vector.array() ==
120 expected_residuals_vector.array()).all())
121 << "Actual:\n" << actual_residuals_vector
122 << "\nExpected:\n" << expected_residuals_vector;
123 }
124
125 if (expected_gradient != NULL) {
126 ConstVectorRef expected_gradient_vector(expected_gradient,
127 expected_num_cols);
128 ConstVectorRef actual_gradient_vector(actual_gradient,
129 expected_num_cols);
130
131 EXPECT_TRUE((actual_gradient_vector.array() ==
132 expected_gradient_vector.array()).all())
133 << "Actual:\n" << actual_gradient_vector.transpose()
134 << "\nExpected:\n" << expected_gradient_vector.transpose();
135 }
136
137 if (expected_jacobian != NULL) {
138 ConstMatrixRef expected_jacobian_matrix(expected_jacobian,
139 expected_num_rows,
140 expected_num_cols);
141 ConstMatrixRef actual_jacobian_matrix(actual_jacobian,
142 expected_num_rows,
143 expected_num_cols);
144 EXPECT_TRUE((actual_jacobian_matrix.array() ==
145 expected_jacobian_matrix.array()).all())
146 << "Actual:\n" << actual_jacobian_matrix
147 << "\nExpected:\n" << expected_jacobian_matrix;
148 }
149}
150
151
Keir Mierle8ebb0732012-04-30 23:09:08 -0700152struct EvaluatorTest
153 : public ::testing::TestWithParam<pair<LinearSolverType, int> > {
154 Evaluator* CreateEvaluator(Program* program) {
155 // This program is straight from the ProblemImpl, and so has no index/offset
156 // yet; compute it here as required by the evalutor implementations.
157 program->SetParameterOffsetsAndIndex();
158
159 VLOG(1) << "Creating evaluator with type: " << GetParam().first
160 << " and num_eliminate_blocks: " << GetParam().second;
161 Evaluator::Options options;
162 options.linear_solver_type = GetParam().first;
163 options.num_eliminate_blocks = GetParam().second;
164 string error;
165 return Evaluator::Create(options, program, &error);
166 }
Keir Mierlef44907f2012-07-06 13:52:32 -0700167
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700168 void EvaluateAndCompare(ProblemImpl *problem,
169 int expected_num_rows,
170 int expected_num_cols,
171 double expected_cost,
172 const double* expected_residuals,
173 const double* expected_gradient,
174 const double* expected_jacobian) {
175 scoped_ptr<Evaluator> evaluator(
176 CreateEvaluator(problem->mutable_program()));
Keir Mierlef44907f2012-07-06 13:52:32 -0700177 int num_residuals = expected_num_rows;
178 int num_parameters = expected_num_cols;
179
180 double cost = -1;
181
182 Vector residuals(num_residuals);
183 residuals.setConstant(-2000);
184
185 Vector gradient(num_parameters);
186 gradient.setConstant(-3000);
187
188 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
189
190 ASSERT_EQ(expected_num_rows, evaluator->NumResiduals());
191 ASSERT_EQ(expected_num_cols, evaluator->NumEffectiveParameters());
192 ASSERT_EQ(expected_num_rows, jacobian->num_rows());
193 ASSERT_EQ(expected_num_cols, jacobian->num_cols());
194
195 vector<double> state(evaluator->NumParameters());
196
197 ASSERT_TRUE(evaluator->Evaluate(
198 &state[0],
199 &cost,
200 expected_residuals != NULL ? &residuals[0] : NULL,
201 expected_gradient != NULL ? &gradient[0] : NULL,
202 expected_jacobian != NULL ? jacobian.get() : NULL));
203
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700204 Matrix actual_jacobian;
Keir Mierlef44907f2012-07-06 13:52:32 -0700205 if (expected_jacobian != NULL) {
Keir Mierlef44907f2012-07-06 13:52:32 -0700206 jacobian->ToDenseMatrix(&actual_jacobian);
Keir Mierlef44907f2012-07-06 13:52:32 -0700207 }
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700208
209 CompareEvaluations(expected_num_rows,
210 expected_num_cols,
211 expected_cost,
212 expected_residuals,
213 expected_gradient,
214 expected_jacobian,
215 cost,
216 &residuals[0],
217 &gradient[0],
218 actual_jacobian.data());
Keir Mierlef44907f2012-07-06 13:52:32 -0700219 }
220
221 // Try all combinations of parameters for the evaluator.
222 void CheckAllEvaluationCombinations(const ExpectedEvaluation &expected) {
223 for (int i = 0; i < 8; ++i) {
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700224 EvaluateAndCompare(&problem,
225 expected.num_rows,
226 expected.num_cols,
227 expected.cost,
228 (i & 1) ? expected.residuals : NULL,
229 (i & 2) ? expected.gradient : NULL,
230 (i & 4) ? expected.jacobian : NULL);
Keir Mierlef44907f2012-07-06 13:52:32 -0700231 }
232 }
233
234 // The values are ignored completely by the cost function.
235 double x[2];
236 double y[3];
237 double z[4];
238
239 ProblemImpl problem;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700240};
241
242void SetSparseMatrixConstant(SparseMatrix* sparse_matrix, double value) {
243 VectorRef(sparse_matrix->mutable_values(),
244 sparse_matrix->num_nonzeros()).setConstant(value);
245}
246
247TEST_P(EvaluatorTest, SingleResidualProblem) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700248 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
249 NULL,
250 x, y, z);
251
Keir Mierlef44907f2012-07-06 13:52:32 -0700252 ExpectedEvaluation expected = {
253 // Rows/columns
254 3, 9,
255 // Cost
256 7.0,
257 // Residuals
258 { 1.0, 2.0, 3.0 },
259 // Gradient
260 { 6.0, 12.0, // x
261 6.0, 12.0, 18.0, // y
262 6.0, 12.0, 18.0, 24.0, // z
263 },
264 // Jacobian
265 // x y z
266 { 1, 2, 1, 2, 3, 1, 2, 3, 4,
267 1, 2, 1, 2, 3, 1, 2, 3, 4,
268 1, 2, 1, 2, 3, 1, 2, 3, 4
269 }
270 };
271 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700272}
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700273
Keir Mierle8ebb0732012-04-30 23:09:08 -0700274TEST_P(EvaluatorTest, SingleResidualProblemWithPermutedParameters) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700275 // Add the parameters in explicit order to force the ordering in the program.
276 problem.AddParameterBlock(x, 2);
277 problem.AddParameterBlock(y, 3);
278 problem.AddParameterBlock(z, 4);
279
280 // Then use a cost function which is similar to the others, but swap around
281 // the ordering of the parameters to the cost function. This shouldn't affect
282 // the jacobian evaluation, but requires explicit handling in the evaluators.
283 // At one point the compressed row evaluator had a bug that went undetected
284 // for a long time, since by chance most users added parameters to the problem
285 // in the same order that they occured as parameters to a cost function.
286 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 4, 3, 2>,
287 NULL,
288 z, y, x);
289
Keir Mierlef44907f2012-07-06 13:52:32 -0700290 ExpectedEvaluation expected = {
291 // Rows/columns
292 3, 9,
293 // Cost
294 7.0,
295 // Residuals
296 { 1.0, 2.0, 3.0 },
297 // Gradient
298 { 6.0, 12.0, // x
299 6.0, 12.0, 18.0, // y
300 6.0, 12.0, 18.0, 24.0, // z
301 },
302 // Jacobian
303 // x y z
304 { 1, 2, 1, 2, 3, 1, 2, 3, 4,
305 1, 2, 1, 2, 3, 1, 2, 3, 4,
306 1, 2, 1, 2, 3, 1, 2, 3, 4
307 }
308 };
309 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700310}
Keir Mierlef44907f2012-07-06 13:52:32 -0700311
Keir Mierle8ebb0732012-04-30 23:09:08 -0700312TEST_P(EvaluatorTest, SingleResidualProblemWithNuisanceParameters) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700313 // These parameters are not used.
Keir Mierlef44907f2012-07-06 13:52:32 -0700314 double a[2];
315 double b[1];
316 double c[1];
317 double d[3];
Keir Mierle8ebb0732012-04-30 23:09:08 -0700318
319 // Add the parameters in a mixed order so the Jacobian is "checkered" with the
320 // values from the other parameters.
Keir Mierlef44907f2012-07-06 13:52:32 -0700321 problem.AddParameterBlock(a, 2);
322 problem.AddParameterBlock(x, 2);
323 problem.AddParameterBlock(b, 1);
324 problem.AddParameterBlock(y, 3);
325 problem.AddParameterBlock(c, 1);
326 problem.AddParameterBlock(z, 4);
327 problem.AddParameterBlock(d, 3);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700328
329 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
330 NULL,
331 x, y, z);
332
Keir Mierlef44907f2012-07-06 13:52:32 -0700333 ExpectedEvaluation expected = {
334 // Rows/columns
335 3, 16,
336 // Cost
337 7.0,
338 // Residuals
339 { 1.0, 2.0, 3.0 },
340 // Gradient
341 { 0.0, 0.0, // a
342 6.0, 12.0, // x
343 0.0, // b
344 6.0, 12.0, 18.0, // y
345 0.0, // c
346 6.0, 12.0, 18.0, 24.0, // z
347 0.0, 0.0, 0.0, // d
348 },
349 // Jacobian
350 // a x b y c z d
351 { 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
352 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
353 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0
354 }
355 };
356 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700357}
358
359TEST_P(EvaluatorTest, MultipleResidualProblem) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700360 // Add the parameters in explicit order to force the ordering in the program.
361 problem.AddParameterBlock(x, 2);
362 problem.AddParameterBlock(y, 3);
363 problem.AddParameterBlock(z, 4);
364
365 // f(x, y) in R^2
366 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
367 NULL,
368 x, y);
369
370 // g(x, z) in R^3
371 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
372 NULL,
373 x, z);
374
375 // h(y, z) in R^4
376 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
377 NULL,
378 y, z);
379
Keir Mierlef44907f2012-07-06 13:52:32 -0700380 ExpectedEvaluation expected = {
381 // Rows/columns
382 9, 9,
383 // Cost
384 // f g h
385 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
386 // Residuals
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700387 { 1.0, 2.0, // f
388 1.0, 2.0, 3.0, // g
389 1.0, 2.0, 3.0, 4.0 // h
390 },
Keir Mierlef44907f2012-07-06 13:52:32 -0700391 // Gradient
392 { 15.0, 30.0, // x
393 33.0, 66.0, 99.0, // y
394 42.0, 84.0, 126.0, 168.0 // z
395 },
396 // Jacobian
Keir Mierle8ebb0732012-04-30 23:09:08 -0700397 // x y z
Keir Mierlef44907f2012-07-06 13:52:32 -0700398 { /* f(x, y) */ 1, 2, 1, 2, 3, 0, 0, 0, 0,
Keir Mierle8ebb0732012-04-30 23:09:08 -0700399 1, 2, 1, 2, 3, 0, 0, 0, 0,
400
401 /* g(x, z) */ 2, 4, 0, 0, 0, 2, 4, 6, 8,
402 2, 4, 0, 0, 0, 2, 4, 6, 8,
403 2, 4, 0, 0, 0, 2, 4, 6, 8,
404
405 /* h(y, z) */ 0, 0, 3, 6, 9, 3, 6, 9, 12,
406 0, 0, 3, 6, 9, 3, 6, 9, 12,
407 0, 0, 3, 6, 9, 3, 6, 9, 12,
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700408 0, 0, 3, 6, 9, 3, 6, 9, 12
Keir Mierlef44907f2012-07-06 13:52:32 -0700409 }
410 };
411 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700412}
413
414TEST_P(EvaluatorTest, MultipleResidualsWithLocalParameterizations) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700415 // Add the parameters in explicit order to force the ordering in the program.
416 problem.AddParameterBlock(x, 2);
417
418 // Fix y's first dimension.
419 vector<int> y_fixed;
420 y_fixed.push_back(0);
421 problem.AddParameterBlock(y, 3, new SubsetParameterization(3, y_fixed));
422
423 // Fix z's second dimension.
424 vector<int> z_fixed;
425 z_fixed.push_back(1);
426 problem.AddParameterBlock(z, 4, new SubsetParameterization(4, z_fixed));
427
428 // f(x, y) in R^2
429 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
430 NULL,
431 x, y);
432
433 // g(x, z) in R^3
434 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
435 NULL,
436 x, z);
437
438 // h(y, z) in R^4
439 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
440 NULL,
441 y, z);
442
Keir Mierlef44907f2012-07-06 13:52:32 -0700443 ExpectedEvaluation expected = {
444 // Rows/columns
445 9, 7,
446 // Cost
447 // f g h
448 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
449 // Residuals
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700450 { 1.0, 2.0, // f
451 1.0, 2.0, 3.0, // g
452 1.0, 2.0, 3.0, 4.0 // h
453 },
Keir Mierlef44907f2012-07-06 13:52:32 -0700454 // Gradient
455 { 15.0, 30.0, // x
456 66.0, 99.0, // y
457 42.0, 126.0, 168.0 // z
458 },
459 // Jacobian
460 // x y z
461 { /* f(x, y) */ 1, 2, 2, 3, 0, 0, 0,
Keir Mierle8ebb0732012-04-30 23:09:08 -0700462 1, 2, 2, 3, 0, 0, 0,
463
464 /* g(x, z) */ 2, 4, 0, 0, 2, 6, 8,
465 2, 4, 0, 0, 2, 6, 8,
466 2, 4, 0, 0, 2, 6, 8,
467
468 /* h(y, z) */ 0, 0, 6, 9, 3, 9, 12,
469 0, 0, 6, 9, 3, 9, 12,
470 0, 0, 6, 9, 3, 9, 12,
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700471 0, 0, 6, 9, 3, 9, 12
Keir Mierlef44907f2012-07-06 13:52:32 -0700472 }
473 };
474 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700475}
476
477TEST_P(EvaluatorTest, MultipleResidualProblemWithSomeConstantParameters) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700478 // The values are ignored completely by the cost function.
479 double x[2];
480 double y[3];
481 double z[4];
Keir Mierle8ebb0732012-04-30 23:09:08 -0700482
483 // Add the parameters in explicit order to force the ordering in the program.
484 problem.AddParameterBlock(x, 2);
485 problem.AddParameterBlock(y, 3);
486 problem.AddParameterBlock(z, 4);
487
488 // f(x, y) in R^2
489 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
490 NULL,
491 x, y);
492
493 // g(x, z) in R^3
494 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
495 NULL,
496 x, z);
497
498 // h(y, z) in R^4
499 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
500 NULL,
501 y, z);
502
503 // For this test, "z" is constant.
504 problem.SetParameterBlockConstant(z);
505
506 // Create the reduced program which is missing the fixed "z" variable.
507 // Normally, the preprocessing of the program that happens in solver_impl
508 // takes care of this, but we don't want to invoke the solver here.
509 Program reduced_program;
Keir Mierlef44907f2012-07-06 13:52:32 -0700510 vector<ParameterBlock*>* parameter_blocks =
511 problem.mutable_program()->mutable_parameter_blocks();
Keir Mierle8ebb0732012-04-30 23:09:08 -0700512
Keir Mierlef44907f2012-07-06 13:52:32 -0700513 // "z" is the last parameter; save it for later and pop it off temporarily.
514 // Note that "z" will still get read during evaluation, so it cannot be
515 // deleted at this point.
516 ParameterBlock* parameter_block_z = parameter_blocks->back();
517 parameter_blocks->pop_back();
Keir Mierle8ebb0732012-04-30 23:09:08 -0700518
Keir Mierlef44907f2012-07-06 13:52:32 -0700519 ExpectedEvaluation expected = {
520 // Rows/columns
521 9, 5,
522 // Cost
523 // f g h
524 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
525 // Residuals
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700526 { 1.0, 2.0, // f
527 1.0, 2.0, 3.0, // g
528 1.0, 2.0, 3.0, 4.0 // h
529 },
Keir Mierlef44907f2012-07-06 13:52:32 -0700530 // Gradient
531 { 15.0, 30.0, // x
532 33.0, 66.0, 99.0, // y
533 },
534 // Jacobian
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700535 // x y
Keir Mierlef44907f2012-07-06 13:52:32 -0700536 { /* f(x, y) */ 1, 2, 1, 2, 3,
Keir Mierle8ebb0732012-04-30 23:09:08 -0700537 1, 2, 1, 2, 3,
538
539 /* g(x, z) */ 2, 4, 0, 0, 0,
540 2, 4, 0, 0, 0,
541 2, 4, 0, 0, 0,
542
543 /* h(y, z) */ 0, 0, 3, 6, 9,
544 0, 0, 3, 6, 9,
545 0, 0, 3, 6, 9,
Keir Mierlef44907f2012-07-06 13:52:32 -0700546 0, 0, 3, 6, 9
547 }
548 };
549 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700550
Keir Mierlef44907f2012-07-06 13:52:32 -0700551 // Restore parameter block z, so it will get freed in a consistent way.
552 parameter_blocks->push_back(parameter_block_z);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700553}
554
555TEST_P(EvaluatorTest, EvaluatorAbortsForResidualsThatFailToEvaluate) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700556 // Switch the return value to failure.
557 problem.AddResidualBlock(
558 new ParameterIgnoringCostFunction<20, 3, 2, 3, 4, false>, NULL, x, y, z);
559
Keir Mierlef44907f2012-07-06 13:52:32 -0700560 // The values are ignored.
561 double state[9];
562
Keir Mierle8ebb0732012-04-30 23:09:08 -0700563 scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
564 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
565 double cost;
Keir Mierlef44907f2012-07-06 13:52:32 -0700566 EXPECT_FALSE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700567}
568
569// In the pairs, the first argument is the linear solver type, and the second
570// argument is num_eliminate_blocks. Changing the num_eliminate_blocks only
571// makes sense for the schur-based solvers.
572//
573// Try all values of num_eliminate_blocks that make sense given that in the
574// tests a maximum of 4 parameter blocks are present.
575INSTANTIATE_TEST_CASE_P(
576 LinearSolvers,
577 EvaluatorTest,
578 ::testing::Values(make_pair(DENSE_QR, 0),
579 make_pair(DENSE_SCHUR, 0),
580 make_pair(DENSE_SCHUR, 1),
581 make_pair(DENSE_SCHUR, 2),
582 make_pair(DENSE_SCHUR, 3),
583 make_pair(DENSE_SCHUR, 4),
584 make_pair(SPARSE_SCHUR, 0),
585 make_pair(SPARSE_SCHUR, 1),
586 make_pair(SPARSE_SCHUR, 2),
587 make_pair(SPARSE_SCHUR, 3),
588 make_pair(SPARSE_SCHUR, 4),
589 make_pair(ITERATIVE_SCHUR, 0),
590 make_pair(ITERATIVE_SCHUR, 1),
591 make_pair(ITERATIVE_SCHUR, 2),
592 make_pair(ITERATIVE_SCHUR, 3),
593 make_pair(ITERATIVE_SCHUR, 4),
594 make_pair(SPARSE_NORMAL_CHOLESKY, 0)));
595
596// Simple cost function used to check if the evaluator is sensitive to
597// state changes.
598class ParameterSensitiveCostFunction : public SizedCostFunction<2, 2> {
599 public:
600 virtual bool Evaluate(double const* const* parameters,
601 double* residuals,
602 double** jacobians) const {
603 double x1 = parameters[0][0];
604 double x2 = parameters[0][1];
605 residuals[0] = x1 * x1;
606 residuals[1] = x2 * x2;
607
608 if (jacobians != NULL) {
609 double* jacobian = jacobians[0];
610 if (jacobian != NULL) {
611 jacobian[0] = 2.0 * x1;
612 jacobian[1] = 0.0;
613 jacobian[2] = 0.0;
614 jacobian[3] = 2.0 * x2;
615 }
616 }
617 return true;
618 }
619};
620
621TEST(Evaluator, EvaluatorRespectsParameterChanges) {
622 ProblemImpl problem;
623
624 double x[2];
625 x[0] = 1.0;
626 x[1] = 1.0;
627
628 problem.AddResidualBlock(new ParameterSensitiveCostFunction(), NULL, x);
629 Program* program = problem.mutable_program();
630 program->SetParameterOffsetsAndIndex();
631
632 Evaluator::Options options;
633 options.linear_solver_type = DENSE_QR;
634 options.num_eliminate_blocks = 0;
635 string error;
636 scoped_ptr<Evaluator> evaluator(Evaluator::Create(options, program, &error));
637 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
638
639 ASSERT_EQ(2, jacobian->num_rows());
640 ASSERT_EQ(2, jacobian->num_cols());
641
642 double state[2];
643 state[0] = 2.0;
644 state[1] = 3.0;
645
646 // The original state of a residual block comes from the user's
647 // state. So the original state is 1.0, 1.0, and the only way we get
648 // the 2.0, 3.0 results in the following tests is if it respects the
649 // values in the state vector.
650
651 // Cost only; no residuals and no jacobian.
652 {
653 double cost = -1;
Keir Mierlef44907f2012-07-06 13:52:32 -0700654 ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700655 EXPECT_EQ(48.5, cost);
656 }
657
658 // Cost and residuals, no jacobian.
659 {
660 double cost = -1;
661 double residuals[2] = { -2, -2 };
Keir Mierlef44907f2012-07-06 13:52:32 -0700662 ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL, NULL));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700663 EXPECT_EQ(48.5, cost);
664 EXPECT_EQ(4, residuals[0]);
665 EXPECT_EQ(9, residuals[1]);
666 }
667
668 // Cost, residuals, and jacobian.
669 {
670 double cost = -1;
671 double residuals[2] = { -2, -2};
672 SetSparseMatrixConstant(jacobian.get(), -1);
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700673 ASSERT_TRUE(evaluator->Evaluate(state,
674 &cost,
675 residuals,
676 NULL,
677 jacobian.get()));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700678 EXPECT_EQ(48.5, cost);
679 EXPECT_EQ(4, residuals[0]);
680 EXPECT_EQ(9, residuals[1]);
681 Matrix actual_jacobian;
682 jacobian->ToDenseMatrix(&actual_jacobian);
683
684 Matrix expected_jacobian(2, 2);
685 expected_jacobian
686 << 2 * state[0], 0,
687 0, 2 * state[1];
688
689 EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
690 << "Actual:\n" << actual_jacobian
691 << "\nExpected:\n" << expected_jacobian;
692 }
693}
694
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700695// Simple cost function used for testing Evaluator::Evaluate.
696//
697// r_i = i - (j + 1) * x_ij^2
698template <int kNumResiduals, int kNumParameterBlocks >
699class QuadraticCostFunction : public CostFunction {
700 public:
701 QuadraticCostFunction() {
702 CHECK_GT(kNumResiduals, 0);
703 CHECK_GT(kNumParameterBlocks, 0);
704 set_num_residuals(kNumResiduals);
705 for (int i = 0; i < kNumParameterBlocks; ++i) {
706 mutable_parameter_block_sizes()->push_back(kNumResiduals);
707 }
708 }
709
710 virtual bool Evaluate(double const* const* parameters,
711 double* residuals,
712 double** jacobians) const {
713 for (int i = 0; i < kNumResiduals; ++i) {
714 residuals[i] = i;
715 for (int j = 0; j < kNumParameterBlocks; ++j) {
716 residuals[i] -= (j + 1.0) * parameters[j][i] * parameters[j][i];
717 }
718 }
719
720 if (jacobians == NULL) {
721 return true;
722 }
723
724 for (int j = 0; j < kNumParameterBlocks; ++j) {
725 if (jacobians[j] != NULL) {
726 MatrixRef(jacobians[j], kNumResiduals, kNumResiduals) =
727 (-2.0 * (j + 1.0) *
728 ConstVectorRef(parameters[j], kNumResiduals)).asDiagonal();
729 }
730 }
731
732 return true;
733 }
734};
735
736// Convert a CRSMatrix to a dense Eigen matrix.
737void CRSToDenseMatrix(const CRSMatrix& input, Matrix* output) {
738 Matrix& m = *CHECK_NOTNULL(output);
739 m.resize(input.num_rows, input.num_cols);
740 m.setZero();
741 for (int row = 0; row < input.num_rows; ++row) {
742 for (int j = input.rows[row]; j < input.rows[row + 1]; ++j) {
743 const int col = input.cols[j];
744 m(row, col) = input.values[j];
745 }
746 }
747}
748
749
750class StaticEvaluateTest : public ::testing::Test {
751 protected:
752 void SetUp() {
753 for (int i = 0; i < 6; ++i) {
754 parameters_[i] = static_cast<double>(i + 1);
755 }
756
757 CostFunction* cost_function = new QuadraticCostFunction<2, 2>;
758
759 // f(x, y)
760 problem_.AddResidualBlock(cost_function,
761 NULL,
762 parameters_,
763 parameters_ + 2);
764 // g(y, z)
765 problem_.AddResidualBlock(cost_function,
766 NULL, parameters_ + 2,
767 parameters_ + 4);
768 // h(z, x)
769 problem_.AddResidualBlock(cost_function,
770 NULL,
771 parameters_ + 4,
772 parameters_);
773 }
774
775
776
777 void EvaluateAndCompare(const int expected_num_rows,
778 const int expected_num_cols,
779 const double expected_cost,
780 const double* expected_residuals,
781 const double* expected_gradient,
782 const double* expected_jacobian) {
783 double cost;
784 vector<double> residuals;
785 vector<double> gradient;
786 CRSMatrix jacobian;
787
788 EXPECT_TRUE(Evaluator::Evaluate(
789 problem_.mutable_program(),
790 1,
791 &cost,
792 expected_residuals != NULL ? &residuals : NULL,
793 expected_gradient != NULL ? &gradient : NULL,
794 expected_jacobian != NULL ? &jacobian : NULL));
795
796 if (expected_residuals != NULL) {
797 EXPECT_EQ(residuals.size(), expected_num_rows);
798 }
799
800 if (expected_gradient != NULL) {
801 EXPECT_EQ(gradient.size(), expected_num_cols);
802 }
803
804 if (expected_jacobian != NULL) {
805 EXPECT_EQ(jacobian.num_rows, expected_num_rows);
806 EXPECT_EQ(jacobian.num_cols, expected_num_cols);
807 }
808
809 Matrix dense_jacobian;
810 if (expected_jacobian != NULL) {
811 CRSToDenseMatrix(jacobian, &dense_jacobian);
812 }
813
814 CompareEvaluations(expected_num_rows,
815 expected_num_cols,
816 expected_cost,
817 expected_residuals,
818 expected_gradient,
819 expected_jacobian,
820 cost,
Sameer Agarwalfba38442012-09-07 17:04:53 -0700821 residuals.size() > 0 ? &residuals[0] : NULL,
822 gradient.size() > 0 ? &gradient[0] : NULL,
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700823 dense_jacobian.data());
824 }
825
826 void CheckAllEvaluationCombinations(const ExpectedEvaluation& expected ) {
827 for (int i = 0; i < 8; ++i) {
828 EvaluateAndCompare(expected.num_rows,
829 expected.num_cols,
830 expected.cost,
831 (i & 1) ? expected.residuals : NULL,
832 (i & 2) ? expected.gradient : NULL,
833 (i & 4) ? expected.jacobian : NULL);
834 }
835
836
837 double new_parameters[6];
838 for (int i = 0; i < 6; ++i) {
839 new_parameters[i] = 0.0;
840 }
841
842 problem_.mutable_program()->StateVectorToParameterBlocks(new_parameters);
843
844 for (int i = 0; i < 8; ++i) {
845 EvaluateAndCompare(expected.num_rows,
846 expected.num_cols,
847 expected.cost,
848 (i & 1) ? expected.residuals : NULL,
849 (i & 2) ? expected.gradient : NULL,
850 (i & 4) ? expected.jacobian : NULL);
851 }
852 }
853
854 ProblemImpl problem_;
855 double parameters_[6];
856};
857
858
859TEST_F(StaticEvaluateTest, MultipleParameterAndResidualBlocks) {
860 ExpectedEvaluation expected = {
861 // Rows/columns
862 6, 6,
863 // Cost
864 7607.0,
865 // Residuals
866 { -19.0, -35.0, // f
867 -59.0, -87.0, // g
868 -27.0, -43.0 // h
869 },
870 // Gradient
871 { 146.0, 484.0, // x
872 582.0, 1256.0, // y
873 1450.0, 2604.0, // z
874 },
875 // Jacobian
876 // x y z
877 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
878 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
879 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
880 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
881 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
882 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
883 }
884 };
885
886 CheckAllEvaluationCombinations(expected);
887}
888
889TEST_F(StaticEvaluateTest, ConstantParameterBlock) {
890 ExpectedEvaluation expected = {
891 // Rows/columns
892 6, 6,
893 // Cost
894 7607.0,
895 // Residuals
896 { -19.0, -35.0, // f
897 -59.0, -87.0, // g
898 -27.0, -43.0 // h
899 },
900
901 // Gradient
902 { 146.0, 484.0, // x
903 0.0, 0.0, // y
904 1450.0, 2604.0, // z
905 },
906
907 // Jacobian
908 // x y z
909 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
910 0.0, -4.0, 0.0, 0.0, 0.0, 0.0,
911 /* g(y, z) */ 0.0, 0.0, 0.0, 0.0, -20.0, 0.0,
912 0.0, 0.0, 0.0, 0.0, 0.0, -24.0,
913 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
914 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
915 }
916 };
917
918 problem_.SetParameterBlockConstant(parameters_ + 2);
919 CheckAllEvaluationCombinations(expected);
920}
921
922TEST_F(StaticEvaluateTest, LocalParameterization) {
923 ExpectedEvaluation expected = {
924 // Rows/columns
925 6, 5,
926 // Cost
927 7607.0,
928 // Residuals
929 { -19.0, -35.0, // f
930 -59.0, -87.0, // g
931 -27.0, -43.0 // h
932 },
933 // Gradient
934 { 146.0, 484.0, // x
935 1256.0, // y with SubsetParameterization
936 1450.0, 2604.0, // z
937 },
938 // Jacobian
939 // x y z
940 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0,
941 0.0, -4.0, -16.0, 0.0, 0.0,
942 /* g(y, z) */ 0.0, 0.0, 0.0, -20.0, 0.0,
943 0.0, 0.0, -8.0, 0.0, -24.0,
944 /* h(z, x) */ -4.0, 0.0, 0.0, -10.0, 0.0,
945 0.0, -8.0, 0.0, 0.0, -12.0
946 }
947 };
948
949 vector<int> constant_parameters;
950 constant_parameters.push_back(0);
951 problem_.SetParameterization(parameters_ + 2,
952 new SubsetParameterization(2,
953 constant_parameters));
954
955 CheckAllEvaluationCombinations(expected);
956}
957
Keir Mierle8ebb0732012-04-30 23:09:08 -0700958} // namespace internal
959} // namespace ceres