blob: 58a49b96bcb87c5d533033b2d847d456179dd83a [file] [log] [blame]
Keir Mierle8ebb0732012-04-30 23:09:08 -07001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2010, 2011, 2012 Google Inc. All rights reserved.
3// http://code.google.com/p/ceres-solver/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors may be
14// used to endorse or promote products derived from this software without
15// specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28//
29// Author: keir@google.com (Keir Mierle)
30
31#include "ceres/gradient_checking_cost_function.h"
32
33#include <cmath>
34#include <vector>
35
36#include <glog/logging.h>
37#include "gmock/gmock.h"
38#include "gtest/gtest.h"
39#include "ceres/mock_log.h"
40#include "ceres/problem_impl.h"
41#include "ceres/program.h"
42#include "ceres/parameter_block.h"
43#include "ceres/residual_block.h"
44#include "ceres/random.h"
45#include "ceres/cost_function.h"
46#include "ceres/internal/scoped_ptr.h"
47#include "ceres/local_parameterization.h"
48#include "ceres/loss_function.h"
49#include "ceres/sized_cost_function.h"
50#include "ceres/types.h"
51
52using testing::AllOf;
53using testing::AnyNumber;
54using testing::HasSubstr;
55using testing::ScopedMockLog;
56using testing::_;
57
58namespace ceres {
59namespace internal {
60
61// Pick a (non-quadratic) function whose derivative are easy:
62//
63// f = exp(- a' x).
64// df = - f a.
65//
66// where 'a' is a vector of the same size as 'x'. In the block
67// version, they are both block vectors, of course.
68template<int bad_block = 1, int bad_variable = 2>
69class TestTerm : public CostFunction {
70 public:
71 // The constructor of this function needs to know the number
72 // of blocks desired, and the size of each block.
73 TestTerm(int arity, int const *dim) : arity_(arity) {
74 // Make 'arity' random vectors.
75 a_.resize(arity_);
76 for (int j = 0; j < arity_; ++j) {
77 a_[j].resize(dim[j]);
78 for (int u = 0; u < dim[j]; ++u) {
79 a_[j][u] = 2.0 * RandDouble() - 1.0;
80 }
81 }
82
83 for (int i = 0; i < arity_; i++) {
84 mutable_parameter_block_sizes()->push_back(dim[i]);
85 }
86 set_num_residuals(1);
87 }
88
89 bool Evaluate(double const* const* parameters,
90 double* residuals,
91 double** jacobians) const {
92 // Compute a . x.
93 double ax = 0;
94 for (int j = 0; j < arity_; ++j) {
95 for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
96 ax += a_[j][u] * parameters[j][u];
97 }
98 }
99
100 // This is the cost, but also appears as a factor
101 // in the derivatives.
102 double f = *residuals = exp(-ax);
103
104 // Accumulate 1st order derivatives.
105 if (jacobians) {
106 for (int j = 0; j < arity_; ++j) {
107 if (jacobians[j]) {
108 for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
109 // See comments before class.
110 jacobians[j][u] = - f * a_[j][u];
111
112 if (bad_block == j && bad_variable == u) {
113 // Whoopsiedoopsie! Deliberately introduce a faulty jacobian entry
114 // like what happens when users make an error in their jacobian
115 // computations. This should get detected.
116 LOG(INFO) << "Poisoning jacobian for parameter block " << j
117 << ", row 0, column " << u;
118 jacobians[j][u] += 500;
119 }
120 }
121 }
122 }
123 }
124
125 return true;
126 }
127
128 private:
129 int arity_;
130 vector<vector<double> > a_;
131};
132
133TEST(GradientCheckingCostFunction, ResidualsAndJacobiansArePreservedTest) {
134 srand(5);
135
136 // Test with 3 blocks of size 2, 3 and 4.
137 int const arity = 3;
138 int const dim[arity] = { 2, 3, 4 };
139
140 // Make a random set of blocks.
141 vector<double*> parameters(arity);
142 for (int j = 0; j < arity; ++j) {
143 parameters[j] = new double[dim[j]];
144 for (int u = 0; u < dim[j]; ++u) {
145 parameters[j][u] = 2.0 * RandDouble() - 1.0;
146 }
147 }
148
149 double original_residual;
150 double residual;
151 vector<double*> original_jacobians(arity);
152 vector<double*> jacobians(arity);
153
154 for (int j = 0; j < arity; ++j) {
155 // Since residual is one dimensional the jacobians have the same
156 // size as the parameter blocks.
157 jacobians[j] = new double[dim[j]];
158 original_jacobians[j] = new double[dim[j]];
159 }
160
161 const double kRelativeStepSize = 1e-6;
162 const double kRelativePrecision = 1e-4;
163
164 TestTerm<-1, -1> term(arity, dim);
165 scoped_ptr<CostFunction> gradient_checking_cost_function(
166 CreateGradientCheckingCostFunction(&term,
167 kRelativeStepSize,
168 kRelativePrecision,
169 "Ignored."));
170 term.Evaluate(&parameters[0],
171 &original_residual,
172 &original_jacobians[0]);
173
174 gradient_checking_cost_function->Evaluate(&parameters[0],
175 &residual,
176 &jacobians[0]);
177 EXPECT_EQ(original_residual, residual);
178
179 for (int j = 0; j < arity; j++) {
180 for (int k = 0; k < dim[j]; ++k) {
181 EXPECT_EQ(original_jacobians[j][k], jacobians[j][k]);
182 }
183
184 delete[] parameters[j];
185 delete[] jacobians[j];
186 delete[] original_jacobians[j];
187 }
188}
189
190TEST(GradientCheckingCostFunction, SmokeTest) {
191 srand(5);
192
193 // Test with 3 blocks of size 2, 3 and 4.
194 int const arity = 3;
195 int const dim[arity] = { 2, 3, 4 };
196
197 // Make a random set of blocks.
198 vector<double*> parameters(arity);
199 for (int j = 0; j < arity; ++j) {
200 parameters[j] = new double[dim[j]];
201 for (int u = 0; u < dim[j]; ++u) {
202 parameters[j][u] = 2.0 * RandDouble() - 1.0;
203 }
204 }
205
206 double residual;
207 vector<double*> jacobians(arity);
208 for (int j = 0; j < arity; ++j) {
209 // Since residual is one dimensional the jacobians have the same size as the
210 // parameter blocks.
211 jacobians[j] = new double[dim[j]];
212 }
213
214 const double kRelativeStepSize = 1e-6;
215 const double kRelativePrecision = 1e-4;
216
217 // Should have one term that's bad, causing everything to get dumped.
218 LOG(INFO) << "Bad gradient";
219 {
220 TestTerm<1, 2> term(arity, dim);
221 scoped_ptr<CostFunction> gradient_checking_cost_function(
222 CreateGradientCheckingCostFunction(&term,
223 kRelativeStepSize,
224 kRelativePrecision,
225 "Fuzzy bananas"));
226
227 ScopedMockLog log;
228 EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
229 EXPECT_CALL(log, Log(WARNING, _,
230 AllOf(HasSubstr("(1,0,2) Relative error worse than"),
231 HasSubstr("Fuzzy bananas"))));
232
233 gradient_checking_cost_function->Evaluate(&parameters[0],
234 &residual,
235 &jacobians[0]);
236 }
237
238 // The gradient is correct, so no errors are reported.
239 LOG(INFO) << "Good gradient";
240 {
241 TestTerm<-1, -1> term(arity, dim);
242 scoped_ptr<CostFunction> gradient_checking_cost_function(
243 CreateGradientCheckingCostFunction(&term,
244 kRelativeStepSize,
245 kRelativePrecision,
246 "Ignored."));
247
248 ScopedMockLog log;
249 EXPECT_CALL(log, Log(_, _, _)).Times(0);
250
251 gradient_checking_cost_function->Evaluate(&parameters[0],
252 &residual,
253 &jacobians[0]);
254 }
255
256 for (int j = 0; j < arity; j++) {
257 delete[] parameters[j];
258 delete[] jacobians[j];
259 }
260}
261
262// The following three classes are for the purposes of defining
263// function signatures. They have dummy Evaluate functions.
264
265// Trivial cost function that accepts a single argument.
266class UnaryCostFunction : public CostFunction {
267 public:
268 UnaryCostFunction(int num_residuals, int16 parameter_block_size) {
269 set_num_residuals(num_residuals);
270 mutable_parameter_block_sizes()->push_back(parameter_block_size);
271 }
272 virtual ~UnaryCostFunction() {}
273
274 virtual bool Evaluate(double const* const* parameters,
275 double* residuals,
276 double** jacobians) const {
277 for (int i = 0; i < num_residuals(); ++i) {
278 residuals[i] = 1;
279 }
280 return true;
281 }
282};
283
284// Trivial cost function that accepts two arguments.
285class BinaryCostFunction: public CostFunction {
286 public:
287 BinaryCostFunction(int num_residuals,
288 int16 parameter_block1_size,
289 int16 parameter_block2_size) {
290 set_num_residuals(num_residuals);
291 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
292 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
293 }
294
295 virtual bool Evaluate(double const* const* parameters,
296 double* residuals,
297 double** jacobians) const {
298 for (int i = 0; i < num_residuals(); ++i) {
299 residuals[i] = 2;
300 }
301 return true;
302 }
303};
304
305// Trivial cost function that accepts three arguments.
306class TernaryCostFunction: public CostFunction {
307 public:
308 TernaryCostFunction(int num_residuals,
309 int16 parameter_block1_size,
310 int16 parameter_block2_size,
311 int16 parameter_block3_size) {
312 set_num_residuals(num_residuals);
313 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
314 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
315 mutable_parameter_block_sizes()->push_back(parameter_block3_size);
316 }
317
318 virtual bool Evaluate(double const* const* parameters,
319 double* residuals,
320 double** jacobians) const {
321 for (int i = 0; i < num_residuals(); ++i) {
322 residuals[i] = 3;
323 }
324 return true;
325 }
326};
327
328// Verify that the two ParameterBlocks are formed from the same user
329// array and have the same LocalParameterization object.
330void ParameterBlocksAreEquivalent(const ParameterBlock* left,
331 const ParameterBlock* right) {
332 CHECK_NOTNULL(left);
333 CHECK_NOTNULL(right);
334 EXPECT_EQ(left->user_state(), right->user_state());
335 EXPECT_EQ(left->Size(), right->Size());
336 EXPECT_EQ(left->Size(), right->Size());
337 EXPECT_EQ(left->LocalSize(), right->LocalSize());
338 EXPECT_EQ(left->local_parameterization(), right->local_parameterization());
339 EXPECT_EQ(left->IsConstant(), right->IsConstant());
340}
341
342TEST(GradientCheckingProblemImpl, ProblemDimensionsMatch) {
Sameer Agarwal451623a2012-07-11 11:15:25 -0700343 // Parameter blocks with arbitrarily chosen initial values.
344 double x[] = {1.0, 2.0, 3.0};
345 double y[] = {4.0, 5.0, 6.0, 7.0};
346 double z[] = {8.0, 9.0, 10.0, 11.0, 12.0};
347 double w[] = {13.0, 14.0, 15.0, 16.0};
Keir Mierle8ebb0732012-04-30 23:09:08 -0700348
349 ProblemImpl problem_impl;
350 problem_impl.AddParameterBlock(x, 3);
351 problem_impl.AddParameterBlock(y, 4);
352 problem_impl.SetParameterBlockConstant(y);
353 problem_impl.AddParameterBlock(z, 5);
354 problem_impl.AddParameterBlock(w, 4, new QuaternionParameterization);
355 problem_impl.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
356 problem_impl.AddResidualBlock(new BinaryCostFunction(6, 5, 4) ,
357 NULL, z, y);
358 problem_impl.AddResidualBlock(new BinaryCostFunction(3, 3, 5),
359 new TrivialLoss, x, z);
360 problem_impl.AddResidualBlock(new BinaryCostFunction(7, 5, 3),
361 NULL, z, x);
362 problem_impl.AddResidualBlock(new TernaryCostFunction(1, 5, 3, 4),
363 NULL, z, x, y);
364
365 scoped_ptr<ProblemImpl> gradient_checking_problem_impl(
366 CreateGradientCheckingProblemImpl(&problem_impl, 1.0, 1.0));
367
368 // The dimensions of the two problems match.
369 EXPECT_EQ(problem_impl.NumParameterBlocks(),
370 gradient_checking_problem_impl->NumParameterBlocks());
371 EXPECT_EQ(problem_impl.NumResidualBlocks(),
372 gradient_checking_problem_impl->NumResidualBlocks());
373
374 EXPECT_EQ(problem_impl.NumParameters(),
375 gradient_checking_problem_impl->NumParameters());
376 EXPECT_EQ(problem_impl.NumResiduals(),
377 gradient_checking_problem_impl->NumResiduals());
378
379 const Program& program = problem_impl.program();
380 const Program& gradient_checking_program =
381 gradient_checking_problem_impl->program();
382
383 // Since we added the ParameterBlocks and ResidualBlocks explicitly,
384 // they should be in the same order in the two programs. It is
385 // possible that may change due to implementation changes to
386 // Program. This is not exepected to be the case and writing code to
387 // anticipate that possibility not worth the extra complexity in
388 // this test.
389 for (int i = 0; i < program.parameter_blocks().size(); ++i) {
390 ParameterBlocksAreEquivalent(
391 program.parameter_blocks()[i],
392 gradient_checking_program.parameter_blocks()[i]);
393 }
394
395 for (int i = 0; i < program.residual_blocks().size(); ++i) {
396 // Compare the sizes of the two ResidualBlocks.
397 const ResidualBlock* original_residual_block =
398 program.residual_blocks()[i];
399 const ResidualBlock* new_residual_block =
400 gradient_checking_program.residual_blocks()[i];
401 EXPECT_EQ(original_residual_block->NumParameterBlocks(),
402 new_residual_block->NumParameterBlocks());
403 EXPECT_EQ(original_residual_block->NumResiduals(),
404 new_residual_block->NumResiduals());
405 EXPECT_EQ(original_residual_block->NumScratchDoublesForEvaluate(),
406 new_residual_block->NumScratchDoublesForEvaluate());
407
408 // Verify that the ParameterBlocks for the two residuals are equivalent.
409 for (int j = 0; j < original_residual_block->NumParameterBlocks(); ++j) {
410 ParameterBlocksAreEquivalent(
411 original_residual_block->parameter_blocks()[j],
412 new_residual_block->parameter_blocks()[j]);
413 }
414 }
415}
416
417} // namespace internal
418} // namespace ceres