blob: 9c51ff9bff90357d905513258f52be64320e47dc [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
Austin Schuh3de38b02024-06-25 18:25:10 -07002// Copyright 2023 Google Inc. All rights reserved.
Austin Schuh70cc9552019-01-21 19:46:48 -08003// 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 Schuh3de38b02024-06-25 18:25:10 -070036#include <string>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080037#include <utility>
Austin Schuh70cc9552019-01-21 19:46:48 -080038#include <vector>
39
40#include "ceres/internal/integer_sequence_algorithm.h"
41#include "ceres/problem_impl.h"
42#include "ceres/residual_block.h"
43#include "ceres/sized_cost_function.h"
44#include "ceres/triplet_sparse_matrix.h"
45#include "gtest/gtest.h"
46
47namespace ceres {
48namespace internal {
49
Austin Schuh70cc9552019-01-21 19:46:48 -080050// A cost function that simply returns its argument.
51class UnaryIdentityCostFunction : public SizedCostFunction<1, 1> {
52 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080053 bool Evaluate(double const* const* parameters,
54 double* residuals,
55 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080056 residuals[0] = parameters[0][0];
57 if (jacobians != nullptr && jacobians[0] != nullptr) {
58 jacobians[0][0] = 1.0;
59 }
60 return true;
61 }
62};
63
64// Templated base class for the CostFunction signatures.
65template <int kNumResiduals, int... Ns>
66class MockCostFunctionBase : public SizedCostFunction<kNumResiduals, Ns...> {
67 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080068 bool Evaluate(double const* const* parameters,
69 double* residuals,
70 double** jacobians) const final {
Austin Schuh3de38b02024-06-25 18:25:10 -070071 constexpr int kNumParameters = (Ns + ... + 0);
Austin Schuh70cc9552019-01-21 19:46:48 -080072
73 for (int i = 0; i < kNumResiduals; ++i) {
74 residuals[i] = kNumResiduals + kNumParameters;
75 }
76 return true;
77 }
78};
79
80class UnaryCostFunction : public MockCostFunctionBase<2, 1> {};
81class BinaryCostFunction : public MockCostFunctionBase<2, 1, 1> {};
82class TernaryCostFunction : public MockCostFunctionBase<2, 1, 1, 1> {};
83
84TEST(Program, RemoveFixedBlocksNothingConstant) {
85 ProblemImpl problem;
86 double x;
87 double y;
88 double z;
89
90 problem.AddParameterBlock(&x, 1);
91 problem.AddParameterBlock(&y, 1);
92 problem.AddParameterBlock(&z, 1);
93 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
94 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
95 problem.AddResidualBlock(new TernaryCostFunction(), nullptr, &x, &y, &z);
96
Austin Schuh3de38b02024-06-25 18:25:10 -070097 std::vector<double*> removed_parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -080098 double fixed_cost = 0.0;
Austin Schuh3de38b02024-06-25 18:25:10 -070099 std::string message;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800100 std::unique_ptr<Program> reduced_program(
101 problem.program().CreateReducedProgram(
Austin Schuh70cc9552019-01-21 19:46:48 -0800102 &removed_parameter_blocks, &fixed_cost, &message));
103
104 EXPECT_EQ(reduced_program->NumParameterBlocks(), 3);
105 EXPECT_EQ(reduced_program->NumResidualBlocks(), 3);
106 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(), nullptr, &x);
116 problem.SetParameterBlockConstant(&x);
117
Austin Schuh3de38b02024-06-25 18:25:10 -0700118 std::vector<double*> removed_parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800119 double fixed_cost = 0.0;
Austin Schuh3de38b02024-06-25 18:25:10 -0700120 std::string message;
Austin Schuh70cc9552019-01-21 19:46:48 -0800121 std::unique_ptr<Program> reduced_program(
122 problem.program().CreateReducedProgram(
123 &removed_parameter_blocks, &fixed_cost, &message));
124
125 EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
126 EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
127 EXPECT_EQ(removed_parameter_blocks.size(), 1);
128 EXPECT_EQ(removed_parameter_blocks[0], &x);
129 EXPECT_EQ(fixed_cost, 9.0);
130}
131
Austin Schuh70cc9552019-01-21 19:46:48 -0800132TEST(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
Austin Schuh3de38b02024-06-25 18:25:10 -0700142 std::vector<double*> removed_parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800143 double fixed_cost = 0.0;
Austin Schuh3de38b02024-06-25 18:25:10 -0700144 std::string message;
Austin Schuh70cc9552019-01-21 19:46:48 -0800145 std::unique_ptr<Program> reduced_program(
146 problem.program().CreateReducedProgram(
147 &removed_parameter_blocks, &fixed_cost, &message));
148 EXPECT_EQ(reduced_program->NumParameterBlocks(), 0);
149 EXPECT_EQ(reduced_program->NumResidualBlocks(), 0);
150 EXPECT_EQ(removed_parameter_blocks.size(), 3);
151 EXPECT_EQ(fixed_cost, 0.0);
152}
153
154TEST(Program, RemoveFixedBlocksOneParameterBlockConstant) {
155 ProblemImpl problem;
156 double x;
157 double y;
158 double z;
159
160 problem.AddParameterBlock(&x, 1);
161 problem.AddParameterBlock(&y, 1);
162 problem.AddParameterBlock(&z, 1);
163
164 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
165 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
166 problem.SetParameterBlockConstant(&x);
167
Austin Schuh3de38b02024-06-25 18:25:10 -0700168 std::vector<double*> removed_parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800169 double fixed_cost = 0.0;
Austin Schuh3de38b02024-06-25 18:25:10 -0700170 std::string message;
Austin Schuh70cc9552019-01-21 19:46:48 -0800171 std::unique_ptr<Program> reduced_program(
172 problem.program().CreateReducedProgram(
173 &removed_parameter_blocks, &fixed_cost, &message));
174 EXPECT_EQ(reduced_program->NumParameterBlocks(), 1);
175 EXPECT_EQ(reduced_program->NumResidualBlocks(), 1);
176}
177
178TEST(Program, RemoveFixedBlocksNumEliminateBlocks) {
179 ProblemImpl problem;
180 double x;
181 double y;
182 double z;
183
184 problem.AddParameterBlock(&x, 1);
185 problem.AddParameterBlock(&y, 1);
186 problem.AddParameterBlock(&z, 1);
187 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
188 problem.AddResidualBlock(new TernaryCostFunction(), nullptr, &x, &y, &z);
189 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
190 problem.SetParameterBlockConstant(&x);
191
Austin Schuh3de38b02024-06-25 18:25:10 -0700192 std::vector<double*> removed_parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800193 double fixed_cost = 0.0;
Austin Schuh3de38b02024-06-25 18:25:10 -0700194 std::string message;
Austin Schuh70cc9552019-01-21 19:46:48 -0800195 std::unique_ptr<Program> reduced_program(
196 problem.program().CreateReducedProgram(
197 &removed_parameter_blocks, &fixed_cost, &message));
198 EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
199 EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
200}
201
202TEST(Program, RemoveFixedBlocksFixedCost) {
203 ProblemImpl problem;
204 double x = 1.23;
205 double y = 4.56;
206 double z = 7.89;
207
208 problem.AddParameterBlock(&x, 1);
209 problem.AddParameterBlock(&y, 1);
210 problem.AddParameterBlock(&z, 1);
211 problem.AddResidualBlock(new UnaryIdentityCostFunction(), nullptr, &x);
212 problem.AddResidualBlock(new TernaryCostFunction(), nullptr, &x, &y, &z);
213 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
214 problem.SetParameterBlockConstant(&x);
215
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800216 ResidualBlock* expected_removed_block =
Austin Schuh70cc9552019-01-21 19:46:48 -0800217 problem.program().residual_blocks()[0];
218 std::unique_ptr<double[]> scratch(
219 new double[expected_removed_block->NumScratchDoublesForEvaluate()]);
220 double expected_fixed_cost;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800221 expected_removed_block->Evaluate(
222 true, &expected_fixed_cost, nullptr, nullptr, scratch.get());
Austin Schuh70cc9552019-01-21 19:46:48 -0800223
Austin Schuh3de38b02024-06-25 18:25:10 -0700224 std::vector<double*> removed_parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800225 double fixed_cost = 0.0;
Austin Schuh3de38b02024-06-25 18:25:10 -0700226 std::string message;
Austin Schuh70cc9552019-01-21 19:46:48 -0800227 std::unique_ptr<Program> reduced_program(
228 problem.program().CreateReducedProgram(
229 &removed_parameter_blocks, &fixed_cost, &message));
230
231 EXPECT_EQ(reduced_program->NumParameterBlocks(), 2);
232 EXPECT_EQ(reduced_program->NumResidualBlocks(), 2);
233 EXPECT_DOUBLE_EQ(fixed_cost, expected_fixed_cost);
234}
235
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800236class BlockJacobianTest : public ::testing::TestWithParam<int> {};
237
238TEST_P(BlockJacobianTest, CreateJacobianBlockSparsityTranspose) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800239 ProblemImpl problem;
240 double x[2];
241 double y[3];
242 double z;
243
244 problem.AddParameterBlock(x, 2);
245 problem.AddParameterBlock(y, 3);
246 problem.AddParameterBlock(&z, 1);
247
248 problem.AddResidualBlock(new MockCostFunctionBase<2, 2>(), nullptr, x);
249 problem.AddResidualBlock(new MockCostFunctionBase<3, 1, 2>(), nullptr, &z, x);
250 problem.AddResidualBlock(new MockCostFunctionBase<4, 1, 3>(), nullptr, &z, y);
251 problem.AddResidualBlock(new MockCostFunctionBase<5, 1, 3>(), nullptr, &z, y);
252 problem.AddResidualBlock(new MockCostFunctionBase<1, 2, 1>(), nullptr, x, &z);
253 problem.AddResidualBlock(new MockCostFunctionBase<2, 1, 3>(), nullptr, &z, y);
254 problem.AddResidualBlock(new MockCostFunctionBase<2, 2, 1>(), nullptr, x, &z);
255 problem.AddResidualBlock(new MockCostFunctionBase<1, 3>(), nullptr, y);
256
257 TripletSparseMatrix expected_block_sparse_jacobian(3, 8, 14);
258 {
259 int* rows = expected_block_sparse_jacobian.mutable_rows();
260 int* cols = expected_block_sparse_jacobian.mutable_cols();
261 double* values = expected_block_sparse_jacobian.mutable_values();
262 rows[0] = 0;
263 cols[0] = 0;
264
265 rows[1] = 2;
266 cols[1] = 1;
267 rows[2] = 0;
268 cols[2] = 1;
269
270 rows[3] = 2;
271 cols[3] = 2;
272 rows[4] = 1;
273 cols[4] = 2;
274
275 rows[5] = 2;
276 cols[5] = 3;
277 rows[6] = 1;
278 cols[6] = 3;
279
280 rows[7] = 0;
281 cols[7] = 4;
282 rows[8] = 2;
283 cols[8] = 4;
284
285 rows[9] = 2;
286 cols[9] = 5;
287 rows[10] = 1;
288 cols[10] = 5;
289
290 rows[11] = 0;
291 cols[11] = 6;
292 rows[12] = 2;
293 cols[12] = 6;
294
295 rows[13] = 1;
296 cols[13] = 7;
297 std::fill(values, values + 14, 1.0);
298 expected_block_sparse_jacobian.set_num_nonzeros(14);
299 }
300
301 Program* program = problem.mutable_program();
302 program->SetParameterOffsetsAndIndex();
303
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800304 const int start_row_block = GetParam();
Austin Schuh70cc9552019-01-21 19:46:48 -0800305 std::unique_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800306 program->CreateJacobianBlockSparsityTranspose(start_row_block));
Austin Schuh70cc9552019-01-21 19:46:48 -0800307
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800308 Matrix expected_full_dense_jacobian;
309 expected_block_sparse_jacobian.ToDenseMatrix(&expected_full_dense_jacobian);
310 Matrix expected_dense_jacobian =
311 expected_full_dense_jacobian.rightCols(8 - start_row_block);
Austin Schuh70cc9552019-01-21 19:46:48 -0800312
313 Matrix actual_dense_jacobian;
314 actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800315 EXPECT_EQ(expected_dense_jacobian.rows(), actual_dense_jacobian.rows());
316 EXPECT_EQ(expected_dense_jacobian.cols(), actual_dense_jacobian.cols());
Austin Schuh70cc9552019-01-21 19:46:48 -0800317 EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
318}
319
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800320INSTANTIATE_TEST_SUITE_P(AllColumns, BlockJacobianTest, ::testing::Range(0, 7));
321
Austin Schuh70cc9552019-01-21 19:46:48 -0800322template <int kNumResiduals, int kNumParameterBlocks>
323class NumParameterBlocksCostFunction : public CostFunction {
324 public:
325 NumParameterBlocksCostFunction() {
326 set_num_residuals(kNumResiduals);
327 for (int i = 0; i < kNumParameterBlocks; ++i) {
328 mutable_parameter_block_sizes()->push_back(1);
329 }
330 }
331
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800332 bool Evaluate(double const* const* parameters,
333 double* residuals,
334 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800335 return true;
336 }
337};
338
339TEST(Program, ReallocationInCreateJacobianBlockSparsityTranspose) {
340 // CreateJacobianBlockSparsityTranspose starts with a conservative
341 // estimate of the size of the sparsity pattern. This test ensures
342 // that when those estimates are violated, the reallocation/resizing
343 // logic works correctly.
344
345 ProblemImpl problem;
346 double x[20];
347
Austin Schuh3de38b02024-06-25 18:25:10 -0700348 std::vector<double*> parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800349 for (int i = 0; i < 20; ++i) {
350 problem.AddParameterBlock(x + i, 1);
351 parameter_blocks.push_back(x + i);
352 }
353
354 problem.AddResidualBlock(new NumParameterBlocksCostFunction<1, 20>(),
355 nullptr,
356 parameter_blocks.data(),
357 static_cast<int>(parameter_blocks.size()));
358
359 TripletSparseMatrix expected_block_sparse_jacobian(20, 1, 20);
360 {
361 int* rows = expected_block_sparse_jacobian.mutable_rows();
362 int* cols = expected_block_sparse_jacobian.mutable_cols();
363 for (int i = 0; i < 20; ++i) {
364 rows[i] = i;
365 cols[i] = 0;
366 }
367
368 double* values = expected_block_sparse_jacobian.mutable_values();
369 std::fill(values, values + 20, 1.0);
370 expected_block_sparse_jacobian.set_num_nonzeros(20);
371 }
372
373 Program* program = problem.mutable_program();
374 program->SetParameterOffsetsAndIndex();
375
376 std::unique_ptr<TripletSparseMatrix> actual_block_sparse_jacobian(
377 program->CreateJacobianBlockSparsityTranspose());
378
379 Matrix expected_dense_jacobian;
380 expected_block_sparse_jacobian.ToDenseMatrix(&expected_dense_jacobian);
381
382 Matrix actual_dense_jacobian;
383 actual_block_sparse_jacobian->ToDenseMatrix(&actual_dense_jacobian);
384 EXPECT_EQ((expected_dense_jacobian - actual_dense_jacobian).norm(), 0.0);
385}
386
387TEST(Program, ProblemHasNanParameterBlocks) {
388 ProblemImpl problem;
389 double x[2];
390 x[0] = 1.0;
391 x[1] = std::numeric_limits<double>::quiet_NaN();
392 problem.AddResidualBlock(new MockCostFunctionBase<1, 2>(), nullptr, x);
Austin Schuh3de38b02024-06-25 18:25:10 -0700393 std::string error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800394 EXPECT_FALSE(problem.program().ParameterBlocksAreFinite(&error));
Austin Schuh3de38b02024-06-25 18:25:10 -0700395 EXPECT_NE(error.find("has at least one invalid value"), std::string::npos)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800396 << error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800397}
398
399TEST(Program, InfeasibleParameterBlock) {
400 ProblemImpl problem;
401 double x[] = {0.0, 0.0};
402 problem.AddResidualBlock(new MockCostFunctionBase<1, 2>(), nullptr, x);
403 problem.SetParameterLowerBound(x, 0, 2.0);
404 problem.SetParameterUpperBound(x, 0, 1.0);
Austin Schuh3de38b02024-06-25 18:25:10 -0700405 std::string error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800406 EXPECT_FALSE(problem.program().IsFeasible(&error));
Austin Schuh3de38b02024-06-25 18:25:10 -0700407 EXPECT_NE(error.find("infeasible bound"), std::string::npos) << error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800408}
409
410TEST(Program, InfeasibleConstantParameterBlock) {
411 ProblemImpl problem;
412 double x[] = {0.0, 0.0};
413 problem.AddResidualBlock(new MockCostFunctionBase<1, 2>(), nullptr, x);
414 problem.SetParameterLowerBound(x, 0, 1.0);
415 problem.SetParameterUpperBound(x, 0, 2.0);
416 problem.SetParameterBlockConstant(x);
Austin Schuh3de38b02024-06-25 18:25:10 -0700417 std::string error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800418 EXPECT_FALSE(problem.program().IsFeasible(&error));
Austin Schuh3de38b02024-06-25 18:25:10 -0700419 EXPECT_NE(error.find("infeasible value"), std::string::npos) << error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800420}
421
422} // namespace internal
423} // namespace ceres