blob: 48dc78af63eaf5592db2af914de355e84a018ba7 [file] [log] [blame]
Keir Mierle8ebb0732012-04-30 23:09:08 -07001// Ceres Solver - A fast non-linear least squares minimizer
Keir Mierle7492b0d2015-03-17 22:30:16 -07002// Copyright 2015 Google Inc. All rights reserved.
3// http://ceres-solver.org/
Keir Mierle8ebb0732012-04-30 23:09:08 -07004//
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"
Sameer Agarwal039ff072013-02-26 09:15:39 -080039#include "ceres/evaluator_test_utils.h"
Sameer Agarwal4997cbc2012-07-02 12:44:34 -070040#include "ceres/internal/eigen.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070041#include "ceres/internal/scoped_ptr.h"
42#include "ceres/local_parameterization.h"
Sameer Agarwal4997cbc2012-07-02 12:44:34 -070043#include "ceres/problem_impl.h"
44#include "ceres/program.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070045#include "ceres/sized_cost_function.h"
Sameer Agarwal4997cbc2012-07-02 12:44:34 -070046#include "ceres/sparse_matrix.h"
Richard Stebbing32530782014-04-26 07:42:23 +010047#include "ceres/stringprintf.h"
Sameer Agarwal4997cbc2012-07-02 12:44:34 -070048#include "ceres/types.h"
49#include "gtest/gtest.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070050
51namespace ceres {
52namespace internal {
53
Sameer Agarwal05a07ec2015-01-07 15:10:46 -080054using std::string;
Sameer Agarwalbcc865f2014-12-17 07:35:09 -080055using std::vector;
56
Keir Mierle8ebb0732012-04-30 23:09:08 -070057// TODO(keir): Consider pushing this into a common test utils file.
58template<int kFactor, int kNumResiduals,
59 int N0 = 0, int N1 = 0, int N2 = 0, bool kSucceeds = true>
60class ParameterIgnoringCostFunction
61 : public SizedCostFunction<kNumResiduals, N0, N1, N2> {
62 typedef SizedCostFunction<kNumResiduals, N0, N1, N2> Base;
63 public:
64 virtual bool Evaluate(double const* const* parameters,
65 double* residuals,
66 double** jacobians) const {
67 for (int i = 0; i < Base::num_residuals(); ++i) {
68 residuals[i] = i + 1;
69 }
70 if (jacobians) {
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:
77 //
78 // 1 2 3 4 ...
79 // 1 2 3 4 ... .* kFactor
80 // 1 2 3 4 ...
81 //
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));
90 }
91 }
92 }
93 }
94 return kSucceeds;
95 }
96};
97
Richard Stebbing32530782014-04-26 07:42:23 +010098struct 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) {}
105
106 LinearSolverType linear_solver_type;
107 int num_eliminate_blocks;
108 bool dynamic_sparsity;
109};
110
Keir Mierle8ebb0732012-04-30 23:09:08 -0700111struct EvaluatorTest
Richard Stebbing32530782014-04-26 07:42:23 +0100112 : public ::testing::TestWithParam<EvaluatorTestOptions> {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700113 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();
117
Richard Stebbing32530782014-04-26 07:42:23 +0100118 if (VLOG_IS_ON(1)) {
119 string report;
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);
125 }
126 StringAppendF(&report, " and num_eliminate_blocks: %d",
127 GetParam().num_eliminate_blocks);
128 VLOG(1) << report;
129 }
Keir Mierle8ebb0732012-04-30 23:09:08 -0700130 Evaluator::Options options;
Richard Stebbing32530782014-04-26 07:42:23 +0100131 options.linear_solver_type = GetParam().linear_solver_type;
132 options.num_eliminate_blocks = GetParam().num_eliminate_blocks;
133 options.dynamic_sparsity = GetParam().dynamic_sparsity;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700134 string error;
135 return Evaluator::Create(options, program, &error);
136 }
Keir Mierlef44907f2012-07-06 13:52:32 -0700137
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700138 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()));
Keir Mierlef44907f2012-07-06 13:52:32 -0700147 int num_residuals = expected_num_rows;
148 int num_parameters = expected_num_cols;
149
150 double cost = -1;
151
152 Vector residuals(num_residuals);
153 residuals.setConstant(-2000);
154
155 Vector gradient(num_parameters);
156 gradient.setConstant(-3000);
157
158 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
159
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());
164
165 vector<double> state(evaluator->NumParameters());
166
167 ASSERT_TRUE(evaluator->Evaluate(
168 &state[0],
169 &cost,
170 expected_residuals != NULL ? &residuals[0] : NULL,
171 expected_gradient != NULL ? &gradient[0] : NULL,
172 expected_jacobian != NULL ? jacobian.get() : NULL));
173
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700174 Matrix actual_jacobian;
Keir Mierlef44907f2012-07-06 13:52:32 -0700175 if (expected_jacobian != NULL) {
Keir Mierlef44907f2012-07-06 13:52:32 -0700176 jacobian->ToDenseMatrix(&actual_jacobian);
Keir Mierlef44907f2012-07-06 13:52:32 -0700177 }
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700178
179 CompareEvaluations(expected_num_rows,
180 expected_num_cols,
181 expected_cost,
182 expected_residuals,
183 expected_gradient,
184 expected_jacobian,
185 cost,
186 &residuals[0],
187 &gradient[0],
188 actual_jacobian.data());
Keir Mierlef44907f2012-07-06 13:52:32 -0700189 }
190
191 // Try all combinations of parameters for the evaluator.
192 void CheckAllEvaluationCombinations(const ExpectedEvaluation &expected) {
193 for (int i = 0; i < 8; ++i) {
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700194 EvaluateAndCompare(&problem,
195 expected.num_rows,
196 expected.num_cols,
197 expected.cost,
198 (i & 1) ? expected.residuals : NULL,
199 (i & 2) ? expected.gradient : NULL,
200 (i & 4) ? expected.jacobian : NULL);
Keir Mierlef44907f2012-07-06 13:52:32 -0700201 }
202 }
203
204 // The values are ignored completely by the cost function.
205 double x[2];
206 double y[3];
207 double z[4];
208
209 ProblemImpl problem;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700210};
211
212void SetSparseMatrixConstant(SparseMatrix* sparse_matrix, double value) {
213 VectorRef(sparse_matrix->mutable_values(),
214 sparse_matrix->num_nonzeros()).setConstant(value);
215}
216
217TEST_P(EvaluatorTest, SingleResidualProblem) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700218 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
219 NULL,
220 x, y, z);
221
Keir Mierlef44907f2012-07-06 13:52:32 -0700222 ExpectedEvaluation expected = {
223 // Rows/columns
224 3, 9,
225 // Cost
226 7.0,
227 // Residuals
228 { 1.0, 2.0, 3.0 },
229 // Gradient
230 { 6.0, 12.0, // x
231 6.0, 12.0, 18.0, // y
232 6.0, 12.0, 18.0, 24.0, // z
233 },
234 // Jacobian
235 // x y 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
239 }
240 };
241 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700242}
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700243
Keir Mierle8ebb0732012-04-30 23:09:08 -0700244TEST_P(EvaluatorTest, SingleResidualProblemWithPermutedParameters) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700245 // 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);
249
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
Sameer Agarwal7ed9e2f2016-10-19 04:45:23 -0700255 // in the same order that they occurred as parameters to a cost function.
Keir Mierle8ebb0732012-04-30 23:09:08 -0700256 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 4, 3, 2>,
257 NULL,
258 z, y, x);
259
Keir Mierlef44907f2012-07-06 13:52:32 -0700260 ExpectedEvaluation expected = {
261 // Rows/columns
262 3, 9,
263 // Cost
264 7.0,
265 // Residuals
266 { 1.0, 2.0, 3.0 },
267 // Gradient
268 { 6.0, 12.0, // x
269 6.0, 12.0, 18.0, // y
270 6.0, 12.0, 18.0, 24.0, // z
271 },
272 // Jacobian
273 // x y 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
277 }
278 };
279 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700280}
Keir Mierlef44907f2012-07-06 13:52:32 -0700281
Keir Mierle8ebb0732012-04-30 23:09:08 -0700282TEST_P(EvaluatorTest, SingleResidualProblemWithNuisanceParameters) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700283 // These parameters are not used.
Keir Mierlef44907f2012-07-06 13:52:32 -0700284 double a[2];
285 double b[1];
286 double c[1];
287 double d[3];
Keir Mierle8ebb0732012-04-30 23:09:08 -0700288
289 // Add the parameters in a mixed order so the Jacobian is "checkered" with the
290 // values from the other parameters.
Keir Mierlef44907f2012-07-06 13:52:32 -0700291 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);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700298
299 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
300 NULL,
301 x, y, z);
302
Keir Mierlef44907f2012-07-06 13:52:32 -0700303 ExpectedEvaluation expected = {
304 // Rows/columns
305 3, 16,
306 // Cost
307 7.0,
308 // Residuals
309 { 1.0, 2.0, 3.0 },
310 // Gradient
311 { 0.0, 0.0, // a
312 6.0, 12.0, // x
313 0.0, // b
314 6.0, 12.0, 18.0, // y
315 0.0, // c
316 6.0, 12.0, 18.0, 24.0, // z
317 0.0, 0.0, 0.0, // d
318 },
319 // Jacobian
320 // a x b y c z d
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
324 }
325 };
326 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700327}
328
329TEST_P(EvaluatorTest, MultipleResidualProblem) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700330 // 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);
334
335 // f(x, y) in R^2
336 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
337 NULL,
338 x, y);
339
340 // g(x, z) in R^3
341 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
342 NULL,
343 x, z);
344
345 // h(y, z) in R^4
346 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
347 NULL,
348 y, z);
349
Keir Mierlef44907f2012-07-06 13:52:32 -0700350 ExpectedEvaluation expected = {
351 // Rows/columns
352 9, 9,
353 // Cost
354 // f g h
355 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
356 // Residuals
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700357 { 1.0, 2.0, // f
358 1.0, 2.0, 3.0, // g
359 1.0, 2.0, 3.0, 4.0 // h
360 },
Keir Mierlef44907f2012-07-06 13:52:32 -0700361 // Gradient
362 { 15.0, 30.0, // x
363 33.0, 66.0, 99.0, // y
364 42.0, 84.0, 126.0, 168.0 // z
365 },
366 // Jacobian
Keir Mierle8ebb0732012-04-30 23:09:08 -0700367 // x y z
Keir Mierlef44907f2012-07-06 13:52:32 -0700368 { /* f(x, y) */ 1, 2, 1, 2, 3, 0, 0, 0, 0,
Keir Mierle8ebb0732012-04-30 23:09:08 -0700369 1, 2, 1, 2, 3, 0, 0, 0, 0,
370
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,
374
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,
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700378 0, 0, 3, 6, 9, 3, 6, 9, 12
Keir Mierlef44907f2012-07-06 13:52:32 -0700379 }
380 };
381 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700382}
383
384TEST_P(EvaluatorTest, MultipleResidualsWithLocalParameterizations) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700385 // Add the parameters in explicit order to force the ordering in the program.
386 problem.AddParameterBlock(x, 2);
387
388 // Fix y's first dimension.
389 vector<int> y_fixed;
390 y_fixed.push_back(0);
391 problem.AddParameterBlock(y, 3, new SubsetParameterization(3, y_fixed));
392
393 // Fix z's second dimension.
394 vector<int> z_fixed;
395 z_fixed.push_back(1);
396 problem.AddParameterBlock(z, 4, new SubsetParameterization(4, z_fixed));
397
398 // f(x, y) in R^2
399 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
400 NULL,
401 x, y);
402
403 // g(x, z) in R^3
404 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
405 NULL,
406 x, z);
407
408 // h(y, z) in R^4
409 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
410 NULL,
411 y, z);
412
Keir Mierlef44907f2012-07-06 13:52:32 -0700413 ExpectedEvaluation expected = {
414 // Rows/columns
415 9, 7,
416 // Cost
417 // f g h
418 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
419 // Residuals
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700420 { 1.0, 2.0, // f
421 1.0, 2.0, 3.0, // g
422 1.0, 2.0, 3.0, 4.0 // h
423 },
Keir Mierlef44907f2012-07-06 13:52:32 -0700424 // Gradient
425 { 15.0, 30.0, // x
426 66.0, 99.0, // y
427 42.0, 126.0, 168.0 // z
428 },
429 // Jacobian
430 // x y z
431 { /* f(x, y) */ 1, 2, 2, 3, 0, 0, 0,
Keir Mierle8ebb0732012-04-30 23:09:08 -0700432 1, 2, 2, 3, 0, 0, 0,
433
434 /* g(x, z) */ 2, 4, 0, 0, 2, 6, 8,
435 2, 4, 0, 0, 2, 6, 8,
436 2, 4, 0, 0, 2, 6, 8,
437
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,
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700441 0, 0, 6, 9, 3, 9, 12
Keir Mierlef44907f2012-07-06 13:52:32 -0700442 }
443 };
444 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700445}
446
447TEST_P(EvaluatorTest, MultipleResidualProblemWithSomeConstantParameters) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700448 // The values are ignored completely by the cost function.
449 double x[2];
450 double y[3];
451 double z[4];
Keir Mierle8ebb0732012-04-30 23:09:08 -0700452
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);
457
458 // f(x, y) in R^2
459 problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 2, 2, 3>,
460 NULL,
461 x, y);
462
463 // g(x, z) in R^3
464 problem.AddResidualBlock(new ParameterIgnoringCostFunction<2, 3, 2, 4>,
465 NULL,
466 x, z);
467
468 // h(y, z) in R^4
469 problem.AddResidualBlock(new ParameterIgnoringCostFunction<3, 4, 3, 4>,
470 NULL,
471 y, z);
472
473 // For this test, "z" is constant.
474 problem.SetParameterBlockConstant(z);
475
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;
Keir Mierlef44907f2012-07-06 13:52:32 -0700480 vector<ParameterBlock*>* parameter_blocks =
481 problem.mutable_program()->mutable_parameter_blocks();
Keir Mierle8ebb0732012-04-30 23:09:08 -0700482
Keir Mierlef44907f2012-07-06 13:52:32 -0700483 // "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();
Keir Mierle8ebb0732012-04-30 23:09:08 -0700488
Keir Mierlef44907f2012-07-06 13:52:32 -0700489 ExpectedEvaluation expected = {
490 // Rows/columns
491 9, 5,
492 // Cost
493 // f g h
494 ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
495 // Residuals
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700496 { 1.0, 2.0, // f
497 1.0, 2.0, 3.0, // g
498 1.0, 2.0, 3.0, 4.0 // h
499 },
Keir Mierlef44907f2012-07-06 13:52:32 -0700500 // Gradient
501 { 15.0, 30.0, // x
502 33.0, 66.0, 99.0, // y
503 },
504 // Jacobian
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700505 // x y
Keir Mierlef44907f2012-07-06 13:52:32 -0700506 { /* f(x, y) */ 1, 2, 1, 2, 3,
Keir Mierle8ebb0732012-04-30 23:09:08 -0700507 1, 2, 1, 2, 3,
508
509 /* g(x, z) */ 2, 4, 0, 0, 0,
510 2, 4, 0, 0, 0,
511 2, 4, 0, 0, 0,
512
513 /* h(y, z) */ 0, 0, 3, 6, 9,
514 0, 0, 3, 6, 9,
515 0, 0, 3, 6, 9,
Keir Mierlef44907f2012-07-06 13:52:32 -0700516 0, 0, 3, 6, 9
517 }
518 };
519 CheckAllEvaluationCombinations(expected);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700520
Keir Mierlef44907f2012-07-06 13:52:32 -0700521 // Restore parameter block z, so it will get freed in a consistent way.
522 parameter_blocks->push_back(parameter_block_z);
Keir Mierle8ebb0732012-04-30 23:09:08 -0700523}
524
525TEST_P(EvaluatorTest, EvaluatorAbortsForResidualsThatFailToEvaluate) {
Keir Mierle8ebb0732012-04-30 23:09:08 -0700526 // Switch the return value to failure.
527 problem.AddResidualBlock(
528 new ParameterIgnoringCostFunction<20, 3, 2, 3, 4, false>, NULL, x, y, z);
529
Keir Mierlef44907f2012-07-06 13:52:32 -0700530 // The values are ignored.
531 double state[9];
532
Keir Mierle8ebb0732012-04-30 23:09:08 -0700533 scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
534 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
535 double cost;
Keir Mierlef44907f2012-07-06 13:52:32 -0700536 EXPECT_FALSE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700537}
538
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.
542//
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.
545INSTANTIATE_TEST_CASE_P(
546 LinearSolvers,
547 EvaluatorTest,
Richard Stebbing32530782014-04-26 07:42:23 +0100548 ::testing::Values(
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)));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700567
568// Simple cost function used to check if the evaluator is sensitive to
569// state changes.
570class ParameterSensitiveCostFunction : public SizedCostFunction<2, 2> {
571 public:
572 virtual bool Evaluate(double const* const* parameters,
573 double* residuals,
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;
579
580 if (jacobians != NULL) {
581 double* jacobian = jacobians[0];
582 if (jacobian != NULL) {
583 jacobian[0] = 2.0 * x1;
584 jacobian[1] = 0.0;
585 jacobian[2] = 0.0;
586 jacobian[3] = 2.0 * x2;
587 }
588 }
589 return true;
590 }
591};
592
593TEST(Evaluator, EvaluatorRespectsParameterChanges) {
594 ProblemImpl problem;
595
596 double x[2];
597 x[0] = 1.0;
598 x[1] = 1.0;
599
600 problem.AddResidualBlock(new ParameterSensitiveCostFunction(), NULL, x);
601 Program* program = problem.mutable_program();
602 program->SetParameterOffsetsAndIndex();
603
604 Evaluator::Options options;
605 options.linear_solver_type = DENSE_QR;
606 options.num_eliminate_blocks = 0;
607 string error;
608 scoped_ptr<Evaluator> evaluator(Evaluator::Create(options, program, &error));
609 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
610
611 ASSERT_EQ(2, jacobian->num_rows());
612 ASSERT_EQ(2, jacobian->num_cols());
613
614 double state[2];
615 state[0] = 2.0;
616 state[1] = 3.0;
617
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.
622
623 // Cost only; no residuals and no jacobian.
624 {
625 double cost = -1;
Keir Mierlef44907f2012-07-06 13:52:32 -0700626 ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700627 EXPECT_EQ(48.5, cost);
628 }
629
630 // Cost and residuals, no jacobian.
631 {
632 double cost = -1;
633 double residuals[2] = { -2, -2 };
Keir Mierlef44907f2012-07-06 13:52:32 -0700634 ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL, NULL));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700635 EXPECT_EQ(48.5, cost);
636 EXPECT_EQ(4, residuals[0]);
637 EXPECT_EQ(9, residuals[1]);
638 }
639
640 // Cost, residuals, and jacobian.
641 {
642 double cost = -1;
643 double residuals[2] = { -2, -2};
644 SetSparseMatrixConstant(jacobian.get(), -1);
Sameer Agarwal4997cbc2012-07-02 12:44:34 -0700645 ASSERT_TRUE(evaluator->Evaluate(state,
646 &cost,
647 residuals,
648 NULL,
649 jacobian.get()));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700650 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);
655
656 Matrix expected_jacobian(2, 2);
657 expected_jacobian
658 << 2 * state[0], 0,
659 0, 2 * state[1];
660
661 EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
662 << "Actual:\n" << actual_jacobian
663 << "\nExpected:\n" << expected_jacobian;
664 }
665}
666
Keir Mierle8ebb0732012-04-30 23:09:08 -0700667} // namespace internal
668} // namespace ceres