blob: 4afe1b55ae422ff7a35fa0b2d03da20f1a1af0c8 [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: sameeragarwal@google.com (Sameer Agarwal)
30// keir@google.com (Keir Mierle)
31
32#include "ceres/problem.h"
33
34#include "gtest/gtest.h"
35#include "ceres/cost_function.h"
36#include "ceres/local_parameterization.h"
37#include "ceres/sized_cost_function.h"
38#include "ceres/internal/scoped_ptr.h"
39
40namespace ceres {
41namespace internal {
42
43// The following three classes are for the purposes of defining
44// function signatures. They have dummy Evaluate functions.
45
46// Trivial cost function that accepts a single argument.
47class UnaryCostFunction : public CostFunction {
48 public:
49 UnaryCostFunction(int num_residuals, int16 parameter_block_size) {
50 set_num_residuals(num_residuals);
51 mutable_parameter_block_sizes()->push_back(parameter_block_size);
52 }
53 virtual ~UnaryCostFunction() {}
54
55 virtual bool Evaluate(double const* const* parameters,
56 double* residuals,
57 double** jacobians) const {
58 for (int i = 0; i < num_residuals(); ++i) {
59 residuals[i] = 1;
60 }
61 return true;
62 }
63};
64
65// Trivial cost function that accepts two arguments.
66class BinaryCostFunction: public CostFunction {
67 public:
68 BinaryCostFunction(int num_residuals,
69 int16 parameter_block1_size,
70 int16 parameter_block2_size) {
71 set_num_residuals(num_residuals);
72 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
73 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
74 }
75
76 virtual bool Evaluate(double const* const* parameters,
77 double* residuals,
78 double** jacobians) const {
79 for (int i = 0; i < num_residuals(); ++i) {
80 residuals[i] = 2;
81 }
82 return true;
83 }
84};
85
86// Trivial cost function that accepts three arguments.
87class TernaryCostFunction: public CostFunction {
88 public:
89 TernaryCostFunction(int num_residuals,
90 int16 parameter_block1_size,
91 int16 parameter_block2_size,
92 int16 parameter_block3_size) {
93 set_num_residuals(num_residuals);
94 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
95 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
96 mutable_parameter_block_sizes()->push_back(parameter_block3_size);
97 }
98
99 virtual bool Evaluate(double const* const* parameters,
100 double* residuals,
101 double** jacobians) const {
102 for (int i = 0; i < num_residuals(); ++i) {
103 residuals[i] = 3;
104 }
105 return true;
106 }
107};
108
109TEST(Problem, AddResidualWithNullCostFunctionDies) {
110 double x[3], y[4], z[5];
111
112 Problem problem;
113 problem.AddParameterBlock(x, 3);
114 problem.AddParameterBlock(y, 4);
115 problem.AddParameterBlock(z, 5);
116
Sameer Agarwalc0149972012-09-18 13:55:18 -0700117 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(NULL, NULL, x),
118 "'cost_function' Must be non NULL");
Keir Mierle8ebb0732012-04-30 23:09:08 -0700119}
120
121TEST(Problem, AddResidualWithIncorrectNumberOfParameterBlocksDies) {
122 double x[3], y[4], z[5];
123
124 Problem problem;
125 problem.AddParameterBlock(x, 3);
126 problem.AddParameterBlock(y, 4);
127 problem.AddParameterBlock(z, 5);
128
129 // UnaryCostFunction takes only one parameter, but two are passed.
Sameer Agarwalc0149972012-09-18 13:55:18 -0700130 EXPECT_DEATH_IF_SUPPORTED(
Keir Mierle8ebb0732012-04-30 23:09:08 -0700131 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x, y),
132 "parameter_blocks.size()");
133}
134
135TEST(Problem, AddResidualWithDifferentSizesOnTheSameVariableDies) {
136 double x[3];
137
138 Problem problem;
139 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
Sameer Agarwalc0149972012-09-18 13:55:18 -0700140 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
141 new UnaryCostFunction(
142 2, 4 /* 4 != 3 */), NULL, x),
143 "different block sizes");
Keir Mierle8ebb0732012-04-30 23:09:08 -0700144}
145
146TEST(Problem, AddResidualWithDuplicateParametersDies) {
147 double x[3], z[5];
148
149 Problem problem;
Sameer Agarwalc0149972012-09-18 13:55:18 -0700150 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
151 new BinaryCostFunction(2, 3, 3), NULL, x, x),
152 "Duplicate parameter blocks");
153 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
154 new TernaryCostFunction(1, 5, 3, 5),
155 NULL, z, x, z),
156 "Duplicate parameter blocks");
Keir Mierle8ebb0732012-04-30 23:09:08 -0700157}
158
159TEST(Problem, AddResidualWithIncorrectSizesOfParameterBlockDies) {
160 double x[3], y[4], z[5];
161
162 Problem problem;
163 problem.AddParameterBlock(x, 3);
164 problem.AddParameterBlock(y, 4);
165 problem.AddParameterBlock(z, 5);
166
167 // The cost function expects the size of the second parameter, z, to be 4
168 // instead of 5 as declared above. This is fatal.
Sameer Agarwalc0149972012-09-18 13:55:18 -0700169 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(
Keir Mierle8ebb0732012-04-30 23:09:08 -0700170 new BinaryCostFunction(2, 3, 4), NULL, x, z),
171 "different block sizes");
172}
173
174TEST(Problem, AddResidualAddsDuplicatedParametersOnlyOnce) {
175 double x[3], y[4], z[5];
176
177 Problem problem;
178 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
179 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
180 problem.AddResidualBlock(new UnaryCostFunction(2, 4), NULL, y);
181 problem.AddResidualBlock(new UnaryCostFunction(2, 5), NULL, z);
182
183 EXPECT_EQ(3, problem.NumParameterBlocks());
184 EXPECT_EQ(12, problem.NumParameters());
185}
186
187TEST(Problem, AddParameterWithDifferentSizesOnTheSameVariableDies) {
188 double x[3], y[4];
189
190 Problem problem;
191 problem.AddParameterBlock(x, 3);
192 problem.AddParameterBlock(y, 4);
193
Sameer Agarwalc0149972012-09-18 13:55:18 -0700194 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(x, 4),
195 "different block sizes");
Keir Mierle8ebb0732012-04-30 23:09:08 -0700196}
197
198static double *IntToPtr(int i) {
199 return reinterpret_cast<double*>(sizeof(double) * i); // NOLINT
200}
201
202TEST(Problem, AddParameterWithAliasedParametersDies) {
203 // Layout is
204 //
205 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
206 // [x] x x x x [y] y y
207 // o==o==o o==o==o o==o
208 // o--o--o o--o--o o--o o--o--o
209 //
210 // Parameter block additions are tested as listed above; expected successful
211 // ones marked with o==o and aliasing ones marked with o--o.
212
213 Problem problem;
214 problem.AddParameterBlock(IntToPtr(5), 5); // x
215 problem.AddParameterBlock(IntToPtr(13), 3); // y
216
Sameer Agarwalc0149972012-09-18 13:55:18 -0700217 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 4), 2),
218 "Aliasing detected");
219 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 4), 3),
220 "Aliasing detected");
221 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 4), 9),
222 "Aliasing detected");
223 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr( 8), 3),
224 "Aliasing detected");
225 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(12), 2),
226 "Aliasing detected");
227 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(14), 3),
228 "Aliasing detected");
Keir Mierle8ebb0732012-04-30 23:09:08 -0700229
230 // These ones should work.
231 problem.AddParameterBlock(IntToPtr( 2), 3);
232 problem.AddParameterBlock(IntToPtr(10), 3);
233 problem.AddParameterBlock(IntToPtr(16), 2);
234
235 ASSERT_EQ(5, problem.NumParameterBlocks());
236}
237
Keir Mierle8ebb0732012-04-30 23:09:08 -0700238TEST(Problem, AddParameterIgnoresDuplicateCalls) {
239 double x[3], y[4];
240
241 Problem problem;
242 problem.AddParameterBlock(x, 3);
243 problem.AddParameterBlock(y, 4);
244
245 // Creating parameter blocks multiple times is ignored.
246 problem.AddParameterBlock(x, 3);
247 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
248
249 // ... even repeatedly.
250 problem.AddParameterBlock(x, 3);
251 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
252
253 // More parameters are fine.
254 problem.AddParameterBlock(y, 4);
255 problem.AddResidualBlock(new UnaryCostFunction(2, 4), NULL, y);
256
257 EXPECT_EQ(2, problem.NumParameterBlocks());
258 EXPECT_EQ(7, problem.NumParameters());
259}
260
261TEST(Problem, AddingParametersAndResidualsResultsInExpectedProblem) {
262 double x[3], y[4], z[5], w[4];
263
264 Problem problem;
265 problem.AddParameterBlock(x, 3);
266 EXPECT_EQ(1, problem.NumParameterBlocks());
267 EXPECT_EQ(3, problem.NumParameters());
268
269 problem.AddParameterBlock(y, 4);
270 EXPECT_EQ(2, problem.NumParameterBlocks());
271 EXPECT_EQ(7, problem.NumParameters());
272
273 problem.AddParameterBlock(z, 5);
274 EXPECT_EQ(3, problem.NumParameterBlocks());
275 EXPECT_EQ(12, problem.NumParameters());
276
277 // Add a parameter that has a local parameterization.
Sameer Agarwal9123e2f2012-09-18 21:49:06 -0700278 w[0] = 1.0; w[1] = 0.0; w[2] = 0.0; w[3] = 0.0;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700279 problem.AddParameterBlock(w, 4, new QuaternionParameterization);
280 EXPECT_EQ(4, problem.NumParameterBlocks());
281 EXPECT_EQ(16, problem.NumParameters());
282
283 problem.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
284 problem.AddResidualBlock(new BinaryCostFunction(6, 5, 4) , NULL, z, y);
285 problem.AddResidualBlock(new BinaryCostFunction(3, 3, 5), NULL, x, z);
286 problem.AddResidualBlock(new BinaryCostFunction(7, 5, 3), NULL, z, x);
287 problem.AddResidualBlock(new TernaryCostFunction(1, 5, 3, 4), NULL, z, x, y);
288
289 const int total_residuals = 2 + 6 + 3 + 7 + 1;
290 EXPECT_EQ(problem.NumResidualBlocks(), 5);
291 EXPECT_EQ(problem.NumResiduals(), total_residuals);
292}
293
294class DestructorCountingCostFunction : public SizedCostFunction<3, 4, 5> {
295 public:
296 explicit DestructorCountingCostFunction(int *counter)
297 : counter_(counter) {}
298
299 virtual ~DestructorCountingCostFunction() {
300 *counter_ += 1;
301 }
302
303 virtual bool Evaluate(double const* const* parameters,
304 double* residuals,
305 double** jacobians) const {
306 return true;
307 }
308
309 private:
310 int* counter_;
311};
312
313TEST(Problem, ReusedCostFunctionsAreOnlyDeletedOnce) {
314 double y[4], z[5];
315 int counter = 0;
316
317 // Add a cost function multiple times and check to make sure that
318 // the destructor on the cost function is only called once.
319 {
320 Problem problem;
321 problem.AddParameterBlock(y, 4);
322 problem.AddParameterBlock(z, 5);
323
324 CostFunction* cost = new DestructorCountingCostFunction(&counter);
325 problem.AddResidualBlock(cost, NULL, y, z);
326 problem.AddResidualBlock(cost, NULL, y, z);
327 problem.AddResidualBlock(cost, NULL, y, z);
328 }
329
330 // Check that the destructor was called only once.
331 CHECK_EQ(counter, 1);
332}
333
334} // namespace internal
335} // namespace ceres