Home | History | Annotate | Download | only in ceres
      1 // Ceres Solver - A fast non-linear least squares minimizer
      2 // Copyright 2014 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 (at) google.com (Sameer Agarwal)
     30 
     31 #include "ceres/program.h"
     32 
     33 #include <limits>
     34 #include <cmath>
     35 #include <vector>
     36 #include "ceres/sized_cost_function.h"
     37 #include "ceres/problem_impl.h"
     38 #include "ceres/residual_block.h"
     39 #include "ceres/triplet_sparse_matrix.h"
     40 #include "gtest/gtest.h"
     41 
     42 namespace ceres {
     43 namespace internal {
     44 
     45 // A cost function that simply returns its argument.
     46 class 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.
     60 template <int kNumResiduals, int N0, int N1, int N2>
     61 class MockCostFunctionBase : public
     62 SizedCostFunction<kNumResiduals, N0, N1, N2> {
     63  public:
     64   virtual bool Evaluate(double const* const* parameters,
     65                         double* residuals,
     66                         double** jacobians) const {
     67     for (int i = 0; i < kNumResiduals; ++i) {
     68       residuals[i] = kNumResiduals +  N0 + N1 + N2;
     69     }
     70     return true;
     71   }
     72 };
     73 
     74 class UnaryCostFunction : public MockCostFunctionBase<2, 1, 0, 0> {};
     75 class BinaryCostFunction : public MockCostFunctionBase<2, 1, 1, 0> {};
     76 class TernaryCostFunction : public MockCostFunctionBase<2, 1, 1, 1> {};
     77 
     78 TEST(Program, RemoveFixedBlocksNothingConstant) {
     79   ProblemImpl problem;
     80   double x;
     81   double y;
     82   double z;
     83 
     84   problem.AddParameterBlock(&x, 1);
     85   problem.AddParameterBlock(&y, 1);
     86   problem.AddParameterBlock(&z, 1);
     87   problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
     88   problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
     89   problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
     90 
     91   vector<double*> removed_parameter_blocks;
     92   double fixed_cost = 0.0;
     93   string message;
     94   scoped_ptr<Program> reduced_program(
     95       CHECK_NOTNULL(problem
     96                     .program()
     97                     .CreateReducedProgram(&removed_parameter_blocks,
     98                                           &fixed_cost,
     99                                           &message)));
    100 
    101   EXPECT_EQ(reduced_program->NumParameterBlocks(), 3);
    102   EXPECT_EQ(reduced_program->NumResidualBlocks(), 3);
    103   EXPECT_EQ(removed_parameter_blocks.size(), 0);
    104   EXPECT_EQ(fixed_cost, 0.0);
    105 }
    106 
    107 TEST(Program, RemoveFixedBlocksAllParameterBlocksConstant) {
    108   ProblemImpl problem;
    109   double x = 1.0;
    110 
    111   problem.AddParameterBlock(&x, 1);
    112   problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
    113   problem.SetParameterBlockConstant(&x);
    114 
    115   vector<double*> removed_parameter_blocks;
    116   double fixed_cost = 0.0;
    117   string message;
    118   scoped_ptr<Program> reduced_program(
    119       CHECK_NOTNULL(problem
    120                     .program()
    121                     .CreateReducedProgram(&removed_parameter_blocks,
    122                                           &fixed_cost,
    123                                           &message)));
    124   EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
    125   EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
    126   EXPECT_EQ(removed_parameter_blocks.size(), 1);
    127   EXPECT_EQ(removed_parameter_blocks[0], &x);
    128   EXPECT_EQ(fixed_cost, 9.0);
    129 }
    130 
    131 
    132 TEST(Program, RemoveFixedBlocksNoResidualBlocks) {
    133   ProblemImpl problem;
    134   double x;
    135   double y;
    136   double z;
    137 
    138   problem.AddParameterBlock(&x, 1);
    139   problem.AddParameterBlock(&y, 1);
    140   problem.AddParameterBlock(&z, 1);
    141 
    142   vector<double*> removed_parameter_blocks;
    143   double fixed_cost = 0.0;
    144   string message;
    145   scoped_ptr<Program> reduced_program(
    146       CHECK_NOTNULL(problem
    147                     .program()
    148                     .CreateReducedProgram(&removed_parameter_blocks,
    149                                           &fixed_cost,
    150                                           &message)));
    151   EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
    152   EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
    153   EXPECT_EQ(removed_parameter_blocks.size(), 3);
    154   EXPECT_EQ(fixed_cost, 0.0);
    155 }
    156 
    157 TEST(Program, RemoveFixedBlocksOneParameterBlockConstant) {
    158   ProblemImpl problem;
    159   double x;
    160   double y;
    161   double z;
    162 
    163   problem.AddParameterBlock(&x, 1);
    164   problem.AddParameterBlock(&y, 1);
    165   problem.AddParameterBlock(&z, 1);
    166 
    167   problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
    168   problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
    169   problem.SetParameterBlockConstant(&x);
    170 
    171   vector<double*> removed_parameter_blocks;
    172   double fixed_cost = 0.0;
    173   string message;
    174   scoped_ptr<Program> reduced_program(
    175       CHECK_NOTNULL(problem
    176                     .program()
    177                     .CreateReducedProgram(&removed_parameter_blocks,
    178                                           &fixed_cost,
    179                                           &message)));
    180   EXPECT_EQ(reduced_program->NumParameterBlocks(), 1);
    181   EXPECT_EQ(reduced_program->NumResidualBlocks(), 1);
    182 }
    183 
    184 TEST(Program, RemoveFixedBlocksNumEliminateBlocks) {
    185   ProblemImpl problem;
    186   double x;
    187   double y;
    188   double z;
    189 
    190   problem.AddParameterBlock(&x, 1);
    191   problem.AddParameterBlock(&y, 1);
    192   problem.AddParameterBlock(&z, 1);
    193   problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
    194   problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
    195   problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
    196   problem.SetParameterBlockConstant(&x);
    197 
    198   vector<double*> removed_parameter_blocks;
    199   double fixed_cost = 0.0;
    200   string message;
    201   scoped_ptr<Program> reduced_program(
    202       CHECK_NOTNULL(problem
    203                     .program()
    204                     .CreateReducedProgram(&removed_parameter_blocks,
    205                                           &fixed_cost,
    206                                           &message)));
    207   EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
    208   EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
    209 }
    210 
    211 TEST(Program, RemoveFixedBlocksFixedCost) {
    212   ProblemImpl problem;
    213   double x = 1.23;
    214   double y = 4.56;
    215   double z = 7.89;
    216 
    217   problem.AddParameterBlock(&x, 1);
    218   problem.AddParameterBlock(&y, 1);
    219   problem.AddParameterBlock(&z, 1);
    220   problem.AddResidualBlock(new UnaryIdentityCostFunction(), NULL, &x);
    221   problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
    222   problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
    223   problem.SetParameterBlockConstant(&x);
    224 
    225   ResidualBlock *expected_removed_block = problem.program().residual_blocks()[0];
    226   scoped_array<double> scratch(
    227       new double[expected_removed_block->NumScratchDoublesForEvaluate()]);
    228   double expected_fixed_cost;
    229   expected_removed_block->Evaluate(true,
    230                                    &expected_fixed_cost,
    231                                    NULL,
    232                                    NULL,
    233                                    scratch.get());
    234 
    235 
    236   vector<double*> removed_parameter_blocks;
    237   double fixed_cost = 0.0;
    238   string message;
    239   scoped_ptr<Program> reduced_program(
    240       CHECK_NOTNULL(problem
    241                     .program()
    242                     .CreateReducedProgram(&removed_parameter_blocks,
    243                                           &fixed_cost,
    244                                           &message)));
    245 
    246   EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
    247   EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
    248   EXPECT_DOUBLE_EQ(fixed_cost, expected_fixed_cost);
    249 }
    250 
    251 TEST(Program, CreateJacobianBlockSparsityTranspose) {
    252   ProblemImpl problem;
    253   double x[2];
    254   double y[3];
    255   double z;
    256 
    257   problem.AddParameterBlock(x, 2);
    258   problem.AddParameterBlock(y, 3);
    259   problem.AddParameterBlock(&z, 1);
    260 
    261   problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 0, 0>(), NULL, x);
    262   problem.AddResidualBlock(new MockCostFunctionBase<3, 1, 2, 0>(), NULL, &z, x);
    263   problem.AddResidualBlock(new MockCostFunctionBase<4, 1, 3, 0>(), NULL, &z, y);
    264   problem.AddResidualBlock(new MockCostFunctionBase<5, 1, 3, 0>(), NULL, &z, y);
    265   problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 1, 0>(), NULL, x, &z);
    266   problem.AddResidualBlock(new MockCostFunctionBase<2, 1, 3, 0>(), NULL, &z, y);
    267   problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 1, 0>(), NULL, x, &z);
    268   problem.AddResidualBlock(new MockCostFunctionBase<1, 3, 0, 0>(), NULL, y);
    269 
    270   TripletSparseMatrix expected_block_sparse_jacobian(3, 8, 14);
    271   {
    272     int* rows = expected_block_sparse_jacobian.mutable_rows();
    273     int* cols = expected_block_sparse_jacobian.mutable_cols();
    274     double* values = expected_block_sparse_jacobian.mutable_values();
    275     rows[0] = 0;
    276     cols[0] = 0;
    277 
    278     rows[1] = 2;
    279     cols[1] = 1;
    280     rows[2] = 0;
    281     cols[2] = 1;
    282 
    283     rows[3] = 2;
    284     cols[3] = 2;
    285     rows[4] = 1;
    286     cols[4] = 2;
    287 
    288     rows[5] = 2;
    289     cols[5] = 3;
    290     rows[6] = 1;
    291     cols[6] = 3;
    292 
    293     rows[7] = 0;
    294     cols[7] = 4;
    295     rows[8] = 2;
    296     cols[8] = 4;
    297 
    298     rows[9] = 2;
    299     cols[9] = 5;
    300     rows[10] = 1;
    301     cols[10] = 5;
    302 
    303     rows[11] = 0;
    304     cols[11] = 6;
    305     rows[12] = 2;
    306     cols[12] = 6;
    307 
    308     rows[13] = 1;
    309     cols[13] = 7;
    310     fill(values, values + 14, 1.0);
    311     expected_block_sparse_jacobian.set_num_nonzeros(14);
    312   }
    313 
    314   Program* program = problem.mutable_program();
    315   program->SetParameterOffsetsAndIndex();
    316 
    317   scoped_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
    318       program->CreateJacobianBlockSparsityTranspose());
    319 
    320   Matrix expected_dense_jacobian;
    321   expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
    322 
    323   Matrix actual_dense_jacobian;
    324   actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
    325   EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
    326 }
    327 
    328 template <int kNumResiduals, int kNumParameterBlocks>
    329 class NumParameterBlocksCostFunction : public CostFunction {
    330  public:
    331   NumParameterBlocksCostFunction() {
    332     set_num_residuals(kNumResiduals);
    333     for (int i = 0; i < kNumParameterBlocks; ++i) {
    334       mutable_parameter_block_sizes()->push_back(1);
    335     }
    336   }
    337 
    338   virtual ~NumParameterBlocksCostFunction() {
    339   }
    340 
    341   virtual bool Evaluate(double const* const* parameters,
    342                         double* residuals,
    343                         double** jacobians) const {
    344     return true;
    345   }
    346 };
    347 
    348 TEST(Program, ReallocationInCreateJacobianBlockSparsityTranspose) {
    349   // CreateJacobianBlockSparsityTranspose starts with a conservative
    350   // estimate of the size of the sparsity pattern. This test ensures
    351   // that when those estimates are violated, the reallocation/resizing
    352   // logic works correctly.
    353 
    354   ProblemImpl problem;
    355   double x[20];
    356 
    357   vector<double*> parameter_blocks;
    358   for (int i = 0; i < 20; ++i) {
    359     problem.AddParameterBlock(x + i, 1);
    360     parameter_blocks.push_back(x + i);
    361   }
    362 
    363   problem.AddResidualBlock(new NumParameterBlocksCostFunction<1, 20>(),
    364                            NULL,
    365                            parameter_blocks);
    366 
    367   TripletSparseMatrix expected_block_sparse_jacobian(20, 1, 20);
    368   {
    369     int* rows = expected_block_sparse_jacobian.mutable_rows();
    370     int* cols = expected_block_sparse_jacobian.mutable_cols();
    371     for (int i = 0; i < 20; ++i) {
    372       rows[i] = i;
    373       cols[i] = 0;
    374     }
    375 
    376     double* values = expected_block_sparse_jacobian.mutable_values();
    377     fill(values, values + 20, 1.0);
    378     expected_block_sparse_jacobian.set_num_nonzeros(20);
    379   }
    380 
    381   Program* program = problem.mutable_program();
    382   program->SetParameterOffsetsAndIndex();
    383 
    384   scoped_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
    385       program->CreateJacobianBlockSparsityTranspose());
    386 
    387   Matrix expected_dense_jacobian;
    388   expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
    389 
    390   Matrix actual_dense_jacobian;
    391   actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
    392   EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
    393 }
    394 
    395 TEST(Program, ProblemHasNanParameterBlocks) {
    396   ProblemImpl problem;
    397   double x[2];
    398   x[0] = 1.0;
    399   x[1] = std::numeric_limits<double>::quiet_NaN();
    400   problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 0, 0>(), NULL, x);
    401   string error;
    402   EXPECT_FALSE(problem.program().ParameterBlocksAreFinite(&error));
    403   EXPECT_NE(error.find("has at least one invalid value"),
    404             string::npos) << error;
    405 }
    406 
    407 TEST(Program, InfeasibleParameterBlock) {
    408   ProblemImpl problem;
    409   double x[] = {0.0, 0.0};
    410   problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 0, 0>(), NULL, x);
    411   problem.SetParameterLowerBound(x, 0, 2.0);
    412   problem.SetParameterUpperBound(x, 0, 1.0);
    413   string error;
    414   EXPECT_FALSE(problem.program().IsFeasible(&error));
    415   EXPECT_NE(error.find("infeasible bound"), string::npos) << error;
    416 }
    417 
    418 TEST(Program, InfeasibleConstantParameterBlock) {
    419   ProblemImpl problem;
    420   double x[] = {0.0, 0.0};
    421   problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 0, 0>(), NULL, x);
    422   problem.SetParameterLowerBound(x, 0, 1.0);
    423   problem.SetParameterUpperBound(x, 0, 2.0);
    424   problem.SetParameterBlockConstant(x);
    425   string error;
    426   EXPECT_FALSE(problem.program().IsFeasible(&error));
    427   EXPECT_NE(error.find("infeasible value"), string::npos) << error;
    428 }
    429 
    430 }  // namespace internal
    431 }  // namespace ceres
    432