blob: 6acae0b3936dafba6631b2c788566b59d8cfe36d [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
31#include "ceres/solver.h"
32
33#include <limits>
34#include <memory>
35#include <cmath>
36#include <vector>
37#include "gtest/gtest.h"
38#include "ceres/evaluation_callback.h"
39#include "ceres/autodiff_cost_function.h"
40#include "ceres/sized_cost_function.h"
41#include "ceres/problem.h"
42#include "ceres/problem_impl.h"
43
44namespace ceres {
45namespace internal {
46
47using std::string;
48
49TEST(SolverOptions, DefaultTrustRegionOptionsAreValid) {
50 Solver::Options options;
51 options.minimizer_type = TRUST_REGION;
52 string error;
53 EXPECT_TRUE(options.IsValid(&error)) << error;
54}
55
56TEST(SolverOptions, DefaultLineSearchOptionsAreValid) {
57 Solver::Options options;
58 options.minimizer_type = LINE_SEARCH;
59 string error;
60 EXPECT_TRUE(options.IsValid(&error)) << error;
61}
62
63struct QuadraticCostFunctor {
64 template <typename T> bool operator()(const T* const x,
65 T* residual) const {
66 residual[0] = T(5.0) - *x;
67 return true;
68 }
69
70 static CostFunction* Create() {
71 return new AutoDiffCostFunction<QuadraticCostFunctor, 1, 1>(
72 new QuadraticCostFunctor);
73 }
74};
75
76struct RememberingCallback : public IterationCallback {
77 explicit RememberingCallback(double *x) : calls(0), x(x) {}
78 virtual ~RememberingCallback() {}
79 virtual CallbackReturnType operator()(const IterationSummary& summary) {
80 x_values.push_back(*x);
81 return SOLVER_CONTINUE;
82 }
83 int calls;
84 double *x;
85 std::vector<double> x_values;
86};
87
88struct NoOpEvaluationCallback : EvaluationCallback {
89 virtual ~NoOpEvaluationCallback() {}
90 virtual void PrepareForEvaluation(bool evaluate_jacobians,
91 bool new_evaluation_point) {
92 (void) evaluate_jacobians;
93 (void) new_evaluation_point;
94 }
95};
96
97TEST(Solver, UpdateStateEveryIterationOption) {
98 double x = 50.0;
99 const double original_x = x;
100
101 std::unique_ptr<CostFunction> cost_function(QuadraticCostFunctor::Create());
102 Problem::Options problem_options;
103 problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP;
104 Problem problem(problem_options);
105 problem.AddResidualBlock(cost_function.get(), NULL, &x);
106
107 Solver::Options options;
108 options.linear_solver_type = DENSE_QR;
109
110 RememberingCallback callback(&x);
111 options.callbacks.push_back(&callback);
112
113 Solver::Summary summary;
114
115 int num_iterations;
116
117 // There are four cases that need to be checked:
118 //
119 // (update_state_every_iteration = true|false) X
120 // (evaluation_callback = NULL|provided)
121 //
122 // These need to get checked since there is some interaction between them.
123
124 // First: update_state_every_iteration=false, evaluation_callback=NULL.
125 Solve(options, &problem, &summary);
126 num_iterations = summary.num_successful_steps +
127 summary.num_unsuccessful_steps;
128 EXPECT_GT(num_iterations, 1);
129 for (int i = 0; i < callback.x_values.size(); ++i) {
130 EXPECT_EQ(50.0, callback.x_values[i]);
131 }
132
133 // Second: update_state_every_iteration=true, evaluation_callback=NULL.
134 x = 50.0;
135 options.update_state_every_iteration = true;
136 callback.x_values.clear();
137 Solve(options, &problem, &summary);
138 num_iterations = summary.num_successful_steps +
139 summary.num_unsuccessful_steps;
140 EXPECT_GT(num_iterations, 1);
141 EXPECT_EQ(original_x, callback.x_values[0]);
142 EXPECT_NE(original_x, callback.x_values[1]);
143
144 NoOpEvaluationCallback evaluation_callback;
145
146 // Third: update_state_every_iteration=true, evaluation_callback=!NULL.
147 x = 50.0;
148 options.update_state_every_iteration = true;
149 options.evaluation_callback = &evaluation_callback;
150 callback.x_values.clear();
151 Solve(options, &problem, &summary);
152 num_iterations = summary.num_successful_steps +
153 summary.num_unsuccessful_steps;
154 EXPECT_GT(num_iterations, 1);
155 EXPECT_EQ(original_x, callback.x_values[0]);
156 EXPECT_NE(original_x, callback.x_values[1]);
157
158 // Fourth: update_state_every_iteration=false, evaluation_callback=!NULL.
159 x = 50.0;
160 options.update_state_every_iteration = false;
161 options.evaluation_callback = &evaluation_callback;
162 callback.x_values.clear();
163 Solve(options, &problem, &summary);
164 num_iterations = summary.num_successful_steps +
165 summary.num_unsuccessful_steps;
166 EXPECT_GT(num_iterations, 1);
167 EXPECT_EQ(original_x, callback.x_values[0]);
168 EXPECT_NE(original_x, callback.x_values[1]);
169}
170
171// The parameters must be in separate blocks so that they can be individually
172// set constant or not.
173struct Quadratic4DCostFunction {
174 template <typename T> bool operator()(const T* const x,
175 const T* const y,
176 const T* const z,
177 const T* const w,
178 T* residual) const {
179 // A 4-dimension axis-aligned quadratic.
180 residual[0] = T(10.0) - *x +
181 T(20.0) - *y +
182 T(30.0) - *z +
183 T(40.0) - *w;
184 return true;
185 }
186
187 static CostFunction* Create() {
188 return new AutoDiffCostFunction<Quadratic4DCostFunction, 1, 1, 1, 1, 1>(
189 new Quadratic4DCostFunction);
190 }
191};
192
193// A cost function that simply returns its argument.
194class UnaryIdentityCostFunction : public SizedCostFunction<1, 1> {
195 public:
196 virtual bool Evaluate(double const* const* parameters,
197 double* residuals,
198 double** jacobians) const {
199 residuals[0] = parameters[0][0];
200 if (jacobians != NULL && jacobians[0] != NULL) {
201 jacobians[0][0] = 1.0;
202 }
203 return true;
204 }
205};
206
207TEST(Solver, TrustRegionProblemHasNoParameterBlocks) {
208 Problem problem;
209 Solver::Options options;
210 options.minimizer_type = TRUST_REGION;
211 Solver::Summary summary;
212 Solve(options, &problem, &summary);
213 EXPECT_EQ(summary.termination_type, CONVERGENCE);
214 EXPECT_EQ(summary.message,
215 "Function tolerance reached. "
216 "No non-constant parameter blocks found.");
217}
218
219TEST(Solver, LineSearchProblemHasNoParameterBlocks) {
220 Problem problem;
221 Solver::Options options;
222 options.minimizer_type = LINE_SEARCH;
223 Solver::Summary summary;
224 Solve(options, &problem, &summary);
225 EXPECT_EQ(summary.termination_type, CONVERGENCE);
226 EXPECT_EQ(summary.message,
227 "Function tolerance reached. "
228 "No non-constant parameter blocks found.");
229}
230
231TEST(Solver, TrustRegionProblemHasZeroResiduals) {
232 Problem problem;
233 double x = 1;
234 problem.AddParameterBlock(&x, 1);
235 Solver::Options options;
236 options.minimizer_type = TRUST_REGION;
237 Solver::Summary summary;
238 Solve(options, &problem, &summary);
239 EXPECT_EQ(summary.termination_type, CONVERGENCE);
240 EXPECT_EQ(summary.message,
241 "Function tolerance reached. "
242 "No non-constant parameter blocks found.");
243}
244
245TEST(Solver, LineSearchProblemHasZeroResiduals) {
246 Problem problem;
247 double x = 1;
248 problem.AddParameterBlock(&x, 1);
249 Solver::Options options;
250 options.minimizer_type = LINE_SEARCH;
251 Solver::Summary summary;
252 Solve(options, &problem, &summary);
253 EXPECT_EQ(summary.termination_type, CONVERGENCE);
254 EXPECT_EQ(summary.message,
255 "Function tolerance reached. "
256 "No non-constant parameter blocks found.");
257}
258
259TEST(Solver, TrustRegionProblemIsConstant) {
260 Problem problem;
261 double x = 1;
262 problem.AddResidualBlock(new UnaryIdentityCostFunction, NULL, &x);
263 problem.SetParameterBlockConstant(&x);
264 Solver::Options options;
265 options.minimizer_type = TRUST_REGION;
266 Solver::Summary summary;
267 Solve(options, &problem, &summary);
268 EXPECT_EQ(summary.termination_type, CONVERGENCE);
269 EXPECT_EQ(summary.initial_cost, 1.0 / 2.0);
270 EXPECT_EQ(summary.final_cost, 1.0 / 2.0);
271}
272
273TEST(Solver, LineSearchProblemIsConstant) {
274 Problem problem;
275 double x = 1;
276 problem.AddResidualBlock(new UnaryIdentityCostFunction, NULL, &x);
277 problem.SetParameterBlockConstant(&x);
278 Solver::Options options;
279 options.minimizer_type = LINE_SEARCH;
280 Solver::Summary summary;
281 Solve(options, &problem, &summary);
282 EXPECT_EQ(summary.termination_type, CONVERGENCE);
283 EXPECT_EQ(summary.initial_cost, 1.0 / 2.0);
284 EXPECT_EQ(summary.final_cost, 1.0 / 2.0);
285}
286
287#if defined(CERES_NO_SUITESPARSE)
288TEST(Solver, SparseNormalCholeskyNoSuiteSparse) {
289 Solver::Options options;
290 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
291 options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
292 string message;
293 EXPECT_FALSE(options.IsValid(&message));
294}
295
296TEST(Solver, SparseSchurNoSuiteSparse) {
297 Solver::Options options;
298 options.sparse_linear_algebra_library_type = SUITE_SPARSE;
299 options.linear_solver_type = SPARSE_SCHUR;
300 string message;
301 EXPECT_FALSE(options.IsValid(&message));
302}
303#endif
304
305#if defined(CERES_NO_CXSPARSE)
306TEST(Solver, SparseNormalCholeskyNoCXSparse) {
307 Solver::Options options;
308 options.sparse_linear_algebra_library_type = CX_SPARSE;
309 options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
310 string message;
311 EXPECT_FALSE(options.IsValid(&message));
312}
313
314TEST(Solver, SparseSchurNoCXSparse) {
315 Solver::Options options;
316 options.sparse_linear_algebra_library_type = CX_SPARSE;
317 options.linear_solver_type = SPARSE_SCHUR;
318 string message;
319 EXPECT_FALSE(options.IsValid(&message));
320}
321#endif
322
323#if defined(CERES_NO_ACCELERATE_SPARSE)
324TEST(Solver, SparseNormalCholeskyNoAccelerateSparse) {
325 Solver::Options options;
326 options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
327 options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
328 string message;
329 EXPECT_FALSE(options.IsValid(&message));
330}
331
332TEST(Solver, SparseSchurNoAccelerateSparse) {
333 Solver::Options options;
334 options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
335 options.linear_solver_type = SPARSE_SCHUR;
336 string message;
337 EXPECT_FALSE(options.IsValid(&message));
338}
339#endif
340
341#if !defined(CERES_USE_EIGEN_SPARSE)
342TEST(Solver, SparseNormalCholeskyNoEigenSparse) {
343 Solver::Options options;
344 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
345 options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
346 string message;
347 EXPECT_FALSE(options.IsValid(&message));
348}
349
350TEST(Solver, SparseSchurNoEigenSparse) {
351 Solver::Options options;
352 options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
353 options.linear_solver_type = SPARSE_SCHUR;
354 string message;
355 EXPECT_FALSE(options.IsValid(&message));
356}
357#endif
358
359TEST(Solver, SparseNormalCholeskyNoSparseLibrary) {
360 Solver::Options options;
361 options.sparse_linear_algebra_library_type = NO_SPARSE;
362 options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
363 string message;
364 EXPECT_FALSE(options.IsValid(&message));
365}
366
367TEST(Solver, SparseSchurNoSparseLibrary) {
368 Solver::Options options;
369 options.sparse_linear_algebra_library_type = NO_SPARSE;
370 options.linear_solver_type = SPARSE_SCHUR;
371 string message;
372 EXPECT_FALSE(options.IsValid(&message));
373}
374
375TEST(Solver, IterativeSchurWithClusterJacobiPerconditionerNoSparseLibrary) {
376 Solver::Options options;
377 options.sparse_linear_algebra_library_type = NO_SPARSE;
378 options.linear_solver_type = ITERATIVE_SCHUR;
379 // Requires SuiteSparse.
380 options.preconditioner_type = CLUSTER_JACOBI;
381 string message;
382 EXPECT_FALSE(options.IsValid(&message));
383}
384
385TEST(Solver, IterativeSchurWithClusterTridiagonalPerconditionerNoSparseLibrary) {
386 Solver::Options options;
387 options.sparse_linear_algebra_library_type = NO_SPARSE;
388 options.linear_solver_type = ITERATIVE_SCHUR;
389 // Requires SuiteSparse.
390 options.preconditioner_type = CLUSTER_TRIDIAGONAL;
391 string message;
392 EXPECT_FALSE(options.IsValid(&message));
393}
394
395TEST(Solver, IterativeLinearSolverForDogleg) {
396 Solver::Options options;
397 options.trust_region_strategy_type = DOGLEG;
398 string message;
399 options.linear_solver_type = ITERATIVE_SCHUR;
400 EXPECT_FALSE(options.IsValid(&message));
401
402 options.linear_solver_type = CGNR;
403 EXPECT_FALSE(options.IsValid(&message));
404}
405
406TEST(Solver, LinearSolverTypeNormalOperation) {
407 Solver::Options options;
408 options.linear_solver_type = DENSE_QR;
409
410 string message;
411 EXPECT_TRUE(options.IsValid(&message));
412
413 options.linear_solver_type = DENSE_NORMAL_CHOLESKY;
414 EXPECT_TRUE(options.IsValid(&message));
415
416 options.linear_solver_type = DENSE_SCHUR;
417 EXPECT_TRUE(options.IsValid(&message));
418
419 options.linear_solver_type = SPARSE_SCHUR;
420#if defined(CERES_NO_SUITESPARSE) && \
421 defined(CERES_NO_CXSPARSE) && \
422 !defined(CERES_USE_EIGEN_SPARSE)
423 EXPECT_FALSE(options.IsValid(&message));
424#else
425 EXPECT_TRUE(options.IsValid(&message));
426#endif
427
428 options.linear_solver_type = ITERATIVE_SCHUR;
429 EXPECT_TRUE(options.IsValid(&message));
430}
431
432TEST(Solver, CantMixEvaluationCallbackWithInnerIterations) {
433 Solver::Options options;
434 NoOpEvaluationCallback evaluation_callback;
435 string message;
436
437 // Can't combine them.
438 options.use_inner_iterations = true;
439 options.evaluation_callback = &evaluation_callback;
440 EXPECT_FALSE(options.IsValid(&message));
441
442 // Either or none is OK.
443 options.use_inner_iterations = false;
444 options.evaluation_callback = &evaluation_callback;
445 EXPECT_TRUE(options.IsValid(&message));
446
447 options.use_inner_iterations = true;
448 options.evaluation_callback = NULL;
449 EXPECT_TRUE(options.IsValid(&message));
450
451 options.use_inner_iterations = false;
452 options.evaluation_callback = NULL;
453 EXPECT_TRUE(options.IsValid(&message));
454}
455
456template <int kNumResiduals, int... Ns>
457class DummyCostFunction : public SizedCostFunction<kNumResiduals, Ns...> {
458 public:
459 bool Evaluate(double const* const* parameters,
460 double* residuals,
461 double** jacobians) const {
462 for (int i = 0; i < kNumResiduals; ++i) {
463 residuals[i] = kNumResiduals * kNumResiduals + i;
464 }
465
466 return true;
467 }
468};
469
470TEST(Solver, FixedCostForConstantProblem) {
471 double x = 1.0;
472 Problem problem;
473 problem.AddResidualBlock(new DummyCostFunction<2, 1>(), NULL, &x);
474 problem.SetParameterBlockConstant(&x);
475 const double expected_cost = 41.0 / 2.0; // 1/2 * ((4 + 0)^2 + (4 + 1)^2)
476 Solver::Options options;
477 Solver::Summary summary;
478 Solve(options, &problem, &summary);
479 EXPECT_TRUE(summary.IsSolutionUsable());
480 EXPECT_EQ(summary.fixed_cost, expected_cost);
481 EXPECT_EQ(summary.initial_cost, expected_cost);
482 EXPECT_EQ(summary.final_cost, expected_cost);
483 EXPECT_EQ(summary.iterations.size(), 0);
484}
485
486} // namespace internal
487} // namespace ceres