blob: 7bf512fe38ff0a40f73b73ad75fee35749b02724 [file] [log] [blame]
Sameer Agarwal87a40da2014-05-29 20:55:07 -07001// Ceres Solver - A fast non-linear least squares minimizer
Keir Mierle7492b0d2015-03-17 22:30:16 -07002// Copyright 2015 Google Inc. All rights reserved.
3// http://ceres-solver.org/
Sameer Agarwal87a40da2014-05-29 20:55:07 -07004//
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/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
42namespace ceres {
43namespace internal {
44
Sameer Agarwal05a07ec2015-01-07 15:10:46 -080045using std::string;
Sameer Agarwalbcc865f2014-12-17 07:35:09 -080046using std::vector;
47
Sameer Agarwal87a40da2014-05-29 20:55:07 -070048// A cost function that simply returns its argument.
49class UnaryIdentityCostFunction : public SizedCostFunction<1, 1> {
50 public:
51 virtual bool Evaluate(double const* const* parameters,
52 double* residuals,
53 double** jacobians) const {
54 residuals[0] = parameters[0][0];
55 if (jacobians != NULL && jacobians[0] != NULL) {
56 jacobians[0][0] = 1.0;
57 }
58 return true;
59 }
60};
61
62// Templated base class for the CostFunction signatures.
63template <int kNumResiduals, int N0, int N1, int N2>
64class MockCostFunctionBase : public
65SizedCostFunction<kNumResiduals, N0, N1, N2> {
66 public:
67 virtual bool Evaluate(double const* const* parameters,
68 double* residuals,
69 double** jacobians) const {
70 for (int i = 0; i < kNumResiduals; ++i) {
71 residuals[i] = kNumResiduals + N0 + N1 + N2;
72 }
73 return true;
74 }
75};
76
77class UnaryCostFunction : public MockCostFunctionBase<2, 1, 0, 0> {};
78class BinaryCostFunction : public MockCostFunctionBase<2, 1, 1, 0> {};
79class TernaryCostFunction : public MockCostFunctionBase<2, 1, 1, 1> {};
80
81TEST(Program, RemoveFixedBlocksNothingConstant) {
82 ProblemImpl problem;
83 double x;
84 double y;
85 double z;
86
87 problem.AddParameterBlock(&x, 1);
88 problem.AddParameterBlock(&y, 1);
89 problem.AddParameterBlock(&z, 1);
90 problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
91 problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
92 problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
93
Sameer Agarwal87a40da2014-05-29 20:55:07 -070094 vector<double*> removed_parameter_blocks;
95 double fixed_cost = 0.0;
96 string message;
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -070097 scoped_ptr<Program> reduced_program(
98 CHECK_NOTNULL(problem
99 .program()
100 .CreateReducedProgram(&removed_parameter_blocks,
101 &fixed_cost,
102 &message)));
103
104 EXPECT_EQ(reduced_program->NumParameterBlocks(), 3);
105 EXPECT_EQ(reduced_program->NumResidualBlocks(), 3);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700106 EXPECT_EQ(removed_parameter_blocks.size(), 0);
107 EXPECT_EQ(fixed_cost, 0.0);
108}
109
110TEST(Program, RemoveFixedBlocksAllParameterBlocksConstant) {
111 ProblemImpl problem;
112 double x = 1.0;
113
114 problem.AddParameterBlock(&x, 1);
115 problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
116 problem.SetParameterBlockConstant(&x);
117
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700118 vector<double*> removed_parameter_blocks;
119 double fixed_cost = 0.0;
120 string message;
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700121 scoped_ptr<Program> reduced_program(
122 CHECK_NOTNULL(problem
123 .program()
124 .CreateReducedProgram(&removed_parameter_blocks,
125 &fixed_cost,
126 &message)));
127 EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
128 EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700129 EXPECT_EQ(removed_parameter_blocks.size(), 1);
130 EXPECT_EQ(removed_parameter_blocks[0], &x);
131 EXPECT_EQ(fixed_cost, 9.0);
132}
133
134
135TEST(Program, RemoveFixedBlocksNoResidualBlocks) {
136 ProblemImpl problem;
137 double x;
138 double y;
139 double z;
140
141 problem.AddParameterBlock(&x, 1);
142 problem.AddParameterBlock(&y, 1);
143 problem.AddParameterBlock(&z, 1);
144
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700145 vector<double*> removed_parameter_blocks;
146 double fixed_cost = 0.0;
147 string message;
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700148 scoped_ptr<Program> reduced_program(
149 CHECK_NOTNULL(problem
150 .program()
151 .CreateReducedProgram(&removed_parameter_blocks,
152 &fixed_cost,
153 &message)));
154 EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
155 EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700156 EXPECT_EQ(removed_parameter_blocks.size(), 3);
157 EXPECT_EQ(fixed_cost, 0.0);
158}
159
160TEST(Program, RemoveFixedBlocksOneParameterBlockConstant) {
161 ProblemImpl problem;
162 double x;
163 double y;
164 double z;
165
166 problem.AddParameterBlock(&x, 1);
167 problem.AddParameterBlock(&y, 1);
168 problem.AddParameterBlock(&z, 1);
169
170 problem.AddResidualBlock(new UnaryCostFunction(), NULL, &x);
171 problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
172 problem.SetParameterBlockConstant(&x);
173
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700174 vector<double*> removed_parameter_blocks;
175 double fixed_cost = 0.0;
176 string message;
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700177 scoped_ptr<Program> reduced_program(
178 CHECK_NOTNULL(problem
179 .program()
180 .CreateReducedProgram(&removed_parameter_blocks,
181 &fixed_cost,
182 &message)));
183 EXPECT_EQ(reduced_program->NumParameterBlocks(), 1);
184 EXPECT_EQ(reduced_program->NumResidualBlocks(), 1);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700185}
186
187TEST(Program, 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
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700201 vector<double*> removed_parameter_blocks;
202 double fixed_cost = 0.0;
203 string message;
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700204 scoped_ptr<Program> reduced_program(
205 CHECK_NOTNULL(problem
206 .program()
207 .CreateReducedProgram(&removed_parameter_blocks,
208 &fixed_cost,
209 &message)));
210 EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
211 EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700212}
213
214TEST(Program, RemoveFixedBlocksFixedCost) {
215 ProblemImpl problem;
216 double x = 1.23;
217 double y = 4.56;
218 double z = 7.89;
219
220 problem.AddParameterBlock(&x, 1);
221 problem.AddParameterBlock(&y, 1);
222 problem.AddParameterBlock(&z, 1);
223 problem.AddResidualBlock(new UnaryIdentityCostFunction(), NULL, &x);
224 problem.AddResidualBlock(new TernaryCostFunction(), NULL, &x, &y, &z);
225 problem.AddResidualBlock(new BinaryCostFunction(), NULL, &x, &y);
226 problem.SetParameterBlockConstant(&x);
227
Sameer Agarwalbcc865f2014-12-17 07:35:09 -0800228 ResidualBlock *expected_removed_block =
229 problem.program().residual_blocks()[0];
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700230 scoped_array<double> scratch(
231 new double[expected_removed_block->NumScratchDoublesForEvaluate()]);
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700232 double expected_fixed_cost;
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700233 expected_removed_block->Evaluate(true,
234 &expected_fixed_cost,
235 NULL,
236 NULL,
237 scratch.get());
238
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700239
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700240 vector<double*> removed_parameter_blocks;
241 double fixed_cost = 0.0;
242 string message;
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700243 scoped_ptr<Program> reduced_program(
244 CHECK_NOTNULL(problem
245 .program()
246 .CreateReducedProgram(&removed_parameter_blocks,
247 &fixed_cost,
248 &message)));
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700249
Sameer Agarwalb4a5f7e2014-06-01 14:47:22 -0700250 EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
251 EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700252 EXPECT_DOUBLE_EQ(fixed_cost, expected_fixed_cost);
253}
254
255TEST(Program, CreateJacobianBlockSparsityTranspose) {
256 ProblemImpl problem;
257 double x[2];
258 double y[3];
259 double z;
260
261 problem.AddParameterBlock(x, 2);
262 problem.AddParameterBlock(y, 3);
263 problem.AddParameterBlock(&z, 1);
264
265 problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 0, 0>(), NULL, x);
266 problem.AddResidualBlock(new MockCostFunctionBase<3, 1, 2, 0>(), NULL, &z, x);
267 problem.AddResidualBlock(new MockCostFunctionBase<4, 1, 3, 0>(), NULL, &z, y);
268 problem.AddResidualBlock(new MockCostFunctionBase<5, 1, 3, 0>(), NULL, &z, y);
269 problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 1, 0>(), NULL, x, &z);
270 problem.AddResidualBlock(new MockCostFunctionBase<2, 1, 3, 0>(), NULL, &z, y);
271 problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 1, 0>(), NULL, x, &z);
272 problem.AddResidualBlock(new MockCostFunctionBase<1, 3, 0, 0>(), NULL, y);
273
274 TripletSparseMatrix expected_block_sparse_jacobian(3, 8, 14);
275 {
276 int* rows = expected_block_sparse_jacobian.mutable_rows();
277 int* cols = expected_block_sparse_jacobian.mutable_cols();
278 double* values = expected_block_sparse_jacobian.mutable_values();
279 rows[0] = 0;
280 cols[0] = 0;
281
282 rows[1] = 2;
283 cols[1] = 1;
284 rows[2] = 0;
285 cols[2] = 1;
286
287 rows[3] = 2;
288 cols[3] = 2;
289 rows[4] = 1;
290 cols[4] = 2;
291
292 rows[5] = 2;
293 cols[5] = 3;
294 rows[6] = 1;
295 cols[6] = 3;
296
297 rows[7] = 0;
298 cols[7] = 4;
299 rows[8] = 2;
300 cols[8] = 4;
301
302 rows[9] = 2;
303 cols[9] = 5;
304 rows[10] = 1;
305 cols[10] = 5;
306
307 rows[11] = 0;
308 cols[11] = 6;
309 rows[12] = 2;
310 cols[12] = 6;
311
312 rows[13] = 1;
313 cols[13] = 7;
Sameer Agarwalbcc865f2014-12-17 07:35:09 -0800314 std::fill(values, values + 14, 1.0);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700315 expected_block_sparse_jacobian.set_num_nonzeros(14);
316 }
317
318 Program* program = problem.mutable_program();
319 program->SetParameterOffsetsAndIndex();
320
321 scoped_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
322 program->CreateJacobianBlockSparsityTranspose());
323
324 Matrix expected_dense_jacobian;
325 expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
326
327 Matrix actual_dense_jacobian;
328 actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
329 EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
330}
331
332template <int kNumResiduals, int kNumParameterBlocks>
333class NumParameterBlocksCostFunction : public CostFunction {
334 public:
335 NumParameterBlocksCostFunction() {
336 set_num_residuals(kNumResiduals);
337 for (int i = 0; i < kNumParameterBlocks; ++i) {
338 mutable_parameter_block_sizes()->push_back(1);
339 }
340 }
341
342 virtual ~NumParameterBlocksCostFunction() {
343 }
344
345 virtual bool Evaluate(double const* const* parameters,
346 double* residuals,
347 double** jacobians) const {
348 return true;
349 }
350};
351
352TEST(Program, ReallocationInCreateJacobianBlockSparsityTranspose) {
353 // CreateJacobianBlockSparsityTranspose starts with a conservative
354 // estimate of the size of the sparsity pattern. This test ensures
355 // that when those estimates are violated, the reallocation/resizing
356 // logic works correctly.
357
358 ProblemImpl problem;
359 double x[20];
360
361 vector<double*> parameter_blocks;
362 for (int i = 0; i < 20; ++i) {
363 problem.AddParameterBlock(x + i, 1);
364 parameter_blocks.push_back(x + i);
365 }
366
367 problem.AddResidualBlock(new NumParameterBlocksCostFunction<1, 20>(),
368 NULL,
369 parameter_blocks);
370
371 TripletSparseMatrix expected_block_sparse_jacobian(20, 1, 20);
372 {
373 int* rows = expected_block_sparse_jacobian.mutable_rows();
374 int* cols = expected_block_sparse_jacobian.mutable_cols();
375 for (int i = 0; i < 20; ++i) {
376 rows[i] = i;
377 cols[i] = 0;
378 }
379
380 double* values = expected_block_sparse_jacobian.mutable_values();
Sameer Agarwalbcc865f2014-12-17 07:35:09 -0800381 std::fill(values, values + 20, 1.0);
Sameer Agarwal87a40da2014-05-29 20:55:07 -0700382 expected_block_sparse_jacobian.set_num_nonzeros(20);
383 }
384
385 Program* program = problem.mutable_program();
386 program->SetParameterOffsetsAndIndex();
387
388 scoped_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
389 program->CreateJacobianBlockSparsityTranspose());
390
391 Matrix expected_dense_jacobian;
392 expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
393
394 Matrix actual_dense_jacobian;
395 actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
396 EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
397}
398
399TEST(Program, ProblemHasNanParameterBlocks) {
400 ProblemImpl problem;
401 double x[2];
402 x[0] = 1.0;
403 x[1] = std::numeric_limits<double>::quiet_NaN();
404 problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 0, 0>(), NULL, x);
405 string error;
406 EXPECT_FALSE(problem.program().ParameterBlocksAreFinite(&error));
407 EXPECT_NE(error.find("has at least one invalid value"),
408 string::npos) << error;
409}
410
411TEST(Program, InfeasibleParameterBlock) {
412 ProblemImpl problem;
413 double x[] = {0.0, 0.0};
414 problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 0, 0>(), NULL, x);
415 problem.SetParameterLowerBound(x, 0, 2.0);
416 problem.SetParameterUpperBound(x, 0, 1.0);
417 string error;
418 EXPECT_FALSE(problem.program().IsFeasible(&error));
419 EXPECT_NE(error.find("infeasible bound"), string::npos) << error;
420}
421
422TEST(Program, InfeasibleConstantParameterBlock) {
423 ProblemImpl problem;
424 double x[] = {0.0, 0.0};
425 problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 0, 0>(), NULL, x);
426 problem.SetParameterLowerBound(x, 0, 1.0);
427 problem.SetParameterUpperBound(x, 0, 2.0);
428 problem.SetParameterBlockConstant(x);
429 string error;
430 EXPECT_FALSE(problem.program().IsFeasible(&error));
431 EXPECT_NE(error.find("infeasible value"), string::npos) << error;
432}
433
434} // namespace internal
435} // namespace ceres