blob: 1d9f49ca9331190d39513c32389b66f3e0c224ba [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2015 Google Inc. All rights reserved.
3// http://ceres-solver.org/
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 "ceres/program.h"
32
33#include <cmath>
34#include <limits>
35#include <memory>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080036#include <utility>
Austin Schuh70cc9552019-01-21 19:46:48 -080037#include <vector>
38
39#include "ceres/internal/integer_sequence_algorithm.h"
40#include "ceres/problem_impl.h"
41#include "ceres/residual_block.h"
42#include "ceres/sized_cost_function.h"
43#include "ceres/triplet_sparse_matrix.h"
44#include "gtest/gtest.h"
45
46namespace ceres {
47namespace internal {
48
49using std::string;
50using std::vector;
51
52// A cost function that simply returns its argument.
53class UnaryIdentityCostFunction : public SizedCostFunction<1, 1> {
54 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080055 bool Evaluate(double const* const* parameters,
56 double* residuals,
57 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080058 residuals[0] = parameters[0][0];
59 if (jacobians != nullptr && jacobians[0] != nullptr) {
60 jacobians[0][0] = 1.0;
61 }
62 return true;
63 }
64};
65
66// Templated base class for the CostFunction signatures.
67template <int kNumResiduals, int... Ns>
68class MockCostFunctionBase : public SizedCostFunction<kNumResiduals, Ns...> {
69 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080070 bool Evaluate(double const* const* parameters,
71 double* residuals,
72 double** jacobians) const final {
73 const int kNumParameters = Sum<std::integer_sequence<int, Ns...>>::Value;
Austin Schuh70cc9552019-01-21 19:46:48 -080074
75 for (int i = 0; i < kNumResiduals; ++i) {
76 residuals[i] = kNumResiduals + kNumParameters;
77 }
78 return true;
79 }
80};
81
82class UnaryCostFunction : public MockCostFunctionBase<2, 1> {};
83class BinaryCostFunction : public MockCostFunctionBase<2, 1, 1> {};
84class TernaryCostFunction : public MockCostFunctionBase<2, 1, 1, 1> {};
85
86TEST(Program, RemoveFixedBlocksNothingConstant) {
87 ProblemImpl problem;
88 double x;
89 double y;
90 double z;
91
92 problem.AddParameterBlock(&x, 1);
93 problem.AddParameterBlock(&y, 1);
94 problem.AddParameterBlock(&z, 1);
95 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
96 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
97 problem.AddResidualBlock(new TernaryCostFunction(), nullptr, &x, &y, &z);
98
99 vector<double*> removed_parameter_blocks;
100 double fixed_cost = 0.0;
101 string message;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800102 std::unique_ptr<Program> reduced_program(
103 problem.program().CreateReducedProgram(
Austin Schuh70cc9552019-01-21 19:46:48 -0800104 &removed_parameter_blocks, &fixed_cost, &message));
105
106 EXPECT_EQ(reduced_program->NumParameterBlocks(), 3);
107 EXPECT_EQ(reduced_program->NumResidualBlocks(), 3);
108 EXPECT_EQ(removed_parameter_blocks.size(), 0);
109 EXPECT_EQ(fixed_cost, 0.0);
110}
111
112TEST(Program, RemoveFixedBlocksAllParameterBlocksConstant) {
113 ProblemImpl problem;
114 double x = 1.0;
115
116 problem.AddParameterBlock(&x, 1);
117 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
118 problem.SetParameterBlockConstant(&x);
119
120 vector<double*> removed_parameter_blocks;
121 double fixed_cost = 0.0;
122 string message;
123 std::unique_ptr<Program> reduced_program(
124 problem.program().CreateReducedProgram(
125 &removed_parameter_blocks, &fixed_cost, &message));
126
127 EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
128 EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
129 EXPECT_EQ(removed_parameter_blocks.size(), 1);
130 EXPECT_EQ(removed_parameter_blocks[0], &x);
131 EXPECT_EQ(fixed_cost, 9.0);
132}
133
Austin Schuh70cc9552019-01-21 19:46:48 -0800134TEST(Program, RemoveFixedBlocksNoResidualBlocks) {
135 ProblemImpl problem;
136 double x;
137 double y;
138 double z;
139
140 problem.AddParameterBlock(&x, 1);
141 problem.AddParameterBlock(&y, 1);
142 problem.AddParameterBlock(&z, 1);
143
144 vector<double*> removed_parameter_blocks;
145 double fixed_cost = 0.0;
146 string message;
147 std::unique_ptr<Program> reduced_program(
148 problem.program().CreateReducedProgram(
149 &removed_parameter_blocks, &fixed_cost, &message));
150 EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
151 EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
152 EXPECT_EQ(removed_parameter_blocks.size(), 3);
153 EXPECT_EQ(fixed_cost, 0.0);
154}
155
156TEST(Program, 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 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
167 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
168 problem.SetParameterBlockConstant(&x);
169
170 vector<double*> removed_parameter_blocks;
171 double fixed_cost = 0.0;
172 string message;
173 std::unique_ptr<Program> reduced_program(
174 problem.program().CreateReducedProgram(
175 &removed_parameter_blocks, &fixed_cost, &message));
176 EXPECT_EQ(reduced_program->NumParameterBlocks(), 1);
177 EXPECT_EQ(reduced_program->NumResidualBlocks(), 1);
178}
179
180TEST(Program, RemoveFixedBlocksNumEliminateBlocks) {
181 ProblemImpl problem;
182 double x;
183 double y;
184 double z;
185
186 problem.AddParameterBlock(&x, 1);
187 problem.AddParameterBlock(&y, 1);
188 problem.AddParameterBlock(&z, 1);
189 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
190 problem.AddResidualBlock(new TernaryCostFunction(), nullptr, &x, &y, &z);
191 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
192 problem.SetParameterBlockConstant(&x);
193
194 vector<double*> removed_parameter_blocks;
195 double fixed_cost = 0.0;
196 string message;
197 std::unique_ptr<Program> reduced_program(
198 problem.program().CreateReducedProgram(
199 &removed_parameter_blocks, &fixed_cost, &message));
200 EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
201 EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
202}
203
204TEST(Program, RemoveFixedBlocksFixedCost) {
205 ProblemImpl problem;
206 double x = 1.23;
207 double y = 4.56;
208 double z = 7.89;
209
210 problem.AddParameterBlock(&x, 1);
211 problem.AddParameterBlock(&y, 1);
212 problem.AddParameterBlock(&z, 1);
213 problem.AddResidualBlock(new UnaryIdentityCostFunction(), nullptr, &x);
214 problem.AddResidualBlock(new TernaryCostFunction(), nullptr, &x, &y, &z);
215 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
216 problem.SetParameterBlockConstant(&x);
217
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800218 ResidualBlock* expected_removed_block =
Austin Schuh70cc9552019-01-21 19:46:48 -0800219 problem.program().residual_blocks()[0];
220 std::unique_ptr<double[]> scratch(
221 new double[expected_removed_block->NumScratchDoublesForEvaluate()]);
222 double expected_fixed_cost;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800223 expected_removed_block->Evaluate(
224 true, &expected_fixed_cost, nullptr, nullptr, scratch.get());
Austin Schuh70cc9552019-01-21 19:46:48 -0800225
226 vector<double*> removed_parameter_blocks;
227 double fixed_cost = 0.0;
228 string message;
229 std::unique_ptr<Program> reduced_program(
230 problem.program().CreateReducedProgram(
231 &removed_parameter_blocks, &fixed_cost, &message));
232
233 EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
234 EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
235 EXPECT_DOUBLE_EQ(fixed_cost, expected_fixed_cost);
236}
237
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800238class BlockJacobianTest : public ::testing::TestWithParam<int> {};
239
240TEST_P(BlockJacobianTest, CreateJacobianBlockSparsityTranspose) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800241 ProblemImpl problem;
242 double x[2];
243 double y[3];
244 double z;
245
246 problem.AddParameterBlock(x, 2);
247 problem.AddParameterBlock(y, 3);
248 problem.AddParameterBlock(&z, 1);
249
250 problem.AddResidualBlock(new MockCostFunctionBase<2, 2>(), nullptr, x);
251 problem.AddResidualBlock(new MockCostFunctionBase<3, 1, 2>(), nullptr, &z, x);
252 problem.AddResidualBlock(new MockCostFunctionBase<4, 1, 3>(), nullptr, &z, y);
253 problem.AddResidualBlock(new MockCostFunctionBase<5, 1, 3>(), nullptr, &z, y);
254 problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 1>(), nullptr, x, &z);
255 problem.AddResidualBlock(new MockCostFunctionBase<2, 1, 3>(), nullptr, &z, y);
256 problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 1>(), nullptr, x, &z);
257 problem.AddResidualBlock(new MockCostFunctionBase<1, 3>(), nullptr, y);
258
259 TripletSparseMatrix expected_block_sparse_jacobian(3, 8, 14);
260 {
261 int* rows = expected_block_sparse_jacobian.mutable_rows();
262 int* cols = expected_block_sparse_jacobian.mutable_cols();
263 double* values = expected_block_sparse_jacobian.mutable_values();
264 rows[0] = 0;
265 cols[0] = 0;
266
267 rows[1] = 2;
268 cols[1] = 1;
269 rows[2] = 0;
270 cols[2] = 1;
271
272 rows[3] = 2;
273 cols[3] = 2;
274 rows[4] = 1;
275 cols[4] = 2;
276
277 rows[5] = 2;
278 cols[5] = 3;
279 rows[6] = 1;
280 cols[6] = 3;
281
282 rows[7] = 0;
283 cols[7] = 4;
284 rows[8] = 2;
285 cols[8] = 4;
286
287 rows[9] = 2;
288 cols[9] = 5;
289 rows[10] = 1;
290 cols[10] = 5;
291
292 rows[11] = 0;
293 cols[11] = 6;
294 rows[12] = 2;
295 cols[12] = 6;
296
297 rows[13] = 1;
298 cols[13] = 7;
299 std::fill(values, values + 14, 1.0);
300 expected_block_sparse_jacobian.set_num_nonzeros(14);
301 }
302
303 Program* program = problem.mutable_program();
304 program->SetParameterOffsetsAndIndex();
305
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800306 const int start_row_block = GetParam();
Austin Schuh70cc9552019-01-21 19:46:48 -0800307 std::unique_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800308 program->CreateJacobianBlockSparsityTranspose(start_row_block));
Austin Schuh70cc9552019-01-21 19:46:48 -0800309
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800310 Matrix expected_full_dense_jacobian;
311 expected_block_sparse_jacobian.ToDenseMatrix(&expected_full_dense_jacobian);
312 Matrix expected_dense_jacobian =
313 expected_full_dense_jacobian.rightCols(8 - start_row_block);
Austin Schuh70cc9552019-01-21 19:46:48 -0800314
315 Matrix actual_dense_jacobian;
316 actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800317 EXPECT_EQ(expected_dense_jacobian.rows(), actual_dense_jacobian.rows());
318 EXPECT_EQ(expected_dense_jacobian.cols(), actual_dense_jacobian.cols());
Austin Schuh70cc9552019-01-21 19:46:48 -0800319 EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
320}
321
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800322INSTANTIATE_TEST_SUITE_P(AllColumns, BlockJacobianTest, ::testing::Range(0, 7));
323
Austin Schuh70cc9552019-01-21 19:46:48 -0800324template <int kNumResiduals, int kNumParameterBlocks>
325class NumParameterBlocksCostFunction : public CostFunction {
326 public:
327 NumParameterBlocksCostFunction() {
328 set_num_residuals(kNumResiduals);
329 for (int i = 0; i < kNumParameterBlocks; ++i) {
330 mutable_parameter_block_sizes()->push_back(1);
331 }
332 }
333
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800334 virtual ~NumParameterBlocksCostFunction() {}
Austin Schuh70cc9552019-01-21 19:46:48 -0800335
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800336 bool Evaluate(double const* const* parameters,
337 double* residuals,
338 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800339 return true;
340 }
341};
342
343TEST(Program, ReallocationInCreateJacobianBlockSparsityTranspose) {
344 // CreateJacobianBlockSparsityTranspose starts with a conservative
345 // estimate of the size of the sparsity pattern. This test ensures
346 // that when those estimates are violated, the reallocation/resizing
347 // logic works correctly.
348
349 ProblemImpl problem;
350 double x[20];
351
352 vector<double*> parameter_blocks;
353 for (int i = 0; i < 20; ++i) {
354 problem.AddParameterBlock(x + i, 1);
355 parameter_blocks.push_back(x + i);
356 }
357
358 problem.AddResidualBlock(new NumParameterBlocksCostFunction<1, 20>(),
359 nullptr,
360 parameter_blocks.data(),
361 static_cast<int>(parameter_blocks.size()));
362
363 TripletSparseMatrix expected_block_sparse_jacobian(20, 1, 20);
364 {
365 int* rows = expected_block_sparse_jacobian.mutable_rows();
366 int* cols = expected_block_sparse_jacobian.mutable_cols();
367 for (int i = 0; i < 20; ++i) {
368 rows[i] = i;
369 cols[i] = 0;
370 }
371
372 double* values = expected_block_sparse_jacobian.mutable_values();
373 std::fill(values, values + 20, 1.0);
374 expected_block_sparse_jacobian.set_num_nonzeros(20);
375 }
376
377 Program* program = problem.mutable_program();
378 program->SetParameterOffsetsAndIndex();
379
380 std::unique_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
381 program->CreateJacobianBlockSparsityTranspose());
382
383 Matrix expected_dense_jacobian;
384 expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
385
386 Matrix actual_dense_jacobian;
387 actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
388 EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
389}
390
391TEST(Program, ProblemHasNanParameterBlocks) {
392 ProblemImpl problem;
393 double x[2];
394 x[0] = 1.0;
395 x[1] = std::numeric_limits<double>::quiet_NaN();
396 problem.AddResidualBlock(new MockCostFunctionBase<1, 2>(), nullptr, x);
397 string error;
398 EXPECT_FALSE(problem.program().ParameterBlocksAreFinite(&error));
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800399 EXPECT_NE(error.find("has at least one invalid value"), string::npos)
400 << error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800401}
402
403TEST(Program, InfeasibleParameterBlock) {
404 ProblemImpl problem;
405 double x[] = {0.0, 0.0};
406 problem.AddResidualBlock(new MockCostFunctionBase<1, 2>(), nullptr, x);
407 problem.SetParameterLowerBound(x, 0, 2.0);
408 problem.SetParameterUpperBound(x, 0, 1.0);
409 string error;
410 EXPECT_FALSE(problem.program().IsFeasible(&error));
411 EXPECT_NE(error.find("infeasible bound"), string::npos) << error;
412}
413
414TEST(Program, InfeasibleConstantParameterBlock) {
415 ProblemImpl problem;
416 double x[] = {0.0, 0.0};
417 problem.AddResidualBlock(new MockCostFunctionBase<1, 2>(), nullptr, x);
418 problem.SetParameterLowerBound(x, 0, 1.0);
419 problem.SetParameterUpperBound(x, 0, 2.0);
420 problem.SetParameterBlockConstant(x);
421 string error;
422 EXPECT_FALSE(problem.program().IsFeasible(&error));
423 EXPECT_NE(error.find("infeasible value"), string::npos) << error;
424}
425
426} // namespace internal
427} // namespace ceres