solver_impl_test.cc revision 1d2624a10e2c559f8ba9ef89eaa30832c0a83a96
1// 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: sameeragarwal@google.com (Sameer Agarwal)
30
31#include "gtest/gtest.h"
32#include "ceres/autodiff_cost_function.h"
33#include "ceres/linear_solver.h"
34#include "ceres/ordered_groups.h"
35#include "ceres/parameter_block.h"
36#include "ceres/problem_impl.h"
37#include "ceres/program.h"
38#include "ceres/residual_block.h"
39#include "ceres/solver_impl.h"
40#include "ceres/sized_cost_function.h"
41
42namespace ceres {
43namespace internal {
44
45// A cost function that sipmply returns its argument.
46class UnaryIdentityCostFunction : public SizedCostFunction<1, 1> {
47 public:
48  virtual bool Evaluate(double const* const* parameters,
49                        double* residuals,
50                        double** jacobians) const {
51    residuals[0] = parameters[0][0];
52    if (jacobians != NULL && jacobians[0] != NULL) {
53      jacobians[0][0] = 1.0;
54    }
55    return true;
56  }
57};
58
59// Templated base class for the CostFunction signatures.
60template <int kNumResiduals, int N0, int N1, int N2>
61class MockCostFunctionBase : public
62SizedCostFunction<kNumResiduals, N0, N1, N2> {
63 public:
64  virtual bool Evaluate(double const* const* parameters,
65                        double* residuals,
66                        double** jacobians) const {
67    // Do nothing. This is never called.
68    return true;
69  }
70};
71
72class UnaryCostFunction : public MockCostFunctionBase<2, 1, 0, 0> {};
73class BinaryCostFunction : public MockCostFunctionBase<2, 1, 1, 0> {};
74class TernaryCostFunction : public MockCostFunctionBase<2, 1, 1, 1> {};
75
76TEST(SolverImpl, RemoveFixedBlocksNothingConstant) {
77  ProblemImpl problem;
78  double x;
79  double y;
80  double z;
81
82  problem.AddParameterBlock(&x, 1);
83  problem.AddParameterBlock(&y, 1);
84  problem.AddParameterBlock(&z, 1);
85  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
86  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
87  problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
88
89  string error;
90  {
91    ParameterBlockOrdering ordering;
92    ordering.AddElementToGroup(&x, 0);
93    ordering.AddElementToGroup(&y, 0);
94    ordering.AddElementToGroup(&z, 0);
95
96    Program program(*problem.mutable_program());
97    EXPECT_TRUE(SolverImpl::RemoveFixedBlocksFromProgram(&program,
98                                                         &ordering,
99                                                         NULL,
100                                                         &error));
101    EXPECT_EQ(program.NumParameterBlocks(), 3);
102    EXPECT_EQ(program.NumResidualBlocks(), 3);
103    EXPECT_EQ(ordering.NumElements(), 3);
104  }
105}
106
107TEST(SolverImpl, RemoveFixedBlocksAllParameterBlocksConstant) {
108  ProblemImpl problem;
109  double x;
110
111  problem.AddParameterBlock(&x, 1);
112  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
113  problem.SetParameterBlockConstant(&x);
114
115  ParameterBlockOrdering ordering;
116  ordering.AddElementToGroup(&x, 0);
117
118  Program program(problem.program());
119  string error;
120  EXPECT_TRUE(SolverImpl::RemoveFixedBlocksFromProgram(&program,
121                                                       &ordering,
122                                                       NULL,
123                                                       &error));
124  EXPECT_EQ(program.NumParameterBlocks(), 0);
125  EXPECT_EQ(program.NumResidualBlocks(), 0);
126  EXPECT_EQ(ordering.NumElements(), 0);
127}
128
129TEST(SolverImpl, RemoveFixedBlocksNoResidualBlocks) {
130  ProblemImpl problem;
131  double x;
132  double y;
133  double z;
134
135  problem.AddParameterBlock(&x, 1);
136  problem.AddParameterBlock(&y, 1);
137  problem.AddParameterBlock(&z, 1);
138
139  ParameterBlockOrdering ordering;
140  ordering.AddElementToGroup(&x, 0);
141  ordering.AddElementToGroup(&y, 0);
142  ordering.AddElementToGroup(&z, 0);
143
144
145  Program program(problem.program());
146  string error;
147  EXPECT_TRUE(SolverImpl::RemoveFixedBlocksFromProgram(&program,
148                                                       &ordering,
149                                                       NULL,
150                                                       &error));
151  EXPECT_EQ(program.NumParameterBlocks(), 0);
152  EXPECT_EQ(program.NumResidualBlocks(), 0);
153  EXPECT_EQ(ordering.NumElements(), 0);
154}
155
156TEST(SolverImpl, RemoveFixedBlocksOneParameterBlockConstant) {
157  ProblemImpl problem;
158  double x;
159  double y;
160  double z;
161
162  problem.AddParameterBlock(&x, 1);
163  problem.AddParameterBlock(&y, 1);
164  problem.AddParameterBlock(&z, 1);
165
166  ParameterBlockOrdering ordering;
167  ordering.AddElementToGroup(&x, 0);
168  ordering.AddElementToGroup(&y, 0);
169  ordering.AddElementToGroup(&z, 0);
170
171  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
172  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
173  problem.SetParameterBlockConstant(&x);
174
175
176  Program program(problem.program());
177  string error;
178  EXPECT_TRUE(SolverImpl::RemoveFixedBlocksFromProgram(&program,
179                                                       &ordering,
180                                                       NULL,
181                                                       &error));
182  EXPECT_EQ(program.NumParameterBlocks(), 1);
183  EXPECT_EQ(program.NumResidualBlocks(), 1);
184  EXPECT_EQ(ordering.NumElements(), 1);
185}
186
187TEST(SolverImpl, RemoveFixedBlocksNumEliminateBlocks) {
188  ProblemImpl problem;
189  double x;
190  double y;
191  double z;
192
193  problem.AddParameterBlock(&x, 1);
194  problem.AddParameterBlock(&y, 1);
195  problem.AddParameterBlock(&z, 1);
196  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
197  problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
198  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
199  problem.SetParameterBlockConstant(&x);
200
201  ParameterBlockOrdering ordering;
202  ordering.AddElementToGroup(&x, 0);
203  ordering.AddElementToGroup(&y, 0);
204  ordering.AddElementToGroup(&z, 1);
205
206  Program program(problem.program());
207  string error;
208  EXPECT_TRUE(SolverImpl::RemoveFixedBlocksFromProgram(&program,
209                                                       &ordering,
210                                                       NULL,
211                                                       &error));
212  EXPECT_EQ(program.NumParameterBlocks(), 2);
213  EXPECT_EQ(program.NumResidualBlocks(), 2);
214  EXPECT_EQ(ordering.NumElements(), 2);
215  EXPECT_EQ(ordering.GroupId(&y), 0);
216  EXPECT_EQ(ordering.GroupId(&z), 1);
217}
218
219TEST(SolverImpl, RemoveFixedBlocksFixedCost) {
220  ProblemImpl problem;
221  double x = 1.23;
222  double y = 4.56;
223  double z = 7.89;
224
225  problem.AddParameterBlock(&x, 1);
226  problem.AddParameterBlock(&y, 1);
227  problem.AddParameterBlock(&z, 1);
228  problem.AddResidualBlock(new UnaryIdentityCostFunction(), NULL, &x);
229  problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
230  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
231  problem.SetParameterBlockConstant(&x);
232
233  ParameterBlockOrdering ordering;
234  ordering.AddElementToGroup(&x, 0);
235  ordering.AddElementToGroup(&y, 0);
236  ordering.AddElementToGroup(&z, 1);
237
238  double fixed_cost = 0.0;
239  Program program(problem.program());
240
241  double expected_fixed_cost;
242  ResidualBlock *expected_removed_block = program.residual_blocks()[0];
243  scoped_array<double> scratch(
244      new double[expected_removed_block->NumScratchDoublesForEvaluate()]);
245  expected_removed_block->Evaluate(true,
246                                   &expected_fixed_cost,
247                                   NULL,
248                                   NULL,
249                                   scratch.get());
250
251  string error;
252  EXPECT_TRUE(SolverImpl::RemoveFixedBlocksFromProgram(&program,
253                                                       &ordering,
254                                                       &fixed_cost,
255                                                       &error));
256  EXPECT_EQ(program.NumParameterBlocks(), 2);
257  EXPECT_EQ(program.NumResidualBlocks(), 2);
258  EXPECT_EQ(ordering.NumElements(), 2);
259  EXPECT_EQ(ordering.GroupId(&y), 0);
260  EXPECT_EQ(ordering.GroupId(&z), 1);
261  EXPECT_DOUBLE_EQ(fixed_cost, expected_fixed_cost);
262}
263
264TEST(SolverImpl, ReorderResidualBlockNormalFunction) {
265  ProblemImpl problem;
266  double x;
267  double y;
268  double z;
269
270  problem.AddParameterBlock(&x, 1);
271  problem.AddParameterBlock(&y, 1);
272  problem.AddParameterBlock(&z, 1);
273
274  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
275  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &x);
276  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &y);
277  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &z);
278  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
279  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &y);
280
281  ParameterBlockOrdering* ordering = new ParameterBlockOrdering;
282  ordering->AddElementToGroup(&x, 0);
283  ordering->AddElementToGroup(&y, 0);
284  ordering->AddElementToGroup(&z, 1);
285
286  Solver::Options options;
287  options.linear_solver_type = DENSE_SCHUR;
288  options.linear_solver_ordering = ordering;
289
290  const vector<ResidualBlock*>& residual_blocks =
291      problem.program().residual_blocks();
292
293  vector<ResidualBlock*> expected_residual_blocks;
294
295  // This is a bit fragile, but it serves the purpose. We know the
296  // bucketing algorithm that the reordering function uses, so we
297  // expect the order for residual blocks for each e_block to be
298  // filled in reverse.
299  expected_residual_blocks.push_back(residual_blocks[4]);
300  expected_residual_blocks.push_back(residual_blocks[1]);
301  expected_residual_blocks.push_back(residual_blocks[0]);
302  expected_residual_blocks.push_back(residual_blocks[5]);
303  expected_residual_blocks.push_back(residual_blocks[2]);
304  expected_residual_blocks.push_back(residual_blocks[3]);
305
306  Program* program = problem.mutable_program();
307  program->SetParameterOffsetsAndIndex();
308
309  string error;
310  EXPECT_TRUE(SolverImpl::LexicographicallyOrderResidualBlocks(
311                  2,
312                  problem.mutable_program(),
313                  &error));
314  EXPECT_EQ(residual_blocks.size(), expected_residual_blocks.size());
315  for (int i = 0; i < expected_residual_blocks.size(); ++i) {
316    EXPECT_EQ(residual_blocks[i], expected_residual_blocks[i]);
317  }
318}
319
320TEST(SolverImpl, ReorderResidualBlockNormalFunctionWithFixedBlocks) {
321  ProblemImpl problem;
322  double x;
323  double y;
324  double z;
325
326  problem.AddParameterBlock(&x, 1);
327  problem.AddParameterBlock(&y, 1);
328  problem.AddParameterBlock(&z, 1);
329
330  // Set one parameter block constant.
331  problem.SetParameterBlockConstant(&z);
332
333  // Mark residuals for x's row block with "x" for readability.
334  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);       // 0 x
335  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &x);  // 1 x
336  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &y);  // 2
337  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &y);  // 3
338  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &z);  // 4 x
339  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &y);  // 5
340  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &z);  // 6 x
341  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &y);       // 7
342
343  ParameterBlockOrdering* ordering = new ParameterBlockOrdering;
344  ordering->AddElementToGroup(&x, 0);
345  ordering->AddElementToGroup(&z, 0);
346  ordering->AddElementToGroup(&y, 1);
347
348  Solver::Options options;
349  options.linear_solver_type = DENSE_SCHUR;
350  options.linear_solver_ordering = ordering;
351
352  // Create the reduced program. This should remove the fixed block "z",
353  // marking the index to -1 at the same time. x and y also get indices.
354  string error;
355  scoped_ptr<Program> reduced_program(
356      SolverImpl::CreateReducedProgram(&options, &problem, NULL, &error));
357
358  const vector<ResidualBlock*>& residual_blocks =
359      problem.program().residual_blocks();
360
361  // This is a bit fragile, but it serves the purpose. We know the
362  // bucketing algorithm that the reordering function uses, so we
363  // expect the order for residual blocks for each e_block to be
364  // filled in reverse.
365
366  vector<ResidualBlock*> expected_residual_blocks;
367
368  // Row block for residuals involving "x". These are marked "x" in the block
369  // of code calling AddResidual() above.
370  expected_residual_blocks.push_back(residual_blocks[6]);
371  expected_residual_blocks.push_back(residual_blocks[4]);
372  expected_residual_blocks.push_back(residual_blocks[1]);
373  expected_residual_blocks.push_back(residual_blocks[0]);
374
375  // Row block for residuals involving "y".
376  expected_residual_blocks.push_back(residual_blocks[7]);
377  expected_residual_blocks.push_back(residual_blocks[5]);
378  expected_residual_blocks.push_back(residual_blocks[3]);
379  expected_residual_blocks.push_back(residual_blocks[2]);
380
381  EXPECT_EQ(reduced_program->residual_blocks().size(),
382            expected_residual_blocks.size());
383  for (int i = 0; i < expected_residual_blocks.size(); ++i) {
384    EXPECT_EQ(reduced_program->residual_blocks()[i],
385              expected_residual_blocks[i]);
386  }
387}
388
389TEST(SolverImpl, AutomaticSchurReorderingRespectsConstantBlocks) {
390  ProblemImpl problem;
391  double x;
392  double y;
393  double z;
394
395  problem.AddParameterBlock(&x, 1);
396  problem.AddParameterBlock(&y, 1);
397  problem.AddParameterBlock(&z, 1);
398
399  // Set one parameter block constant.
400  problem.SetParameterBlockConstant(&z);
401
402  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
403  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &x);
404  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &y);
405  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &y);
406  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &z);
407  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &z, &y);
408  problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &z);
409  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &y);
410  problem.AddResidualBlock(new UnaryCostFunction(), NULL, &z);
411
412  ParameterBlockOrdering* ordering = new ParameterBlockOrdering;
413  ordering->AddElementToGroup(&x, 0);
414  ordering->AddElementToGroup(&z, 0);
415  ordering->AddElementToGroup(&y, 0);
416
417  Solver::Options options;
418  options.linear_solver_type = DENSE_SCHUR;
419  options.linear_solver_ordering = ordering;
420
421  string error;
422  scoped_ptr<Program> reduced_program(
423      SolverImpl::CreateReducedProgram(&options, &problem, NULL, &error));
424
425  const vector<ResidualBlock*>& residual_blocks =
426      reduced_program->residual_blocks();
427  const vector<ParameterBlock*>& parameter_blocks =
428      reduced_program->parameter_blocks();
429
430  const vector<ResidualBlock*>& original_residual_blocks =
431      problem.program().residual_blocks();
432
433  EXPECT_EQ(residual_blocks.size(), 8);
434  EXPECT_EQ(reduced_program->parameter_blocks().size(), 2);
435
436  // Verify that right parmeter block and the residual blocks have
437  // been removed.
438  for (int i = 0; i < 8; ++i) {
439    EXPECT_NE(residual_blocks[i], original_residual_blocks.back());
440  }
441  for (int i = 0; i < 2; ++i) {
442    EXPECT_NE(parameter_blocks[i]->mutable_user_state(), &z);
443  }
444}
445
446TEST(SolverImpl, ApplyUserOrderingOrderingTooSmall) {
447  ProblemImpl problem;
448  double x;
449  double y;
450  double z;
451
452  problem.AddParameterBlock(&x, 1);
453  problem.AddParameterBlock(&y, 1);
454  problem.AddParameterBlock(&z, 1);
455
456  ParameterBlockOrdering ordering;
457  ordering.AddElementToGroup(&x, 0);
458  ordering.AddElementToGroup(&y, 1);
459
460  Program program(problem.program());
461  string error;
462  EXPECT_FALSE(SolverImpl::ApplyUserOrdering(problem.parameter_map(),
463                                             &ordering,
464                                             &program,
465                                             &error));
466}
467
468TEST(SolverImpl, ApplyUserOrderingNormal) {
469  ProblemImpl problem;
470  double x;
471  double y;
472  double z;
473
474  problem.AddParameterBlock(&x, 1);
475  problem.AddParameterBlock(&y, 1);
476  problem.AddParameterBlock(&z, 1);
477
478  ParameterBlockOrdering ordering;
479  ordering.AddElementToGroup(&x, 0);
480  ordering.AddElementToGroup(&y, 2);
481  ordering.AddElementToGroup(&z, 1);
482
483  Program* program = problem.mutable_program();
484  string error;
485
486  EXPECT_TRUE(SolverImpl::ApplyUserOrdering(problem.parameter_map(),
487                                            &ordering,
488                                            program,
489                                            &error));
490  const vector<ParameterBlock*>& parameter_blocks = program->parameter_blocks();
491
492  EXPECT_EQ(parameter_blocks.size(), 3);
493  EXPECT_EQ(parameter_blocks[0]->user_state(), &x);
494  EXPECT_EQ(parameter_blocks[1]->user_state(), &z);
495  EXPECT_EQ(parameter_blocks[2]->user_state(), &y);
496}
497
498#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
499TEST(SolverImpl, CreateLinearSolverNoSuiteSparse) {
500  Solver::Options options;
501  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
502  // CreateLinearSolver assumes a non-empty ordering.
503  options.linear_solver_ordering = new ParameterBlockOrdering;
504  string error;
505  EXPECT_FALSE(SolverImpl::CreateLinearSolver(&options, &error));
506}
507#endif
508
509TEST(SolverImpl, CreateLinearSolverNegativeMaxNumIterations) {
510  Solver::Options options;
511  options.linear_solver_type = DENSE_QR;
512  options.max_linear_solver_iterations = -1;
513  // CreateLinearSolver assumes a non-empty ordering.
514  options.linear_solver_ordering = new ParameterBlockOrdering;
515  string error;
516  EXPECT_EQ(SolverImpl::CreateLinearSolver(&options, &error),
517            static_cast<LinearSolver*>(NULL));
518}
519
520TEST(SolverImpl, CreateLinearSolverNegativeMinNumIterations) {
521  Solver::Options options;
522  options.linear_solver_type = DENSE_QR;
523  options.min_linear_solver_iterations = -1;
524  // CreateLinearSolver assumes a non-empty ordering.
525  options.linear_solver_ordering = new ParameterBlockOrdering;
526  string error;
527  EXPECT_EQ(SolverImpl::CreateLinearSolver(&options, &error),
528            static_cast<LinearSolver*>(NULL));
529}
530
531TEST(SolverImpl, CreateLinearSolverMaxLessThanMinIterations) {
532  Solver::Options options;
533  options.linear_solver_type = DENSE_QR;
534  options.min_linear_solver_iterations = 10;
535  options.max_linear_solver_iterations = 5;
536  options.linear_solver_ordering = new ParameterBlockOrdering;
537  string error;
538  EXPECT_EQ(SolverImpl::CreateLinearSolver(&options, &error),
539            static_cast<LinearSolver*>(NULL));
540}
541
542TEST(SolverImpl, CreateLinearSolverDenseSchurMultipleThreads) {
543  Solver::Options options;
544  options.linear_solver_type = DENSE_SCHUR;
545  options.num_linear_solver_threads = 2;
546  // The Schur type solvers can only be created with the Ordering
547  // contains at least one elimination group.
548  options.linear_solver_ordering = new ParameterBlockOrdering;
549  double x;
550  double y;
551  options.linear_solver_ordering->AddElementToGroup(&x, 0);
552  options.linear_solver_ordering->AddElementToGroup(&y, 0);
553
554  string error;
555  scoped_ptr<LinearSolver> solver(
556      SolverImpl::CreateLinearSolver(&options, &error));
557  EXPECT_TRUE(solver != NULL);
558  EXPECT_EQ(options.linear_solver_type, DENSE_SCHUR);
559  EXPECT_EQ(options.num_linear_solver_threads, 2);
560}
561
562TEST(SolverImpl, CreateIterativeLinearSolverForDogleg) {
563  Solver::Options options;
564  options.trust_region_strategy_type = DOGLEG;
565  // CreateLinearSolver assumes a non-empty ordering.
566  options.linear_solver_ordering = new ParameterBlockOrdering;
567  string error;
568  options.linear_solver_type = ITERATIVE_SCHUR;
569  EXPECT_EQ(SolverImpl::CreateLinearSolver(&options, &error),
570            static_cast<LinearSolver*>(NULL));
571
572  options.linear_solver_type = CGNR;
573  EXPECT_EQ(SolverImpl::CreateLinearSolver(&options, &error),
574            static_cast<LinearSolver*>(NULL));
575}
576
577TEST(SolverImpl, CreateLinearSolverNormalOperation) {
578  Solver::Options options;
579  scoped_ptr<LinearSolver> solver;
580  options.linear_solver_type = DENSE_QR;
581  // CreateLinearSolver assumes a non-empty ordering.
582  options.linear_solver_ordering = new ParameterBlockOrdering;
583  string error;
584  solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
585  EXPECT_EQ(options.linear_solver_type, DENSE_QR);
586  EXPECT_TRUE(solver.get() != NULL);
587
588  options.linear_solver_type = DENSE_NORMAL_CHOLESKY;
589  solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
590  EXPECT_EQ(options.linear_solver_type, DENSE_NORMAL_CHOLESKY);
591  EXPECT_TRUE(solver.get() != NULL);
592
593#ifndef CERES_NO_SUITESPARSE
594  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
595  options.sparse_linear_algebra_library = SUITE_SPARSE;
596  solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
597  EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
598  EXPECT_TRUE(solver.get() != NULL);
599#endif
600
601#ifndef CERES_NO_CXSPARSE
602  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
603  options.sparse_linear_algebra_library = CX_SPARSE;
604  solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
605  EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
606  EXPECT_TRUE(solver.get() != NULL);
607#endif
608
609  double x;
610  double y;
611  options.linear_solver_ordering->AddElementToGroup(&x, 0);
612  options.linear_solver_ordering->AddElementToGroup(&y, 0);
613
614  options.linear_solver_type = DENSE_SCHUR;
615  solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
616  EXPECT_EQ(options.linear_solver_type, DENSE_SCHUR);
617  EXPECT_TRUE(solver.get() != NULL);
618
619  options.linear_solver_type = SPARSE_SCHUR;
620  solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
621
622#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
623  EXPECT_TRUE(SolverImpl::CreateLinearSolver(&options, &error) == NULL);
624#else
625  EXPECT_TRUE(solver.get() != NULL);
626  EXPECT_EQ(options.linear_solver_type, SPARSE_SCHUR);
627#endif
628
629  options.linear_solver_type = ITERATIVE_SCHUR;
630  solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
631  EXPECT_EQ(options.linear_solver_type, ITERATIVE_SCHUR);
632  EXPECT_TRUE(solver.get() != NULL);
633}
634
635struct QuadraticCostFunction {
636  template <typename T> bool operator()(const T* const x,
637                                        T* residual) const {
638    residual[0] = T(5.0) - *x;
639    return true;
640  }
641};
642
643struct RememberingCallback : public IterationCallback {
644  explicit RememberingCallback(double *x) : calls(0), x(x) {}
645  virtual ~RememberingCallback() {}
646  virtual CallbackReturnType operator()(const IterationSummary& summary) {
647    x_values.push_back(*x);
648    return SOLVER_CONTINUE;
649  }
650  int calls;
651  double *x;
652  vector<double> x_values;
653};
654
655TEST(SolverImpl, UpdateStateEveryIterationOption) {
656  double x = 50.0;
657  const double original_x = x;
658
659  scoped_ptr<CostFunction> cost_function(
660      new AutoDiffCostFunction<QuadraticCostFunction, 1, 1>(
661          new QuadraticCostFunction));
662
663  Problem::Options problem_options;
664  problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP;
665  ProblemImpl problem(problem_options);
666  problem.AddResidualBlock(cost_function.get(), NULL, &x);
667
668  Solver::Options options;
669  options.linear_solver_type = DENSE_QR;
670
671  RememberingCallback callback(&x);
672  options.callbacks.push_back(&callback);
673
674  Solver::Summary summary;
675
676  int num_iterations;
677
678  // First try: no updating.
679  SolverImpl::Solve(options, &problem, &summary);
680  num_iterations = summary.num_successful_steps +
681                   summary.num_unsuccessful_steps;
682  EXPECT_GT(num_iterations, 1);
683  for (int i = 0; i < callback.x_values.size(); ++i) {
684    EXPECT_EQ(50.0, callback.x_values[i]);
685  }
686
687  // Second try: with updating
688  x = 50.0;
689  options.update_state_every_iteration = true;
690  callback.x_values.clear();
691  SolverImpl::Solve(options, &problem, &summary);
692  num_iterations = summary.num_successful_steps +
693                   summary.num_unsuccessful_steps;
694  EXPECT_GT(num_iterations, 1);
695  EXPECT_EQ(original_x, callback.x_values[0]);
696  EXPECT_NE(original_x, callback.x_values[1]);
697}
698
699// The parameters must be in separate blocks so that they can be individually
700// set constant or not.
701struct Quadratic4DCostFunction {
702  template <typename T> bool operator()(const T* const x,
703                                        const T* const y,
704                                        const T* const z,
705                                        const T* const w,
706                                        T* residual) const {
707    // A 4-dimension axis-aligned quadratic.
708    residual[0] = T(10.0) - *x +
709                  T(20.0) - *y +
710                  T(30.0) - *z +
711                  T(40.0) - *w;
712    return true;
713  }
714};
715
716TEST(SolverImpl, ConstantParameterBlocksDoNotChangeAndStateInvariantKept) {
717  double x = 50.0;
718  double y = 50.0;
719  double z = 50.0;
720  double w = 50.0;
721  const double original_x = 50.0;
722  const double original_y = 50.0;
723  const double original_z = 50.0;
724  const double original_w = 50.0;
725
726  scoped_ptr<CostFunction> cost_function(
727      new AutoDiffCostFunction<Quadratic4DCostFunction, 1, 1, 1, 1, 1>(
728          new Quadratic4DCostFunction));
729
730  Problem::Options problem_options;
731  problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP;
732
733  ProblemImpl problem(problem_options);
734  problem.AddResidualBlock(cost_function.get(), NULL, &x, &y, &z, &w);
735  problem.SetParameterBlockConstant(&x);
736  problem.SetParameterBlockConstant(&w);
737
738  Solver::Options options;
739  options.linear_solver_type = DENSE_QR;
740
741  Solver::Summary summary;
742  SolverImpl::Solve(options, &problem, &summary);
743
744  // Verify only the non-constant parameters were mutated.
745  EXPECT_EQ(original_x, x);
746  EXPECT_NE(original_y, y);
747  EXPECT_NE(original_z, z);
748  EXPECT_EQ(original_w, w);
749
750  // Check that the parameter block state pointers are pointing back at the
751  // user state, instead of inside a random temporary vector made by Solve().
752  EXPECT_EQ(&x, problem.program().parameter_blocks()[0]->state());
753  EXPECT_EQ(&y, problem.program().parameter_blocks()[1]->state());
754  EXPECT_EQ(&z, problem.program().parameter_blocks()[2]->state());
755  EXPECT_EQ(&w, problem.program().parameter_blocks()[3]->state());
756}
757
758TEST(SolverImpl, NoParameterBlocks) {
759  ProblemImpl problem_impl;
760  Solver::Options options;
761  Solver::Summary summary;
762  SolverImpl::Solve(options, &problem_impl, &summary);
763  EXPECT_EQ(summary.termination_type, DID_NOT_RUN);
764  EXPECT_EQ(summary.error, "Problem contains no parameter blocks.");
765}
766
767TEST(SolverImpl, NoResiduals) {
768  ProblemImpl problem_impl;
769  Solver::Options options;
770  Solver::Summary summary;
771  double x = 1;
772  problem_impl.AddParameterBlock(&x, 1);
773  SolverImpl::Solve(options, &problem_impl, &summary);
774  EXPECT_EQ(summary.termination_type, DID_NOT_RUN);
775  EXPECT_EQ(summary.error, "Problem contains no residual blocks.");
776}
777
778
779TEST(SolverImpl, ProblemIsConstant) {
780  ProblemImpl problem_impl;
781  Solver::Options options;
782  Solver::Summary summary;
783  double x = 1;
784  problem_impl.AddResidualBlock(new UnaryIdentityCostFunction, NULL, &x);
785  problem_impl.SetParameterBlockConstant(&x);
786  SolverImpl::Solve(options, &problem_impl, &summary);
787  EXPECT_EQ(summary.termination_type, FUNCTION_TOLERANCE);
788  EXPECT_EQ(summary.initial_cost, 1.0 / 2.0);
789  EXPECT_EQ(summary.final_cost, 1.0 / 2.0);
790}
791
792TEST(SolverImpl, AlternateLinearSolverForSchurTypeLinearSolver) {
793  Solver::Options options;
794
795  options.linear_solver_type = DENSE_QR;
796  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
797  EXPECT_EQ(options.linear_solver_type, DENSE_QR);
798
799  options.linear_solver_type = DENSE_NORMAL_CHOLESKY;
800  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
801  EXPECT_EQ(options.linear_solver_type, DENSE_NORMAL_CHOLESKY);
802
803  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
804  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
805  EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
806
807  options.linear_solver_type = CGNR;
808  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
809  EXPECT_EQ(options.linear_solver_type, CGNR);
810
811  options.linear_solver_type = DENSE_SCHUR;
812  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
813  EXPECT_EQ(options.linear_solver_type, DENSE_QR);
814
815  options.linear_solver_type = SPARSE_SCHUR;
816  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
817  EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
818
819  options.linear_solver_type = ITERATIVE_SCHUR;
820  options.preconditioner_type = IDENTITY;
821  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
822  EXPECT_EQ(options.linear_solver_type, CGNR);
823  EXPECT_EQ(options.preconditioner_type, IDENTITY);
824
825  options.linear_solver_type = ITERATIVE_SCHUR;
826  options.preconditioner_type = JACOBI;
827  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
828  EXPECT_EQ(options.linear_solver_type, CGNR);
829  EXPECT_EQ(options.preconditioner_type, JACOBI);
830
831  options.linear_solver_type = ITERATIVE_SCHUR;
832  options.preconditioner_type = SCHUR_JACOBI;
833  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
834  EXPECT_EQ(options.linear_solver_type, CGNR);
835  EXPECT_EQ(options.preconditioner_type, JACOBI);
836
837  options.linear_solver_type = ITERATIVE_SCHUR;
838  options.preconditioner_type = CLUSTER_JACOBI;
839  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
840  EXPECT_EQ(options.linear_solver_type, CGNR);
841  EXPECT_EQ(options.preconditioner_type, JACOBI);
842
843  options.linear_solver_type = ITERATIVE_SCHUR;
844  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
845  SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
846  EXPECT_EQ(options.linear_solver_type, CGNR);
847  EXPECT_EQ(options.preconditioner_type, JACOBI);
848}
849
850TEST(SolverImpl, CreateJacobianBlockSparsityTranspose) {
851  ProblemImpl problem;
852  double x[2];
853  double y[3];
854  double z;
855
856  problem.AddParameterBlock(x, 2);
857  problem.AddParameterBlock(y, 3);
858  problem.AddParameterBlock(&z, 1);
859
860  problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 0, 0>(), NULL, x);
861  problem.AddResidualBlock(new MockCostFunctionBase<3, 1, 2, 0>(), NULL, &z, x);
862  problem.AddResidualBlock(new MockCostFunctionBase<4, 1, 3, 0>(), NULL, &z, y);
863  problem.AddResidualBlock(new MockCostFunctionBase<5, 1, 3, 0>(), NULL, &z, y);
864  problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 1, 0>(), NULL, x, &z);
865  problem.AddResidualBlock(new MockCostFunctionBase<2, 1, 3, 0>(), NULL, &z, y);
866  problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 1, 0>(), NULL, x, &z);
867  problem.AddResidualBlock(new MockCostFunctionBase<1, 3, 0, 0>(), NULL, y);
868
869  TripletSparseMatrix expected_block_sparse_jacobian(3, 8, 14);
870  {
871    int* rows = expected_block_sparse_jacobian.mutable_rows();
872    int* cols = expected_block_sparse_jacobian.mutable_cols();
873    double* values = expected_block_sparse_jacobian.mutable_values();
874    rows[0] = 0;
875    cols[0] = 0;
876
877    rows[1] = 2;
878    cols[1] = 1;
879    rows[2] = 0;
880    cols[2] = 1;
881
882    rows[3] = 2;
883    cols[3] = 2;
884    rows[4] = 1;
885    cols[4] = 2;
886
887    rows[5] = 2;
888    cols[5] = 3;
889    rows[6] = 1;
890    cols[6] = 3;
891
892    rows[7] = 0;
893    cols[7] = 4;
894    rows[8] = 2;
895    cols[8] = 4;
896
897    rows[9] = 2;
898    cols[9] = 5;
899    rows[10] = 1;
900    cols[10] = 5;
901
902    rows[11] = 0;
903    cols[11] = 6;
904    rows[12] = 2;
905    cols[12] = 6;
906
907    rows[13] = 1;
908    cols[13] = 7;
909    fill(values, values + 14, 1.0);
910    expected_block_sparse_jacobian.set_num_nonzeros(14);
911  }
912
913  Program* program = problem.mutable_program();
914  program->SetParameterOffsetsAndIndex();
915
916  scoped_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
917      SolverImpl::CreateJacobianBlockSparsityTranspose(program));
918
919  Matrix expected_dense_jacobian;
920  expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
921
922  Matrix actual_dense_jacobian;
923  actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
924  EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
925}
926
927template <int kNumResiduals, int kNumParameterBlocks>
928class NumParameterBlocksCostFunction : public CostFunction {
929 public:
930  NumParameterBlocksCostFunction() {
931    set_num_residuals(kNumResiduals);
932    for (int i = 0; i < kNumParameterBlocks; ++i) {
933      mutable_parameter_block_sizes()->push_back(1);
934    }
935  }
936
937  virtual ~NumParameterBlocksCostFunction() {
938  }
939
940  virtual bool Evaluate(double const* const* parameters,
941                        double* residuals,
942                        double** jacobians) const {
943    return true;
944  }
945};
946
947TEST(SolverImpl, ReallocationInCreateJacobianBlockSparsityTranspose) {
948  // CreateJacobianBlockSparsityTranspose starts with a conservative
949  // estimate of the size of the sparsity pattern. This test ensures
950  // that when those estimates are violated, the reallocation/resizing
951  // logic works correctly.
952
953  ProblemImpl problem;
954  double x[20];
955
956  vector<double*> parameter_blocks;
957  for (int i = 0; i < 20; ++i) {
958    problem.AddParameterBlock(x + i, 1);
959    parameter_blocks.push_back(x + i);
960  }
961
962  problem.AddResidualBlock(new NumParameterBlocksCostFunction<1, 20>(),
963                           NULL,
964                           parameter_blocks);
965
966  TripletSparseMatrix expected_block_sparse_jacobian(20, 1, 20);
967  {
968    int* rows = expected_block_sparse_jacobian.mutable_rows();
969    int* cols = expected_block_sparse_jacobian.mutable_cols();
970    for (int i = 0; i < 20; ++i) {
971      rows[i] = i;
972      cols[i] = 0;
973    }
974
975    double* values = expected_block_sparse_jacobian.mutable_values();
976    fill(values, values + 20, 1.0);
977    expected_block_sparse_jacobian.set_num_nonzeros(20);
978  }
979
980  Program* program = problem.mutable_program();
981  program->SetParameterOffsetsAndIndex();
982
983  scoped_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
984      SolverImpl::CreateJacobianBlockSparsityTranspose(program));
985
986  Matrix expected_dense_jacobian;
987  expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
988
989  Matrix actual_dense_jacobian;
990  actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
991  EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
992}
993
994}  // namespace internal
995}  // namespace ceres
996